From 1ee371df5a598884981a179e2115ae27824ed25b Mon Sep 17 00:00:00 2001 From: Benyu Zhang Date: Fri, 18 Sep 2015 09:45:37 -0700 Subject: [PATCH 0001/2505] install lwan-config.h which is included in lwan.h --- common/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 877755765..894d32dae 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -72,6 +72,6 @@ set(ADDITIONAL_LIBRARIES ${ADDITIONAL_LIBRARIES} PARENT_SCOPE) INSTALL(TARGETS lwan-common DESTINATION "lib" ) -INSTALL(FILES lwan.h lwan-coro.h lwan-trie.h lwan-status.h strbuf.h hash.h lwan-template.h lwan-serve-files.h +INSTALL(FILES lwan.h lwan-coro.h lwan-trie.h lwan-status.h strbuf.h hash.h lwan-template.h lwan-serve-files.h lwan-config.h DESTINATION "include/lwan" ) From 9599d90fc6052e7a86cedd2d95b24d89c895fa2b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 22 Sep 2015 00:00:58 -0300 Subject: [PATCH 0002/2505] Allow dropping privileges, chrooting To use, declare a `straitjacket` section in the beginning of `lwan.conf`: straitjacket { user = nobody chroot = /mnt/storage-ro/wwwroot } This requires root privileges. Note that any path referenced in the configuration file will be relative to the new chroot path, so change accordingly. The chroot option is optional, if one only wants to drop privileges. Eventually, this will also allow limiting which system calls Lwan can perform, possibly by implementing OpenBSD's tame(2) using seccomp-bpf. --- common/CMakeLists.txt | 1 + common/lwan-private.h | 2 + common/lwan-straitjacket.c | 135 +++++++++++++++++++++++++++++++++++++ common/lwan.c | 15 +++-- 4 files changed, 147 insertions(+), 6 deletions(-) create mode 100644 common/lwan-straitjacket.c diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 894d32dae..ec4730bee 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -17,6 +17,7 @@ set(SOURCES lwan-serve-files.c lwan-socket.c lwan-status.c + lwan-straitjacket.c lwan-tables.c lwan-template.c lwan-thread.c diff --git a/common/lwan-private.h b/common/lwan-private.h index 6cf547d92..692c64306 100644 --- a/common/lwan-private.h +++ b/common/lwan-private.h @@ -45,6 +45,8 @@ void lwan_tables_shutdown(void); char *lwan_process_request(lwan_t *l, lwan_request_t *request, lwan_value_t *buffer, char *next_request); +void lwan_straitjacket_enforce(config_t *c, config_line_t *l); + #undef static_assert #if HAVE_STATIC_ASSERT #define static_assert(expr, msg) _Static_assert(expr, msg) diff --git a/common/lwan-straitjacket.c b/common/lwan-straitjacket.c new file mode 100644 index 000000000..82173fb28 --- /dev/null +++ b/common/lwan-straitjacket.c @@ -0,0 +1,135 @@ +/* + * lwan - simple web server + * Copyright (c) 2015 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include "lwan-config.h" +#include "lwan-status.h" + +static bool get_user_uid_gid(const char *user, uid_t *uid, gid_t *gid) +{ + struct passwd pwd = { }; + struct passwd *result; + char *buf; + long pw_size_max = sysconf(_SC_GETPW_R_SIZE_MAX); + int r; + + if (!user || !*user) { + lwan_status_error("Username should be provided"); + return false; + } + + if (pw_size_max < 0) + pw_size_max = 16384; + + buf = malloc((size_t)pw_size_max); + if (!buf) { + lwan_status_error("Could not allocate buffer for passwd struct"); + return false; + } + + r = getpwnam_r(user, &pwd, buf, (size_t)pw_size_max, &result); + *uid = pwd.pw_uid; + *gid = pwd.pw_gid; + free(buf); + + if (result) + return true; + + if (!r) { + lwan_status_error("Username not found: %s", user); + } else { + errno = r; + lwan_status_perror("Could not obtain uid/gid for user %s", user); + } + + return false; +} + +static bool switch_to_user(uid_t uid, gid_t gid, const char *username) +{ + lwan_status_debug("Dropping privileges to UID %d, GID %d (%s)", + uid, gid, username); + + if (setgid(gid) < 0) + return false; + if (setuid(uid) < 0) + return false; + + return getegid() == gid && geteuid() == uid; +} + +void lwan_straitjacket_enforce(config_t *c, config_line_t *l) +{ + char *user_name = NULL; + char *chroot_path = NULL; + uid_t uid; + gid_t gid; + + if (geteuid() != 0) { + config_error(c, "Straitjacket requires root privileges"); + return; + } + + while (config_read_line(c, l)) { + switch (l->type) { + case CONFIG_LINE_TYPE_LINE: + /* TODO: limit_syscalls */ + if (!strcmp(l->line.key, "user")) { + user_name = strdupa(l->line.value); + } else if (!strcmp(l->line.key, "chroot")) { + chroot_path = strdupa(l->line.value); + } else { + config_error(c, "Invalid key: %s", l->line.key); + return; + } + break; + case CONFIG_LINE_TYPE_SECTION: + config_error(c, "Straitjacket accepts no sections"); + return; + case CONFIG_LINE_TYPE_SECTION_END: + if (!get_user_uid_gid(user_name, &uid, &gid)) { + config_error(c, "Unknown user: %s", user_name); + return; + } + + if (chroot_path) { + if (chroot(chroot_path) < 0) { + lwan_status_critical_perror("Could not chroot() to %s", + chroot_path); + } + lwan_status_debug("Jailed to %s", chroot_path); + } + + if (!switch_to_user(uid, gid, user_name)) { + lwan_status_critical("Could not drop privileges to %s, aborting", + user_name); + } + return; + } + } + + config_error(c, "Expecting section end while parsing straitjacket"); +} diff --git a/common/lwan.c b/common/lwan.c index 37f001d2c..07a528c13 100644 --- a/common/lwan.c +++ b/common/lwan.c @@ -412,14 +412,17 @@ static bool setup_from_config(lwan_t *lwan) config_error(&conf, "Unknown config key: %s", line.line.key); break; case CONFIG_LINE_TYPE_SECTION: - if (!has_listener) { - has_listener = true; - if (!strcmp(line.section.name, "listener")) + if (!strcmp(line.section.name, "listener")) { + if (!has_listener) { parse_listener(&conf, &line, lwan); - else - config_error(&conf, "Unknown section type: %s", line.section.name); + has_listener = true; + } else { + config_error(&conf, "Only one listener supported"); + } + } else if (!strcmp(line.section.name, "straitjacket")) { + lwan_straitjacket_enforce(&conf, &line); } else { - config_error(&conf, "Only one listener supported"); + config_error(&conf, "Unknown section type: %s", line.section.name); } break; case CONFIG_LINE_TYPE_SECTION_END: From a24339ae0952615c78043fdbf29fa5a628d2025e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 24 Sep 2015 23:53:49 -0300 Subject: [PATCH 0003/2505] Fix debug build warning --- common/lwan-straitjacket.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lwan-straitjacket.c b/common/lwan-straitjacket.c index 82173fb28..a46877b1a 100644 --- a/common/lwan-straitjacket.c +++ b/common/lwan-straitjacket.c @@ -70,7 +70,7 @@ static bool get_user_uid_gid(const char *user, uid_t *uid, gid_t *gid) static bool switch_to_user(uid_t uid, gid_t gid, const char *username) { - lwan_status_debug("Dropping privileges to UID %d, GID %d (%s)", + lwan_status_info("Dropping privileges to UID %d, GID %d (%s)", uid, gid, username); if (setgid(gid) < 0) From 6ea69be4822f04a8e0786ad8e037f26e20ea59bd Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 24 Sep 2015 23:54:01 -0300 Subject: [PATCH 0004/2505] openat() might block even if O_NONBLOCK is passed --- common/lwan-io-wrappers.c | 1 + 1 file changed, 1 insertion(+) diff --git a/common/lwan-io-wrappers.c b/common/lwan-io-wrappers.c index 3451f0248..1c24204fa 100644 --- a/common/lwan-io-wrappers.c +++ b/common/lwan-io-wrappers.c @@ -41,6 +41,7 @@ lwan_openat(lwan_request_t *request, } switch (errno) { + case EWOULDBLOCK: case EINTR: case EMFILE: case ENFILE: From d158bf67d4e2623422b169327cc2ae4ac587cd7c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 27 Sep 2015 15:41:12 -0300 Subject: [PATCH 0005/2505] Get rid of `last` variable in parse_key_values() --- common/lwan-request.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index 315431e7c..4b15ca43f 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -152,7 +152,6 @@ parse_key_values(lwan_request_t *request, char *ptr = helper_value->value; lwan_key_value_t kvs[256]; size_t values = 0; - bool last = true; if (!helper_value->len) return; @@ -176,10 +175,6 @@ parse_key_values(lwan_request_t *request, if (ptr) { *ptr = '\0'; ptr++; - - last = false; - } else { - last = true; } if (UNLIKELY(!decode_value(key) || !decode_value(value))) @@ -189,7 +184,7 @@ parse_key_values(lwan_request_t *request, kvs[values].value = value; values++; - } while (!last && values < N_ELEMENTS(kvs)); + } while (ptr && values < N_ELEMENTS(kvs)); kvs[values].key = kvs[values].value = NULL; From dcd0431a1d2b8cb0128d964a68a3e7934d8641de Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 27 Sep 2015 16:20:16 -0300 Subject: [PATCH 0006/2505] Do not copy temporary vector in parse_key_values() This reduces the amount of stack usage. --- common/lwan-request.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index 4b15ca43f..acd07881e 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -149,13 +149,20 @@ parse_key_values(lwan_request_t *request, lwan_value_t *helper_value, lwan_key_value_t **base, size_t *len, size_t (*decode_value)(char *value), const char separator) { + const size_t n_elements = 32; char *ptr = helper_value->value; - lwan_key_value_t kvs[256]; + lwan_key_value_t *kvs; size_t values = 0; if (!helper_value->len) return; + kvs = coro_malloc(request->conn->coro, n_elements * sizeof(*kvs)); + if (UNLIKELY(!kvs)) { + coro_yield(request->conn->coro, CONN_CORO_ABORT); + __builtin_unreachable(); + } + do { char *key, *value; @@ -184,17 +191,13 @@ parse_key_values(lwan_request_t *request, kvs[values].value = value; values++; - } while (ptr && values < N_ELEMENTS(kvs)); + } while (ptr && values < n_elements); kvs[values].key = kvs[values].value = NULL; - lwan_key_value_t *kv = coro_malloc(request->conn->coro, - (1 + values) * sizeof(lwan_key_value_t)); - if (LIKELY(kv)) { - qsort(kvs, values, sizeof(lwan_key_value_t), key_value_compare_qsort_key); - *base = memcpy(kv, kvs, (1 + values) * sizeof(lwan_key_value_t)); - *len = values; - } + qsort(kvs, values, sizeof(lwan_key_value_t), key_value_compare_qsort_key); + *base = kvs; + *len = values; } static size_t From ec969cba7e6e3c477b72e27b4f8448a0a5a6bd7a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 3 Oct 2015 11:41:30 -0300 Subject: [PATCH 0007/2505] Batch coro_defer() on coroutine reset Instead of running all coro_defer from a given coroutine every reset, batch them and run only every 16 resets. Run them all at once when freeing a coroutine regardless of how many times a coroutine has been reset, though. The 16 resets value is arbitrary and is only there so it has a boundary. No experimentation or measurement has been made with any other boundary value. It's low enough to avoid resource exaustion, and high enough to slightly reduce latency (or so I hope). --- common/lwan-coro.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/common/lwan-coro.c b/common/lwan-coro.c index 29310c7a0..cf1afee22 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -32,6 +32,8 @@ #include #endif +#define CORO_RESET_THRESHOLD 16 + #define CORO_STACK_MIN ((3 * (PTHREAD_STACK_MIN)) / 2) static_assert(DEFAULT_BUFFER_SIZE < (CORO_STACK_MIN + PTHREAD_STACK_MIN), @@ -58,6 +60,7 @@ struct coro_t_ { coro_defer_t *defer; void *data; + unsigned char reset_count; bool ended; }; @@ -154,6 +157,7 @@ coro_run_deferred(coro_t *coro) free(tmp); } coro->defer = NULL; + coro->reset_count = CORO_RESET_THRESHOLD; } void @@ -164,7 +168,8 @@ coro_reset(coro_t *coro, coro_function_t func, void *data) coro->ended = false; coro->data = data; - coro_run_deferred(coro); + if (!coro->reset_count--) + coro_run_deferred(coro); #if defined(__x86_64__) coro->context[6 /* RDI */] = (uintptr_t) coro; @@ -204,6 +209,7 @@ coro_new(coro_switcher_t *switcher, coro_function_t function, void *data) coro->switcher = switcher; coro->defer = NULL; + coro->reset_count = CORO_RESET_THRESHOLD; coro_reset(coro, function, data); #if !defined(NDEBUG) && defined(USE_VALGRIND) From e1d2ba99e4dc11998f9124ec447100dc6f12738b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 3 Oct 2015 11:51:28 -0300 Subject: [PATCH 0008/2505] Try to run deferred statements on coro_malloc() failure --- common/lwan-coro.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/common/lwan-coro.c b/common/lwan-coro.c index cf1afee22..25eebf3ab 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -317,8 +317,14 @@ void * coro_malloc_full(coro_t *coro, size_t size, void (*destroy_func)()) { coro_defer_t *defer = malloc(sizeof(*defer) + size); - if (UNLIKELY(!defer)) - return NULL; + + if (UNLIKELY(!defer)) { + coro_run_deferred(coro); + + defer = malloc(sizeof(*defer) + size); + if (UNLIKELY(!defer)) + return NULL; + } defer->next = coro->defer; defer->func = destroy_func; From 5052fe2bad8a67159483852bfcd618e5fd48f134 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 4 Oct 2015 13:00:36 -0300 Subject: [PATCH 0009/2505] Do not call posix_fadvise() when serving files over 7KiB POSIX_FADV_SEQUENTIAL doesn't seem like the best advice. Remove it to cut down on the number of syscalls. --- common/lwan-io-wrappers.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/common/lwan-io-wrappers.c b/common/lwan-io-wrappers.c index 1c24204fa..5c8ef9cd0 100644 --- a/common/lwan-io-wrappers.c +++ b/common/lwan-io-wrappers.c @@ -235,12 +235,6 @@ sendfile_linux_sendfile(coro_t *coro, int in_fd, int out_fd, off_t offset, size_ ssize_t lwan_sendfile(lwan_request_t *request, int in_fd, off_t offset, size_t count) { - if (count > BUFFER_SIZE * 5) { - if (UNLIKELY(posix_fadvise(in_fd, offset, (off_t)count, - POSIX_FADV_SEQUENTIAL) < 0)) - lwan_status_perror("posix_fadvise"); - } - ssize_t written_bytes = sendfile_linux_sendfile( request->conn->coro, in_fd, request->fd, offset, count); From aa0415c97b89fd7f0d395d104ca663acb822b82b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 4 Oct 2015 13:13:45 -0300 Subject: [PATCH 0010/2505] When lwan_openat() were to fail, try running coro's coro_defer This should free up some file descriptors. Yield the coroutine afterwards, in order to attend to another connection. --- common/lwan-coro.c | 2 +- common/lwan-coro.h | 2 ++ common/lwan-io-wrappers.c | 5 +++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/common/lwan-coro.c b/common/lwan-coro.c index 25eebf3ab..3436dedcc 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -147,7 +147,7 @@ coro_entry_point(coro_t *coro, coro_function_t func) coro_yield(coro, return_value); } -static void +void coro_run_deferred(coro_t *coro) { for (coro_defer_t *defer = coro->defer; defer;) { diff --git a/common/lwan-coro.h b/common/lwan-coro.h index d56081412..8c82c0868 100644 --- a/common/lwan-coro.h +++ b/common/lwan-coro.h @@ -46,6 +46,8 @@ void coro_free(coro_t *coro); void coro_reset(coro_t *coro, coro_function_t func, void *data); +void coro_run_deferred(coro_t *coro); + int coro_resume(coro_t *coro); int coro_resume_value(coro_t *coro, int value); int coro_yield(coro_t *coro, int value); diff --git a/common/lwan-io-wrappers.c b/common/lwan-io-wrappers.c index 5c8ef9cd0..88fc73bea 100644 --- a/common/lwan-io-wrappers.c +++ b/common/lwan-io-wrappers.c @@ -41,10 +41,11 @@ lwan_openat(lwan_request_t *request, } switch (errno) { - case EWOULDBLOCK: - case EINTR: case EMFILE: case ENFILE: + coro_run_deferred(request->conn->coro); + case EWOULDBLOCK: + case EINTR: case ENOMEM: coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); break; From 79be18f6d983906d65e93e36bd0fb3c2ab69ca23 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 4 Oct 2015 13:25:48 -0300 Subject: [PATCH 0011/2505] Revert commits aa0415c and e1d2ba99 It's dangerous to call coro_run_deferred() unless the coroutine is being destroyed and/or reset. There might be resources still being used by the coroutine at that point, which would lead to serious consequences. The idea of "running the garbage collector" is sound, however, but there must be some flag of sorts to mark that a resource isn't used. Kind of a generational GC (each generation being a coroutine reset). --- common/lwan-coro.c | 11 +++-------- common/lwan-coro.h | 2 -- common/lwan-io-wrappers.c | 1 - 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/common/lwan-coro.c b/common/lwan-coro.c index 3436dedcc..48554c177 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -147,7 +147,7 @@ coro_entry_point(coro_t *coro, coro_function_t func) coro_yield(coro, return_value); } -void +static void coro_run_deferred(coro_t *coro) { for (coro_defer_t *defer = coro->defer; defer;) { @@ -318,13 +318,8 @@ coro_malloc_full(coro_t *coro, size_t size, void (*destroy_func)()) { coro_defer_t *defer = malloc(sizeof(*defer) + size); - if (UNLIKELY(!defer)) { - coro_run_deferred(coro); - - defer = malloc(sizeof(*defer) + size); - if (UNLIKELY(!defer)) - return NULL; - } + if (UNLIKELY(!defer)) + return NULL; defer->next = coro->defer; defer->func = destroy_func; diff --git a/common/lwan-coro.h b/common/lwan-coro.h index 8c82c0868..d56081412 100644 --- a/common/lwan-coro.h +++ b/common/lwan-coro.h @@ -46,8 +46,6 @@ void coro_free(coro_t *coro); void coro_reset(coro_t *coro, coro_function_t func, void *data); -void coro_run_deferred(coro_t *coro); - int coro_resume(coro_t *coro); int coro_resume_value(coro_t *coro, int value); int coro_yield(coro_t *coro, int value); diff --git a/common/lwan-io-wrappers.c b/common/lwan-io-wrappers.c index 88fc73bea..49f8ec3d6 100644 --- a/common/lwan-io-wrappers.c +++ b/common/lwan-io-wrappers.c @@ -43,7 +43,6 @@ lwan_openat(lwan_request_t *request, switch (errno) { case EMFILE: case ENFILE: - coro_run_deferred(request->conn->coro); case EWOULDBLOCK: case EINTR: case ENOMEM: From 6a57d5ef98a9d817e81568375162310a936cf0f9 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 6 Oct 2015 07:24:21 -0300 Subject: [PATCH 0012/2505] Properly test for world-readability when serving files --- common/lwan-serve-files.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/lwan-serve-files.c b/common/lwan-serve-files.c index 081bedd55..99d9d5264 100644 --- a/common/lwan-serve-files.c +++ b/common/lwan-serve-files.c @@ -524,6 +524,7 @@ create_cache_entry_from_funcs(serve_files_priv_t *priv, const char *full_path, static struct cache_entry_t * create_cache_entry(const char *key, void *context) { + const mode_t world_readable = S_IRUSR | S_IRGRP | S_IROTH; serve_files_priv_t *priv = context; file_cache_entry_t *fce; struct stat st; @@ -534,7 +535,7 @@ create_cache_entry(const char *key, void *context) key, full_path, &st))) return NULL; - if (UNLIKELY(!(st.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)))) + if (UNLIKELY((st.st_mode & world_readable) != world_readable)) return NULL; if (UNLIKELY(strncmp(full_path, priv->root.path, priv->root.path_len))) From 0c91c23085e2d1ec8fac40b0132c4a27e756268f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 14 Oct 2015 19:52:27 -0300 Subject: [PATCH 0013/2505] Do not ignore return of write() while shutting down threads --- common/lwan-thread.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/common/lwan-thread.c b/common/lwan-thread.c index 3a88d82b6..a85970762 100644 --- a/common/lwan-thread.c +++ b/common/lwan-thread.c @@ -443,6 +443,7 @@ lwan_thread_shutdown(lwan_t *l) for (int i = l->thread.count - 1; i >= 0; i--) { lwan_thread_t *t = &l->thread.threads[i]; char less_than_int = 0; + ssize_t r; lwan_status_debug("Closing epoll for thread %d (fd=%d)", i, t->epoll_fd); @@ -450,7 +451,19 @@ lwan_thread_shutdown(lwan_t *l) /* Close the epoll_fd and write less than an int to signal the * thread to gracefully finish. */ close(t->epoll_fd); - write(t->pipe_fd[1], &less_than_int, sizeof(less_than_int)); + + while (true) { + r = write(t->pipe_fd[1], &less_than_int, sizeof(less_than_int)); + + if (r >= 0) + break; + + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) + continue; + + lwan_status_error("Could not write to I/O thread (%d) pipe to shutdown", i); + break; + } } for (int i = l->thread.count - 1; i >= 0; i--) { From da76714956782b8516bea103fae77b5a9724292d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 14 Oct 2015 19:52:44 -0300 Subject: [PATCH 0014/2505] Make has_zero_byte() return a uint32_t instead of a unsigned long This should reduce code size a tiny wee little bit. --- common/lwan-request.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index acd07881e..9948b37d5 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -60,8 +60,8 @@ struct request_parser_helper { static char decode_hex_digit(char ch) __attribute__((pure)); static bool is_hex_digit(char ch) __attribute__((pure)); -static unsigned long has_zero_byte(unsigned long n) __attribute__((pure)); -static unsigned long is_space(char ch) __attribute__((pure)); +static uint32_t has_zero_byte(uint32_t n) __attribute__((pure)); +static uint32_t is_space(char ch) __attribute__((pure)); static char *ignore_leading_whitespace(char *buffer) __attribute__((pure)); static lwan_request_flags_t get_http_method(const char *buffer) __attribute__((pure)); @@ -484,16 +484,16 @@ parse_accept_encoding(lwan_request_t *request, struct request_parser_helper *hel } } -static ALWAYS_INLINE unsigned long -has_zero_byte(unsigned long n) +static ALWAYS_INLINE uint32_t +has_zero_byte(uint32_t n) { - return ((n - 0x01010101UL) & ~n) & 0x80808080UL; + return ((n - 0x01010101U) & ~n) & 0x80808080U; } -static ALWAYS_INLINE unsigned long +static ALWAYS_INLINE uint32_t is_space(char ch) { - return has_zero_byte((0x1010101UL * (unsigned long)ch) ^ 0x090a0d20UL); + return has_zero_byte((0x1010101U * (uint32_t)ch) ^ 0x090a0d20U); } static ALWAYS_INLINE char * From 0b2fde263c91d289accbad7790a92a18f1800d2f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 14 Oct 2015 19:53:20 -0300 Subject: [PATCH 0015/2505] No need to check for '\0' while skipping spaces/separators --- common/lwan-request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index 9948b37d5..d5d123c4d 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -166,7 +166,7 @@ parse_key_values(lwan_request_t *request, do { char *key, *value; - while (*ptr && (*ptr == ' ' || *ptr == '\t' || *ptr == separator)) + while (*ptr == ' ' || *ptr == separator) ptr++; if (UNLIKELY(*ptr == '\0')) return; From ad8cdb14a938be054ebcc173c91717e8cf2694c2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 15 Oct 2015 22:48:37 -0300 Subject: [PATCH 0016/2505] Fix out of bounds access in config::remove_trailing_spaces() --- common/lwan-config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lwan-config.c b/common/lwan-config.c index 2b8323433..7c36c4c3c 100644 --- a/common/lwan-config.c +++ b/common/lwan-config.c @@ -138,7 +138,7 @@ static char *remove_trailing_spaces(char *line) { char *end = rawmemchr(line, '\0'); - for (end--; isspace(*end); end--); + for (end--; end >= line && isspace(*end); end--); *(end + 1) = '\0'; return line; From 547065ffecd62ad30182811b09cdddf2fbd0b3b2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 16 Oct 2015 00:49:53 -0300 Subject: [PATCH 0017/2505] No need for error_vlexeme() return anything --- common/lwan-template.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/common/lwan-template.c b/common/lwan-template.c index 92e880fe1..39e7d6baf 100644 --- a/common/lwan-template.c +++ b/common/lwan-template.c @@ -182,7 +182,7 @@ static void *parser_negate_iter(struct parser *parser, struct lexeme *lexeme); static void *parser_meta(struct parser *parser, struct lexeme *lexeme); static void *parser_text(struct parser *parser, struct lexeme *lexeme); -static void *error_vlexeme(struct lexeme *lexeme, const char *msg, va_list ap) +static void error_vlexeme(struct lexeme *lexeme, const char *msg, va_list ap) __attribute__((format(printf, 2, 0))); static void *error_lexeme(struct lexeme *lexeme, const char *msg, ...) __attribute__((format(printf, 2, 3))); @@ -292,7 +292,7 @@ static void backup(struct lexer *lexer) lexer->pos--; } -static void *error_vlexeme(struct lexeme *lexeme, const char *msg, va_list ap) +static void error_vlexeme(struct lexeme *lexeme, const char *msg, va_list ap) { int r; @@ -314,14 +314,13 @@ static void *error_vlexeme(struct lexeme *lexeme, const char *msg, va_list ap) static void *error_lexeme(struct lexeme *lexeme, const char *msg, ...) { - void *ret; va_list ap; va_start(ap, msg); - ret = error_vlexeme(lexeme, msg, ap); + error_vlexeme(lexeme, msg, ap); va_end(ap); - return ret; + return NULL; } static void *lex_error(struct lexer *lexer, const char *msg, ...) From 17d0ef00ab048b42c06ce596d3cccc4ae29255de Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 17 Oct 2015 11:50:01 -0300 Subject: [PATCH 0018/2505] Store indices rather than pointers in template while parsing Memory is reallocated and pointers might change. Store data as indices, and then move them back to pointers after parsing so runtime isn't affected. --- common/lwan-template.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/common/lwan-template.c b/common/lwan-template.c index 39e7d6baf..a09573172 100644 --- a/common/lwan-template.c +++ b/common/lwan-template.c @@ -599,7 +599,7 @@ static void *parser_end_iter(struct parser *parser, struct lexeme *lexeme) if (iter->action != ACTION_START_ITER) continue; if (iter->data == symbol) { - emit_chunk(parser, ACTION_END_ITER, 0, iter); + emit_chunk(parser, ACTION_END_ITER, 0, (void *)(ptrdiff_t)idx); symtab_pop(parser); return parser_text; } @@ -970,9 +970,14 @@ post_process_template(struct parser *parser) for (prev_chunk = chunk; ; chunk++) { if (chunk->action == ACTION_LAST) break; - if (chunk->action == ACTION_END_ITER && chunk->data == prev_chunk) { - chunk->flags |= flags; - break; + if (chunk->action == ACTION_END_ITER) { + struct chunk *end_iter_data = parser->chunks.data + (ptrdiff_t)chunk->data; + + if (end_iter_data == prev_chunk) { + chunk->data = end_iter_data; + chunk->flags |= flags; + break; + } } } From 651f33848ea88706f51f19a7503410f0b277085d Mon Sep 17 00:00:00 2001 From: Malthe Borch Date: Mon, 19 Oct 2015 00:03:15 +0200 Subject: [PATCH 0019/2505] Add support for PROXY protocol This closes issue #117 --- common/lwan-request.c | 277 +++++++++++++++++++++++++++++++++++++++--- common/lwan.c | 10 +- common/lwan.h | 29 +++-- tools/testsuite.py | 25 ++++ 4 files changed, 315 insertions(+), 26 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index d5d123c4d..28072bf26 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -70,9 +70,12 @@ get_http_method(const char *buffer) { /* Note: keep in sync in identify_http_method() */ enum { - HTTP_STR_GET = MULTICHAR_CONSTANT('G','E','T',' '), - HTTP_STR_HEAD = MULTICHAR_CONSTANT('H','E','A','D'), - HTTP_STR_POST = MULTICHAR_CONSTANT('P','O','S','T') + HTTP_STR_GET = MULTICHAR_CONSTANT('G','E','T',' '), + HTTP_STR_HEAD = MULTICHAR_CONSTANT('H','E','A','D'), + HTTP_STR_POST = MULTICHAR_CONSTANT('P','O','S','T'), + HTTP_STR_PROXY1 = MULTICHAR_CONSTANT('P','R','O','X'), + HTTP_STR_PROXY21 = MULTICHAR_CONSTANT('\x00','\x0D','\x0A','\x51'), + HTTP_STR_PROXY22 = MULTICHAR_CONSTANT('\x55','\x49','\x54','\x0A') }; STRING_SWITCH(buffer) { @@ -82,15 +85,245 @@ get_http_method(const char *buffer) return REQUEST_METHOD_HEAD; case HTTP_STR_POST: return REQUEST_METHOD_POST; + case HTTP_STR_PROXY1: + if (buffer[4] != 'Y') break; + return REQUEST_METHOD_PROXY1; + case HTTP_STR_PROXY21: + if ((string_as_int32(buffer + 4) == HTTP_STR_PROXY22) && + (*(buffer + 8) >> 4 == 2)) + return REQUEST_METHOD_PROXY2; } return 0; } +static lwan_request_flags_t +parse_proxy_protocol(lwan_request_t *request, char **buffer, int version) +{ + union header_t_ { + struct { + char line[108]; + } v1; + struct { + uint8_t sig[8]; + uint8_t cmd : 4; + uint8_t ver : 4; + uint8_t fam; + uint16_t len; + union { + struct { + uint32_t src_addr; + uint32_t dst_addr; + uint16_t src_port; + uint16_t dst_port; + } ip4; + struct { + uint8_t src_addr[16]; + uint8_t dst_addr[16]; + uint16_t src_port; + uint16_t dst_port; + } ip6; + struct { + uint8_t src_addr[108]; + uint8_t dst_addr[108]; + } unx; + } addr; + } v2; + }; + + unsigned int size; + lwan_proxy_t *proxy = &request->conn->thread->lwan->proxies[request->fd]; + union header_t_ *hdr = (union header_t_ *) *buffer; + + if (version == 1) { + char *end, *ptr, *protocol, *src_addr, *dst_addr, *src_port, *dst_port; + + end = memchr(hdr->v1.line, '\r', sizeof(*hdr)); + if (!end || end[1] != '\n') { + return 0; + } + + *end = '\0'; + size = (unsigned int) (end + 2 - hdr->v1.line); + + ptr = hdr->v1.line; + strsep(&ptr, " "); + + protocol = strsep(&ptr, " "); + src_addr = strsep(&ptr, " "); + dst_addr = strsep(&ptr, " "); + src_port = strsep(&ptr, " "); + dst_port = ptr; + + if (protocol != NULL && dst_port != NULL) { + enum { + TCP4 = MULTICHAR_CONSTANT('T', 'C', 'P', '4'), + TCP6 = MULTICHAR_CONSTANT('T', 'C', 'P', '6') + }; + + STRING_SWITCH(protocol) { + case TCP4: + do { + long porttmp; + + struct sockaddr_in *from = &proxy->from.ipv4; + struct sockaddr_in *to = &proxy->to.ipv4; + + from->sin_family = to->sin_family = AF_INET; + + if (inet_pton(AF_INET, src_addr, &from->sin_addr) != 1) { + return 0; + } + + if (inet_pton(AF_INET, dst_addr, &to->sin_addr) != 1) { + return 0; + } + + porttmp = strtol(src_port, NULL, 10); + if (!(porttmp > 0 && porttmp < 65536)) { + return 0; + } + from->sin_port = htons((uint16_t) porttmp); + + porttmp = strtol(dst_port, NULL, 10); + if (!(porttmp > 0 && porttmp < 65536)) { + return 0; + } + + to->sin_port = htons((uint16_t) porttmp); + } while (0); + break; + case TCP6: + do { + long porttmp; + + struct sockaddr_in6 *from = &proxy->from.ipv6; + struct sockaddr_in6 *to = &proxy->to.ipv6; + + from->sin6_family = to->sin6_family = AF_INET6; + + if (inet_pton(AF_INET6, src_addr, &from->sin6_addr) != 1) { + return 0; + } + + if (inet_pton(AF_INET6, dst_addr, &to->sin6_addr) != 1) { + return 0; + } + + porttmp = strtol(src_port, NULL, 10); + if (!(porttmp > 0 && porttmp < 65536)) { + return 0; + } + + from->sin6_port = htons((uint16_t) porttmp); + + porttmp = strtol(dst_port, NULL, 10); + if (!(porttmp > 0 && porttmp < 65536)) { + return 0; + } + + to->sin6_port = htons((uint16_t) porttmp); + } while (0); + break; + default: + if (memcmp(protocol, "UNKNOWN", 7) != 0) return 0; + + struct sockaddr_in *from = &proxy->from.ipv4; + struct sockaddr_in *to = &proxy->to.ipv4; + + from->sin_family = to->sin_family = AF_UNSPEC; + } + } + + goto done; + } + + if (version == 2) { + size = 12 + (unsigned int) ntohs(hdr->v2.len); + if (size > sizeof(union header_t_)) return 0; + + enum { + LOCAL = 0, + PROXY = 1 + }; + + switch (hdr->v2.cmd) { + case LOCAL: + do { + struct sockaddr_in *from = &proxy->from.ipv4; + struct sockaddr_in *to = &proxy->to.ipv4; + + from->sin_family = to->sin_family = AF_UNSPEC; + } while (0); + break; + case PROXY: + do { + enum { + TCP4 = 0x11, + TCP6 = 0x21 + }; + + switch (hdr->v2.fam) { + case TCP4: + do { + struct sockaddr_in *from = &proxy->from.ipv4; + struct sockaddr_in *to = &proxy->to.ipv4; + + to->sin_family = from->sin_family = AF_INET; + + from->sin_addr.s_addr = hdr->v2.addr.ip4.src_addr; + from->sin_port = hdr->v2.addr.ip4.src_port; + + to->sin_addr.s_addr = hdr->v2.addr.ip4.dst_addr; + to->sin_port = hdr->v2.addr.ip4.dst_port; + } while (0); + break; + case TCP6: + do { + struct sockaddr_in6 *from = &proxy->from.ipv6; + struct sockaddr_in6 *to = &proxy->to.ipv6; + + from->sin6_family = to->sin6_family = AF_INET6; + + memcpy(&from->sin6_addr, hdr->v2.addr.ip6.src_addr, 16); + from->sin6_port = hdr->v2.addr.ip6.src_port; + + memcpy(&to->sin6_addr, hdr->v2.addr.ip6.dst_addr, 16); + to->sin6_port = hdr->v2.addr.ip6.dst_port; + } while (0); + break; + default: + return 0; + } + } while (0); + break; + default: + return 0; + } + + goto done; + } + + return 0; + + done: + request->conn->flags |= CONN_PROXIED; + *buffer += size; + return get_http_method(*buffer); +} + static ALWAYS_INLINE char * identify_http_method(lwan_request_t *request, char *buffer) { + char *path; lwan_request_flags_t flags = get_http_method(buffer); + + if (flags == REQUEST_METHOD_PROXY1) + flags = parse_proxy_protocol(request, &buffer, 1); + + if (flags == REQUEST_METHOD_PROXY2) + flags = parse_proxy_protocol(request, &buffer, 2); + static const char sizes[] = { [0] = 0, [REQUEST_METHOD_GET] = sizeof("GET ") - 1, @@ -666,15 +899,15 @@ read_post_data(lwan_request_t *request __attribute__((unused)), static lwan_http_status_t parse_http_request(lwan_request_t *request, struct request_parser_helper *helper) { - char *buffer; - - buffer = ignore_leading_whitespace(helper->buffer->value); - if (UNLIKELY(!*buffer)) - return HTTP_BAD_REQUEST; + char *buffer = ignore_leading_whitespace(helper->buffer->value); char *path = identify_http_method(request, buffer); - if (UNLIKELY(buffer == path)) + if (UNLIKELY(buffer == path)) { + if (UNLIKELY(!*buffer)) + return HTTP_BAD_REQUEST; + return HTTP_NOT_ALLOWED; + } buffer = identify_http_path(request, path, helper); if (UNLIKELY(!buffer)) @@ -882,14 +1115,26 @@ const char * lwan_request_get_remote_address(lwan_request_t *request, char buffer[static INET6_ADDRSTRLEN]) { - struct sockaddr_storage sock_addr = { 0 }; - socklen_t sock_len = sizeof(struct sockaddr_storage); - if (UNLIKELY(getpeername(request->fd, (struct sockaddr *)&sock_addr, &sock_len) < 0)) - return NULL; + struct sockaddr_storage __sock_addr; + struct sockaddr_storage *sock_addr = &__sock_addr; - if (sock_addr.ss_family == AF_INET) - return inet_ntop(AF_INET, &((struct sockaddr_in *)&sock_addr)->sin_addr, + if (request->conn->flags & CONN_PROXIED) { + sock_addr = (struct sockaddr_storage *) + &request->conn->thread->lwan->proxies[request->fd].from; + } else { + socklen_t sock_len = sizeof(__sock_addr); + if (UNLIKELY(getpeername(request->fd, + (struct sockaddr *) sock_addr, + &sock_len) < 0)) + return NULL; + } + + if (sock_addr->ss_family == AF_INET) + return inet_ntop(AF_INET, + &((struct sockaddr_in *) sock_addr)->sin_addr, buffer, INET6_ADDRSTRLEN); - return inet_ntop(AF_INET6, &((struct sockaddr_in6 *)&sock_addr)->sin6_addr, + + return inet_ntop(AF_INET6, + &((struct sockaddr_in6 *) sock_addr)->sin6_addr, buffer, INET6_ADDRSTRLEN); } diff --git a/common/lwan.c b/common/lwan.c index 07a528c13..c91ce190a 100644 --- a/common/lwan.c +++ b/common/lwan.c @@ -466,8 +466,14 @@ static void allocate_connections(lwan_t *l, size_t max_open_files) { l->conns = calloc(max_open_files, sizeof(lwan_connection_t)); - if (!l->conns) - lwan_status_critical_perror("calloc"); + if (!l->conns) goto err; + + l->proxies = calloc(max_open_files, sizeof(lwan_proxy_t)); + if (!l->proxies) goto err; + + return; + err: + lwan_status_critical_perror("calloc"); } static unsigned short int diff --git a/common/lwan.h b/common/lwan.h index 19236b494..63c12b8da 100644 --- a/common/lwan.h +++ b/common/lwan.h @@ -110,6 +110,7 @@ typedef struct lwan_thread_t_ lwan_thread_t; typedef struct lwan_url_map_t_ lwan_url_map_t; typedef struct lwan_value_t_ lwan_value_t; typedef struct lwan_config_t_ lwan_config_t; +typedef struct lwan_proxy_t_ lwan_proxy_t; typedef struct lwan_connection_t_ lwan_connection_t; typedef enum { @@ -150,13 +151,15 @@ typedef enum { REQUEST_METHOD_GET = 1<<0, REQUEST_METHOD_HEAD = 1<<1, REQUEST_METHOD_POST = 1<<2, - REQUEST_ACCEPT_DEFLATE = 1<<3, - REQUEST_ACCEPT_GZIP = 1<<4, - REQUEST_IS_HTTP_1_0 = 1<<5, - RESPONSE_SENT_HEADERS = 1<<6, - RESPONSE_CHUNKED_ENCODING = 1<<7, - RESPONSE_NO_CONTENT_LENGTH = 1<<8, - RESPONSE_URL_REWRITTEN = 1<<9 + REQUEST_METHOD_PROXY1 = 1<<3, + REQUEST_METHOD_PROXY2 = 1<<4, + REQUEST_ACCEPT_DEFLATE = 1<<5, + REQUEST_ACCEPT_GZIP = 1<<6, + REQUEST_IS_HTTP_1_0 = 1<<7, + RESPONSE_SENT_HEADERS = 1<<8, + RESPONSE_CHUNKED_ENCODING = 1<<9, + RESPONSE_NO_CONTENT_LENGTH = 1<<10, + RESPONSE_URL_REWRITTEN = 1<<11 } lwan_request_flags_t; typedef enum { @@ -165,7 +168,8 @@ typedef enum { CONN_IS_ALIVE = 1<<1, CONN_SHOULD_RESUME_CORO = 1<<2, CONN_WRITE_EVENTS = 1<<3, - CONN_MUST_READ = 1<<4 + CONN_MUST_READ = 1<<4, + CONN_PROXIED = 1<<5 } lwan_connection_flags_t; typedef enum { @@ -277,9 +281,18 @@ struct lwan_config_t_ { short unsigned int n_threads; }; +struct lwan_proxy_t_ { + union { + int64_t __ss_align; + struct sockaddr_in ipv4; + struct sockaddr_in6 ipv6; + } to, from; +}; + struct lwan_t_ { lwan_trie_t url_map_trie; lwan_connection_t *conns; + lwan_proxy_t *proxies; struct { lwan_thread_t *threads; diff --git a/tools/testsuite.py b/tools/testsuite.py index 9497e255f..254332df5 100755 --- a/tools/testsuite.py +++ b/tools/testsuite.py @@ -479,6 +479,31 @@ def test_cache_mmaps_once_even_after_timeout(self): requests.get('http://127.0.0.1:8080/100.html') self.assertEqual(self.count_mmaps('/100.html'), 1) +class TestProxyProtocolRequests(SocketTest): + def test_proxy_version1(self): + req = '''PROXY TCP4 192.168.0.1 192.168.0.11 56324 443\r +GET / HTTP/1.1\r +Host: 192.168.0.11\r\n\r\n''' + + sock = self.connect() + sock.send(req) + response = sock.recv(1024) + self.assertTrue(response.startswith('HTTP/1.1 200 OK'), response) + + def test_proxy_version2(self): + req = ( + "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A" + "\x21\x11\x00\x0B" + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B" + "GET / HTTP/1.1\r\n" + "Host: 192.168.0.11\r\n\r\n" + ) + + sock = self.connect() + sock.send(req) + response = sock.recv(1024) + self.assertTrue(response.startswith('HTTP/1.1 200 OK'), response) + class TestPipelinedRequests(SocketTest): def test_pipelined_requests(self): response_separator = re.compile('\r\n\r\n') From 67061dd2853dc6af08d9a08a5ca0ca5459b77cba Mon Sep 17 00:00:00 2001 From: Malthe Borch Date: Tue, 20 Oct 2015 14:12:20 +0200 Subject: [PATCH 0020/2505] PEP8 --- tools/testsuite.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/testsuite.py b/tools/testsuite.py index 254332df5..8d42fa639 100755 --- a/tools/testsuite.py +++ b/tools/testsuite.py @@ -23,9 +23,10 @@ class LwanTest(unittest.TestCase): def setUp(self): for spawn_try in range(20): - self.lwan=subprocess.Popen([LWAN_PATH], - stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - + self.lwan=subprocess.Popen( + [LWAN_PATH], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT + ) for request_try in range(20): try: requests.get('http://127.0.0.1:8080/hello') From d93bffe3e4789e2ed1e9afa4e6ef298fa1d6e8a8 Mon Sep 17 00:00:00 2001 From: Malthe Borch Date: Tue, 20 Oct 2015 14:12:39 +0200 Subject: [PATCH 0021/2505] Wait until port is open, no need to make a request --- tools/testsuite.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tools/testsuite.py b/tools/testsuite.py index 8d42fa639..7c929090a 100755 --- a/tools/testsuite.py +++ b/tools/testsuite.py @@ -27,12 +27,15 @@ def setUp(self): [LWAN_PATH], stdout=subprocess.PIPE, stderr=subprocess.STDOUT ) + s = socket.socket() for request_try in range(20): try: - requests.get('http://127.0.0.1:8080/hello') - return - except requests.ConnectionError: + s.connect(('127.0.0.1', 8080)) + except: time.sleep(0.1) + else: + s.close() + return time.sleep(0.1) From 1ed0462a173bbdd3d133794688e730fd59ef8de6 Mon Sep 17 00:00:00 2001 From: Malthe Borch Date: Tue, 20 Oct 2015 16:06:46 +0200 Subject: [PATCH 0022/2505] Accept proxy protocol only when explicitly enabled --- common/lwan-request.c | 2 ++ common/lwan.c | 4 ++++ common/lwan.h | 1 + lwan.conf | 1 + 4 files changed, 8 insertions(+) diff --git a/common/lwan-request.c b/common/lwan-request.c index 28072bf26..0b9453146 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -100,6 +100,8 @@ get_http_method(const char *buffer) static lwan_request_flags_t parse_proxy_protocol(lwan_request_t *request, char **buffer, int version) { + if (!request->conn->thread->lwan->config.proxy_protocol) return 0; + union header_t_ { struct { char line[108]; diff --git a/common/lwan.c b/common/lwan.c index c91ce190a..27e37a6ff 100644 --- a/common/lwan.c +++ b/common/lwan.c @@ -48,6 +48,7 @@ static const lwan_config_t default_config = { .keep_alive_timeout = 15, .quiet = false, .reuse_port = false, + .proxy_protocol = false, .expires = 1 * ONE_WEEK, .n_threads = 0 }; @@ -399,6 +400,9 @@ static bool setup_from_config(lwan_t *lwan) else if (!strcmp(line.line.key, "reuse_port")) lwan->config.reuse_port = parse_bool(line.line.value, default_config.reuse_port); + else if (!strcmp(line.line.key, "proxy_protocol")) + lwan->config.proxy_protocol = parse_bool(line.line.value, + default_config.proxy_protocol); else if (!strcmp(line.line.key, "expires")) lwan->config.expires = parse_time_period(line.line.value, default_config.expires); diff --git a/common/lwan.h b/common/lwan.h index 63c12b8da..bcaa4ca3c 100644 --- a/common/lwan.h +++ b/common/lwan.h @@ -277,6 +277,7 @@ struct lwan_config_t_ { unsigned short keep_alive_timeout; bool quiet; bool reuse_port; + bool proxy_protocol; unsigned int expires; short unsigned int n_threads; }; diff --git a/lwan.conf b/lwan.conf index 2637af8f7..57e49fb70 100644 --- a/lwan.conf +++ b/lwan.conf @@ -1,6 +1,7 @@ keep_alive_timeout = 15 quiet = false reuse_port = false +proxy_protocol = true expires = 1M 1w threads = 0 From 5a0f0a18812cd54080eeec7b11c6e1b9a3a77386 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 25 Oct 2015 16:32:07 -0200 Subject: [PATCH 0023/2505] Move `lwan_proxy_t` to `lwan_request_t` This avoids some pointer indirections. --- common/lwan-request.c | 36 ++++++++++++++++++------------------ common/lwan.c | 10 ++-------- common/lwan.h | 19 +++++++------------ 3 files changed, 27 insertions(+), 38 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index 0b9453146..5e7c60ae9 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -134,7 +134,6 @@ parse_proxy_protocol(lwan_request_t *request, char **buffer, int version) }; unsigned int size; - lwan_proxy_t *proxy = &request->conn->thread->lwan->proxies[request->fd]; union header_t_ *hdr = (union header_t_ *) *buffer; if (version == 1) { @@ -168,8 +167,8 @@ parse_proxy_protocol(lwan_request_t *request, char **buffer, int version) do { long porttmp; - struct sockaddr_in *from = &proxy->from.ipv4; - struct sockaddr_in *to = &proxy->to.ipv4; + struct sockaddr_in *from = &request->proxy_from.ipv4; + struct sockaddr_in *to = &request->proxy_to.ipv4; from->sin_family = to->sin_family = AF_INET; @@ -199,8 +198,8 @@ parse_proxy_protocol(lwan_request_t *request, char **buffer, int version) do { long porttmp; - struct sockaddr_in6 *from = &proxy->from.ipv6; - struct sockaddr_in6 *to = &proxy->to.ipv6; + struct sockaddr_in6 *from = &request->proxy_from.ipv6; + struct sockaddr_in6 *to = &request->proxy_to.ipv6; from->sin6_family = to->sin6_family = AF_INET6; @@ -230,8 +229,8 @@ parse_proxy_protocol(lwan_request_t *request, char **buffer, int version) default: if (memcmp(protocol, "UNKNOWN", 7) != 0) return 0; - struct sockaddr_in *from = &proxy->from.ipv4; - struct sockaddr_in *to = &proxy->to.ipv4; + struct sockaddr_in *from = &request->proxy_from.ipv4; + struct sockaddr_in *to = &request->proxy_to.ipv4; from->sin_family = to->sin_family = AF_UNSPEC; } @@ -252,8 +251,8 @@ parse_proxy_protocol(lwan_request_t *request, char **buffer, int version) switch (hdr->v2.cmd) { case LOCAL: do { - struct sockaddr_in *from = &proxy->from.ipv4; - struct sockaddr_in *to = &proxy->to.ipv4; + struct sockaddr_in *from = &request->proxy_from.ipv4; + struct sockaddr_in *to = &request->proxy_to.ipv4; from->sin_family = to->sin_family = AF_UNSPEC; } while (0); @@ -268,8 +267,8 @@ parse_proxy_protocol(lwan_request_t *request, char **buffer, int version) switch (hdr->v2.fam) { case TCP4: do { - struct sockaddr_in *from = &proxy->from.ipv4; - struct sockaddr_in *to = &proxy->to.ipv4; + struct sockaddr_in *from = &request->proxy_from.ipv4; + struct sockaddr_in *to = &request->proxy_to.ipv4; to->sin_family = from->sin_family = AF_INET; @@ -282,8 +281,8 @@ parse_proxy_protocol(lwan_request_t *request, char **buffer, int version) break; case TCP6: do { - struct sockaddr_in6 *from = &proxy->from.ipv6; - struct sockaddr_in6 *to = &proxy->to.ipv6; + struct sockaddr_in6 *from = &request->proxy_from.ipv6; + struct sockaddr_in6 *to = &request->proxy_to.ipv6; from->sin6_family = to->sin6_family = AF_INET6; @@ -1117,14 +1116,15 @@ const char * lwan_request_get_remote_address(lwan_request_t *request, char buffer[static INET6_ADDRSTRLEN]) { - struct sockaddr_storage __sock_addr; - struct sockaddr_storage *sock_addr = &__sock_addr; + struct sockaddr_storage non_proxied_addr, *sock_addr; if (request->conn->flags & CONN_PROXIED) { - sock_addr = (struct sockaddr_storage *) - &request->conn->thread->lwan->proxies[request->fd].from; + sock_addr = (struct sockaddr_storage *)&request->proxy_from; } else { - socklen_t sock_len = sizeof(__sock_addr); + socklen_t sock_len = sizeof(non_proxied_addr); + + sock_addr = &non_proxied_addr; + if (UNLIKELY(getpeername(request->fd, (struct sockaddr *) sock_addr, &sock_len) < 0)) diff --git a/common/lwan.c b/common/lwan.c index 27e37a6ff..c8f00edb6 100644 --- a/common/lwan.c +++ b/common/lwan.c @@ -470,14 +470,8 @@ static void allocate_connections(lwan_t *l, size_t max_open_files) { l->conns = calloc(max_open_files, sizeof(lwan_connection_t)); - if (!l->conns) goto err; - - l->proxies = calloc(max_open_files, sizeof(lwan_proxy_t)); - if (!l->proxies) goto err; - - return; - err: - lwan_status_critical_perror("calloc"); + if (!l->conns) + lwan_status_critical_perror("calloc"); } static unsigned short int diff --git a/common/lwan.h b/common/lwan.h index bcaa4ca3c..762c8a47c 100644 --- a/common/lwan.h +++ b/common/lwan.h @@ -110,7 +110,6 @@ typedef struct lwan_thread_t_ lwan_thread_t; typedef struct lwan_url_map_t_ lwan_url_map_t; typedef struct lwan_value_t_ lwan_value_t; typedef struct lwan_config_t_ lwan_config_t; -typedef struct lwan_proxy_t_ lwan_proxy_t; typedef struct lwan_connection_t_ lwan_connection_t; typedef enum { @@ -218,6 +217,11 @@ struct lwan_request_t_ { lwan_value_t original_url; lwan_connection_t *conn; + struct { + struct sockaddr_in ipv4; + struct sockaddr_in6 ipv6; + } proxy_from, proxy_to; + struct { lwan_key_value_t *base; size_t len; @@ -275,25 +279,16 @@ struct lwan_thread_t_ { struct lwan_config_t_ { char *listener; unsigned short keep_alive_timeout; + unsigned int expires; + short unsigned int n_threads; bool quiet; bool reuse_port; bool proxy_protocol; - unsigned int expires; - short unsigned int n_threads; -}; - -struct lwan_proxy_t_ { - union { - int64_t __ss_align; - struct sockaddr_in ipv4; - struct sockaddr_in6 ipv6; - } to, from; }; struct lwan_t_ { lwan_trie_t url_map_trie; lwan_connection_t *conns; - lwan_proxy_t *proxies; struct { lwan_thread_t *threads; From af87ed2de892decd1f76dc9963c6ec586908490a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 25 Oct 2015 16:33:15 -0200 Subject: [PATCH 0024/2505] Fix compile warning in parse_proxy_protocol() --- common/lwan-request.c | 1 - 1 file changed, 1 deletion(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index 5e7c60ae9..363e15a89 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -316,7 +316,6 @@ parse_proxy_protocol(lwan_request_t *request, char **buffer, int version) static ALWAYS_INLINE char * identify_http_method(lwan_request_t *request, char *buffer) { - char *path; lwan_request_flags_t flags = get_http_method(buffer); if (flags == REQUEST_METHOD_PROXY1) From 22331c0f0ea81b954000d0bffb95b86062b40862 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 25 Oct 2015 16:34:22 -0200 Subject: [PATCH 0025/2505] Rename `header_t_` to `proxy_protocol_header` and make it global --- common/lwan-request.c | 70 +++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index 363e15a89..973b5bc0b 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -58,6 +58,37 @@ struct request_parser_helper { char connection; }; +union proxy_protocol_header { + struct { + char line[108]; + } v1; + struct { + uint8_t sig[8]; + uint8_t cmd : 4; + uint8_t ver : 4; + uint8_t fam; + uint16_t len; + union { + struct { + uint32_t src_addr; + uint32_t dst_addr; + uint16_t src_port; + uint16_t dst_port; + } ip4; + struct { + uint8_t src_addr[16]; + uint8_t dst_addr[16]; + uint16_t src_port; + uint16_t dst_port; + } ip6; + struct { + uint8_t src_addr[108]; + uint8_t dst_addr[108]; + } unx; + } addr; + } v2; +}; + static char decode_hex_digit(char ch) __attribute__((pure)); static bool is_hex_digit(char ch) __attribute__((pure)); static uint32_t has_zero_byte(uint32_t n) __attribute__((pure)); @@ -100,41 +131,10 @@ get_http_method(const char *buffer) static lwan_request_flags_t parse_proxy_protocol(lwan_request_t *request, char **buffer, int version) { - if (!request->conn->thread->lwan->config.proxy_protocol) return 0; - - union header_t_ { - struct { - char line[108]; - } v1; - struct { - uint8_t sig[8]; - uint8_t cmd : 4; - uint8_t ver : 4; - uint8_t fam; - uint16_t len; - union { - struct { - uint32_t src_addr; - uint32_t dst_addr; - uint16_t src_port; - uint16_t dst_port; - } ip4; - struct { - uint8_t src_addr[16]; - uint8_t dst_addr[16]; - uint16_t src_port; - uint16_t dst_port; - } ip6; - struct { - uint8_t src_addr[108]; - uint8_t dst_addr[108]; - } unx; - } addr; - } v2; - }; - unsigned int size; - union header_t_ *hdr = (union header_t_ *) *buffer; + union proxy_protocol_header *hdr = (union proxy_protocol_header *) *buffer; + + if (!request->conn->thread->lwan->config.proxy_protocol) return 0; if (version == 1) { char *end, *ptr, *protocol, *src_addr, *dst_addr, *src_port, *dst_port; @@ -241,7 +241,7 @@ parse_proxy_protocol(lwan_request_t *request, char **buffer, int version) if (version == 2) { size = 12 + (unsigned int) ntohs(hdr->v2.len); - if (size > sizeof(union header_t_)) return 0; + if (size > sizeof(union proxy_protocol_header)) return 0; enum { LOCAL = 0, From 7d8ca7a7052f349cd7a28bcd5388b6636c1fad97 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 25 Oct 2015 16:37:27 -0200 Subject: [PATCH 0026/2505] Split parse_proxy_protocol() in two functions, one for each version --- common/lwan-request.c | 287 +++++++++++++++++++++--------------------- 1 file changed, 144 insertions(+), 143 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index 973b5bc0b..dd443ac08 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -129,185 +129,186 @@ get_http_method(const char *buffer) } static lwan_request_flags_t -parse_proxy_protocol(lwan_request_t *request, char **buffer, int version) +parse_proxy_protocol_v1(lwan_request_t *request, char **buffer) { + char *end, *ptr, *protocol, *src_addr, *dst_addr, *src_port, *dst_port; unsigned int size; union proxy_protocol_header *hdr = (union proxy_protocol_header *) *buffer; if (!request->conn->thread->lwan->config.proxy_protocol) return 0; - if (version == 1) { - char *end, *ptr, *protocol, *src_addr, *dst_addr, *src_port, *dst_port; + end = memchr(hdr->v1.line, '\r', sizeof(*hdr)); + if (!end || end[1] != '\n') { + return 0; + } + + *end = '\0'; + size = (unsigned int) (end + 2 - hdr->v1.line); + + ptr = hdr->v1.line; + strsep(&ptr, " "); + + protocol = strsep(&ptr, " "); + src_addr = strsep(&ptr, " "); + dst_addr = strsep(&ptr, " "); + src_port = strsep(&ptr, " "); + dst_port = ptr; + + if (protocol != NULL && dst_port != NULL) { + enum { + TCP4 = MULTICHAR_CONSTANT('T', 'C', 'P', '4'), + TCP6 = MULTICHAR_CONSTANT('T', 'C', 'P', '6') + }; + + STRING_SWITCH(protocol) { + case TCP4: + do { + long porttmp; + + struct sockaddr_in *from = &request->proxy_from.ipv4; + struct sockaddr_in *to = &request->proxy_to.ipv4; + + from->sin_family = to->sin_family = AF_INET; + + if (inet_pton(AF_INET, src_addr, &from->sin_addr) != 1) { + return 0; + } + + if (inet_pton(AF_INET, dst_addr, &to->sin_addr) != 1) { + return 0; + } + + porttmp = strtol(src_port, NULL, 10); + if (!(porttmp > 0 && porttmp < 65536)) { + return 0; + } + from->sin_port = htons((uint16_t) porttmp); + + porttmp = strtol(dst_port, NULL, 10); + if (!(porttmp > 0 && porttmp < 65536)) { + return 0; + } - end = memchr(hdr->v1.line, '\r', sizeof(*hdr)); - if (!end || end[1] != '\n') { - return 0; + to->sin_port = htons((uint16_t) porttmp); + } while (0); + break; + case TCP6: + do { + long porttmp; + + struct sockaddr_in6 *from = &request->proxy_from.ipv6; + struct sockaddr_in6 *to = &request->proxy_to.ipv6; + + from->sin6_family = to->sin6_family = AF_INET6; + + if (inet_pton(AF_INET6, src_addr, &from->sin6_addr) != 1) { + return 0; + } + + if (inet_pton(AF_INET6, dst_addr, &to->sin6_addr) != 1) { + return 0; + } + + porttmp = strtol(src_port, NULL, 10); + if (!(porttmp > 0 && porttmp < 65536)) { + return 0; + } + + from->sin6_port = htons((uint16_t) porttmp); + + porttmp = strtol(dst_port, NULL, 10); + if (!(porttmp > 0 && porttmp < 65536)) { + return 0; + } + + to->sin6_port = htons((uint16_t) porttmp); + } while (0); + break; + default: + if (memcmp(protocol, "UNKNOWN", 7) != 0) return 0; + + struct sockaddr_in *from = &request->proxy_from.ipv4; + struct sockaddr_in *to = &request->proxy_to.ipv4; + + from->sin_family = to->sin_family = AF_UNSPEC; } + } - *end = '\0'; - size = (unsigned int) (end + 2 - hdr->v1.line); + request->conn->flags |= CONN_PROXIED; + *buffer += size; + return get_http_method(*buffer); +} - ptr = hdr->v1.line; - strsep(&ptr, " "); +static lwan_request_flags_t +parse_proxy_protocol_v2(lwan_request_t *request, char **buffer) +{ + unsigned int size; + union proxy_protocol_header *hdr = (union proxy_protocol_header *) *buffer; - protocol = strsep(&ptr, " "); - src_addr = strsep(&ptr, " "); - dst_addr = strsep(&ptr, " "); - src_port = strsep(&ptr, " "); - dst_port = ptr; + if (!request->conn->thread->lwan->config.proxy_protocol) return 0; + + size = 12 + (unsigned int) ntohs(hdr->v2.len); + if (size > sizeof(union proxy_protocol_header)) return 0; + + enum { + LOCAL = 0, + PROXY = 1 + }; - if (protocol != NULL && dst_port != NULL) { + switch (hdr->v2.cmd) { + case LOCAL: + do { + struct sockaddr_in *from = &request->proxy_from.ipv4; + struct sockaddr_in *to = &request->proxy_to.ipv4; + + from->sin_family = to->sin_family = AF_UNSPEC; + } while (0); + break; + case PROXY: + do { enum { - TCP4 = MULTICHAR_CONSTANT('T', 'C', 'P', '4'), - TCP6 = MULTICHAR_CONSTANT('T', 'C', 'P', '6') + TCP4 = 0x11, + TCP6 = 0x21 }; - STRING_SWITCH(protocol) { + switch (hdr->v2.fam) { case TCP4: do { - long porttmp; - struct sockaddr_in *from = &request->proxy_from.ipv4; struct sockaddr_in *to = &request->proxy_to.ipv4; - from->sin_family = to->sin_family = AF_INET; + to->sin_family = from->sin_family = AF_INET; - if (inet_pton(AF_INET, src_addr, &from->sin_addr) != 1) { - return 0; - } + from->sin_addr.s_addr = hdr->v2.addr.ip4.src_addr; + from->sin_port = hdr->v2.addr.ip4.src_port; - if (inet_pton(AF_INET, dst_addr, &to->sin_addr) != 1) { - return 0; - } - - porttmp = strtol(src_port, NULL, 10); - if (!(porttmp > 0 && porttmp < 65536)) { - return 0; - } - from->sin_port = htons((uint16_t) porttmp); - - porttmp = strtol(dst_port, NULL, 10); - if (!(porttmp > 0 && porttmp < 65536)) { - return 0; - } - - to->sin_port = htons((uint16_t) porttmp); + to->sin_addr.s_addr = hdr->v2.addr.ip4.dst_addr; + to->sin_port = hdr->v2.addr.ip4.dst_port; } while (0); break; case TCP6: do { - long porttmp; - struct sockaddr_in6 *from = &request->proxy_from.ipv6; struct sockaddr_in6 *to = &request->proxy_to.ipv6; from->sin6_family = to->sin6_family = AF_INET6; - if (inet_pton(AF_INET6, src_addr, &from->sin6_addr) != 1) { - return 0; - } - - if (inet_pton(AF_INET6, dst_addr, &to->sin6_addr) != 1) { - return 0; - } - - porttmp = strtol(src_port, NULL, 10); - if (!(porttmp > 0 && porttmp < 65536)) { - return 0; - } - - from->sin6_port = htons((uint16_t) porttmp); + memcpy(&from->sin6_addr, hdr->v2.addr.ip6.src_addr, 16); + from->sin6_port = hdr->v2.addr.ip6.src_port; - porttmp = strtol(dst_port, NULL, 10); - if (!(porttmp > 0 && porttmp < 65536)) { - return 0; - } - - to->sin6_port = htons((uint16_t) porttmp); + memcpy(&to->sin6_addr, hdr->v2.addr.ip6.dst_addr, 16); + to->sin6_port = hdr->v2.addr.ip6.dst_port; } while (0); break; default: - if (memcmp(protocol, "UNKNOWN", 7) != 0) return 0; - - struct sockaddr_in *from = &request->proxy_from.ipv4; - struct sockaddr_in *to = &request->proxy_to.ipv4; - - from->sin_family = to->sin_family = AF_UNSPEC; + return 0; } - } - - goto done; - } - - if (version == 2) { - size = 12 + (unsigned int) ntohs(hdr->v2.len); - if (size > sizeof(union proxy_protocol_header)) return 0; - - enum { - LOCAL = 0, - PROXY = 1 - }; - - switch (hdr->v2.cmd) { - case LOCAL: - do { - struct sockaddr_in *from = &request->proxy_from.ipv4; - struct sockaddr_in *to = &request->proxy_to.ipv4; - - from->sin_family = to->sin_family = AF_UNSPEC; - } while (0); - break; - case PROXY: - do { - enum { - TCP4 = 0x11, - TCP6 = 0x21 - }; - - switch (hdr->v2.fam) { - case TCP4: - do { - struct sockaddr_in *from = &request->proxy_from.ipv4; - struct sockaddr_in *to = &request->proxy_to.ipv4; - - to->sin_family = from->sin_family = AF_INET; - - from->sin_addr.s_addr = hdr->v2.addr.ip4.src_addr; - from->sin_port = hdr->v2.addr.ip4.src_port; - - to->sin_addr.s_addr = hdr->v2.addr.ip4.dst_addr; - to->sin_port = hdr->v2.addr.ip4.dst_port; - } while (0); - break; - case TCP6: - do { - struct sockaddr_in6 *from = &request->proxy_from.ipv6; - struct sockaddr_in6 *to = &request->proxy_to.ipv6; - - from->sin6_family = to->sin6_family = AF_INET6; - - memcpy(&from->sin6_addr, hdr->v2.addr.ip6.src_addr, 16); - from->sin6_port = hdr->v2.addr.ip6.src_port; - - memcpy(&to->sin6_addr, hdr->v2.addr.ip6.dst_addr, 16); - to->sin6_port = hdr->v2.addr.ip6.dst_port; - } while (0); - break; - default: - return 0; - } - } while (0); - break; - default: - return 0; - } - - goto done; + } while (0); + break; + default: + return 0; } - return 0; - - done: request->conn->flags |= CONN_PROXIED; *buffer += size; return get_http_method(*buffer); @@ -319,10 +320,10 @@ identify_http_method(lwan_request_t *request, char *buffer) lwan_request_flags_t flags = get_http_method(buffer); if (flags == REQUEST_METHOD_PROXY1) - flags = parse_proxy_protocol(request, &buffer, 1); + flags = parse_proxy_protocol_v1(request, &buffer); if (flags == REQUEST_METHOD_PROXY2) - flags = parse_proxy_protocol(request, &buffer, 2); + flags = parse_proxy_protocol_v2(request, &buffer); static const char sizes[] = { [0] = 0, From d50c0364d4a78e4d446bb3b35670284041cb20a3 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 25 Oct 2015 16:56:19 -0200 Subject: [PATCH 0027/2505] Move proxy-parsing stuff to their respective functions --- common/lwan-request.c | 132 +++++++++++++++++++++--------------------- 1 file changed, 65 insertions(+), 67 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index dd443ac08..d1c87420b 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -104,9 +104,6 @@ get_http_method(const char *buffer) HTTP_STR_GET = MULTICHAR_CONSTANT('G','E','T',' '), HTTP_STR_HEAD = MULTICHAR_CONSTANT('H','E','A','D'), HTTP_STR_POST = MULTICHAR_CONSTANT('P','O','S','T'), - HTTP_STR_PROXY1 = MULTICHAR_CONSTANT('P','R','O','X'), - HTTP_STR_PROXY21 = MULTICHAR_CONSTANT('\x00','\x0D','\x0A','\x51'), - HTTP_STR_PROXY22 = MULTICHAR_CONSTANT('\x55','\x49','\x54','\x0A') }; STRING_SWITCH(buffer) { @@ -116,26 +113,17 @@ get_http_method(const char *buffer) return REQUEST_METHOD_HEAD; case HTTP_STR_POST: return REQUEST_METHOD_POST; - case HTTP_STR_PROXY1: - if (buffer[4] != 'Y') break; - return REQUEST_METHOD_PROXY1; - case HTTP_STR_PROXY21: - if ((string_as_int32(buffer + 4) == HTTP_STR_PROXY22) && - (*(buffer + 8) >> 4 == 2)) - return REQUEST_METHOD_PROXY2; } return 0; } -static lwan_request_flags_t -parse_proxy_protocol_v1(lwan_request_t *request, char **buffer) +static char * +parse_proxy_protocol_v1(lwan_request_t *request, char *buffer) { + union proxy_protocol_header *hdr = (union proxy_protocol_header *) buffer; char *end, *ptr, *protocol, *src_addr, *dst_addr, *src_port, *dst_port; unsigned int size; - union proxy_protocol_header *hdr = (union proxy_protocol_header *) *buffer; - - if (!request->conn->thread->lwan->config.proxy_protocol) return 0; end = memchr(hdr->v1.line, '\r', sizeof(*hdr)); if (!end || end[1] != '\n') { @@ -170,24 +158,19 @@ parse_proxy_protocol_v1(lwan_request_t *request, char **buffer) from->sin_family = to->sin_family = AF_INET; - if (inet_pton(AF_INET, src_addr, &from->sin_addr) != 1) { - return 0; - } - - if (inet_pton(AF_INET, dst_addr, &to->sin_addr) != 1) { - return 0; - } + if (inet_pton(AF_INET, src_addr, &from->sin_addr) != 1) + return NULL; + if (inet_pton(AF_INET, dst_addr, &to->sin_addr) != 1) + return NULL; porttmp = strtol(src_port, NULL, 10); - if (!(porttmp > 0 && porttmp < 65536)) { - return 0; - } + if (!(porttmp > 0 && porttmp < 65536)) + return NULL; from->sin_port = htons((uint16_t) porttmp); porttmp = strtol(dst_port, NULL, 10); - if (!(porttmp > 0 && porttmp < 65536)) { - return 0; - } + if (!(porttmp > 0 && porttmp < 65536)) + return NULL; to->sin_port = htons((uint16_t) porttmp); } while (0); @@ -201,26 +184,19 @@ parse_proxy_protocol_v1(lwan_request_t *request, char **buffer) from->sin6_family = to->sin6_family = AF_INET6; - if (inet_pton(AF_INET6, src_addr, &from->sin6_addr) != 1) { - return 0; - } - - if (inet_pton(AF_INET6, dst_addr, &to->sin6_addr) != 1) { - return 0; - } + if (inet_pton(AF_INET6, src_addr, &from->sin6_addr) != 1) + return NULL; + if (inet_pton(AF_INET6, dst_addr, &to->sin6_addr) != 1) + return NULL; porttmp = strtol(src_port, NULL, 10); - if (!(porttmp > 0 && porttmp < 65536)) { - return 0; - } - + if (!(porttmp > 0 && porttmp < 65536)) + return NULL; from->sin6_port = htons((uint16_t) porttmp); porttmp = strtol(dst_port, NULL, 10); - if (!(porttmp > 0 && porttmp < 65536)) { - return 0; - } - + if (!(porttmp > 0 && porttmp < 65536)) + return NULL; to->sin6_port = htons((uint16_t) porttmp); } while (0); break; @@ -234,21 +210,18 @@ parse_proxy_protocol_v1(lwan_request_t *request, char **buffer) } } - request->conn->flags |= CONN_PROXIED; - *buffer += size; - return get_http_method(*buffer); + return buffer + size; } -static lwan_request_flags_t -parse_proxy_protocol_v2(lwan_request_t *request, char **buffer) +static char * +parse_proxy_protocol_v2(lwan_request_t *request, char *buffer) { + union proxy_protocol_header *hdr = (union proxy_protocol_header *)buffer; unsigned int size; - union proxy_protocol_header *hdr = (union proxy_protocol_header *) *buffer; - - if (!request->conn->thread->lwan->config.proxy_protocol) return 0; size = 12 + (unsigned int) ntohs(hdr->v2.len); - if (size > sizeof(union proxy_protocol_header)) return 0; + if (size > sizeof(union proxy_protocol_header)) + return NULL; enum { LOCAL = 0, @@ -301,36 +274,27 @@ parse_proxy_protocol_v2(lwan_request_t *request, char **buffer) } while (0); break; default: - return 0; + return NULL; } } while (0); break; default: - return 0; + return NULL; } - request->conn->flags |= CONN_PROXIED; - *buffer += size; - return get_http_method(*buffer); + return buffer + size; } static ALWAYS_INLINE char * identify_http_method(lwan_request_t *request, char *buffer) { - lwan_request_flags_t flags = get_http_method(buffer); - - if (flags == REQUEST_METHOD_PROXY1) - flags = parse_proxy_protocol_v1(request, &buffer); - - if (flags == REQUEST_METHOD_PROXY2) - flags = parse_proxy_protocol_v2(request, &buffer); - static const char sizes[] = { [0] = 0, [REQUEST_METHOD_GET] = sizeof("GET ") - 1, [REQUEST_METHOD_HEAD] = sizeof("HEAD ") - 1, [REQUEST_METHOD_POST] = sizeof("POST ") - 1, }; + lwan_request_flags_t flags = get_http_method(buffer); request->flags |= flags; return buffer + sizes[flags]; } @@ -897,11 +861,45 @@ read_post_data(lwan_request_t *request __attribute__((unused)), return HTTP_NOT_IMPLEMENTED; } +static int +identify_proxy_version(char *buffer) +{ + enum { + HTTP_PROXY_VER1 = MULTICHAR_CONSTANT('P','R','O','X'), + HTTP_PROXY_VER2 = MULTICHAR_CONSTANT('\x00','\x0D','\x0A','\x51'), + HTTP_PROXY_VER2_ = MULTICHAR_CONSTANT('\x55','\x49','\x54','\x0A') + }; + + STRING_SWITCH(buffer) { + case HTTP_PROXY_VER1: + return 1; + case HTTP_PROXY_VER2: + if (*(buffer + 8) >> 4 == 2) + return 2; + } + + return 0; +} + static lwan_http_status_t -parse_http_request(lwan_request_t *request, struct request_parser_helper *helper) +parse_http_request(lwan_t *l, lwan_request_t *request, struct request_parser_helper *helper) { char *buffer = ignore_leading_whitespace(helper->buffer->value); + if (l->config.proxy_protocol) { + int proxy_version; + + proxy_version = identify_proxy_version(buffer); + + if (proxy_version == 1) + buffer = parse_proxy_protocol_v1(request, buffer); + else if (proxy_version == 2) + buffer = parse_proxy_protocol_v2(request, buffer); + + if (UNLIKELY(!buffer)) + return HTTP_BAD_REQUEST; + } + char *path = identify_http_method(request, buffer); if (UNLIKELY(buffer == path)) { if (UNLIKELY(!*buffer)) @@ -1025,7 +1023,7 @@ lwan_process_request(lwan_t *l, lwan_request_t *request, __builtin_unreachable(); } - status = parse_http_request(request, &helper); + status = parse_http_request(l, request, &helper); if (UNLIKELY(status != HTTP_OK)) { lwan_default_response(request, status); goto out; From a36cfb3e842e4018238e776b68156f710a6b3d6c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 25 Oct 2015 17:38:23 -0200 Subject: [PATCH 0028/2505] Reduce branching while parsing proxy protocol stuff --- common/lwan-request.c | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index d1c87420b..e794c230e 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -210,6 +210,7 @@ parse_proxy_protocol_v1(lwan_request_t *request, char *buffer) } } + request->conn->flags |= CONN_PROXIED; return buffer + size; } @@ -219,6 +220,9 @@ parse_proxy_protocol_v2(lwan_request_t *request, char *buffer) union proxy_protocol_header *hdr = (union proxy_protocol_header *)buffer; unsigned int size; + if (UNLIKELY(*(buffer + 8) >> 4 != 2)) + return NULL; + size = 12 + (unsigned int) ntohs(hdr->v2.len); if (size > sizeof(union proxy_protocol_header)) return NULL; @@ -282,6 +286,7 @@ parse_proxy_protocol_v2(lwan_request_t *request, char *buffer) return NULL; } + request->conn->flags |= CONN_PROXIED; return buffer + size; } @@ -861,24 +866,22 @@ read_post_data(lwan_request_t *request __attribute__((unused)), return HTTP_NOT_IMPLEMENTED; } -static int -identify_proxy_version(char *buffer) +static char * +parse_proxy_protocol(lwan_request_t *request, char *buffer) { enum { - HTTP_PROXY_VER1 = MULTICHAR_CONSTANT('P','R','O','X'), + HTTP_PROXY_VER1 = MULTICHAR_CONSTANT('P','R','O','X'), HTTP_PROXY_VER2 = MULTICHAR_CONSTANT('\x00','\x0D','\x0A','\x51'), - HTTP_PROXY_VER2_ = MULTICHAR_CONSTANT('\x55','\x49','\x54','\x0A') }; STRING_SWITCH(buffer) { case HTTP_PROXY_VER1: - return 1; + return parse_proxy_protocol_v1(request, buffer); case HTTP_PROXY_VER2: - if (*(buffer + 8) >> 4 == 2) - return 2; + return parse_proxy_protocol_v2(request, buffer); } - return 0; + return buffer; } static lwan_http_status_t @@ -887,14 +890,7 @@ parse_http_request(lwan_t *l, lwan_request_t *request, struct request_parser_hel char *buffer = ignore_leading_whitespace(helper->buffer->value); if (l->config.proxy_protocol) { - int proxy_version; - - proxy_version = identify_proxy_version(buffer); - - if (proxy_version == 1) - buffer = parse_proxy_protocol_v1(request, buffer); - else if (proxy_version == 2) - buffer = parse_proxy_protocol_v2(request, buffer); + buffer = parse_proxy_protocol(request, buffer); if (UNLIKELY(!buffer)) return HTTP_BAD_REQUEST; From 0cc3ab3125aa2cc92cac63dfb83a7c6841fcd3eb Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 25 Oct 2015 17:05:04 -0200 Subject: [PATCH 0029/2505] Clean up parse_proxy_protocol_v1() --- common/lwan-request.c | 158 +++++++++++++++++++++++------------------- 1 file changed, 88 insertions(+), 70 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index e794c230e..c5568842c 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -118,96 +118,114 @@ get_http_method(const char *buffer) return 0; } +static bool +parse_ascii_port(char *port, unsigned short *out) +{ + unsigned long parsed; + char *end_ptr; + + errno = 0; + parsed = strtoul(port, &end_ptr, 10); + + if (UNLIKELY(errno != 0)) + return false; + + if (UNLIKELY(*end_ptr != '\0')) + return false; + + if (UNLIKELY((unsigned long)(unsigned short)parsed != parsed)) + return false; + + *out = htons((unsigned short)parsed); + return true; +} + +static char * +strsep_char(char *strp, char delim) +{ + char *ptr; + + if (UNLIKELY(!strp)) + return NULL; + + ptr = strchr(strp, delim); + if (UNLIKELY(!ptr)) + return NULL; + + *ptr = '\0'; + return ptr + 1; +} + static char * parse_proxy_protocol_v1(lwan_request_t *request, char *buffer) { union proxy_protocol_header *hdr = (union proxy_protocol_header *) buffer; - char *end, *ptr, *protocol, *src_addr, *dst_addr, *src_port, *dst_port; + char *end, *protocol, *src_addr, *dst_addr, *src_port, *dst_port; unsigned int size; - end = memchr(hdr->v1.line, '\r', sizeof(*hdr)); - if (!end || end[1] != '\n') { - return 0; - } - + end = memchr(hdr->v1.line, '\r', sizeof(hdr->v1.line)); + if (UNLIKELY(!end || end[1] != '\n')) + return NULL; *end = '\0'; size = (unsigned int) (end + 2 - hdr->v1.line); - ptr = hdr->v1.line; - strsep(&ptr, " "); + protocol = hdr->v1.line + sizeof("PROXY ") - 1; + src_addr = strsep_char(protocol, ' '); + dst_addr = strsep_char(src_addr, ' '); + src_port = strsep_char(dst_addr, ' '); + dst_port = strsep_char(src_port, ' '); - protocol = strsep(&ptr, " "); - src_addr = strsep(&ptr, " "); - dst_addr = strsep(&ptr, " "); - src_port = strsep(&ptr, " "); - dst_port = ptr; + if (UNLIKELY(!dst_port)) + return NULL; - if (protocol != NULL && dst_port != NULL) { - enum { - TCP4 = MULTICHAR_CONSTANT('T', 'C', 'P', '4'), - TCP6 = MULTICHAR_CONSTANT('T', 'C', 'P', '6') - }; + enum { + TCP4 = MULTICHAR_CONSTANT('T', 'C', 'P', '4'), + TCP6 = MULTICHAR_CONSTANT('T', 'C', 'P', '6') + }; - STRING_SWITCH(protocol) { - case TCP4: - do { - long porttmp; + STRING_SWITCH(protocol) { + case TCP4: { + struct sockaddr_in *from = &request->proxy_from.ipv4; + struct sockaddr_in *to = &request->proxy_to.ipv4; - struct sockaddr_in *from = &request->proxy_from.ipv4; - struct sockaddr_in *to = &request->proxy_to.ipv4; + from->sin_family = to->sin_family = AF_INET; - from->sin_family = to->sin_family = AF_INET; + if (inet_pton(AF_INET, src_addr, &from->sin_addr) != 1) + return NULL; + if (inet_pton(AF_INET, dst_addr, &to->sin_addr) != 1) + return NULL; + if (!parse_ascii_port(src_port, &from->sin_port)) + return NULL; + if (!parse_ascii_port(dst_port, &to->sin_port)) + return NULL; - if (inet_pton(AF_INET, src_addr, &from->sin_addr) != 1) - return NULL; - if (inet_pton(AF_INET, dst_addr, &to->sin_addr) != 1) - return NULL; + break; + } + case TCP6: { + struct sockaddr_in6 *from = &request->proxy_from.ipv6; + struct sockaddr_in6 *to = &request->proxy_to.ipv6; - porttmp = strtol(src_port, NULL, 10); - if (!(porttmp > 0 && porttmp < 65536)) - return NULL; - from->sin_port = htons((uint16_t) porttmp); + from->sin6_family = to->sin6_family = AF_INET6; - porttmp = strtol(dst_port, NULL, 10); - if (!(porttmp > 0 && porttmp < 65536)) - return NULL; + if (inet_pton(AF_INET6, src_addr, &from->sin6_addr) != 1) + return NULL; + if (inet_pton(AF_INET6, dst_addr, &to->sin6_addr) != 1) + return NULL; + if (!parse_ascii_port(src_port, &from->sin6_port)) + return NULL; + if (!parse_ascii_port(dst_port, &to->sin6_port)) + return NULL; - to->sin_port = htons((uint16_t) porttmp); - } while (0); - break; - case TCP6: - do { - long porttmp; - - struct sockaddr_in6 *from = &request->proxy_from.ipv6; - struct sockaddr_in6 *to = &request->proxy_to.ipv6; - - from->sin6_family = to->sin6_family = AF_INET6; - - if (inet_pton(AF_INET6, src_addr, &from->sin6_addr) != 1) - return NULL; - if (inet_pton(AF_INET6, dst_addr, &to->sin6_addr) != 1) - return NULL; - - porttmp = strtol(src_port, NULL, 10); - if (!(porttmp > 0 && porttmp < 65536)) - return NULL; - from->sin6_port = htons((uint16_t) porttmp); - - porttmp = strtol(dst_port, NULL, 10); - if (!(porttmp > 0 && porttmp < 65536)) - return NULL; - to->sin6_port = htons((uint16_t) porttmp); - } while (0); - break; - default: - if (memcmp(protocol, "UNKNOWN", 7) != 0) return 0; + break; + } + default: + if (memcmp(protocol, "UNKNOWN", 7) != 0) + return NULL; - struct sockaddr_in *from = &request->proxy_from.ipv4; - struct sockaddr_in *to = &request->proxy_to.ipv4; + struct sockaddr_in *from = &request->proxy_from.ipv4; + struct sockaddr_in *to = &request->proxy_to.ipv4; - from->sin_family = to->sin_family = AF_UNSPEC; - } + from->sin_family = to->sin_family = AF_UNSPEC; } request->conn->flags |= CONN_PROXIED; From ee5a8e041b5f1c195d1cefd8b6f77abee82dadeb Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 25 Oct 2015 17:51:48 -0200 Subject: [PATCH 0030/2505] Clean up parse_proxy_protocol_v2() --- common/lwan-request.c | 94 +++++++++++++++++++------------------------ 1 file changed, 41 insertions(+), 53 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index c5568842c..26834aa43 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -236,69 +236,57 @@ static char * parse_proxy_protocol_v2(lwan_request_t *request, char *buffer) { union proxy_protocol_header *hdr = (union proxy_protocol_header *)buffer; + const unsigned int proto_signature_length = 12; unsigned int size; - if (UNLIKELY(*(buffer + 8) >> 4 != 2)) - return NULL; - - size = 12 + (unsigned int) ntohs(hdr->v2.len); - if (size > sizeof(union proxy_protocol_header)) - return NULL; - enum { LOCAL = 0, - PROXY = 1 + PROXY = 1, + TCP4 = 0x11, + TCP6 = 0x21 }; + size = proto_signature_length + (unsigned int) ntohs(hdr->v2.len); + if (UNLIKELY(size > sizeof(hdr->v2))) + return NULL; + + if (UNLIKELY((hdr->v2.ver) != 2)) + return NULL; + switch (hdr->v2.cmd) { - case LOCAL: - do { - struct sockaddr_in *from = &request->proxy_from.ipv4; - struct sockaddr_in *to = &request->proxy_to.ipv4; + case LOCAL: { + struct sockaddr_in *from = &request->proxy_from.ipv4; + struct sockaddr_in *to = &request->proxy_to.ipv4; - from->sin_family = to->sin_family = AF_UNSPEC; - } while (0); + from->sin_family = to->sin_family = AF_UNSPEC; break; + } case PROXY: - do { - enum { - TCP4 = 0x11, - TCP6 = 0x21 - }; - - switch (hdr->v2.fam) { - case TCP4: - do { - struct sockaddr_in *from = &request->proxy_from.ipv4; - struct sockaddr_in *to = &request->proxy_to.ipv4; - - to->sin_family = from->sin_family = AF_INET; - - from->sin_addr.s_addr = hdr->v2.addr.ip4.src_addr; - from->sin_port = hdr->v2.addr.ip4.src_port; - - to->sin_addr.s_addr = hdr->v2.addr.ip4.dst_addr; - to->sin_port = hdr->v2.addr.ip4.dst_port; - } while (0); - break; - case TCP6: - do { - struct sockaddr_in6 *from = &request->proxy_from.ipv6; - struct sockaddr_in6 *to = &request->proxy_to.ipv6; - - from->sin6_family = to->sin6_family = AF_INET6; - - memcpy(&from->sin6_addr, hdr->v2.addr.ip6.src_addr, 16); - from->sin6_port = hdr->v2.addr.ip6.src_port; - - memcpy(&to->sin6_addr, hdr->v2.addr.ip6.dst_addr, 16); - to->sin6_port = hdr->v2.addr.ip6.dst_port; - } while (0); - break; - default: - return NULL; - } - } while (0); + if (hdr->v2.fam == TCP4) { + struct sockaddr_in *from = &request->proxy_from.ipv4; + struct sockaddr_in *to = &request->proxy_to.ipv4; + + to->sin_family = from->sin_family = AF_INET; + + from->sin_addr.s_addr = hdr->v2.addr.ip4.src_addr; + from->sin_port = hdr->v2.addr.ip4.src_port; + + to->sin_addr.s_addr = hdr->v2.addr.ip4.dst_addr; + to->sin_port = hdr->v2.addr.ip4.dst_port; + } else if (hdr->v2.fam == TCP6) { + struct sockaddr_in6 *from = &request->proxy_from.ipv6; + struct sockaddr_in6 *to = &request->proxy_to.ipv6; + + from->sin6_family = to->sin6_family = AF_INET6; + + memcpy(&from->sin6_addr, hdr->v2.addr.ip6.src_addr, 16); + from->sin6_port = hdr->v2.addr.ip6.src_port; + + memcpy(&to->sin6_addr, hdr->v2.addr.ip6.dst_addr, 16); + to->sin6_port = hdr->v2.addr.ip6.dst_port; + } else { + return NULL; + } break; default: return NULL; From f103e1f24449f8539971ca41f192554839f52784 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 25 Oct 2015 18:01:28 -0200 Subject: [PATCH 0031/2505] Use sizeof(in6_addr) instead of hardcoded 16 --- common/lwan-request.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index 26834aa43..a7ae36aa7 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -279,10 +279,10 @@ parse_proxy_protocol_v2(lwan_request_t *request, char *buffer) from->sin6_family = to->sin6_family = AF_INET6; - memcpy(&from->sin6_addr, hdr->v2.addr.ip6.src_addr, 16); + memcpy(&from->sin6_addr, hdr->v2.addr.ip6.src_addr, sizeof(from->sin6_addr)); from->sin6_port = hdr->v2.addr.ip6.src_port; - memcpy(&to->sin6_addr, hdr->v2.addr.ip6.dst_addr, 16); + memcpy(&to->sin6_addr, hdr->v2.addr.ip6.dst_addr, sizeof(to->sin6_addr)); to->sin6_port = hdr->v2.addr.ip6.dst_port; } else { return NULL; From 4b769f43f9769fdae61a72c02c87eff6aaca5c62 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 25 Oct 2015 18:44:27 -0200 Subject: [PATCH 0032/2505] Properly handle UNKNOWN protocol in V1 proxy headers --- common/lwan-request.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index a7ae36aa7..695542529 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -180,7 +180,8 @@ parse_proxy_protocol_v1(lwan_request_t *request, char *buffer) enum { TCP4 = MULTICHAR_CONSTANT('T', 'C', 'P', '4'), - TCP6 = MULTICHAR_CONSTANT('T', 'C', 'P', '6') + TCP6 = MULTICHAR_CONSTANT('T', 'C', 'P', '6'), + UNKN = MULTICHAR_CONSTANT('U', 'N', 'K', 'N') }; STRING_SWITCH(protocol) { @@ -218,14 +219,11 @@ parse_proxy_protocol_v1(lwan_request_t *request, char *buffer) break; } + case UNKN: + request->conn->flags &= ~CONN_PROXIED; + return buffer + size; default: - if (memcmp(protocol, "UNKNOWN", 7) != 0) - return NULL; - - struct sockaddr_in *from = &request->proxy_from.ipv4; - struct sockaddr_in *to = &request->proxy_to.ipv4; - - from->sin_family = to->sin_family = AF_UNSPEC; + return NULL; } request->conn->flags |= CONN_PROXIED; From b66fec93f04fdb99c4b10f86a9ba0fc1e243c9e5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 25 Oct 2015 19:09:56 -0200 Subject: [PATCH 0033/2505] Reduce size of union proxy_protocol_header --- common/lwan-request.c | 22 +++++++++++----------- tools/testsuite.py | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index 695542529..9e0098b6d 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -60,7 +60,7 @@ struct request_parser_helper { union proxy_protocol_header { struct { - char line[108]; + char line[56]; } v1; struct { uint8_t sig[8]; @@ -70,20 +70,20 @@ union proxy_protocol_header { uint16_t len; union { struct { - uint32_t src_addr; - uint32_t dst_addr; - uint16_t src_port; - uint16_t dst_port; + uint32_t src_addr; + uint32_t dst_addr; + uint16_t src_port; + uint16_t dst_port; } ip4; struct { - uint8_t src_addr[16]; - uint8_t dst_addr[16]; - uint16_t src_port; - uint16_t dst_port; + uint8_t src_addr[sizeof(struct in6_addr)]; + uint8_t dst_addr[sizeof(struct in6_addr)]; + uint16_t src_port; + uint16_t dst_port; } ip6; struct { - uint8_t src_addr[108]; - uint8_t dst_addr[108]; + uint8_t src_addr[INET6_ADDRSTRLEN]; + uint8_t dst_addr[INET6_ADDRSTRLEN]; } unx; } addr; } v2; diff --git a/tools/testsuite.py b/tools/testsuite.py index 7c929090a..0853360d7 100755 --- a/tools/testsuite.py +++ b/tools/testsuite.py @@ -485,7 +485,7 @@ def test_cache_mmaps_once_even_after_timeout(self): class TestProxyProtocolRequests(SocketTest): def test_proxy_version1(self): - req = '''PROXY TCP4 192.168.0.1 192.168.0.11 56324 443\r + req = '''PROXY TCP4 192.168.242.221 192.168.242.242 56324 31337\r GET / HTTP/1.1\r Host: 192.168.0.11\r\n\r\n''' From 4f57d3107199d1fc396b29ddec69bc58df62edae Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 25 Oct 2015 19:10:25 -0200 Subject: [PATCH 0034/2505] Compare output of inet_pton() with <= 0 Comparing with 0 is slightly cheaper. --- common/lwan-request.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index 9e0098b6d..d1f211ddd 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -191,9 +191,9 @@ parse_proxy_protocol_v1(lwan_request_t *request, char *buffer) from->sin_family = to->sin_family = AF_INET; - if (inet_pton(AF_INET, src_addr, &from->sin_addr) != 1) + if (inet_pton(AF_INET, src_addr, &from->sin_addr) <= 0) return NULL; - if (inet_pton(AF_INET, dst_addr, &to->sin_addr) != 1) + if (inet_pton(AF_INET, dst_addr, &to->sin_addr) <= 0) return NULL; if (!parse_ascii_port(src_port, &from->sin_port)) return NULL; @@ -208,9 +208,9 @@ parse_proxy_protocol_v1(lwan_request_t *request, char *buffer) from->sin6_family = to->sin6_family = AF_INET6; - if (inet_pton(AF_INET6, src_addr, &from->sin6_addr) != 1) + if (inet_pton(AF_INET6, src_addr, &from->sin6_addr) <= 0) return NULL; - if (inet_pton(AF_INET6, dst_addr, &to->sin6_addr) != 1) + if (inet_pton(AF_INET6, dst_addr, &to->sin6_addr) <= 0) return NULL; if (!parse_ascii_port(src_port, &from->sin6_port)) return NULL; From 1d59a7b2894a1116b12f931129b41424d45c0a1f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 25 Oct 2015 19:12:30 -0200 Subject: [PATCH 0035/2505] Remove unused `unx` struct from proxy_protocol_header.v2.addr --- common/lwan-request.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index d1f211ddd..d1e731b77 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -81,10 +81,6 @@ union proxy_protocol_header { uint16_t src_port; uint16_t dst_port; } ip6; - struct { - uint8_t src_addr[INET6_ADDRSTRLEN]; - uint8_t dst_addr[INET6_ADDRSTRLEN]; - } unx; } addr; } v2; }; From b772c2845e268417c4ac4fc6ea0bc862266b7988 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 25 Oct 2015 19:14:35 -0200 Subject: [PATCH 0036/2505] Fix build of template engine --- common/lwan-template.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/common/lwan-template.c b/common/lwan-template.c index a09573172..2275fd05d 100644 --- a/common/lwan-template.c +++ b/common/lwan-template.c @@ -302,14 +302,12 @@ static void error_vlexeme(struct lexeme *lexeme, const char *msg, va_list ap) if (r < 0) { lexeme->value.value = strdup(strerror(errno)); if (!lexeme->value.value) - return NULL; + return; lexeme->value.len = strlen(lexeme->value.value); } else { lexeme->value.len = (size_t)r; } - - return NULL; } static void *error_lexeme(struct lexeme *lexeme, const char *msg, ...) From 911d9e7de2b21e14dc2a0503864e2891a1301696 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 25 Oct 2015 19:22:31 -0200 Subject: [PATCH 0037/2505] Revert d93bffe3 as it was causing problems in build bot --- tools/testsuite.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tools/testsuite.py b/tools/testsuite.py index 0853360d7..28891d5b9 100755 --- a/tools/testsuite.py +++ b/tools/testsuite.py @@ -27,15 +27,12 @@ def setUp(self): [LWAN_PATH], stdout=subprocess.PIPE, stderr=subprocess.STDOUT ) - s = socket.socket() for request_try in range(20): try: - s.connect(('127.0.0.1', 8080)) - except: - time.sleep(0.1) - else: - s.close() + requests.get('http://127.0.0.1:8080/hello') return + except requests.ConnectionError: + time.sleep(0.1) time.sleep(0.1) From 68f3c4a55864d676bcbe8b1a266f1fd46a0e79a2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 25 Oct 2015 19:26:58 -0200 Subject: [PATCH 0038/2505] Initialize ss_family to make clang-analyzer happy --- common/lwan-request.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index d1e731b77..c07c16080 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -1110,7 +1110,8 @@ const char * lwan_request_get_remote_address(lwan_request_t *request, char buffer[static INET6_ADDRSTRLEN]) { - struct sockaddr_storage non_proxied_addr, *sock_addr; + struct sockaddr_storage non_proxied_addr = { .ss_family = AF_UNSPEC }; + struct sockaddr_storage *sock_addr; if (request->conn->flags & CONN_PROXIED) { sock_addr = (struct sockaddr_storage *)&request->proxy_from; From 6ac4efcef866285e35551d36f45620c82af59d5f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 25 Oct 2015 19:45:20 -0200 Subject: [PATCH 0039/2505] Reset CONN_PROXIED flag if V2 header parsing fails --- common/lwan-request.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index c07c16080..507151faf 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -242,20 +242,17 @@ parse_proxy_protocol_v2(lwan_request_t *request, char *buffer) size = proto_signature_length + (unsigned int) ntohs(hdr->v2.len); if (UNLIKELY(size > sizeof(hdr->v2))) - return NULL; + goto no_proxy; if (UNLIKELY((hdr->v2.ver) != 2)) - return NULL; + goto no_proxy; - switch (hdr->v2.cmd) { - case LOCAL: { + if (hdr->v2.cmd) { struct sockaddr_in *from = &request->proxy_from.ipv4; struct sockaddr_in *to = &request->proxy_to.ipv4; from->sin_family = to->sin_family = AF_UNSPEC; - break; - } - case PROXY: + } else if (hdr->v2.cmd == PROXY) { if (hdr->v2.fam == TCP4) { struct sockaddr_in *from = &request->proxy_from.ipv4; struct sockaddr_in *to = &request->proxy_to.ipv4; @@ -279,15 +276,18 @@ parse_proxy_protocol_v2(lwan_request_t *request, char *buffer) memcpy(&to->sin6_addr, hdr->v2.addr.ip6.dst_addr, sizeof(to->sin6_addr)); to->sin6_port = hdr->v2.addr.ip6.dst_port; } else { - return NULL; + goto no_proxy; } - break; - default: - return NULL; + } else { + goto no_proxy; } request->conn->flags |= CONN_PROXIED; return buffer + size; + +no_proxy: + request->conn->flags &= ~CONN_PROXIED; + return NULL; } static ALWAYS_INLINE char * From ce6d172bbc786c1cbfd86c312ab5371d371e9c63 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 25 Oct 2015 20:36:55 -0200 Subject: [PATCH 0040/2505] Properly check for LOCAL cmd in parse_proxy_protocol_v2() --- common/lwan-request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index 507151faf..bf3afd5bd 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -247,7 +247,7 @@ parse_proxy_protocol_v2(lwan_request_t *request, char *buffer) if (UNLIKELY((hdr->v2.ver) != 2)) goto no_proxy; - if (hdr->v2.cmd) { + if (hdr->v2.cmd == LOCAL) { struct sockaddr_in *from = &request->proxy_from.ipv4; struct sockaddr_in *to = &request->proxy_to.ipv4; From 931422503916ccdd2855bdbc49c65cf12d8ed5b3 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 25 Oct 2015 20:42:08 -0200 Subject: [PATCH 0041/2505] Add UNLIKELY() to PROXY v1 dst/src address parsers --- common/lwan-request.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index bf3afd5bd..c392e720d 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -187,13 +187,13 @@ parse_proxy_protocol_v1(lwan_request_t *request, char *buffer) from->sin_family = to->sin_family = AF_INET; - if (inet_pton(AF_INET, src_addr, &from->sin_addr) <= 0) + if (UNLIKELY(inet_pton(AF_INET, src_addr, &from->sin_addr) <= 0)) return NULL; - if (inet_pton(AF_INET, dst_addr, &to->sin_addr) <= 0) + if (UNLIKELY(inet_pton(AF_INET, dst_addr, &to->sin_addr) <= 0)) return NULL; - if (!parse_ascii_port(src_port, &from->sin_port)) + if (UNLIKELY(!parse_ascii_port(src_port, &from->sin_port))) return NULL; - if (!parse_ascii_port(dst_port, &to->sin_port)) + if (UNLIKELY(!parse_ascii_port(dst_port, &to->sin_port))) return NULL; break; @@ -204,13 +204,13 @@ parse_proxy_protocol_v1(lwan_request_t *request, char *buffer) from->sin6_family = to->sin6_family = AF_INET6; - if (inet_pton(AF_INET6, src_addr, &from->sin6_addr) <= 0) + if (UNLIKELY(inet_pton(AF_INET6, src_addr, &from->sin6_addr) <= 0)) return NULL; - if (inet_pton(AF_INET6, dst_addr, &to->sin6_addr) <= 0) + if (UNLIKELY(inet_pton(AF_INET6, dst_addr, &to->sin6_addr) <= 0)) return NULL; - if (!parse_ascii_port(src_port, &from->sin6_port)) + if (UNLIKELY(!parse_ascii_port(src_port, &from->sin6_port))) return NULL; - if (!parse_ascii_port(dst_port, &to->sin6_port)) + if (UNLIKELY(!parse_ascii_port(dst_port, &to->sin6_port))) return NULL; break; From 309c931c98f6a40c36a894e961f21aab4339773a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 25 Oct 2015 20:47:54 -0200 Subject: [PATCH 0042/2505] Remove unused flags from lwan_request_flags_t --- common/lwan.h | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/common/lwan.h b/common/lwan.h index 762c8a47c..afb3c8c2e 100644 --- a/common/lwan.h +++ b/common/lwan.h @@ -150,15 +150,13 @@ typedef enum { REQUEST_METHOD_GET = 1<<0, REQUEST_METHOD_HEAD = 1<<1, REQUEST_METHOD_POST = 1<<2, - REQUEST_METHOD_PROXY1 = 1<<3, - REQUEST_METHOD_PROXY2 = 1<<4, - REQUEST_ACCEPT_DEFLATE = 1<<5, - REQUEST_ACCEPT_GZIP = 1<<6, - REQUEST_IS_HTTP_1_0 = 1<<7, - RESPONSE_SENT_HEADERS = 1<<8, - RESPONSE_CHUNKED_ENCODING = 1<<9, - RESPONSE_NO_CONTENT_LENGTH = 1<<10, - RESPONSE_URL_REWRITTEN = 1<<11 + REQUEST_ACCEPT_DEFLATE = 1<<3, + REQUEST_ACCEPT_GZIP = 1<<4, + REQUEST_IS_HTTP_1_0 = 1<<5, + RESPONSE_SENT_HEADERS = 1<<6, + RESPONSE_CHUNKED_ENCODING = 1<<7, + RESPONSE_NO_CONTENT_LENGTH = 1<<8, + RESPONSE_URL_REWRITTEN = 1<<9, } lwan_request_flags_t; typedef enum { From 597e0702891496a5dc1c8ce24b6094405af968b6 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 25 Oct 2015 20:49:07 -0200 Subject: [PATCH 0043/2505] Move PROXIED flag to request Since it is parsed once per request, it makes sense to move it to the request flags enum. In addition, this also removes some pointer indirections to get the connection struct from the request struct. --- common/lwan-request.c | 10 +++++----- common/lwan.h | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index c392e720d..3ae570c16 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -216,13 +216,13 @@ parse_proxy_protocol_v1(lwan_request_t *request, char *buffer) break; } case UNKN: - request->conn->flags &= ~CONN_PROXIED; + request->flags &= ~REQUEST_PROXIED; return buffer + size; default: return NULL; } - request->conn->flags |= CONN_PROXIED; + request->flags |= REQUEST_PROXIED; return buffer + size; } @@ -282,11 +282,11 @@ parse_proxy_protocol_v2(lwan_request_t *request, char *buffer) goto no_proxy; } - request->conn->flags |= CONN_PROXIED; + request->flags |= REQUEST_PROXIED; return buffer + size; no_proxy: - request->conn->flags &= ~CONN_PROXIED; + request->flags &= ~REQUEST_PROXIED; return NULL; } @@ -1113,7 +1113,7 @@ lwan_request_get_remote_address(lwan_request_t *request, struct sockaddr_storage non_proxied_addr = { .ss_family = AF_UNSPEC }; struct sockaddr_storage *sock_addr; - if (request->conn->flags & CONN_PROXIED) { + if (request->flags & REQUEST_PROXIED) { sock_addr = (struct sockaddr_storage *)&request->proxy_from; } else { socklen_t sock_len = sizeof(non_proxied_addr); diff --git a/common/lwan.h b/common/lwan.h index afb3c8c2e..1d3626f2a 100644 --- a/common/lwan.h +++ b/common/lwan.h @@ -157,6 +157,7 @@ typedef enum { RESPONSE_CHUNKED_ENCODING = 1<<7, RESPONSE_NO_CONTENT_LENGTH = 1<<8, RESPONSE_URL_REWRITTEN = 1<<9, + REQUEST_PROXIED = 1<<10 } lwan_request_flags_t; typedef enum { @@ -166,7 +167,6 @@ typedef enum { CONN_SHOULD_RESUME_CORO = 1<<2, CONN_WRITE_EVENTS = 1<<3, CONN_MUST_READ = 1<<4, - CONN_PROXIED = 1<<5 } lwan_connection_flags_t; typedef enum { From 3aa67953120084c4e74ddd9b252e1fafa0e6e4d4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 25 Oct 2015 22:44:32 -0200 Subject: [PATCH 0044/2505] Resize proxy_protocol_header.v1.line back to 108 elements --- common/lwan-request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index 3ae570c16..9a86a98dd 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -60,7 +60,7 @@ struct request_parser_helper { union proxy_protocol_header { struct { - char line[56]; + char line[108]; } v1; struct { uint8_t sig[8]; From f39eaf3991ebef7189f31eacda65d716e0f31d32 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 25 Oct 2015 22:45:02 -0200 Subject: [PATCH 0045/2505] Cast sizeof() to unsigned int in parse_protocol_v2() --- common/lwan-request.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index 9a86a98dd..20f157267 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -240,8 +240,8 @@ parse_proxy_protocol_v2(lwan_request_t *request, char *buffer) TCP6 = 0x21 }; - size = proto_signature_length + (unsigned int) ntohs(hdr->v2.len); - if (UNLIKELY(size > sizeof(hdr->v2))) + size = proto_signature_length + (unsigned int)ntohs(hdr->v2.len); + if (UNLIKELY(size > (unsigned int)sizeof(hdr->v2))) goto no_proxy; if (UNLIKELY((hdr->v2.ver) != 2)) From fc80f5b391885778e6856732ac5930ac38fa116c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 25 Oct 2015 22:45:23 -0200 Subject: [PATCH 0046/2505] Do not ignore leading whitespace for PROXY requests --- common/lwan-request.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index 20f157267..e12863902 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -887,7 +887,7 @@ parse_proxy_protocol(lwan_request_t *request, char *buffer) static lwan_http_status_t parse_http_request(lwan_t *l, lwan_request_t *request, struct request_parser_helper *helper) { - char *buffer = ignore_leading_whitespace(helper->buffer->value); + char *buffer = helper->buffer->value; if (l->config.proxy_protocol) { buffer = parse_proxy_protocol(request, buffer); @@ -896,6 +896,8 @@ parse_http_request(lwan_t *l, lwan_request_t *request, struct request_parser_hel return HTTP_BAD_REQUEST; } + buffer = ignore_leading_whitespace(buffer); + char *path = identify_http_method(request, buffer); if (UNLIKELY(buffer == path)) { if (UNLIKELY(!*buffer)) From 27143a7bc78cdbff969b713a57ef04883043ec20 Mon Sep 17 00:00:00 2001 From: Malthe Borch Date: Mon, 26 Oct 2015 13:37:51 +0100 Subject: [PATCH 0047/2505] The proxy signature length changes without whitespace stripping --- common/lwan-request.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index e12863902..ef9d03ea2 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -63,7 +63,7 @@ union proxy_protocol_header { char line[108]; } v1; struct { - uint8_t sig[8]; + uint8_t sig[12]; uint8_t cmd : 4; uint8_t ver : 4; uint8_t fam; @@ -230,7 +230,7 @@ static char * parse_proxy_protocol_v2(lwan_request_t *request, char *buffer) { union proxy_protocol_header *hdr = (union proxy_protocol_header *)buffer; - const unsigned int proto_signature_length = 12; + const unsigned int proto_signature_length = 16; unsigned int size; enum { @@ -871,7 +871,7 @@ parse_proxy_protocol(lwan_request_t *request, char *buffer) { enum { HTTP_PROXY_VER1 = MULTICHAR_CONSTANT('P','R','O','X'), - HTTP_PROXY_VER2 = MULTICHAR_CONSTANT('\x00','\x0D','\x0A','\x51'), + HTTP_PROXY_VER2 = MULTICHAR_CONSTANT('\x0D','\x0A','\x0D','\x0A'), }; STRING_SWITCH(buffer) { From 48a8ad45bb2309cd0a9a488a67a775ee167e58fd Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 27 Oct 2015 22:54:37 -0200 Subject: [PATCH 0048/2505] Only allow one proxy header per connection --- common/lwan-request.c | 25 +++++++++---------------- common/lwan-thread.c | 6 ++++++ common/lwan.h | 3 ++- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index ef9d03ea2..f45f2ead5 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -177,7 +177,6 @@ parse_proxy_protocol_v1(lwan_request_t *request, char *buffer) enum { TCP4 = MULTICHAR_CONSTANT('T', 'C', 'P', '4'), TCP6 = MULTICHAR_CONSTANT('T', 'C', 'P', '6'), - UNKN = MULTICHAR_CONSTANT('U', 'N', 'K', 'N') }; STRING_SWITCH(protocol) { @@ -215,9 +214,6 @@ parse_proxy_protocol_v1(lwan_request_t *request, char *buffer) break; } - case UNKN: - request->flags &= ~REQUEST_PROXIED; - return buffer + size; default: return NULL; } @@ -242,10 +238,10 @@ parse_proxy_protocol_v2(lwan_request_t *request, char *buffer) size = proto_signature_length + (unsigned int)ntohs(hdr->v2.len); if (UNLIKELY(size > (unsigned int)sizeof(hdr->v2))) - goto no_proxy; + return NULL; if (UNLIKELY((hdr->v2.ver) != 2)) - goto no_proxy; + return NULL; if (hdr->v2.cmd == LOCAL) { struct sockaddr_in *from = &request->proxy_from.ipv4; @@ -276,18 +272,14 @@ parse_proxy_protocol_v2(lwan_request_t *request, char *buffer) memcpy(&to->sin6_addr, hdr->v2.addr.ip6.dst_addr, sizeof(to->sin6_addr)); to->sin6_port = hdr->v2.addr.ip6.dst_port; } else { - goto no_proxy; + return NULL; } } else { - goto no_proxy; + return NULL; } request->flags |= REQUEST_PROXIED; return buffer + size; - -no_proxy: - request->flags &= ~REQUEST_PROXIED; - return NULL; } static ALWAYS_INLINE char * @@ -885,13 +877,14 @@ parse_proxy_protocol(lwan_request_t *request, char *buffer) } static lwan_http_status_t -parse_http_request(lwan_t *l, lwan_request_t *request, struct request_parser_helper *helper) +parse_http_request(lwan_request_t *request, struct request_parser_helper *helper) { char *buffer = helper->buffer->value; - if (l->config.proxy_protocol) { - buffer = parse_proxy_protocol(request, buffer); + if (request->flags & REQUEST_ALLOW_PROXY_REQS) { + /* REQUEST_ALLOW_PROXY_REQS will be cleared in lwan_process_request() */ + buffer = parse_proxy_protocol(request, buffer); if (UNLIKELY(!buffer)) return HTTP_BAD_REQUEST; } @@ -1021,7 +1014,7 @@ lwan_process_request(lwan_t *l, lwan_request_t *request, __builtin_unreachable(); } - status = parse_http_request(l, request, &helper); + status = parse_http_request(request, &helper); if (UNLIKELY(status != HTTP_OK)) { lwan_default_response(request, status); goto out; diff --git a/common/lwan-thread.c b/common/lwan-thread.c index a85970762..d82e3595e 100644 --- a/common/lwan-thread.c +++ b/common/lwan-thread.c @@ -138,6 +138,7 @@ min(const int a, const int b) static int process_request_coro(coro_t *coro) { + const lwan_request_flags_t flags_filter = REQUEST_PROXIED; strbuf_t *strbuf = coro_malloc_full(coro, sizeof(*strbuf), strbuf_free); lwan_connection_t *conn = coro_get_data(coro); lwan_t *lwan = conn->thread->lwan; @@ -148,6 +149,8 @@ process_request_coro(coro_t *coro) .len = 0 }; char *next_request = NULL; + lwan_request_flags_t flags = + lwan->config.proxy_protocol ? REQUEST_ALLOW_PROXY_REQS : 0; strbuf_init(strbuf); @@ -158,6 +161,7 @@ process_request_coro(coro_t *coro) .response = { .buffer = strbuf }, + .flags = flags }; assert(conn->flags & CONN_IS_ALIVE); @@ -170,6 +174,8 @@ process_request_coro(coro_t *coro) if (UNLIKELY(!strbuf_reset_length(strbuf))) return CONN_CORO_ABORT; + + flags = request.flags & flags_filter; } return CONN_CORO_FINISHED; diff --git a/common/lwan.h b/common/lwan.h index 1d3626f2a..98001c5c7 100644 --- a/common/lwan.h +++ b/common/lwan.h @@ -157,7 +157,8 @@ typedef enum { RESPONSE_CHUNKED_ENCODING = 1<<7, RESPONSE_NO_CONTENT_LENGTH = 1<<8, RESPONSE_URL_REWRITTEN = 1<<9, - REQUEST_PROXIED = 1<<10 + REQUEST_ALLOW_PROXY_REQS = 1<<10, + REQUEST_PROXIED = 1<<11 } lwan_request_flags_t; typedef enum { From fc74a3c0a82e9ee0795bd3ba30fb2abcaa75230c Mon Sep 17 00:00:00 2001 From: Malthe Borch Date: Tue, 27 Oct 2015 23:11:24 -0200 Subject: [PATCH 0049/2505] Proxy object is per connection --- common/lwan-request.c | 24 +++++++++++++----------- common/lwan-thread.c | 4 +++- common/lwan.h | 14 +++++++++----- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index f45f2ead5..a2f9d1434 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -158,6 +158,7 @@ parse_proxy_protocol_v1(lwan_request_t *request, char *buffer) union proxy_protocol_header *hdr = (union proxy_protocol_header *) buffer; char *end, *protocol, *src_addr, *dst_addr, *src_port, *dst_port; unsigned int size; + lwan_proxy_t *const proxy = request->proxy; end = memchr(hdr->v1.line, '\r', sizeof(hdr->v1.line)); if (UNLIKELY(!end || end[1] != '\n')) @@ -181,8 +182,8 @@ parse_proxy_protocol_v1(lwan_request_t *request, char *buffer) STRING_SWITCH(protocol) { case TCP4: { - struct sockaddr_in *from = &request->proxy_from.ipv4; - struct sockaddr_in *to = &request->proxy_to.ipv4; + struct sockaddr_in *from = &proxy->from.ipv4; + struct sockaddr_in *to = &proxy->to.ipv4; from->sin_family = to->sin_family = AF_INET; @@ -198,8 +199,8 @@ parse_proxy_protocol_v1(lwan_request_t *request, char *buffer) break; } case TCP6: { - struct sockaddr_in6 *from = &request->proxy_from.ipv6; - struct sockaddr_in6 *to = &request->proxy_to.ipv6; + struct sockaddr_in6 *from = &proxy->from.ipv6; + struct sockaddr_in6 *to = &proxy->to.ipv6; from->sin6_family = to->sin6_family = AF_INET6; @@ -228,6 +229,7 @@ parse_proxy_protocol_v2(lwan_request_t *request, char *buffer) union proxy_protocol_header *hdr = (union proxy_protocol_header *)buffer; const unsigned int proto_signature_length = 16; unsigned int size; + lwan_proxy_t *const proxy = request->proxy; enum { LOCAL = 0, @@ -244,14 +246,14 @@ parse_proxy_protocol_v2(lwan_request_t *request, char *buffer) return NULL; if (hdr->v2.cmd == LOCAL) { - struct sockaddr_in *from = &request->proxy_from.ipv4; - struct sockaddr_in *to = &request->proxy_to.ipv4; + struct sockaddr_in *from = &proxy->from.ipv4; + struct sockaddr_in *to = &proxy->to.ipv4; from->sin_family = to->sin_family = AF_UNSPEC; } else if (hdr->v2.cmd == PROXY) { if (hdr->v2.fam == TCP4) { - struct sockaddr_in *from = &request->proxy_from.ipv4; - struct sockaddr_in *to = &request->proxy_to.ipv4; + struct sockaddr_in *from = &proxy->from.ipv4; + struct sockaddr_in *to = &proxy->to.ipv4; to->sin_family = from->sin_family = AF_INET; @@ -261,8 +263,8 @@ parse_proxy_protocol_v2(lwan_request_t *request, char *buffer) to->sin_addr.s_addr = hdr->v2.addr.ip4.dst_addr; to->sin_port = hdr->v2.addr.ip4.dst_port; } else if (hdr->v2.fam == TCP6) { - struct sockaddr_in6 *from = &request->proxy_from.ipv6; - struct sockaddr_in6 *to = &request->proxy_to.ipv6; + struct sockaddr_in6 *from = &proxy->from.ipv6; + struct sockaddr_in6 *to = &proxy->to.ipv6; from->sin6_family = to->sin6_family = AF_INET6; @@ -1109,7 +1111,7 @@ lwan_request_get_remote_address(lwan_request_t *request, struct sockaddr_storage *sock_addr; if (request->flags & REQUEST_PROXIED) { - sock_addr = (struct sockaddr_storage *)&request->proxy_from; + sock_addr = (struct sockaddr_storage *)&request->proxy->from; } else { socklen_t sock_len = sizeof(non_proxied_addr); diff --git a/common/lwan-thread.c b/common/lwan-thread.c index d82e3595e..4c6e3b0f1 100644 --- a/common/lwan-thread.c +++ b/common/lwan-thread.c @@ -151,6 +151,7 @@ process_request_coro(coro_t *coro) char *next_request = NULL; lwan_request_flags_t flags = lwan->config.proxy_protocol ? REQUEST_ALLOW_PROXY_REQS : 0; + lwan_proxy_t proxy; strbuf_init(strbuf); @@ -161,7 +162,8 @@ process_request_coro(coro_t *coro) .response = { .buffer = strbuf }, - .flags = flags + .flags = flags, + .proxy = &proxy }; assert(conn->flags & CONN_IS_ALIVE); diff --git a/common/lwan.h b/common/lwan.h index 98001c5c7..add850d74 100644 --- a/common/lwan.h +++ b/common/lwan.h @@ -110,6 +110,7 @@ typedef struct lwan_thread_t_ lwan_thread_t; typedef struct lwan_url_map_t_ lwan_url_map_t; typedef struct lwan_value_t_ lwan_value_t; typedef struct lwan_config_t_ lwan_config_t; +typedef struct lwan_proxy_t_ lwan_proxy_t; typedef struct lwan_connection_t_ lwan_connection_t; typedef enum { @@ -209,17 +210,20 @@ struct lwan_connection_t_ { int prev, next; /* for death queue */ }; +struct lwan_proxy_t_ { + union { + struct sockaddr_in ipv4; + struct sockaddr_in6 ipv6; + } from, to; +}; + struct lwan_request_t_ { lwan_request_flags_t flags; int fd; lwan_value_t url; lwan_value_t original_url; lwan_connection_t *conn; - - struct { - struct sockaddr_in ipv4; - struct sockaddr_in6 ipv6; - } proxy_from, proxy_to; + lwan_proxy_t *proxy; struct { lwan_key_value_t *base; From 42f893ee26149c983d4b5f9816b0844053bc6408 Mon Sep 17 00:00:00 2001 From: Malthe Borch Date: Mon, 26 Oct 2015 13:35:37 +0100 Subject: [PATCH 0050/2505] Improve proxy protocol test - Add "Connection: keep-alive" header - Test multiple requests per connection - Add an "X-Proxy" response header to check value --- lwan.conf | 3 +++ lwan/main.c | 21 +++++++++++++++++++++ tools/testsuite.py | 29 ++++++++++++++++++----------- 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/lwan.conf b/lwan.conf index 57e49fb70..9750edf95 100644 --- a/lwan.conf +++ b/lwan.conf @@ -9,6 +9,9 @@ listener *:8080 { prefix /hello { handler = hello_world } + prefix /proxy { + handler = test_proxy + } prefix /chunked { handler = test_chunked_encoding } diff --git a/lwan/main.c b/lwan/main.c index 4286ca546..16ed1d46f 100644 --- a/lwan/main.c +++ b/lwan/main.c @@ -91,6 +91,27 @@ test_server_sent_event(lwan_request_t *request, return HTTP_OK; } +lwan_http_status_t +test_proxy(lwan_request_t *request, + lwan_response_t *response, + void *data __attribute__((unused))) +{ + lwan_key_value_t *headers = coro_malloc(request->conn->coro, sizeof(*headers) * 2); + if (UNLIKELY(!headers)) + return HTTP_INTERNAL_ERROR; + + char *buffer = coro_malloc(request->conn->coro, INET6_ADDRSTRLEN); + + headers[0].key = "X-Proxy"; + headers[0].value = (char*) lwan_request_get_remote_address(request, buffer); + headers[1].key = NULL; + headers[1].value = NULL; + + response->headers = headers; + + return HTTP_OK; +} + lwan_http_status_t hello_world(lwan_request_t *request, lwan_response_t *response, diff --git a/tools/testsuite.py b/tools/testsuite.py index 28891d5b9..097bb0276 100755 --- a/tools/testsuite.py +++ b/tools/testsuite.py @@ -482,28 +482,35 @@ def test_cache_mmaps_once_even_after_timeout(self): class TestProxyProtocolRequests(SocketTest): def test_proxy_version1(self): - req = '''PROXY TCP4 192.168.242.221 192.168.242.242 56324 31337\r -GET / HTTP/1.1\r + proxy = "PROXY TCP4 192.168.242.221 192.168.242.242 56324 31337\r\n" + req = '''GET /proxy HTTP/1.1\r +Connection: keep-alive\r Host: 192.168.0.11\r\n\r\n''' sock = self.connect() - sock.send(req) - response = sock.recv(1024) - self.assertTrue(response.startswith('HTTP/1.1 200 OK'), response) + + for request in range(5): + sock.send(proxy + req if request == 0 else req) + response = sock.recv(4096) + self.assertTrue(response.startswith('HTTP/1.1 200 OK'), response) + self.assertTrue('X-Proxy: 192.168.242.221' in response, response) def test_proxy_version2(self): - req = ( + proxy = ( "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A" "\x21\x11\x00\x0B" "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B" - "GET / HTTP/1.1\r\n" - "Host: 192.168.0.11\r\n\r\n" ) + req = '''GET /proxy HTTP/1.1\r +Connection: keep-alive\r +Host: 192.168.0.11\r\n\r\n''' sock = self.connect() - sock.send(req) - response = sock.recv(1024) - self.assertTrue(response.startswith('HTTP/1.1 200 OK'), response) + for request in range(5): + sock.send(proxy + req if request == 0 else req) + response = sock.recv(4096) + self.assertTrue(response.startswith('HTTP/1.1 200 OK'), response) + self.assertTrue('X-Proxy: 1.2.3.4' in response, response) class TestPipelinedRequests(SocketTest): def test_pipelined_requests(self): From 5ad70dba73bd726ee4b367902362c76da75df90e Mon Sep 17 00:00:00 2001 From: Malthe Borch Date: Fri, 23 Oct 2015 17:06:25 +0200 Subject: [PATCH 0051/2505] Move coro defer to thread's request processing function --- common/lwan-coro.c | 36 +++++++++++++++++++++--------------- common/lwan-coro.h | 3 ++- common/lwan-thread.c | 11 ++++++++--- 3 files changed, 31 insertions(+), 19 deletions(-) diff --git a/common/lwan-coro.c b/common/lwan-coro.c index 48554c177..4ab11f4a6 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -32,8 +32,6 @@ #include #endif -#define CORO_RESET_THRESHOLD 16 - #define CORO_STACK_MIN ((3 * (PTHREAD_STACK_MIN)) / 2) static_assert(DEFAULT_BUFFER_SIZE < (CORO_STACK_MIN + PTHREAD_STACK_MIN), @@ -44,6 +42,7 @@ typedef struct coro_defer_t_ coro_defer_t; struct coro_defer_t_ { coro_defer_t *next; void (*func)(); + bool sticky; void *data1; void *data2; }; @@ -60,7 +59,6 @@ struct coro_t_ { coro_defer_t *defer; void *data; - unsigned char reset_count; bool ended; }; @@ -147,17 +145,25 @@ coro_entry_point(coro_t *coro, coro_function_t func) coro_yield(coro, return_value); } -static void -coro_run_deferred(coro_t *coro) +void +coro_run_deferred(coro_t *coro, bool sticky) { + coro_defer_t *first = NULL; + for (coro_defer_t *defer = coro->defer; defer;) { coro_defer_t *tmp = defer; - defer->func(defer->data1, defer->data2); defer = tmp->next; - free(tmp); + if (!sticky && tmp->sticky) { + first = tmp; + } else { + tmp->func(tmp->data1, tmp->data2); + free(tmp); + } + } + coro->defer = first; + if (first) { + first->next = NULL; } - coro->defer = NULL; - coro->reset_count = CORO_RESET_THRESHOLD; } void @@ -168,8 +174,8 @@ coro_reset(coro_t *coro, coro_function_t func, void *data) coro->ended = false; coro->data = data; - if (!coro->reset_count--) - coro_run_deferred(coro); + if (coro->defer) + coro_run_deferred(coro, true); #if defined(__x86_64__) coro->context[6 /* RDI */] = (uintptr_t) coro; @@ -209,7 +215,6 @@ coro_new(coro_switcher_t *switcher, coro_function_t function, void *data) coro->switcher = switcher; coro->defer = NULL; - coro->reset_count = CORO_RESET_THRESHOLD; coro_reset(coro, function, data); #if !defined(NDEBUG) && defined(USE_VALGRIND) @@ -279,7 +284,7 @@ coro_free(coro_t *coro) #if !defined(NDEBUG) && defined(USE_VALGRIND) VALGRIND_STACK_DEREGISTER(coro->vg_stack_id); #endif - coro_run_deferred(coro); + coro_run_deferred(coro, true); free(coro); } @@ -314,7 +319,7 @@ coro_defer2(coro_t *coro, void (*func)(void *data1, void *data2), } void * -coro_malloc_full(coro_t *coro, size_t size, void (*destroy_func)()) +coro_malloc_full(coro_t *coro, size_t size, bool sticky, void (*destroy_func)()) { coro_defer_t *defer = malloc(sizeof(*defer) + size); @@ -322,6 +327,7 @@ coro_malloc_full(coro_t *coro, size_t size, void (*destroy_func)()) return NULL; defer->next = coro->defer; + defer->sticky = sticky; defer->func = destroy_func; defer->data1 = defer + 1; defer->data2 = NULL; @@ -338,7 +344,7 @@ static void nothing() inline void * coro_malloc(coro_t *coro, size_t size) { - return coro_malloc_full(coro, size, nothing); + return coro_malloc_full(coro, size, false, nothing); } char * diff --git a/common/lwan-coro.h b/common/lwan-coro.h index d56081412..b2cb0e6fd 100644 --- a/common/lwan-coro.h +++ b/common/lwan-coro.h @@ -55,8 +55,9 @@ void *coro_get_data(coro_t *coro); void coro_defer(coro_t *coro, void (*func)(void *data), void *data); void coro_defer2(coro_t *coro, void (*func)(void *data1, void *data2), void *data1, void *data2); +void coro_run_deferred(coro_t *coro, bool sticky); void *coro_malloc(coro_t *coro, size_t sz); -void *coro_malloc_full(coro_t *coro, size_t size, void (*destroy_func)()); +void *coro_malloc_full(coro_t *coro, size_t size, bool sticky, void (*destroy_func)()); char *coro_strdup(coro_t *coro, const char *str); char *coro_printf(coro_t *coro, const char *fmt, ...); diff --git a/common/lwan-thread.c b/common/lwan-thread.c index 4c6e3b0f1..1b3d4f4c3 100644 --- a/common/lwan-thread.c +++ b/common/lwan-thread.c @@ -29,6 +29,8 @@ #include "lwan-private.h" +#define CORO_DEFER_THRESHOLD 16 + struct death_queue_t { const lwan_t *lwan; lwan_connection_t *conns; @@ -139,11 +141,12 @@ static int process_request_coro(coro_t *coro) { const lwan_request_flags_t flags_filter = REQUEST_PROXIED; - strbuf_t *strbuf = coro_malloc_full(coro, sizeof(*strbuf), strbuf_free); + strbuf_t *strbuf = coro_malloc_full(coro, sizeof(*strbuf), true, strbuf_free); lwan_connection_t *conn = coro_get_data(coro); lwan_t *lwan = conn->thread->lwan; int fd = lwan_connection_get_fd(lwan, conn); char request_buffer[DEFAULT_BUFFER_SIZE]; + unsigned char i = 0; lwan_value_t buffer = { .value = request_buffer, .len = 0 @@ -169,8 +172,10 @@ process_request_coro(coro_t *coro) assert(conn->flags & CONN_IS_ALIVE); next_request = lwan_process_request(lwan, &request, &buffer, next_request); - if (!next_request) - break; + if (++i == CORO_DEFER_THRESHOLD) { + coro_run_deferred(coro, false); + i = 0; + } coro_yield(coro, CONN_CORO_MAY_RESUME); From b7f800435565be1bad479e3174f6d02f106d5bc1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 27 Oct 2015 23:58:54 -0200 Subject: [PATCH 0052/2505] Do not expose coro_run_deferred() Even with the `sticky` parameter, it's a dangerous function call. Instead, expose a `coro_collect_garbage()` function, that calls the static `coro_run_deferred()` function. Also, make the comparison within the loop be with `0` instead of with `CORO_DEFER_THRESHOLD` (which has been renamed to GC_THRESHOLD). --- common/lwan-coro.c | 10 ++++++++-- common/lwan-coro.h | 3 ++- common/lwan-thread.c | 10 +++++----- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/common/lwan-coro.c b/common/lwan-coro.c index 4ab11f4a6..e5e876c28 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -42,9 +42,9 @@ typedef struct coro_defer_t_ coro_defer_t; struct coro_defer_t_ { coro_defer_t *next; void (*func)(); - bool sticky; void *data1; void *data2; + bool sticky; }; struct coro_t_ { @@ -145,7 +145,7 @@ coro_entry_point(coro_t *coro, coro_function_t func) coro_yield(coro, return_value); } -void +static void coro_run_deferred(coro_t *coro, bool sticky) { coro_defer_t *first = NULL; @@ -166,6 +166,12 @@ coro_run_deferred(coro_t *coro, bool sticky) } } +void +coro_collect_garbage(coro_t *coro) +{ + coro_run_deferred(coro, false); +} + void coro_reset(coro_t *coro, coro_function_t func, void *data) { diff --git a/common/lwan-coro.h b/common/lwan-coro.h index b2cb0e6fd..6b7f03da5 100644 --- a/common/lwan-coro.h +++ b/common/lwan-coro.h @@ -55,7 +55,8 @@ void *coro_get_data(coro_t *coro); void coro_defer(coro_t *coro, void (*func)(void *data), void *data); void coro_defer2(coro_t *coro, void (*func)(void *data1, void *data2), void *data1, void *data2); -void coro_run_deferred(coro_t *coro, bool sticky); +void coro_collect_garbage(coro_t *coro); + void *coro_malloc(coro_t *coro, size_t sz); void *coro_malloc_full(coro_t *coro, size_t size, bool sticky, void (*destroy_func)()); char *coro_strdup(coro_t *coro, const char *str); diff --git a/common/lwan-thread.c b/common/lwan-thread.c index 1b3d4f4c3..30bd871d6 100644 --- a/common/lwan-thread.c +++ b/common/lwan-thread.c @@ -29,7 +29,7 @@ #include "lwan-private.h" -#define CORO_DEFER_THRESHOLD 16 +#define CORO_GC_THRESHOLD 16 struct death_queue_t { const lwan_t *lwan; @@ -146,7 +146,6 @@ process_request_coro(coro_t *coro) lwan_t *lwan = conn->thread->lwan; int fd = lwan_connection_get_fd(lwan, conn); char request_buffer[DEFAULT_BUFFER_SIZE]; - unsigned char i = 0; lwan_value_t buffer = { .value = request_buffer, .len = 0 @@ -155,6 +154,7 @@ process_request_coro(coro_t *coro) lwan_request_flags_t flags = lwan->config.proxy_protocol ? REQUEST_ALLOW_PROXY_REQS : 0; lwan_proxy_t proxy; + int gc_counter = CORO_GC_THRESHOLD; strbuf_init(strbuf); @@ -172,9 +172,9 @@ process_request_coro(coro_t *coro) assert(conn->flags & CONN_IS_ALIVE); next_request = lwan_process_request(lwan, &request, &buffer, next_request); - if (++i == CORO_DEFER_THRESHOLD) { - coro_run_deferred(coro, false); - i = 0; + if (!gc_counter--) { + coro_collect_garbage(coro); + gc_counter = CORO_GC_THRESHOLD; } coro_yield(coro, CONN_CORO_MAY_RESUME); From d91c5665f0891fb6198d03b48a7a4e4ce26a7611 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 28 Oct 2015 00:17:06 -0200 Subject: [PATCH 0053/2505] Store `sticky` bit in the least significant bit of defer->next This saves 8 bytes due to alignment, making coro_defer_t a 32-byte struct again (on x86-64). Also restitch the sticky linked list. --- common/lwan-coro.c | 44 +++++++++++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/common/lwan-coro.c b/common/lwan-coro.c index e5e876c28..92fcfdafe 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -44,7 +44,6 @@ struct coro_defer_t_ { void (*func)(); void *data1; void *data2; - bool sticky; }; struct coro_t_ { @@ -145,25 +144,45 @@ coro_entry_point(coro_t *coro, coro_function_t func) coro_yield(coro, return_value); } +static inline coro_defer_t * +next_defer(const coro_defer_t *defer) +{ + ptrdiff_t ptr = (ptrdiff_t)defer->next; + + if (!ptr) + return NULL; + + ptr &= ~(ptrdiff_t)1; + return (coro_defer_t *)ptr; +} + +static inline bool +is_sticky(const coro_defer_t *defer) +{ + ptrdiff_t ptr = (ptrdiff_t)defer->next; + + return ptr & (ptrdiff_t)1; +} + static void coro_run_deferred(coro_t *coro, bool sticky) { - coro_defer_t *first = NULL; + coro_defer_t *sticked = NULL; for (coro_defer_t *defer = coro->defer; defer;) { coro_defer_t *tmp = defer; - defer = tmp->next; - if (!sticky && tmp->sticky) { - first = tmp; + + defer = next_defer(defer); + if (!sticky && is_sticky(tmp)) { + tmp->next = sticked; + sticked = tmp; } else { tmp->func(tmp->data1, tmp->data2); free(tmp); } } - coro->defer = first; - if (first) { - first->next = NULL; - } + + coro->defer = sticked; } void @@ -328,12 +347,15 @@ void * coro_malloc_full(coro_t *coro, size_t size, bool sticky, void (*destroy_func)()) { coro_defer_t *defer = malloc(sizeof(*defer) + size); + ptrdiff_t next = (ptrdiff_t)coro->defer; if (UNLIKELY(!defer)) return NULL; - defer->next = coro->defer; - defer->sticky = sticky; + if (sticky) + next |= 1; + + defer->next = (coro_defer_t *)next; defer->func = destroy_func; defer->data1 = defer + 1; defer->data2 = NULL; From 7fc3934bc11d9899bbd06ce79ec1e5f626e6cf3c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 28 Oct 2015 01:32:00 -0200 Subject: [PATCH 0054/2505] Add comments to lwan.conf explaining some of the configuration keys. --- common/lwan.c | 3 +++ lwan.conf | 20 +++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/common/lwan.c b/common/lwan.c index c8f00edb6..a0e64932e 100644 --- a/common/lwan.c +++ b/common/lwan.c @@ -521,6 +521,9 @@ lwan_init_with_config(lwan_t *l, const lwan_config_t *config) if (config == &default_config) { if (!setup_from_config(l)) lwan_status_warning("Could not read config file, using defaults"); + + /* `config` key might have changed value. */ + lwan_status_init(l); } /* Continue initialization as normal. */ diff --git a/lwan.conf b/lwan.conf index 9750edf95..a552198e5 100644 --- a/lwan.conf +++ b/lwan.conf @@ -1,10 +1,24 @@ +# Timeout in seconds to keep a connection alive. keep_alive_timeout = 15 + +# Set to true to not print any debugging messages. (Only effective in +# release builds.) quiet = false + +# Set SO_REUSEPORT=1 in the master socket. reuse_port = false -proxy_protocol = true + +# Value of "Expires" header. Default is 1 month and 1 week. expires = 1M 1w + +# Number of I/O threads. Default (0) is number of online CPUs. threads = 0 +# This flag is enabled here so that the automated tests can be executed +# properly, but should be disabled unless absolutely needed (an example +# would be haproxy). +proxy_protocol = true + listener *:8080 { prefix /hello { handler = hello_world @@ -49,6 +63,10 @@ listener *:8080 { } serve_files / { path = ./wwwroot + + # When requesting for file.ext, look for a smaller/newer file.ext.gz, + # and serve that instead if `Accept-Encoding: gzip` is in the + # request headers. serve precompressed files = true } } From 3160b3ba21091b9a7c099ac15a8158b69fc972b1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 28 Oct 2015 09:49:51 -0200 Subject: [PATCH 0055/2505] Fix crash after adding sticky bit to defer->next pointer Store it in the func pointer instead. --- common/lwan-coro.c | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/common/lwan-coro.c b/common/lwan-coro.c index 92fcfdafe..88e2fbe2e 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -144,40 +144,40 @@ coro_entry_point(coro_t *coro, coro_function_t func) coro_yield(coro, return_value); } -static inline coro_defer_t * -next_defer(const coro_defer_t *defer) +static inline void * +get_func_ptr(void (*func)()) { - ptrdiff_t ptr = (ptrdiff_t)defer->next; + uintptr_t ptr = (uintptr_t)func; - if (!ptr) - return NULL; - - ptr &= ~(ptrdiff_t)1; + ptr &= ~(uintptr_t)1; return (coro_defer_t *)ptr; } static inline bool -is_sticky(const coro_defer_t *defer) +is_sticky(void (*func)()) { - ptrdiff_t ptr = (ptrdiff_t)defer->next; + uintptr_t ptr = (uintptr_t)func; - return ptr & (ptrdiff_t)1; + return ptr & (uintptr_t)1; } static void coro_run_deferred(coro_t *coro, bool sticky) { + coro_defer_t *defer = coro->defer; coro_defer_t *sticked = NULL; - for (coro_defer_t *defer = coro->defer; defer;) { + while (defer) { coro_defer_t *tmp = defer; - defer = next_defer(defer); - if (!sticky && is_sticky(tmp)) { + defer = defer->next; + if (!sticky && is_sticky(tmp->func)) { tmp->next = sticked; sticked = tmp; } else { - tmp->func(tmp->data1, tmp->data2); + void (*defer_func)() = get_func_ptr(tmp->func); + + defer_func(tmp->data1, tmp->data2); free(tmp); } } @@ -347,16 +347,18 @@ void * coro_malloc_full(coro_t *coro, size_t size, bool sticky, void (*destroy_func)()) { coro_defer_t *defer = malloc(sizeof(*defer) + size); - ptrdiff_t next = (ptrdiff_t)coro->defer; + uintptr_t destroy_func_ptr = (uintptr_t)destroy_func; if (UNLIKELY(!defer)) return NULL; + if (UNLIKELY(!destroy_func)) + return NULL; if (sticky) - next |= 1; + destroy_func_ptr |= (uintptr_t)1; - defer->next = (coro_defer_t *)next; - defer->func = destroy_func; + defer->next = coro->defer; + defer->func = (void (*)())destroy_func_ptr; defer->data1 = defer + 1; defer->data2 = NULL; From 14ad7bdf24e36d891372f6b22e5f9d378ac9fea5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 28 Oct 2015 21:07:01 -0200 Subject: [PATCH 0056/2505] After restitching sticky coro_defer, reverse the list Or else this won't preserve their FIFO attributes. --- common/lwan-coro.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/common/lwan-coro.c b/common/lwan-coro.c index 88e2fbe2e..4c5494aeb 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -161,6 +161,22 @@ is_sticky(void (*func)()) return ptr & (uintptr_t)1; } +static coro_defer_t * +reverse(coro_defer_t *root) +{ + coro_defer_t *new = NULL; + + while (root) { + coro_defer_t *next = root->next; + + root->next = new; + new = root; + root = next; + } + + return new; +} + static void coro_run_deferred(coro_t *coro, bool sticky) { @@ -182,7 +198,7 @@ coro_run_deferred(coro_t *coro, bool sticky) } } - coro->defer = sticked; + coro->defer = reverse(sticked); } void From 07fae084693e4eb25d70b7f0b88498ad97862bd0 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 28 Oct 2015 21:08:06 -0200 Subject: [PATCH 0057/2505] Use a typedef to document the correct type for a defer function --- common/lwan-coro.c | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/common/lwan-coro.c b/common/lwan-coro.c index 4c5494aeb..d52964a4a 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -39,9 +39,11 @@ static_assert(DEFAULT_BUFFER_SIZE < (CORO_STACK_MIN + PTHREAD_STACK_MIN), typedef struct coro_defer_t_ coro_defer_t; +typedef void (*defer_func)(); + struct coro_defer_t_ { coro_defer_t *next; - void (*func)(); + defer_func func; void *data1; void *data2; }; @@ -144,17 +146,17 @@ coro_entry_point(coro_t *coro, coro_function_t func) coro_yield(coro, return_value); } -static inline void * -get_func_ptr(void (*func)()) +static inline defer_func +get_func_ptr(defer_func func) { uintptr_t ptr = (uintptr_t)func; ptr &= ~(uintptr_t)1; - return (coro_defer_t *)ptr; + return (defer_func)ptr; } static inline bool -is_sticky(void (*func)()) +is_sticky(defer_func func) { uintptr_t ptr = (uintptr_t)func; @@ -191,9 +193,7 @@ coro_run_deferred(coro_t *coro, bool sticky) tmp->next = sticked; sticked = tmp; } else { - void (*defer_func)() = get_func_ptr(tmp->func); - - defer_func(tmp->data1, tmp->data2); + get_func_ptr(tmp->func)(tmp->data1, tmp->data2); free(tmp); } } @@ -330,7 +330,7 @@ coro_free(coro_t *coro) } static void -coro_defer_any(coro_t *coro, void (*func)(), void *data1, void *data2) +coro_defer_any(coro_t *coro, defer_func func, void *data1, void *data2) { coro_defer_t *defer = malloc(sizeof(*defer)); if (UNLIKELY(!defer)) @@ -363,18 +363,15 @@ void * coro_malloc_full(coro_t *coro, size_t size, bool sticky, void (*destroy_func)()) { coro_defer_t *defer = malloc(sizeof(*defer) + size); - uintptr_t destroy_func_ptr = (uintptr_t)destroy_func; - if (UNLIKELY(!defer)) return NULL; - if (UNLIKELY(!destroy_func)) - return NULL; + uintptr_t destroy_func_ptr = (uintptr_t)destroy_func; if (sticky) destroy_func_ptr |= (uintptr_t)1; defer->next = coro->defer; - defer->func = (void (*)())destroy_func_ptr; + defer->func = (defer_func)destroy_func_ptr; defer->data1 = defer + 1; defer->data2 = NULL; From a01fb6bfc1ef34b25a4763fa0828d633522de8df Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 28 Oct 2015 21:09:08 -0200 Subject: [PATCH 0058/2505] spawn_or_reset_coro() can be simplified to just spawn_coro() now coro_garbage_collect() will perform a task similar to what coro_reset() was performing earlier. --- common/lwan-thread.c | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/common/lwan-thread.c b/common/lwan-thread.c index 30bd871d6..5d33c6023 100644 --- a/common/lwan-thread.c +++ b/common/lwan-thread.c @@ -286,21 +286,17 @@ update_date_cache(lwan_thread_t *thread) } static ALWAYS_INLINE void -spawn_or_reset_coro_if_needed(lwan_connection_t *conn, +spawn_coro(lwan_connection_t *conn, coro_switcher_t *switcher, struct death_queue_t *dq) { - if (conn->coro) { - if (conn->flags & CONN_SHOULD_RESUME_CORO) - return; + assert(!conn->coro); + assert(!(conn->flags & CONN_IS_ALIVE)); + assert(!(conn->flags & CONN_SHOULD_RESUME_CORO)); - coro_reset(conn->coro, process_request_coro, conn); - } else { - conn->coro = coro_new(switcher, process_request_coro, conn); + conn->coro = coro_new(switcher, process_request_coro, conn); - death_queue_insert(dq, conn); - conn->flags |= CONN_IS_ALIVE; - } - conn->flags |= CONN_SHOULD_RESUME_CORO; + death_queue_insert(dq, conn); + conn->flags |= (CONN_IS_ALIVE | CONN_SHOULD_RESUME_CORO); conn->flags &= ~CONN_WRITE_EVENTS; } @@ -367,7 +363,8 @@ thread_io_loop(void *data) conn = grab_and_watch_client(epoll_fd, read_pipe_fd, conns); if (UNLIKELY(!conn)) continue; - spawn_or_reset_coro_if_needed(conn, &switcher, &dq); + + spawn_coro(conn, &switcher, &dq); } else { conn = ep_event->data.ptr; if (UNLIKELY(ep_event->events & (EPOLLRDHUP | EPOLLHUP))) { @@ -375,7 +372,6 @@ thread_io_loop(void *data) continue; } - spawn_or_reset_coro_if_needed(conn, &switcher, &dq); resume_coro_if_needed(&dq, conn, epoll_fd); } From b46d274f52461c929bdf376c431a5a68cb87af18 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 28 Oct 2015 21:59:49 -0200 Subject: [PATCH 0059/2505] get_remote_address(): Handle AF_UNSPEC families when using PROXY v2 --- common/lwan-request.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/lwan-request.c b/common/lwan-request.c index a2f9d1434..e362051b7 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -1112,6 +1112,9 @@ lwan_request_get_remote_address(lwan_request_t *request, if (request->flags & REQUEST_PROXIED) { sock_addr = (struct sockaddr_storage *)&request->proxy->from; + + if (UNLIKELY(sock_addr->ss_family == AF_UNSPEC)) + return "*unspecified*"; } else { socklen_t sock_len = sizeof(non_proxied_addr); From 8be25f2b08009aca5d5d6506081af179dd94127c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 29 Oct 2015 07:11:03 -0200 Subject: [PATCH 0060/2505] Use strsep_char() in parse_key_values() --- common/lwan-request.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index e362051b7..fd2820678 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -369,17 +369,11 @@ parse_key_values(lwan_request_t *request, return; key = ptr; - value = strchr(ptr, '='); + value = strsep_char(ptr, '='); if (UNLIKELY(!value)) return; - *value = '\0'; - value++; - ptr = strchr(value, separator); - if (ptr) { - *ptr = '\0'; - ptr++; - } + ptr = strsep_char(value, separator); if (UNLIKELY(!decode_value(key) || !decode_value(value))) return; From 4985ef2752b761b7f3463d89cb224892a036c593 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 29 Oct 2015 07:14:01 -0200 Subject: [PATCH 0061/2505] For PROXY v2, test for version and command at the same time Gets rid of an explicit branch, and possibly implicit operations such as masks and shifts. --- common/lwan-request.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index fd2820678..4f45cd735 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -64,8 +64,7 @@ union proxy_protocol_header { } v1; struct { uint8_t sig[12]; - uint8_t cmd : 4; - uint8_t ver : 4; + uint8_t cmd_ver; uint8_t fam; uint16_t len; union { @@ -232,8 +231,8 @@ parse_proxy_protocol_v2(lwan_request_t *request, char *buffer) lwan_proxy_t *const proxy = request->proxy; enum { - LOCAL = 0, - PROXY = 1, + LOCAL = 0x20, + PROXY = 0x21, TCP4 = 0x11, TCP6 = 0x21 }; @@ -242,15 +241,12 @@ parse_proxy_protocol_v2(lwan_request_t *request, char *buffer) if (UNLIKELY(size > (unsigned int)sizeof(hdr->v2))) return NULL; - if (UNLIKELY((hdr->v2.ver) != 2)) - return NULL; - - if (hdr->v2.cmd == LOCAL) { + if (hdr->v2.cmd_ver == LOCAL) { struct sockaddr_in *from = &proxy->from.ipv4; struct sockaddr_in *to = &proxy->to.ipv4; from->sin_family = to->sin_family = AF_UNSPEC; - } else if (hdr->v2.cmd == PROXY) { + } else if (hdr->v2.cmd_ver == PROXY) { if (hdr->v2.fam == TCP4) { struct sockaddr_in *from = &proxy->from.ipv4; struct sockaddr_in *to = &proxy->to.ipv4; From 358df26fb61df986a33f601af9acf097b6321b43 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 29 Oct 2015 07:24:00 -0200 Subject: [PATCH 0062/2505] memcpy() the unspecified address to the supplied remote address buffer --- common/lwan-request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index 4f45cd735..50ecd442b 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -1104,7 +1104,7 @@ lwan_request_get_remote_address(lwan_request_t *request, sock_addr = (struct sockaddr_storage *)&request->proxy->from; if (UNLIKELY(sock_addr->ss_family == AF_UNSPEC)) - return "*unspecified*"; + return memcpy(buffer, "*unspecified*", sizeof("*unspecified*")); } else { socklen_t sock_len = sizeof(non_proxied_addr); From 0141aeae6bf5a4925ab76586f40af7f923af3109 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 1 Nov 2015 12:52:34 -0200 Subject: [PATCH 0063/2505] Use aligned_alloc() to allocate connection array This should ensure that the array is aligned to the cache line size. --- common/lwan.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/common/lwan.c b/common/lwan.c index a0e64932e..c52b0b08d 100644 --- a/common/lwan.c +++ b/common/lwan.c @@ -469,9 +469,20 @@ setup_open_file_count_limits(void) static void allocate_connections(lwan_t *l, size_t max_open_files) { +#if defined(_ISOC11_SOURCE) + const size_t sz = max_open_files * sizeof(lwan_connection_t); + + l->conns = aligned_alloc(64, sz); + if (!l->conns) + lwan_status_critical_perror("aligned_alloc"); + + memset(l->conns, 0, sz); +#else l->conns = calloc(max_open_files, sizeof(lwan_connection_t)); + if (!l->conns) lwan_status_critical_perror("calloc"); +#endif } static unsigned short int From 7eac2ceda5a2f3ddedf05a4a688ba2bd79d8f131 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 2 Nov 2015 12:18:38 -0200 Subject: [PATCH 0064/2505] Fix typo in comment --- common/lwan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lwan.c b/common/lwan.c index c52b0b08d..7f5777ca3 100644 --- a/common/lwan.c +++ b/common/lwan.c @@ -533,7 +533,7 @@ lwan_init_with_config(lwan_t *l, const lwan_config_t *config) if (!setup_from_config(l)) lwan_status_warning("Could not read config file, using defaults"); - /* `config` key might have changed value. */ + /* `quiet` key might have changed value. */ lwan_status_init(l); } From 040952022e11f806bb4fa407d3215663a127c763 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 2 Nov 2015 14:07:04 -0200 Subject: [PATCH 0065/2505] Remove unneeded casts in lwan_table_init() --- common/lwan-tables.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/lwan-tables.c b/common/lwan-tables.c index b20669aac..fa2bc939f 100644 --- a/common/lwan-tables.c +++ b/common/lwan-tables.c @@ -53,9 +53,9 @@ lwan_tables_init(void) unsigned char *ptr = uncompressed_mime_entries; for (size_t i = 0; i < MIME_ENTRIES; i++) { mime_entries[i].extension = (char*)ptr; - ptr = (unsigned char *)rawmemchr(ptr + 1, '\0') + 1; + ptr = rawmemchr(ptr + 1, '\0') + 1; mime_entries[i].type = (char*)ptr; - ptr = (unsigned char *)rawmemchr(ptr + 1, '\0') + 1; + ptr = rawmemchr(ptr + 1, '\0') + 1; } mime_entries_initialized = true; From be00f0f886f753cd8b9f771e0e953a8ca8552419 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 2 Nov 2015 14:07:21 -0200 Subject: [PATCH 0066/2505] Formatting fixes for switch-case statements --- common/lwan-tables.c | 114 ++++++++++++++++++++++++++++--------------- 1 file changed, 76 insertions(+), 38 deletions(-) diff --git a/common/lwan-tables.c b/common/lwan-tables.c index fa2bc939f..5b534a3b3 100644 --- a/common/lwan-tables.c +++ b/common/lwan-tables.c @@ -91,12 +91,18 @@ lwan_determine_mime_type_for_file_name(const char *file_name) }; STRING_SWITCH_L(last_dot) { - case EXT_CSS: return "text/css"; - case EXT_HTM: return "text/html"; - case EXT_JPG: return "image/jpeg"; - case EXT_JS: return "application/javascript"; - case EXT_PNG: return "image/png"; - case EXT_TXT: return "text/plain"; + case EXT_CSS: + return "text/css"; + case EXT_HTM: + return "text/html"; + case EXT_JPG: + return "image/jpeg"; + case EXT_JS: + return "application/javascript"; + case EXT_PNG: + return "image/png"; + case EXT_TXT: + return "text/plain"; } if (LIKELY(*last_dot)) { @@ -116,22 +122,38 @@ const char * lwan_http_status_as_string_with_code(lwan_http_status_t status) { switch (status) { - case HTTP_OK: return "200 OK"; - case HTTP_PARTIAL_CONTENT: return "206 Partial content"; - case HTTP_MOVED_PERMANENTLY: return "301 Moved permanently"; - case HTTP_NOT_MODIFIED: return "304 Not modified"; - case HTTP_BAD_REQUEST: return "400 Bad request"; - case HTTP_NOT_AUTHORIZED: return "401 Not authorized"; - case HTTP_FORBIDDEN: return "403 Forbidden"; - case HTTP_NOT_FOUND: return "404 Not found"; - case HTTP_NOT_ALLOWED: return "405 Not allowed"; - case HTTP_TIMEOUT: return "408 Request timeout"; - case HTTP_TOO_LARGE: return "413 Request too large"; - case HTTP_RANGE_UNSATISFIABLE: return "416 Requested range unsatisfiable"; - case HTTP_I_AM_A_TEAPOT: return "418 I'm a teapot"; - case HTTP_INTERNAL_ERROR: return "500 Internal server error"; - case HTTP_NOT_IMPLEMENTED: return "501 Not implemented"; - case HTTP_UNAVAILABLE: return "503 Service unavailable"; + case HTTP_OK: + return "200 OK"; + case HTTP_PARTIAL_CONTENT: + return "206 Partial content"; + case HTTP_MOVED_PERMANENTLY: + return "301 Moved permanently"; + case HTTP_NOT_MODIFIED: + return "304 Not modified"; + case HTTP_BAD_REQUEST: + return "400 Bad request"; + case HTTP_NOT_AUTHORIZED: + return "401 Not authorized"; + case HTTP_FORBIDDEN: + return "403 Forbidden"; + case HTTP_NOT_FOUND: + return "404 Not found"; + case HTTP_NOT_ALLOWED: + return "405 Not allowed"; + case HTTP_TIMEOUT: + return "408 Request timeout"; + case HTTP_TOO_LARGE: + return "413 Request too large"; + case HTTP_RANGE_UNSATISFIABLE: + return "416 Requested range unsatisfiable"; + case HTTP_I_AM_A_TEAPOT: + return "418 I'm a teapot"; + case HTTP_INTERNAL_ERROR: + return "500 Internal server error"; + case HTTP_NOT_IMPLEMENTED: + return "501 Not implemented"; + case HTTP_UNAVAILABLE: + return "503 Service unavailable"; } return "999 Invalid"; } @@ -146,22 +168,38 @@ const char * lwan_http_status_as_descriptive_string(lwan_http_status_t status) { switch (status) { - case HTTP_OK: return "Success!"; - case HTTP_PARTIAL_CONTENT: return "Delivering part of requested resource."; - case HTTP_MOVED_PERMANENTLY: return "This content has moved to another place."; - case HTTP_NOT_MODIFIED: return "The content has not changed since previous request."; - case HTTP_BAD_REQUEST: return "The client has issued a bad request."; - case HTTP_NOT_AUTHORIZED: return "Client has no authorization to access this resource."; - case HTTP_FORBIDDEN: return "Access to this resource has been denied."; - case HTTP_NOT_FOUND: return "The requested resource could not be found on this server."; - case HTTP_NOT_ALLOWED: return "The requested method is not allowed by this server."; - case HTTP_TIMEOUT: return "Client did not produce a request within expected timeframe."; - case HTTP_TOO_LARGE: return "The request entity is too large."; - case HTTP_RANGE_UNSATISFIABLE: return "The server can't supply the requested portion of the requested resource."; - case HTTP_I_AM_A_TEAPOT: return "Client requested to brew coffee but device is a teapot."; - case HTTP_INTERNAL_ERROR: return "The server encountered an internal error that couldn't be recovered from."; - case HTTP_NOT_IMPLEMENTED: return "Server lacks the ability to fulfil the request."; - case HTTP_UNAVAILABLE: return "The server is either overloaded or down for maintenance."; + case HTTP_OK: + return "Success!"; + case HTTP_PARTIAL_CONTENT: + return "Delivering part of requested resource."; + case HTTP_MOVED_PERMANENTLY: + return "This content has moved to another place."; + case HTTP_NOT_MODIFIED: + return "The content has not changed since previous request."; + case HTTP_BAD_REQUEST: + return "The client has issued a bad request."; + case HTTP_NOT_AUTHORIZED: + return "Client has no authorization to access this resource."; + case HTTP_FORBIDDEN: + return "Access to this resource has been denied."; + case HTTP_NOT_FOUND: + return "The requested resource could not be found on this server."; + case HTTP_NOT_ALLOWED: + return "The requested method is not allowed by this server."; + case HTTP_TIMEOUT: + return "Client did not produce a request within expected timeframe."; + case HTTP_TOO_LARGE: + return "The request entity is too large."; + case HTTP_RANGE_UNSATISFIABLE: + return "The server can't supply the requested portion of the requested resource."; + case HTTP_I_AM_A_TEAPOT: + return "Client requested to brew coffee but device is a teapot."; + case HTTP_INTERNAL_ERROR: + return "The server encountered an internal error that couldn't be recovered from."; + case HTTP_NOT_IMPLEMENTED: + return "Server lacks the ability to fulfil the request."; + case HTTP_UNAVAILABLE: + return "The server is either overloaded or down for maintenance."; } return "Invalid"; } From 9bc34bfe6d45234aeedcc191b6c9104efe041a22 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 3 Nov 2015 20:45:21 -0200 Subject: [PATCH 0067/2505] Reduce number of branches while checking for hex digits --- common/lwan-request.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index 50ecd442b..0e2101cbf 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -303,7 +303,9 @@ decode_hex_digit(char ch) static ALWAYS_INLINE bool is_hex_digit(char ch) { - return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'); + return ((ch - '0') <= ('9' - '0')) + || ((ch - 'a') <= ('f' - 'a')) + || ((ch - 'A') <= ('F' - 'A')); } static size_t From 94b38c97c28306c803a5261cbfcac60b8b0b5e96 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 4 Nov 2015 08:19:41 -0200 Subject: [PATCH 0068/2505] Ensure is_hex_digit() works with unsigned chars --- common/lwan-request.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index 0e2101cbf..af316926b 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -303,9 +303,11 @@ decode_hex_digit(char ch) static ALWAYS_INLINE bool is_hex_digit(char ch) { - return ((ch - '0') <= ('9' - '0')) - || ((ch - 'a') <= ('f' - 'a')) - || ((ch - 'A') <= ('F' - 'A')); + unsigned char c = (unsigned char)ch; + + return ((c - '0') <= ('9' - '0')) + || ((c - 'a') <= ('f' - 'a')) + || ((c - 'A') <= ('F' - 'A')); } static size_t From 27a845f0ec6f7afda547bf85636fc9e8239ba3b6 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 4 Nov 2015 08:19:57 -0200 Subject: [PATCH 0069/2505] Use proper address structures for proxy protocol union --- common/lwan-request.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index af316926b..30d3f80fa 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -69,14 +69,14 @@ union proxy_protocol_header { uint16_t len; union { struct { - uint32_t src_addr; - uint32_t dst_addr; + in_addr_t src_addr; + in_addr_t dst_addr; uint16_t src_port; uint16_t dst_port; } ip4; struct { - uint8_t src_addr[sizeof(struct in6_addr)]; - uint8_t dst_addr[sizeof(struct in6_addr)]; + struct in6_addr src_addr; + struct in6_addr dst_addr; uint16_t src_port; uint16_t dst_port; } ip6; @@ -264,10 +264,10 @@ parse_proxy_protocol_v2(lwan_request_t *request, char *buffer) from->sin6_family = to->sin6_family = AF_INET6; - memcpy(&from->sin6_addr, hdr->v2.addr.ip6.src_addr, sizeof(from->sin6_addr)); + from->sin6_addr = hdr->v2.addr.ip6.src_addr; from->sin6_port = hdr->v2.addr.ip6.src_port; - memcpy(&to->sin6_addr, hdr->v2.addr.ip6.dst_addr, sizeof(to->sin6_addr)); + to->sin6_addr = hdr->v2.addr.ip6.dst_addr; to->sin6_port = hdr->v2.addr.ip6.dst_port; } else { return NULL; From f777cb4711119fbb3dabc98a11cf269482a22bde Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 11 Dec 2015 18:32:09 -0200 Subject: [PATCH 0070/2505] dup() won't work as commented --- common/lwan-serve-files.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/common/lwan-serve-files.c b/common/lwan-serve-files.c index 99d9d5264..d7cc90f56 100644 --- a/common/lwan-serve-files.c +++ b/common/lwan-serve-files.c @@ -85,12 +85,6 @@ struct mmap_cache_data_t_ { }; struct sendfile_cache_data_t_ { - /* - * FIXME Investigate if keeping files open and dup()ing them - * is faster than openat()ing. This won't scale as well, - * but might be a good alternative for popular files. - */ - struct { char *filename; size_t size; From 1611f32a509710d6a3399b9bf2491aa15139bf1f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 11 Dec 2015 18:32:41 -0200 Subject: [PATCH 0071/2505] Only call setrlimit() if necessary --- common/lwan.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/common/lwan.c b/common/lwan.c index 7f5777ca3..9a1f72277 100644 --- a/common/lwan.c +++ b/common/lwan.c @@ -452,16 +452,14 @@ setup_open_file_count_limits(void) if (getrlimit(RLIMIT_NOFILE, &r) < 0) lwan_status_critical_perror("getrlimit"); - if (r.rlim_max == r.rlim_cur) - return r.rlim_cur; - - if (r.rlim_max == RLIM_INFINITY) - r.rlim_cur *= 8; - else if (r.rlim_cur < r.rlim_max) - r.rlim_cur = r.rlim_max; - - if (setrlimit(RLIMIT_NOFILE, &r) < 0) - lwan_status_critical_perror("setrlimit"); + if (r.rlim_max != r.rlim_cur) { + if (r.rlim_max == RLIM_INFINITY) + r.rlim_cur *= 8; + else if (r.rlim_cur < r.rlim_max) + r.rlim_cur = r.rlim_max; + if (setrlimit(RLIMIT_NOFILE, &r) < 0) + lwan_status_critical_perror("setrlimit"); + } return r.rlim_cur; } From b1df1e72148ffd94d5e0fed8670fcd7482dc6147 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 12 Dec 2015 17:42:52 -0200 Subject: [PATCH 0072/2505] Fix double free on exit lwan_shutdown() will already free the listener if it's different from the default listener, so there's no need to free it again. Fixes #121. --- lwan/main.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/lwan/main.c b/lwan/main.c index 16ed1d46f..1c56fae82 100644 --- a/lwan/main.c +++ b/lwan/main.c @@ -252,9 +252,7 @@ main(int argc, char *argv[]) lwan_shutdown(&l); out: - free(root); - free(c.listener); return exit_status; } From bdc5bcf34cf596d42b9e316ef39de1a5f4a0b0b0 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 23 Dec 2015 00:47:55 -0200 Subject: [PATCH 0073/2505] Use bsearch() instead of value_array_bsearch() Glibc has an inline version in stdlib.h, making the open-coded version in Lwan not necessary. The only difference is that strcmp() is now used instead of strncmp(), but since compared values are guaranteed to be nul-terminated, this isn't much of a problem. --- common/lwan-request.c | 44 +++++++++++++++++-------------------------- 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index 30d3f80fa..381f9142c 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -1044,51 +1044,41 @@ lwan_process_request(lwan_t *l, lwan_request_t *request, return helper.next_request; } -static const char * -value_array_bsearch(lwan_key_value_t *base, const size_t len, const char *key) +static int +compare_key_value(const void *a, const void *b) { - if (UNLIKELY(!len)) - return NULL; + const lwan_key_value_t *kva = a; + const lwan_key_value_t *kvb = b; - size_t lower_bound = 0; - size_t upper_bound = len; - size_t key_len = strlen(key); - - while (lower_bound < upper_bound) { - /* lower_bound + upper_bound will never overflow */ - size_t idx = (lower_bound + upper_bound) / 2; - lwan_key_value_t *ptr = base + idx; - int cmp = strncmp(key, ptr->key, key_len); - if (LIKELY(!cmp)) - return ptr->value; - if (cmp > 0) - lower_bound = idx + 1; - else - upper_bound = idx; - } + return strcmp(kva->key, kvb->key); +} + +static inline void * +value_lookup(const void *base, size_t len, const char *key) +{ + lwan_key_value_t *entry, k = { .key = (char *)key }; - return NULL; + entry = bsearch(&k, base, len, sizeof(k), compare_key_value); + return LIKELY(entry) ? entry->value : NULL; } const char * lwan_request_get_query_param(lwan_request_t *request, const char *key) { - return value_array_bsearch(request->query_params.base, - request->query_params.len, key); + return value_lookup(request->query_params.base, request->query_params.len, + key); } const char * lwan_request_get_post_param(lwan_request_t *request, const char *key) { - return value_array_bsearch(request->post_data.base, - request->post_data.len, key); + return value_lookup(request->post_data.base, request->post_data.len, key); } const char * lwan_request_get_cookie(lwan_request_t *request, const char *key) { - return value_array_bsearch(request->cookies.base, - request->cookies.len, key); + return value_lookup(request->cookies.base, request->cookies.len, key); } ALWAYS_INLINE int From 309eeeafcab6c37b98ca41f37091475970e4571f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 2 Jan 2016 11:42:49 -0200 Subject: [PATCH 0074/2505] Specify file list and default response template from files This adds two new configuration keys: `error_template` (global) and `directory_list_template` (for the file serving module). By specifying a file path in either of these, the template will come from those files instead of using the hardcoded, built-in version. No example files provided, but the strings from `lwan-response.c` and `lwan-serve-files.c` respectively can be used as a starting point. --- common/lwan-private.h | 4 ++-- common/lwan-response.c | 12 +++++++++--- common/lwan-serve-files.c | 12 +++++++++--- common/lwan-serve-files.h | 4 +++- common/lwan.c | 11 ++++++++--- common/lwan.h | 1 + 6 files changed, 32 insertions(+), 12 deletions(-) diff --git a/common/lwan-private.h b/common/lwan-private.h index 692c64306..0aab62b8b 100644 --- a/common/lwan-private.h +++ b/common/lwan-private.h @@ -21,8 +21,8 @@ #include "lwan.h" -void lwan_response_init(void); -void lwan_response_shutdown(void); +void lwan_response_init(lwan_t *l); +void lwan_response_shutdown(lwan_t *l); void lwan_socket_init(lwan_t *l); void lwan_socket_shutdown(lwan_t *l); diff --git a/common/lwan-response.c b/common/lwan-response.c index 3a2a97d55..a5d0b71e2 100644 --- a/common/lwan-response.c +++ b/common/lwan-response.c @@ -62,7 +62,7 @@ struct error_template_t { }; void -lwan_response_init(void) +lwan_response_init(lwan_t *l) { static lwan_var_descriptor_t error_descriptor[] = { TPL_VAR_STR(struct error_template_t, short_message), @@ -74,13 +74,19 @@ lwan_response_init(void) lwan_status_debug("Initializing default response"); - error_template = lwan_tpl_compile_string(error_template_str, error_descriptor); + if (l->config.error_template) { + error_template = lwan_tpl_compile_file(l->config.error_template, + error_descriptor); + } else { + error_template = lwan_tpl_compile_string(error_template_str, + error_descriptor); + } if (UNLIKELY(!error_template)) lwan_status_critical_perror("lwan_tpl_compile_string"); } void -lwan_response_shutdown(void) +lwan_response_shutdown(lwan_t *l __attribute__((unused))) { lwan_status_debug("Shutting down response"); assert(error_template); diff --git a/common/lwan-serve-files.c b/common/lwan-serve-files.c index d7cc90f56..e80e4a0bb 100644 --- a/common/lwan-serve-files.c +++ b/common/lwan-serve-files.c @@ -665,8 +665,13 @@ serve_files_init(void *args) goto out_cache_create; } - priv->directory_list_tpl = lwan_tpl_compile_string( - directory_list_tpl_str, file_list_desc); + if (settings->directory_list_template) { + priv->directory_list_tpl = lwan_tpl_compile_file( + settings->directory_list_template, file_list_desc); + } else { + priv->directory_list_tpl = lwan_tpl_compile_string( + directory_list_tpl_str, file_list_desc); + } if (!priv->directory_list_tpl) { lwan_status_error("Could not compile directory list template"); goto out_tpl_compile; @@ -700,7 +705,8 @@ serve_files_init_from_hash(const struct hash *hash) .root_path = hash_find(hash, "path"), .index_html = hash_find(hash, "index_path"), .serve_precompressed_files = - parse_bool(hash_find(hash, "serve_precompressed_files"), true) + parse_bool(hash_find(hash, "serve_precompressed_files"), true), + .directory_list_template = hash_find(hash, "directory_list_template") }; return serve_files_init(&settings); } diff --git a/common/lwan-serve-files.h b/common/lwan-serve-files.h index a972e246c..491226c53 100644 --- a/common/lwan-serve-files.h +++ b/common/lwan-serve-files.h @@ -28,6 +28,7 @@ extern "C" { struct lwan_serve_files_settings_t { const char *root_path; const char *index_html; + const char *directory_list_template; bool serve_precompressed_files; }; @@ -36,7 +37,8 @@ struct lwan_serve_files_settings_t { .args = ((struct lwan_serve_files_settings_t[]) {{ \ .root_path = root_path_, \ .index_html = index_html_, \ - .serve_precompressed_files = serve_precompressed_files_ \ + .serve_precompressed_files = serve_precompressed_files_, \ + .directory_list_template = NULL \ }}), \ .flags = (lwan_handler_flags_t)0 diff --git a/common/lwan.c b/common/lwan.c index 9a1f72277..abd1e4371 100644 --- a/common/lwan.c +++ b/common/lwan.c @@ -406,7 +406,10 @@ static bool setup_from_config(lwan_t *lwan) else if (!strcmp(line.line.key, "expires")) lwan->config.expires = parse_time_period(line.line.value, default_config.expires); - else if (!strcmp(line.line.key, "threads")) { + else if (!strcmp(line.line.key, "error_template")) { + free(lwan->config.error_template); + lwan->config.error_template = strdup(line.line.value); + } else if (!strcmp(line.line.key, "threads")) { long n_threads = parse_long(line.line.value, default_config.n_threads); if (n_threads < 0) config_error(&conf, "Invalid number of threads: %d", n_threads); @@ -521,7 +524,6 @@ lwan_init_with_config(lwan_t *l, const lwan_config_t *config) * printed if we're on a debug build, so the quiet setting will be * respected. */ lwan_job_thread_init(); - lwan_response_init(); lwan_tables_init(); lwan_module_init(l); @@ -535,6 +537,8 @@ lwan_init_with_config(lwan_t *l, const lwan_config_t *config) lwan_status_init(l); } + lwan_response_init(l); + /* Continue initialization as normal. */ lwan_status_debug("Initializing lwan web server"); @@ -567,6 +571,7 @@ lwan_shutdown(lwan_t *l) if (l->config.listener != default_config.listener) free(l->config.listener); + free(l->config.error_template); lwan_job_thread_shutdown(); lwan_thread_shutdown(l); @@ -576,7 +581,7 @@ lwan_shutdown(lwan_t *l) free(l->conns); - lwan_response_shutdown(); + lwan_response_shutdown(l); lwan_tables_shutdown(); lwan_status_shutdown(l); lwan_http_authorize_shutdown(); diff --git a/common/lwan.h b/common/lwan.h index add850d74..10f63265b 100644 --- a/common/lwan.h +++ b/common/lwan.h @@ -281,6 +281,7 @@ struct lwan_thread_t_ { struct lwan_config_t_ { char *listener; + char *error_template; unsigned short keep_alive_timeout; unsigned int expires; short unsigned int n_threads; From 913ec0db1acafcb32d8cabf9845e7b0fa179276c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 2 Jan 2016 12:23:05 -0200 Subject: [PATCH 0075/2505] Ensure all calls to coro_malloc() are null-checked --- common/lwan-http-authorize.c | 3 +++ common/lwan-io-wrappers.c | 5 +++++ common/lwan-thread.c | 3 +++ 3 files changed, 11 insertions(+) diff --git a/common/lwan-http-authorize.c b/common/lwan-http-authorize.c index 2e3e4766b..d0c81d299 100644 --- a/common/lwan-http-authorize.c +++ b/common/lwan-http-authorize.c @@ -204,6 +204,9 @@ lwan_http_authorize(lwan_request_t *request, unauthorized: headers = coro_malloc(request->conn->coro, 2 * sizeof(*headers)); + if (UNLIKELY(!headers)) + return false; + headers[0].key = "WWW-Authenticate"; headers[0].value = coro_printf(request->conn->coro, authenticate_tmpl, realm); diff --git a/common/lwan-io-wrappers.c b/common/lwan-io-wrappers.c index 49f8ec3d6..a2d89a042 100644 --- a/common/lwan-io-wrappers.c +++ b/common/lwan-io-wrappers.c @@ -176,6 +176,11 @@ sendfile_read_write(coro_t *coro, int in_fd, int out_fd, off_t offset, size_t co * inside the coroutine */ char *buffer = coro_malloc(coro, BUFFER_SIZE); + if (UNLIKELY(!buffer)) { + lwan_status_perror("coro_malloc"); + return -ENOMEM; + } + if (offset && lseek(in_fd, offset, SEEK_SET) < 0) { lwan_status_perror("lseek"); return -1; diff --git a/common/lwan-thread.c b/common/lwan-thread.c index 5d33c6023..25c890fed 100644 --- a/common/lwan-thread.c +++ b/common/lwan-thread.c @@ -156,6 +156,9 @@ process_request_coro(coro_t *coro) lwan_proxy_t proxy; int gc_counter = CORO_GC_THRESHOLD; + if (UNLIKELY(!strbuf)) + return CONN_CORO_ABORT; + strbuf_init(strbuf); while (true) { From d2dfe75abf98a89663980ec45584d37d1f5c11b2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 3 Jan 2016 11:37:04 -0200 Subject: [PATCH 0076/2505] Parse key-value pairs without values This considers them to be an empty string. Fixes #122. --- common/lwan-request.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index 381f9142c..da4e99631 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -369,13 +369,15 @@ parse_key_values(lwan_request_t *request, return; key = ptr; - value = strsep_char(ptr, '='); + ptr = strsep_char(key, separator); + + value = strsep_char(key, '='); if (UNLIKELY(!value)) + value = ""; + else if (UNLIKELY(!decode_value(value))) return; - ptr = strsep_char(value, separator); - - if (UNLIKELY(!decode_value(key) || !decode_value(value))) + if (UNLIKELY(!decode_value(key))) return; kvs[values].key = key; From e499211cfa0eec1837fb79485138d544537a888f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 3 Jan 2016 17:12:25 -0200 Subject: [PATCH 0077/2505] Only use sendfile() while sending larger files Get rid of the buggy read()+write() loop. --- common/lwan-io-wrappers.c | 68 ++++----------------------------------- 1 file changed, 6 insertions(+), 62 deletions(-) diff --git a/common/lwan-io-wrappers.c b/common/lwan-io-wrappers.c index a2d89a042..7856b5506 100644 --- a/common/lwan-io-wrappers.c +++ b/common/lwan-io-wrappers.c @@ -167,63 +167,23 @@ lwan_send(lwan_request_t *request, const void *buf, size_t count, int flags) __builtin_unreachable(); } -static ALWAYS_INLINE ssize_t -sendfile_read_write(coro_t *coro, int in_fd, int out_fd, off_t offset, size_t count) -{ - /* FIXME: Use lwan_{read,write}() here */ - ssize_t total_bytes_written = 0; - /* This buffer is allocated on the heap in order to minimize stack usage - * inside the coroutine */ - char *buffer = coro_malloc(coro, BUFFER_SIZE); - - if (UNLIKELY(!buffer)) { - lwan_status_perror("coro_malloc"); - return -ENOMEM; - } - - if (offset && lseek(in_fd, offset, SEEK_SET) < 0) { - lwan_status_perror("lseek"); - return -1; - } - - while (count > 0) { - ssize_t read_bytes = read(in_fd, buffer, BUFFER_SIZE); - if (read_bytes < 0) { - coro_yield(coro, CONN_CORO_ABORT); - __builtin_unreachable(); - } - - ssize_t bytes_written = write(out_fd, buffer, (size_t)read_bytes); - if (bytes_written < 0) { - coro_yield(coro, CONN_CORO_ABORT); - __builtin_unreachable(); - } - - total_bytes_written += bytes_written; - count -= (size_t)bytes_written; - coro_yield(coro, CONN_CORO_MAY_RESUME); - } - - return total_bytes_written; -} - -static ALWAYS_INLINE ssize_t -sendfile_linux_sendfile(coro_t *coro, int in_fd, int out_fd, off_t offset, size_t count) +ssize_t +lwan_sendfile(lwan_request_t *request, int in_fd, off_t offset, size_t count) { size_t total_written = 0; size_t to_be_written = count; do { - ssize_t written = sendfile(out_fd, in_fd, &offset, to_be_written); + ssize_t written = sendfile(request->fd, in_fd, &offset, to_be_written); if (written < 0) { switch (errno) { case EAGAIN: case EINTR: - coro_yield(coro, CONN_CORO_MAY_RESUME); + coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); continue; default: - coro_yield(coro, CONN_CORO_ABORT); + coro_yield(request->conn->coro, CONN_CORO_ABORT); __builtin_unreachable(); } } @@ -231,24 +191,8 @@ sendfile_linux_sendfile(coro_t *coro, int in_fd, int out_fd, off_t offset, size_ total_written += (size_t)written; to_be_written -= (size_t)written; - coro_yield(coro, CONN_CORO_MAY_RESUME); + coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); } while (to_be_written > 0); return (ssize_t)total_written; } - -ssize_t -lwan_sendfile(lwan_request_t *request, int in_fd, off_t offset, size_t count) -{ - ssize_t written_bytes = sendfile_linux_sendfile( - request->conn->coro, in_fd, request->fd, offset, count); - - if (UNLIKELY(written_bytes < 0)) { - switch (errno) { - case ENOSYS: - case EINVAL: - return sendfile_read_write(request->conn->coro, in_fd, request->fd, offset, count); - } - } - return written_bytes; -} From 6279972c50ffd0acb81c44922015830fa2dd0120 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 6 Jan 2016 00:02:32 -0200 Subject: [PATCH 0078/2505] No need to add 1 to then subtract 1 while appending str in rewrite Just use `>=` when comparing the sizes. --- common/lwan-rewrite.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/lwan-rewrite.c b/common/lwan-rewrite.c index 1cbc54028..cc033dab5 100644 --- a/common/lwan-rewrite.c +++ b/common/lwan-rewrite.c @@ -80,15 +80,15 @@ module_rewrite_as(lwan_request_t *request, const char *url) static bool append_str(struct str_builder *builder, const char *src, size_t src_len) { - size_t total_size = builder->len + src_len + 1 /* for the \0 */; + size_t total_size = builder->len + src_len; char *dest; - if (total_size > builder->size) + if (total_size >= builder->size) return false; dest = mempcpy(builder->buffer + builder->len, src, src_len); *dest = '\0'; - builder->len = total_size - 1; + builder->len = total_size; return true; } From d35b2ef9e7c125f34c647d916574c2042c5e0036 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 6 Jan 2016 02:00:49 -0200 Subject: [PATCH 0079/2505] Reduce number of allocations in template If using built-in template strings, reference them directly instead of a newly-allocated-then-copied chunk of memory. This introduces `strbuf_new_static()`, which creates a dynamically-allocated `strbuf_t` with the `STATIC` flag set. If loading the template from a file, chunks of memory are allocated, then copied. --- common/lwan-response.c | 4 ++-- common/lwan-serve-files.c | 5 +++-- common/lwan-template.c | 39 +++++++++++++++++++++++++++++++-------- common/lwan-template.h | 5 +++++ common/strbuf.c | 15 +++++++++++++++ common/strbuf.h | 1 + 6 files changed, 57 insertions(+), 12 deletions(-) diff --git a/common/lwan-response.c b/common/lwan-response.c index a5d0b71e2..07b38a85b 100644 --- a/common/lwan-response.c +++ b/common/lwan-response.c @@ -78,8 +78,8 @@ lwan_response_init(lwan_t *l) error_template = lwan_tpl_compile_file(l->config.error_template, error_descriptor); } else { - error_template = lwan_tpl_compile_string(error_template_str, - error_descriptor); + error_template = lwan_tpl_compile_string_full(error_template_str, + error_descriptor, LWAN_TPL_FLAG_CONST_TEMPLATE); } if (UNLIKELY(!error_template)) lwan_status_critical_perror("lwan_tpl_compile_string"); diff --git a/common/lwan-serve-files.c b/common/lwan-serve-files.c index e80e4a0bb..a440d4ef3 100644 --- a/common/lwan-serve-files.c +++ b/common/lwan-serve-files.c @@ -669,8 +669,9 @@ serve_files_init(void *args) priv->directory_list_tpl = lwan_tpl_compile_file( settings->directory_list_template, file_list_desc); } else { - priv->directory_list_tpl = lwan_tpl_compile_string( - directory_list_tpl_str, file_list_desc); + priv->directory_list_tpl = lwan_tpl_compile_string_full( + directory_list_tpl_str, file_list_desc, + LWAN_TPL_FLAG_CONST_TEMPLATE); } if (!priv->directory_list_tpl) { lwan_status_error("Could not compile directory list template"); diff --git a/common/lwan-template.c b/common/lwan-template.c index 2275fd05d..d86b8767c 100644 --- a/common/lwan-template.c +++ b/common/lwan-template.c @@ -145,6 +145,7 @@ struct parser { struct chunk *data; size_t used, reserved; } chunks; + lwan_tpl_flag_t template_flags; }; struct stacked_lexeme { @@ -791,10 +792,20 @@ static void *parser_text(struct parser *parser, struct lexeme *lexeme) if (lexeme->value.len == 1) { emit_chunk(parser, ACTION_APPEND_CHAR, 0, (void *)(uintptr_t)*lexeme->value.value); } else { - strbuf_t *buf = strbuf_new_with_size(lexeme->value.len); - if (!buf) - return error_lexeme(lexeme, "Out of memory"); - strbuf_set(buf, lexeme->value.value, lexeme->value.len); + strbuf_t *buf; + + if (parser->template_flags & LWAN_TPL_FLAG_CONST_TEMPLATE) { + buf = strbuf_new_static(lexeme->value.value, lexeme->value.len); + if (!buf) + goto no_buf; + } else { + buf = strbuf_new_with_size(lexeme->value.len); + if (!buf) + goto no_buf; + + strbuf_set(buf, lexeme->value.value, lexeme->value.len); + } + emit_chunk(parser, ACTION_APPEND, 0, buf); } parser->tpl->minimum_size += lexeme->value.len; @@ -806,6 +817,9 @@ static void *parser_text(struct parser *parser, struct lexeme *lexeme) } return unexpected_lexeme(lexeme); + +no_buf: + return error_lexeme(lexeme, "Out of memory"); } void @@ -1097,13 +1111,15 @@ static bool parser_shutdown(struct parser *parser, struct lexeme *lexeme) return success; } -static bool parse_string(lwan_tpl_t *tpl, const char *string, const lwan_var_descriptor_t *descriptor) +static bool parse_string(lwan_tpl_t *tpl, const char *string, const lwan_var_descriptor_t *descriptor, + lwan_tpl_flag_t flags) { struct parser parser = { .tpl = tpl, .symtab = NULL, .chunks = { .used = 0, .reserved = array_increment_step }, - .descriptor = descriptor + .descriptor = descriptor, + .template_flags = flags }; void *(*state)(struct parser *parser, struct lexeme *lexeme) = parser_text; struct lexeme *lexeme = NULL; @@ -1118,13 +1134,14 @@ static bool parse_string(lwan_tpl_t *tpl, const char *string, const lwan_var_des } lwan_tpl_t * -lwan_tpl_compile_string(const char *string, const lwan_var_descriptor_t *descriptor) +lwan_tpl_compile_string_full(const char *string, const lwan_var_descriptor_t *descriptor, + lwan_tpl_flag_t flags) { lwan_tpl_t *tpl; tpl = calloc(1, sizeof(*tpl)); if (tpl) { - if (parse_string(tpl, string, descriptor)) + if (parse_string(tpl, string, descriptor, flags)) return tpl; } @@ -1132,6 +1149,12 @@ lwan_tpl_compile_string(const char *string, const lwan_var_descriptor_t *descrip return NULL; } +lwan_tpl_t * +lwan_tpl_compile_string(const char *string, const lwan_var_descriptor_t *descriptor) +{ + return lwan_tpl_compile_string_full(string, descriptor, 0); +} + lwan_tpl_t * lwan_tpl_compile_file(const char *filename, const lwan_var_descriptor_t *descriptor) { diff --git a/common/lwan-template.h b/common/lwan-template.h index e73ff9c09..c41362d0c 100644 --- a/common/lwan-template.h +++ b/common/lwan-template.h @@ -27,6 +27,10 @@ typedef struct lwan_var_descriptor_t_ lwan_var_descriptor_t; typedef int (*lwan_tpl_list_generator_t)(coro_t *coro); +typedef enum { + LWAN_TPL_FLAG_CONST_TEMPLATE = 1<<0 +} lwan_tpl_flag_t; + struct lwan_var_descriptor_t_ { const char *name; const off_t offset; @@ -82,6 +86,7 @@ bool lwan_tpl_str_is_empty(void *ptr); void lwan_append_double_to_strbuf(strbuf_t *buf, void *ptr); bool lwan_tpl_double_is_empty(void *ptr); +lwan_tpl_t *lwan_tpl_compile_string_full(const char *string, const lwan_var_descriptor_t *descriptor, lwan_tpl_flag_t flags); lwan_tpl_t *lwan_tpl_compile_string(const char *string, const lwan_var_descriptor_t *descriptor); lwan_tpl_t *lwan_tpl_compile_file(const char *filename, const lwan_var_descriptor_t *descriptor); strbuf_t *lwan_tpl_apply(lwan_tpl_t *tpl, void *variables); diff --git a/common/strbuf.c b/common/strbuf.c index 4b90a51cc..c33e703b5 100644 --- a/common/strbuf.c +++ b/common/strbuf.c @@ -136,6 +136,21 @@ strbuf_new(void) return strbuf_new_with_size(DEFAULT_BUF_SIZE); } +ALWAYS_INLINE strbuf_t * +strbuf_new_static(const char *str, size_t size) +{ + strbuf_t *s = malloc(sizeof(*s)); + + if (!s) + return NULL; + + s->flags = STATIC | DYNAMICALLY_ALLOCATED; + s->value.static_buffer = str; + s->len.buffer = s->len.allocated = size; + + return s; +} + void strbuf_free(strbuf_t *s) { diff --git a/common/strbuf.h b/common/strbuf.h index 99a47a0a6..db5a14fe2 100644 --- a/common/strbuf.h +++ b/common/strbuf.h @@ -37,6 +37,7 @@ struct strbuf_t_ { bool strbuf_init_with_size(strbuf_t *buf, size_t size); bool strbuf_init(strbuf_t *buf); +strbuf_t *strbuf_new_static(const char *str, size_t size); strbuf_t *strbuf_new_with_size(size_t size); strbuf_t *strbuf_new(void); void strbuf_free(strbuf_t *s); From 90fbc2703fde57969f0e359b9b3420dd1dd5847d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 6 Jan 2016 10:14:57 -0200 Subject: [PATCH 0080/2505] epoll is not used in lwan.c --- common/lwan.c | 1 - 1 file changed, 1 deletion(-) diff --git a/common/lwan.c b/common/lwan.c index abd1e4371..15d09ca72 100644 --- a/common/lwan.c +++ b/common/lwan.c @@ -26,7 +26,6 @@ #include #include #include -#include #include #include From 07a19fc99826e0aafecbb0495e9cfe5f78179586 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 7 Jan 2016 23:24:09 -0200 Subject: [PATCH 0081/2505] Fix miscellaneous warnings from Clang --- common/lwan-cache.h | 2 +- common/lwan-io-wrappers.c | 1 - common/lwan.c | 4 ++-- freegeoip/freegeoip.c | 8 ++++---- techempower/json.c | 2 +- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/common/lwan-cache.h b/common/lwan-cache.h index 7396d8404..8e4d23647 100644 --- a/common/lwan-cache.h +++ b/common/lwan-cache.h @@ -27,7 +27,7 @@ struct cache_entry_t { struct list_node entries; char *key; - unsigned refs; + int refs; unsigned flags; time_t time_to_die; }; diff --git a/common/lwan-io-wrappers.c b/common/lwan-io-wrappers.c index 7856b5506..1fe9ae8b2 100644 --- a/common/lwan-io-wrappers.c +++ b/common/lwan-io-wrappers.c @@ -27,7 +27,6 @@ #include "lwan-io-wrappers.h" static const int MAX_FAILED_TRIES = 5; -static const size_t BUFFER_SIZE = 1400; int lwan_openat(lwan_request_t *request, diff --git a/common/lwan.c b/common/lwan.c index 15d09ca72..b501c59ac 100644 --- a/common/lwan.c +++ b/common/lwan.c @@ -199,11 +199,11 @@ static void parse_listener_prefix_authorization(config_t *c, static void parse_listener_prefix(config_t *c, config_line_t *l, lwan_t *lwan, const lwan_module_t *module) { - lwan_url_map_t url_map = {0}; + lwan_url_map_t url_map = { }; struct hash *hash = hash_str_new(free, free); void *handler = NULL; char *prefix = strdupa(l->line.value); - config_t isolated = {0}; + config_t isolated = { }; if (!config_isolate_section(c, l, &isolated)) { config_error(c, "Could not isolate configuration file"); diff --git a/freegeoip/freegeoip.c b/freegeoip/freegeoip.c index 9c86ec9f7..c3ec3a4af 100644 --- a/freegeoip/freegeoip.c +++ b/freegeoip/freegeoip.c @@ -68,7 +68,7 @@ static const lwan_var_descriptor_t template_descriptor[] = { TPL_VAR_SENTINEL }; -static const char const json_template_str[] = \ +static const char json_template_str[] = \ "{{callback?}}{{callback}}({{/callback?}}" \ "{" \ "\"country_code\":\"{{country.code}}\"," \ @@ -85,7 +85,7 @@ static const char const json_template_str[] = \ "}" "{{callback?}});{{/callback?}}"; -static const char const xml_template_str[] = \ +static const char xml_template_str[] = \ "" \ "" \ "{{ip}}" \ @@ -101,7 +101,7 @@ static const char const xml_template_str[] = \ "{{metro.area}}" \ ""; -static const char const csv_template_str[] = \ +static const char csv_template_str[] = \ "\"{{ip}}\"," \ "\"{{country.code}}\"," \ "\"{{country.name}}\"," \ @@ -115,7 +115,7 @@ static const char const csv_template_str[] = \ "\"{{metro.area}}\""; -static const char const ip_to_city_query[] = \ +static const char ip_to_city_query[] = \ "SELECT " \ " city_location.country_code, country_blocks.country_name," \ " city_location.region_code, region_names.region_name," \ diff --git a/techempower/json.c b/techempower/json.c index 203474e81..9cf6ec1a9 100644 --- a/techempower/json.c +++ b/techempower/json.c @@ -804,7 +804,7 @@ static bool parse_object(const char **sp, JsonNode **out) bool parse_string(const char **sp, char **out) { const char *s = (char *)*sp; - SB sb = {0}; + SB sb = { }; char throwaway_buffer[4]; /* enough space for a UTF-8 character */ char *b; From 6234a732a984f0c7c6195caca99f5a5011bf4502 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 8 Jan 2016 20:32:01 -0200 Subject: [PATCH 0082/2505] Modify lwan_sendfile() to take a header+length This makes it easier to use the BSD variant of sendfile(). Also changes the prototype so no values are returned, since the coroutine will be aborted automatically if the file could not be fully transfered. Other I/O wrappers might be likewise changed. --- common/lwan-io-wrappers.c | 9 +++++---- common/lwan-io-wrappers.h | 5 +++-- common/lwan-serve-files.c | 4 ++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/common/lwan-io-wrappers.c b/common/lwan-io-wrappers.c index 1fe9ae8b2..16d3f3c7a 100644 --- a/common/lwan-io-wrappers.c +++ b/common/lwan-io-wrappers.c @@ -166,12 +166,15 @@ lwan_send(lwan_request_t *request, const void *buf, size_t count, int flags) __builtin_unreachable(); } -ssize_t -lwan_sendfile(lwan_request_t *request, int in_fd, off_t offset, size_t count) +void +lwan_sendfile(lwan_request_t *request, int in_fd, off_t offset, size_t count, + const char *header, size_t header_len) { size_t total_written = 0; size_t to_be_written = count; + lwan_send(request, header, header_len, MSG_MORE); + do { ssize_t written = sendfile(request->fd, in_fd, &offset, to_be_written); if (written < 0) { @@ -192,6 +195,4 @@ lwan_sendfile(lwan_request_t *request, int in_fd, off_t offset, size_t count) coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); } while (to_be_written > 0); - - return (ssize_t)total_written; } diff --git a/common/lwan-io-wrappers.h b/common/lwan-io-wrappers.h index 2715f7127..37d7859ba 100644 --- a/common/lwan-io-wrappers.h +++ b/common/lwan-io-wrappers.h @@ -32,6 +32,7 @@ ssize_t lwan_write(lwan_request_t *request, const void *buffer, size_t count); ssize_t lwan_send(lwan_request_t *request, const void *buf, size_t count, int flags); -ssize_t lwan_sendfile(lwan_request_t *request, int in_fd, - off_t offset, size_t count); +void lwan_sendfile(lwan_request_t *request, int in_fd, + off_t offset, size_t count, + const char *header, size_t header_len); diff --git a/common/lwan-serve-files.c b/common/lwan-serve-files.c index a440d4ef3..411ba302f 100644 --- a/common/lwan-serve-files.c +++ b/common/lwan-serve-files.c @@ -875,8 +875,8 @@ sendfile_serve(lwan_request_t *request, void *data) } } - lwan_send(request, headers, header_len, MSG_MORE); - lwan_sendfile(request, file_fd, from, (size_t)to); + lwan_sendfile(request, file_fd, from, (size_t)to, + headers, header_len); } return return_status; From 39d5cc85483af2c83aa664591025b0cc15f407bb Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 11 Jan 2016 22:24:50 -0200 Subject: [PATCH 0083/2505] `syscall.h` is in `sys/syscall.h` on both Linux and FreeBSD --- common/hash.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/hash.c b/common/hash.c index 40a048682..3a9a02a87 100644 --- a/common/hash.c +++ b/common/hash.c @@ -28,7 +28,7 @@ #include #include #include -#include +#include #include #include #include From a5c430e592067aad1b100938f4eed8852d51d49f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 12 Jan 2016 23:26:42 -0200 Subject: [PATCH 0084/2505] Add some compilation flags Enable immediate binding, so that all symbols are resolved on program load, and also enable read-only relocations, to protect the global offset table from being tampered with. While at it, try to align data to cache lines by passing `-malign-data=cacheline`, if available. --- CMakeLists.txt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 03fbdad85..3d398170f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,10 +60,26 @@ set(C_FLAGS_REL "-mtune=native") include(CheckCCompilerFlag) if (${CMAKE_BUILD_TYPE} MATCHES "Rel") + check_c_compiler_flag(-Wl,-z,now HAS_GOT_PROTECTION1) + if (HAS_GOT_PROTECTION1) + set(C_FLAGS_REL "${CFLAGS_REL} -Wl,-z,now") + endif () + + check_c_compiler_flag(-Wl,-z,relro HAS_GOT_PROTECTION2) + if (HAS_GOT_PROTECTION2) + set(C_FLAGS_REL "${CFLAGS_REL} -Wl,-z,relro") + endif () + + check_c_compiler_flag(-malign-data=cacheline HAS_MALIGN_DATA_CACHELINE) + if (HAS_MALIGN_DATA_CACHELINE) + set(C_FLAGS_REL "${CFLAGS_REL} -malign-data=cacheline") + endif () + check_c_compiler_flag(-fno-asynchronous-unwind-tables HAS_ASYNC_UNWIND_TABLES) if (HAS_ASYNC_UNWIND_TABLES) set(C_FLAGS_REL "${CFLAGS_REL} -fno-asynchronous-unwind-tables") endif () + check_c_compiler_flag(-flto HAS_LTO) if (HAS_LTO) set(C_FLAGS_REL "${C_FLAGS_REL} -flto") From a0b57111bf967a6c68b1646f22b634d1f489d32f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 13 Jan 2016 00:43:07 -0200 Subject: [PATCH 0085/2505] Do not link directly with `dl`, use CMAKE_DL_LIBS There's no need to link with `-ldl` in FreeBSD, as it provides the necessary symbols in its libc. --- freegeoip/CMakeLists.txt | 2 +- lwan/CMakeLists.txt | 2 +- techempower/CMakeLists.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/freegeoip/CMakeLists.txt b/freegeoip/CMakeLists.txt index 21d571fe9..2a211616d 100644 --- a/freegeoip/CMakeLists.txt +++ b/freegeoip/CMakeLists.txt @@ -8,7 +8,7 @@ if (SQLITE_FOUND) target_link_libraries(freegeoip -Wl,-whole-archive lwan-common -Wl,-no-whole-archive - dl + ${CMAKE_DL_LIBS} ${ADDITIONAL_LIBRARIES} ${SQLITE_LIBRARIES} ) diff --git a/lwan/CMakeLists.txt b/lwan/CMakeLists.txt index 9dbbaef06..0b3c342ff 100644 --- a/lwan/CMakeLists.txt +++ b/lwan/CMakeLists.txt @@ -2,7 +2,7 @@ add_executable(lwan main.c) target_link_libraries(lwan -Wl,-whole-archive lwan-common -Wl,-no-whole-archive - dl + ${CMAKE_DL_LIBS} ${ADDITIONAL_LIBRARIES} ) diff --git a/techempower/CMakeLists.txt b/techempower/CMakeLists.txt index 65525a9ae..ad572b128 100644 --- a/techempower/CMakeLists.txt +++ b/techempower/CMakeLists.txt @@ -28,7 +28,7 @@ if (MYSQL_INCLUDE_DIR AND SQLITE_FOUND) target_link_libraries(techempower -Wl,-whole-archive lwan-common -Wl,-no-whole-archive - dl + ${CMAKE_DL_LIBS} ${ADDITIONAL_LIBRARIES} ${SQLITE_LIBRARIES} ${MYSQL_LIBRARY} From 48e442e8333e76f818b861bfc0e6a09a60aca25b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 13 Jan 2016 09:10:57 -0200 Subject: [PATCH 0086/2505] Use time(NULL) instead of clock_gettime() on OS X --- common/lwan-cache.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/common/lwan-cache.c b/common/lwan-cache.c index a1732c1d4..42685a9da 100644 --- a/common/lwan-cache.c +++ b/common/lwan-cache.c @@ -60,7 +60,9 @@ struct cache_t { struct { time_t time_to_live; +#ifndef __MACH__ clockid_t clock_id; +#endif } settings; unsigned flags; @@ -90,8 +92,12 @@ static clockid_t detect_fastest_monotonic_clock(void) static ALWAYS_INLINE void clock_monotonic_gettime(struct cache_t *cache, struct timespec *ts) { +#ifdef __MACH__ + ts->tv_sec = time(NULL); +#else if (UNLIKELY(clock_gettime(cache->settings.clock_id, ts) < 0)) lwan_status_perror("clock_gettime"); +#endif } struct cache_t *cache_create(CreateEntryCallback create_entry_cb, @@ -122,7 +128,9 @@ struct cache_t *cache_create(CreateEntryCallback create_entry_cb, cache->cb.destroy_entry = destroy_entry_cb; cache->cb.context = cb_context; +#ifndef __MACH__ cache->settings.clock_id = detect_fastest_monotonic_clock(); +#endif cache->settings.time_to_live = time_to_live; list_head_init(&cache->queue.list); From fe51d4be99c2fa8405e23d1942c6fc7b316e830c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 13 Jan 2016 09:11:37 -0200 Subject: [PATCH 0087/2505] Use the FreeBSD sendfile() variant --- common/lwan-io-wrappers.c | 58 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/common/lwan-io-wrappers.c b/common/lwan-io-wrappers.c index 16d3f3c7a..e27a382d5 100644 --- a/common/lwan-io-wrappers.c +++ b/common/lwan-io-wrappers.c @@ -21,7 +21,14 @@ #include #include #include +#include +#include + +#if defined(__linux__) #include +#elif defined(__FreeBSD__) +#include +#endif #include "lwan.h" #include "lwan-io-wrappers.h" @@ -166,6 +173,7 @@ lwan_send(lwan_request_t *request, const void *buf, size_t count, int flags) __builtin_unreachable(); } +#if defined(__linux__) void lwan_sendfile(lwan_request_t *request, int in_fd, off_t offset, size_t count, const char *header, size_t header_len) @@ -191,8 +199,56 @@ lwan_sendfile(lwan_request_t *request, int in_fd, off_t offset, size_t count, } total_written += (size_t)written; - to_be_written -= (size_t)written; + to_be_written -= (size_t)count; coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); } while (to_be_written > 0); } +#elif defined(__FreeBSD__) +void +lwan_sendfile(lwan_request_t *request, int in_fd, off_t offset, size_t count, + const char *header, size_t header_len) +{ + struct sf_hdtr headers = { + .headers = (struct iovec[]) { + { + .iov_base = (void *)header, + .iov_len = header_len + } + }, + .hdr_cnt = 1 + }; + size_t total_written = 0; + + do { + off_t sbytes; + int r; + + r = sendfile(request->fd, in_fd, offset, count, &headers, &sbytes, SF_MNOWAIT); + + if (UNLIKELY(r < 0)) { + switch (errno) { + case EAGAIN: + case EBUSY: + case EINTR: + coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); + continue; + + default: + coro_yield(request->conn->coro, CONN_CORO_ABORT); + __builtin_unreachable(); + } + } + + total_written += (size_t)sbytes; + + coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); + } while (total_written < count); +} +#else +void +lwan_sendfile(lwan_request_t *request, int in_fd, off_t offset, size_t count, + const char *header, size_t header_len) +{ +} +#endif From 71e7afbcecda2ad6d1285ab5d47c90312b08b1b8 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 13 Jan 2016 09:12:07 -0200 Subject: [PATCH 0088/2505] Use pthread_timedjoin_np() instead of pthread_tryjoin_np() on BSD --- common/lwan-job.c | 22 ++++++++++++++++++++-- common/lwan-thread.c | 5 +++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/common/lwan-job.c b/common/lwan-job.c index 6770b4348..9f680a6a7 100644 --- a/common/lwan-job.c +++ b/common/lwan-job.c @@ -26,6 +26,10 @@ #include #include +#ifdef __FreeBSD__ +#include +#endif + #include "lwan.h" #include "lwan-status.h" #include "list.h" @@ -99,13 +103,27 @@ void lwan_job_thread_shutdown(void) if (LIKELY(!pthread_mutex_lock(&queue_mutex))) { struct job_t *node, *next; + int r; + list_for_each_safe(&jobs, node, next, jobs) { list_del(&node->jobs); free(node); } running = false; - if (pthread_tryjoin_np(self, NULL) < 0) - lwan_status_critical_perror("pthread_join"); + +#ifdef __FreeBSD__ + r = pthread_timedjoin_np(self, NULL, &(const struct timespec) { .tv_sec = 1 }); + if (r) { + errno = r; + lwan_status_perror("pthread_timedjoin_np"); + } +#else + r = pthread_tryjoin_np(self, NULL); + if (r) { + errno = r; + lwan_status_perror("pthread_tryjoin_np"); + } +#endif pthread_mutex_unlock(&queue_mutex); } } diff --git a/common/lwan-thread.c b/common/lwan-thread.c index 25c890fed..c50238119 100644 --- a/common/lwan-thread.c +++ b/common/lwan-thread.c @@ -482,7 +482,12 @@ lwan_thread_shutdown(lwan_t *l) lwan_thread_t *t = &l->thread.threads[i]; lwan_status_debug("Waiting for thread %d to finish", i); +#ifdef __FreeBSD__ + pthread_timedjoin_np(l->thread.threads[i].self, NULL, + &(const struct timespec) { .tv_sec = 1 }); +#else pthread_join(l->thread.threads[i].self, NULL); +#endif lwan_status_debug("Closing pipe (%d, %d)", t->pipe_fd[0], t->pipe_fd[1]); From 17f78a4d1e8288cdd2df31685fac6a32ba8cf701 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 13 Jan 2016 09:14:08 -0200 Subject: [PATCH 0089/2505] Use the XSI definition of strerror_r() --- common/lwan-status.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/common/lwan-status.c b/common/lwan-status.c index 5f63878f0..1797b44c3 100644 --- a/common/lwan-status.c +++ b/common/lwan-status.c @@ -92,6 +92,18 @@ get_color_end_for_type(lwan_status_type_t type __attribute__((unused)), return retval; } +static inline char * +strerror_thunk_r(int error_number, char *buffer, size_t len) +{ +#ifdef __GLIBC__ + return strerror_r(error_number, buffer, len); +#else + if (!strerror_r(error_number, buffer, len)) + return buffer; + return NULL; +#endif +} + static void #ifdef NDEBUG status_out_msg(lwan_status_type_t type, const char *msg, size_t msg_len) @@ -120,10 +132,15 @@ status_out_msg(const char *file, const int line, const char *func, if (type & STATUS_PERROR) { char buffer[512]; - char *error_msg = strerror_r(error_number, buffer, sizeof(buffer) - 1); + char *errmsg = strerror_thunk_r(error_number, buffer, sizeof(buffer) - 1); + fputc(':', stdout); fputc(' ', stdout); - fwrite(error_msg, strlen(error_msg), 1, stdout); + if (errmsg) { + fprintf(stdout, "%s (error number %d)", errmsg, error_number); + } else { + fprintf(stdout, "Unknown (error number %d)", error_number); + } } fputc('.', stdout); From 00694aaf1e1079c510e352c25a09919ba59cc265 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 13 Jan 2016 09:14:32 -0200 Subject: [PATCH 0090/2505] Include libgen.h for basename() --- common/lwan-status.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/lwan-status.c b/common/lwan-status.c index 1797b44c3..a6e71a66f 100644 --- a/common/lwan-status.c +++ b/common/lwan-status.c @@ -24,6 +24,7 @@ #include #include #include +#include #ifndef NDEBUG #include @@ -122,7 +123,7 @@ status_out_msg(const char *file, const int line, const char *func, #ifndef NDEBUG fprintf(stdout, "\033[32;1m%ld\033[0m", syscall(SYS_gettid)); - fprintf(stdout, " \033[3m%s:%d\033[0m", basename(file), line); + fprintf(stdout, " \033[3m%s:%d\033[0m", basename(strdupa(file)), line); fprintf(stdout, " \033[33m%s()\033[0m", func); fprintf(stdout, " "); #endif From dc24b37c91e281f10b73fcb19c5f2e0bebc4fa97 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 13 Jan 2016 09:16:06 -0200 Subject: [PATCH 0091/2505] Use thr_self() instead of gettid() on FreeBSD --- common/lwan-status.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/common/lwan-status.c b/common/lwan-status.c index a6e71a66f..af21e85b0 100644 --- a/common/lwan-status.c +++ b/common/lwan-status.c @@ -31,6 +31,10 @@ #include #endif +#ifdef __FreeBSD__ +#include +#endif + #include "lwan.h" typedef enum { @@ -93,6 +97,7 @@ get_color_end_for_type(lwan_status_type_t type __attribute__((unused)), return retval; } +#ifndef NDEBUG static inline char * strerror_thunk_r(int error_number, char *buffer, size_t len) { @@ -105,6 +110,23 @@ strerror_thunk_r(int error_number, char *buffer, size_t len) #endif } +static inline long +gettid(void) +{ + long tid; + +#if defined(__linux__) + tid = syscall(SYS_gettid); +#elif (__FreeBSD__) + thr_self(&tid); +#else + tid = (long)pthread_self(); +#endif + + return tid; +} +#endif + static void #ifdef NDEBUG status_out_msg(lwan_status_type_t type, const char *msg, size_t msg_len) @@ -122,7 +144,7 @@ status_out_msg(const char *file, const int line, const char *func, perror("pthread_mutex_lock"); #ifndef NDEBUG - fprintf(stdout, "\033[32;1m%ld\033[0m", syscall(SYS_gettid)); + fprintf(stdout, "\033[32;1m%ld\033[0m", gettid()); fprintf(stdout, " \033[3m%s:%d\033[0m", basename(strdupa(file)), line); fprintf(stdout, " \033[33m%s()\033[0m", func); fprintf(stdout, " "); From fdd9c3257ed54d1161bf3412c87bdd6851268f7a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 13 Jan 2016 09:18:10 -0200 Subject: [PATCH 0092/2505] On FreeBSD, closing a socket won't make a blocked accept4() return Shutdown the socket as well, which should work under Linux. Also, fix a bunch of warnings when building this on BSD due to sig_atomic_t being of a different size than an int. --- common/lwan.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/common/lwan.c b/common/lwan.c index b501c59ac..684b3babd 100644 --- a/common/lwan.c +++ b/common/lwan.c @@ -611,12 +611,15 @@ schedule_client(lwan_t *l, int fd) static volatile sig_atomic_t main_socket = -1; +static_assert(sizeof(main_socket) >= sizeof(int), "size of sig_atomic_t > size of int"); + static void sigint_handler(int signal_number __attribute__((unused))) { if (main_socket < 0) return; - close(main_socket); + shutdown((int)main_socket, SHUT_RDWR); + close((int)main_socket); main_socket = -1; } @@ -631,20 +634,20 @@ lwan_main_loop(lwan_t *l) lwan_status_info("Ready to serve"); for (;;) { - int client_fd = accept4(main_socket, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC); + int client_fd = accept4((int)main_socket, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC); if (UNLIKELY(client_fd < 0)) { - if (errno != EBADF) { - lwan_status_perror("accept"); - continue; - } - - if (main_socket < 0) { - lwan_status_info("Signal 2 (Interrupt) received"); - } else { - lwan_status_info("Main socket closed for unknown reasons"); + switch (errno) { + case EBADF: + case ECONNABORTED: + if (main_socket < 0) { + lwan_status_info("Signal 2 (Interrupt) received"); + } else { + lwan_status_info("Main socket closed for unknown reasons"); + } + return; } - break; + lwan_status_perror("accept"); } schedule_client(l, client_fd); From 994ad11a37fc336695060a53725cb960e4137178 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 13 Jan 2016 09:19:18 -0200 Subject: [PATCH 0093/2505] Use the portable getcwd() instead of get_current_dir_name() --- lwan/main.c | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/lwan/main.c b/lwan/main.c index 1c56fae82..5f292aa03 100644 --- a/lwan/main.c +++ b/lwan/main.c @@ -21,6 +21,7 @@ #include #include #include +#include #include "lwan.h" #include "lwan-serve-files.h" @@ -167,7 +168,7 @@ hello_world(lwan_request_t *request, } static enum args -parse_args(int argc, char *argv[], lwan_config_t *config, char **root) +parse_args(int argc, char *argv[], lwan_config_t *config, char *root) { static const struct option opts[] = { { .name = "root", .has_arg = 1, .val = 'r' }, @@ -187,8 +188,7 @@ parse_args(int argc, char *argv[], lwan_config_t *config, char **root) break; case 'r': - free(*root); - *root = strdup(optarg); + memcpy(root, optarg, strnlen(optarg, PATH_MAX - 1) + 1); result = ARGS_SERVE_FILES; break; @@ -223,13 +223,15 @@ main(int argc, char *argv[]) { lwan_t l; lwan_config_t c; - char *root = get_current_dir_name(); - int exit_status = EXIT_SUCCESS; + char root[PATH_MAX]; + + if (!getcwd(root, PATH_MAX)) + return 1; c = *lwan_get_default_config(); c.listener = strdup("*:8080"); - switch (parse_args(argc, argv, &c, &root)) { + switch (parse_args(argc, argv, &c, root)) { case ARGS_SERVE_FILES: lwan_status_info("Serving files from %s", root); lwan_init_with_config(&l, &c); @@ -244,15 +246,11 @@ main(int argc, char *argv[]) lwan_init(&l); break; case ARGS_FAILED: - exit_status = EXIT_FAILURE; - goto out; + return EXIT_FAILURE; } lwan_main_loop(&l); lwan_shutdown(&l); -out: - free(root); - - return exit_status; + return EXIT_SUCCESS; } From 9ecb0466d087ebbd65068a2887b1bfd156cc10c5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 13 Jan 2016 09:20:02 -0200 Subject: [PATCH 0094/2505] Implement enough of epoll on top of kqueue so Lwan stuff works --- common/CMakeLists.txt | 11 ++- common/epoll-bsd.c | 151 ++++++++++++++++++++++++++++++++++++++++++ common/epoll-bsd.h | 56 ++++++++++++++++ common/lwan-thread.c | 8 ++- 4 files changed, 224 insertions(+), 2 deletions(-) create mode 100644 common/epoll-bsd.c create mode 100644 common/epoll-bsd.h diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index ec4730bee..56cfe716d 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -26,11 +26,20 @@ set(SOURCES patterns.c reallocarray.c realpathat.c - sd-daemon.c strbuf.c + sd-daemon.c ) +include(CheckIncludeFiles) +check_include_files(sys/epoll.h HAVE_EPOLL_H) +if (NOT HAVE_EPOLL_H) + list(APPEND SOURCES epoll-bsd.c) +else () + list(APPEND SOURCES sd-daemon.c) +endif () + + include(FindPkgConfig) foreach (pc_file luajit lua lua51 lua5.1 lua-5.1) if (${pc_file} STREQUAL "luajit") diff --git a/common/epoll-bsd.c b/common/epoll-bsd.c new file mode 100644 index 000000000..8834c1857 --- /dev/null +++ b/common/epoll-bsd.c @@ -0,0 +1,151 @@ +/* + * lwan - simple web server + * Copyright (c) 2016 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include "epoll-bsd.h" +#include "lwan-status.h" +#include "lwan.h" + +int +epoll_create1(int flags __attribute__((unused))) +{ + return kqueue(); +} + +static struct kevent +add_or_mod(int fd, struct epoll_event *event) +{ + struct kevent ev; + int events = 0; + int flags = EV_ADD; + + if (event->events & EPOLLIN) + events = EVFILT_READ; + else if (event->events & EPOLLOUT) + events = EVFILT_WRITE; + + if (event->events & EPOLLONESHOT) + flags |= EV_ONESHOT; + if (event->events & EPOLLRDHUP) + flags |= EV_EOF; + if (event->events & EPOLLERR) + flags |= EV_ERROR; + + EV_SET(&ev, fd, events, flags, 0, 0, event->data.ptr); + + return ev; +} + +static struct kevent +del(int fd, struct epoll_event *event __attribute__((unused))) +{ + struct kevent ev; + + EV_SET(&ev, fd, 0, EV_DELETE, 0, 0, 0); + + return ev; +} + +int +epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) +{ + struct kevent ev; + + switch (op) { + case EPOLL_CTL_ADD: + case EPOLL_CTL_MOD: + ev = add_or_mod(fd, event); + break; + case EPOLL_CTL_DEL: + ev = del(fd, event); + break; + default: + errno = EINVAL; + return -1; + } + + return kevent(epfd, &ev, 1, NULL, 0, NULL); +} + +static struct timespec * +to_timespec(struct timespec *t, int ms) +{ + if (ms < 0) + return NULL; + + t->tv_sec = ms / 1000; + t->tv_nsec = (ms % 1000) * 1000000; + + return t; +} + +static int +kevent_compare(const void *a, const void *b) +{ + const struct kevent *ka = a; + const struct kevent *kb = b; + + if (ka->flags & (EV_ERROR | EV_EOF) || kb->flags & (EV_ERROR | EV_EOF)) + return 1; + return (ka > kb) - (ka < kb); +} + +int +epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) +{ + struct epoll_event *ev = events; + struct kevent evs[maxevents]; + struct timespec tmspec; + int i, r; + + r = kevent(epfd, NULL, 0, evs, maxevents, to_timespec(&tmspec, timeout)); + if (UNLIKELY(r < 0)) { + if (errno != EINTR) + lwan_status_perror("kevent"); + goto out; + } + + qsort(evs, (size_t)r, sizeof(struct kevent), kevent_compare); + + for (i = 0; i < r; i++, ev++) { + struct kevent *kev = &evs[i]; + + ev->events = 0; + ev->data.ptr = kev->udata; + + if (kev->flags & EV_ERROR) + ev->events |= EPOLLERR; + if (kev->flags & EV_EOF) + ev->events |= EPOLLRDHUP; + + if (kev->filter == EVFILT_READ) + ev->events |= EPOLLIN; + else if (kev->filter == EVFILT_WRITE) + ev->events |= EPOLLOUT; + } + +out: + return r; +} diff --git a/common/epoll-bsd.h b/common/epoll-bsd.h new file mode 100644 index 000000000..6a06e40b6 --- /dev/null +++ b/common/epoll-bsd.h @@ -0,0 +1,56 @@ +/* + * lwan - simple web server + * Copyright (c) 2016 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include + +enum { + EPOLLIN = 1<<0, + EPOLLOUT = 1<<1, + EPOLLONESHOT = 1<<2, + EPOLLRDHUP = 1<<3, + EPOLLERR = 1<<4, + EPOLLET = 1<<5, + EPOLLHUP = EPOLLRDHUP +} epoll_event_flag; + +enum { + EPOLL_CTL_ADD, + EPOLL_CTL_MOD, + EPOLL_CTL_DEL +} epoll_op; + +enum { + EPOLL_CLOEXEC = 1<<0 +} epoll_create_flags; + +struct epoll_event { + uint32_t events; + union { + void *ptr; + int fd; + uint32_t u32; + uint64_t u64; + } data; +}; + +int epoll_create1(int flags); +int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); +int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); diff --git a/common/lwan-thread.c b/common/lwan-thread.c index c50238119..358c98b39 100644 --- a/common/lwan-thread.c +++ b/common/lwan-thread.c @@ -24,9 +24,15 @@ #include #include #include -#include #include +#ifdef __FreeBSD__ +#include +#include "epoll-bsd.h" +#elif __linux__ +#include +#endif + #include "lwan-private.h" #define CORO_GC_THRESHOLD 16 From 486324aa3796026ea3f5324a40a828758d10d491 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 13 Jan 2016 09:22:38 -0200 Subject: [PATCH 0095/2505] On FreeBSD, use sysctl(kern.proc.pathname) instead of readlink(/proc/self/exe) --- common/lwan.c | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/common/lwan.c b/common/lwan.c index 684b3babd..72ad359d9 100644 --- a/common/lwan.c +++ b/common/lwan.c @@ -29,6 +29,10 @@ #include #include +#if defined(__FreeBSD__) +#include +#endif + #include "lwan.h" #include "lwan-private.h" @@ -349,18 +353,34 @@ static void parse_listener(config_t *c, config_line_t *l, lwan_t *lwan) const char *get_config_path(char *path_buf) { char *path = NULL; - ssize_t path_len; + int ret; - /* FIXME: This should ideally (and portably) done by using argv[0] */ +#if defined(__linux__) + ssize_t path_len; path_len = readlink("/proc/self/exe", path_buf, PATH_MAX); - if (path_len < 0) + if (path_len < 0) { + lwan_status_perror("readlink"); goto out; + } path_buf[path_len] = '\0'; +#elif defined(__FreeBSD__) + size_t path_len = PATH_MAX; + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; + + ret = sysctl(mib, N_ELEMENTS(mib), path_buf, &path_len, NULL, 0); + if (ret < 0) { + lwan_status_perror("sysctl"); + goto out; + } +#else + goto out; +#endif + path = strrchr(path_buf, '/'); if (!path) goto out; - int ret = snprintf(path_buf, PATH_MAX, "%s.conf", path + 1); + ret = snprintf(path_buf, PATH_MAX, "%s.conf", path + 1); if (ret < 0 || ret >= PATH_MAX) goto out; From 0396e0145871ed59b99a9bcc87756611843b4e45 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 13 Jan 2016 09:26:36 -0200 Subject: [PATCH 0096/2505] Add missing system includes --- common/lwan-request.c | 2 ++ common/lwan.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/common/lwan-request.c b/common/lwan-request.c index da4e99631..06309e9cc 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -26,6 +26,8 @@ #include #include #include +#include +#include #include "lwan.h" #include "lwan-config.h" diff --git a/common/lwan.c b/common/lwan.c index 72ad359d9..ac4af8522 100644 --- a/common/lwan.c +++ b/common/lwan.c @@ -28,6 +28,8 @@ #include #include #include +#include +#include #if defined(__FreeBSD__) #include From 8aee90afa2139f34a66436930e01b0d6c0e528f7 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 14 Jan 2016 22:45:41 -0200 Subject: [PATCH 0097/2505] Check for GNU extensions and provide implementation when N/A --- common/CMakeLists.txt | 15 ++++++++++++-- common/lwan-config.c | 1 + common/lwan-private.h | 42 ++++++++++++++++++++++++++++++++++++++ common/lwan-response.c | 1 + common/lwan-rewrite.c | 1 + common/lwan-serve-files.c | 1 + common/lwan-socket.c | 2 +- common/lwan-status.c | 1 + common/lwan-straitjacket.c | 1 + common/lwan-tables.c | 2 +- common/lwan-template.c | 1 + common/realpathat.c | 1 + 12 files changed, 65 insertions(+), 4 deletions(-) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 56cfe716d..fa97013f6 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -35,8 +35,19 @@ include(CheckIncludeFiles) check_include_files(sys/epoll.h HAVE_EPOLL_H) if (NOT HAVE_EPOLL_H) list(APPEND SOURCES epoll-bsd.c) -else () - list(APPEND SOURCES sd-daemon.c) +endif () + + +include(CheckFunctionExists) +set(CMAKE_EXTRA_INCLUDE_FILES string.h) +check_function_exists(rawmemchr HAS_RAWMEMCHR) +if (HAS_RAWMEMCHR) + add_definitions(-DHAS_RAWMEMCHR) +endif () + +check_function_exists(mempcpy HAS_MEMPCPY) +if (HAS_MEMPCPY) + add_definitions(-DHAS_MEMPCPY) endif () diff --git a/common/lwan-config.c b/common/lwan-config.c index 7c36c4c3c..191ca0708 100644 --- a/common/lwan-config.c +++ b/common/lwan-config.c @@ -28,6 +28,7 @@ #include #include +#include "lwan-private.h" #include "lwan-config.h" #include "lwan-status.h" #include "hash.h" diff --git a/common/lwan-private.h b/common/lwan-private.h index 0aab62b8b..a1d1cddcc 100644 --- a/common/lwan-private.h +++ b/common/lwan-private.h @@ -54,3 +54,45 @@ void lwan_straitjacket_enforce(config_t *c, config_line_t *l); #define static_assert(expr, msg) #endif +#define strndupa_impl(s, l) ({ \ + char *strndupa_tmp_s = alloca(l + 1); \ + strndupa_tmp_s[l] = '\0'; \ + memcpy(strndupa_tmp_s, s, l); \ +}) + +#ifndef strndupa +#define strndupa(s, l) strndupa_impl((s), strnlen((s), (l))) +#endif + +#ifndef strdupa +#define strdupa(s) strndupa((s), strlen(s)) +#endif + +#ifndef HAS_RAWMEMCHR +#define rawmemchr(s, c) memchr((s), (c), SIZE_MAX) +#endif + +#ifndef HAS_MEMPCPY +static inline void * +mempcpy(void *dest, const void *src, size_t len) +{ + char *p = memcpy(dest, src, len); + return p + len; +} +#endif + +#ifndef MSG_MORE +#define MSG_MORE 0 +#endif + +#ifndef O_NOATIME +#define O_NOATIME 0 +#endif + +#ifndef O_PATH +#define O_PATH 0 +#endif + +#ifndef SOCK_CLOEXEC +#define SOCK_CLOEXEC 0 +#endif diff --git a/common/lwan-response.c b/common/lwan-response.c index 07b38a85b..548e96f78 100644 --- a/common/lwan-response.c +++ b/common/lwan-response.c @@ -28,6 +28,7 @@ #include "lwan.h" #include "lwan-io-wrappers.h" #include "lwan-template.h" +#include "lwan-private.h" static lwan_tpl_t *error_template = NULL; diff --git a/common/lwan-rewrite.c b/common/lwan-rewrite.c index cc033dab5..f3bc72ed8 100644 --- a/common/lwan-rewrite.c +++ b/common/lwan-rewrite.c @@ -27,6 +27,7 @@ #include "lwan-rewrite.h" #include "list.h" #include "patterns.h" +#include "lwan-private.h" struct private_data { struct list_head patterns; diff --git a/common/lwan-serve-files.c b/common/lwan-serve-files.c index 411ba302f..c73f8cb2d 100644 --- a/common/lwan-serve-files.c +++ b/common/lwan-serve-files.c @@ -35,6 +35,7 @@ #include "lwan-template.h" #include "realpathat.h" #include "hash.h" +#include "lwan-private.h" static const char *compression_none = NULL; static const char *compression_gzip = "gzip"; diff --git a/common/lwan-socket.c b/common/lwan-socket.c index 190b06c62..c6c253589 100644 --- a/common/lwan-socket.c +++ b/common/lwan-socket.c @@ -32,7 +32,7 @@ #include "lwan.h" #include "sd-daemon.h" #include "int-to-str.h" - +#include "lwan-private.h" static int get_backlog_size(void) diff --git a/common/lwan-status.c b/common/lwan-status.c index af21e85b0..3edb51f0a 100644 --- a/common/lwan-status.c +++ b/common/lwan-status.c @@ -36,6 +36,7 @@ #endif #include "lwan.h" +#include "lwan-private.h" typedef enum { STATUS_INFO = 1<<0, diff --git a/common/lwan-straitjacket.c b/common/lwan-straitjacket.c index a46877b1a..61c04bce6 100644 --- a/common/lwan-straitjacket.c +++ b/common/lwan-straitjacket.c @@ -27,6 +27,7 @@ #include "lwan-config.h" #include "lwan-status.h" +#include "lwan-private.h" static bool get_user_uid_gid(const char *user, uid_t *uid, gid_t *gid) { diff --git a/common/lwan-tables.c b/common/lwan-tables.c index 5b534a3b3..120534ff2 100644 --- a/common/lwan-tables.c +++ b/common/lwan-tables.c @@ -25,7 +25,7 @@ #include "lwan.h" #include "mime-types.h" - +#include "lwan-private.h" static unsigned char uncompressed_mime_entries[MIME_UNCOMPRESSED_LEN]; static struct mime_entry mime_entries[MIME_ENTRIES]; diff --git a/common/lwan-template.c b/common/lwan-template.c index d86b8767c..38f7dfd84 100644 --- a/common/lwan-template.c +++ b/common/lwan-template.c @@ -43,6 +43,7 @@ #include "lwan-template.h" #include "strbuf.h" #include "reallocarray.h" +#include "lwan-private.h" enum action { ACTION_APPEND, diff --git a/common/realpathat.c b/common/realpathat.c index e0b3e5f2b..200f35615 100644 --- a/common/realpathat.c +++ b/common/realpathat.c @@ -33,6 +33,7 @@ #include #include "lwan.h" +#include "lwan-private.h" char * realpathat2(int dirfd, char *dirfdpath, const char *name, char *resolved, From 13725976e90d0c85fea3b8172cf87dd6e5248c08 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 14 Jan 2016 22:57:24 -0200 Subject: [PATCH 0098/2505] TCP_FASTOPEN and TCP_QUICKACK are Linux-only --- common/lwan-socket.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/lwan-socket.c b/common/lwan-socket.c index c6c253589..530ff7bbe 100644 --- a/common/lwan-socket.c +++ b/common/lwan-socket.c @@ -244,10 +244,12 @@ lwan_socket_init(lwan_t *l) SET_SOCKET_OPTION(SOL_SOCKET, SO_LINGER, (&(struct linger){ .l_onoff = 1, .l_linger = 1 }), sizeof(struct linger)); +#ifdef __linux__ SET_SOCKET_OPTION_MAY_FAIL(SOL_TCP, TCP_FASTOPEN, (int[]){ 5 }, sizeof(int)); SET_SOCKET_OPTION_MAY_FAIL(SOL_TCP, TCP_QUICKACK, (int[]){ 0 }, sizeof(int)); +#endif l->main_socket = fd; } From edee93fc070b6b9fa9e98f1409d1e4b57abbc0d5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 15 Jan 2016 00:20:29 -0200 Subject: [PATCH 0099/2505] Add missing includes from sd-daemon.c These seem to be required by FreeBSD. Although there isn't and there won't be a systemd on FreeBSD, the socket activation mechanism is sound and can be left there. --- common/sd-daemon.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/sd-daemon.c b/common/sd-daemon.c index 2f7c2789c..b630bb1f8 100644 --- a/common/sd-daemon.c +++ b/common/sd-daemon.c @@ -25,7 +25,9 @@ #include #include #include +#include #include +#include #include #include "sd-daemon.h" From aebcb9d8cdc1937885eb5a0d26f8b88099cfe07a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 15 Jan 2016 00:25:43 -0200 Subject: [PATCH 0100/2505] Simplify call to strerror_thunk_r() --- common/lwan-status.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/common/lwan-status.c b/common/lwan-status.c index 3edb51f0a..5484f939b 100644 --- a/common/lwan-status.c +++ b/common/lwan-status.c @@ -107,7 +107,7 @@ strerror_thunk_r(int error_number, char *buffer, size_t len) #else if (!strerror_r(error_number, buffer, len)) return buffer; - return NULL; + return "Unknown"; #endif } @@ -158,13 +158,7 @@ status_out_msg(const char *file, const int line, const char *func, char buffer[512]; char *errmsg = strerror_thunk_r(error_number, buffer, sizeof(buffer) - 1); - fputc(':', stdout); - fputc(' ', stdout); - if (errmsg) { - fprintf(stdout, "%s (error number %d)", errmsg, error_number); - } else { - fprintf(stdout, "Unknown (error number %d)", error_number); - } + fprintf(stdout, ": %s (error number %d)", errmsg, error_number); } fputc('.', stdout); From 02ae8455324b43139140db3fc91d48b7af618fc3 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 15 Jan 2016 09:46:35 -0200 Subject: [PATCH 0101/2505] Release builds also need the strerror_r() call in lwan-status (That's what you get when programming late at night.) --- common/lwan-status.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lwan-status.c b/common/lwan-status.c index 5484f939b..227287c0d 100644 --- a/common/lwan-status.c +++ b/common/lwan-status.c @@ -98,7 +98,6 @@ get_color_end_for_type(lwan_status_type_t type __attribute__((unused)), return retval; } -#ifndef NDEBUG static inline char * strerror_thunk_r(int error_number, char *buffer, size_t len) { @@ -111,6 +110,7 @@ strerror_thunk_r(int error_number, char *buffer, size_t len) #endif } +#ifndef NDEBUG static inline long gettid(void) { From c87ac2519476977a7fff639f75c1ec8051750159 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 16 Jan 2016 09:31:09 -0200 Subject: [PATCH 0102/2505] While shutting down job thread, use pthread_join() --- common/lwan-job.c | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/common/lwan-job.c b/common/lwan-job.c index 9f680a6a7..cd8f7e57e 100644 --- a/common/lwan-job.c +++ b/common/lwan-job.c @@ -111,19 +111,12 @@ void lwan_job_thread_shutdown(void) } running = false; -#ifdef __FreeBSD__ - r = pthread_timedjoin_np(self, NULL, &(const struct timespec) { .tv_sec = 1 }); - if (r) { - errno = r; - lwan_status_perror("pthread_timedjoin_np"); - } -#else - r = pthread_tryjoin_np(self, NULL); + r = pthread_join(self, NULL); if (r) { errno = r; - lwan_status_perror("pthread_tryjoin_np"); + lwan_status_perror("pthread_join"); } -#endif + pthread_mutex_unlock(&queue_mutex); } } From 8cab18bb80779b4a55bcf93505910aa15227afd0 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 30 Jan 2016 11:56:23 +0100 Subject: [PATCH 0103/2505] Do not overwrite path_buf in get_config_path() If the lwan executable is in a path that's short enough, get_config_path() would possibly overwrite the buffer returned by either readlink() or sysctl(), possibly corrupting the file name. Fix by allocating a buffer of PATH_MAX bytes in the function itself to use as a temporary buffer. --- common/lwan.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/common/lwan.c b/common/lwan.c index ac4af8522..3cb24df5f 100644 --- a/common/lwan.c +++ b/common/lwan.c @@ -354,23 +354,24 @@ static void parse_listener(config_t *c, config_line_t *l, lwan_t *lwan) const char *get_config_path(char *path_buf) { + char buffer[PATH_MAX]; char *path = NULL; int ret; #if defined(__linux__) ssize_t path_len; - path_len = readlink("/proc/self/exe", path_buf, PATH_MAX); + path_len = readlink("/proc/self/exe", buffer, PATH_MAX); if (path_len < 0) { lwan_status_perror("readlink"); goto out; } - path_buf[path_len] = '\0'; + buffer[path_len] = '\0'; #elif defined(__FreeBSD__) size_t path_len = PATH_MAX; int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; - ret = sysctl(mib, N_ELEMENTS(mib), path_buf, &path_len, NULL, 0); + ret = sysctl(mib, N_ELEMENTS(mib), buffer, &path_len, NULL, 0); if (ret < 0) { lwan_status_perror("sysctl"); goto out; @@ -379,7 +380,7 @@ const char *get_config_path(char *path_buf) goto out; #endif - path = strrchr(path_buf, '/'); + path = strrchr(buffer, '/'); if (!path) goto out; ret = snprintf(path_buf, PATH_MAX, "%s.conf", path + 1); From 325cfee72fde3aef8bb773d06f0a1eff0b1c388c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 6 Feb 2016 09:26:16 -0200 Subject: [PATCH 0104/2505] Non-sticky defers were not being executed in some machines I don't have time to see why this was failing only in the machine that runs the tests, so use a simpler approach. --- common/lwan-coro.c | 34 ++++++++-------------------------- 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/common/lwan-coro.c b/common/lwan-coro.c index d52964a4a..2e9b28937 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -46,6 +46,7 @@ struct coro_defer_t_ { defer_func func; void *data1; void *data2; + bool sticky; }; struct coro_t_ { @@ -146,23 +147,6 @@ coro_entry_point(coro_t *coro, coro_function_t func) coro_yield(coro, return_value); } -static inline defer_func -get_func_ptr(defer_func func) -{ - uintptr_t ptr = (uintptr_t)func; - - ptr &= ~(uintptr_t)1; - return (defer_func)ptr; -} - -static inline bool -is_sticky(defer_func func) -{ - uintptr_t ptr = (uintptr_t)func; - - return ptr & (uintptr_t)1; -} - static coro_defer_t * reverse(coro_defer_t *root) { @@ -189,12 +173,12 @@ coro_run_deferred(coro_t *coro, bool sticky) coro_defer_t *tmp = defer; defer = defer->next; - if (!sticky && is_sticky(tmp->func)) { + if (sticky) { + defer->func(tmp->data1, tmp->data2); + free(tmp); + } else if (defer->sticky) { tmp->next = sticked; sticked = tmp; - } else { - get_func_ptr(tmp->func)(tmp->data1, tmp->data2); - free(tmp); } } @@ -343,6 +327,7 @@ coro_defer_any(coro_t *coro, defer_func func, void *data1, void *data2) defer->func = func; defer->data1 = data1; defer->data2 = data2; + defer->sticky = false; coro->defer = defer; } @@ -366,14 +351,11 @@ coro_malloc_full(coro_t *coro, size_t size, bool sticky, void (*destroy_func)()) if (UNLIKELY(!defer)) return NULL; - uintptr_t destroy_func_ptr = (uintptr_t)destroy_func; - if (sticky) - destroy_func_ptr |= (uintptr_t)1; - defer->next = coro->defer; - defer->func = (defer_func)destroy_func_ptr; + defer->func = destroy_func; defer->data1 = defer + 1; defer->data2 = NULL; + defer->sticky = sticky; coro->defer = defer; From ff8b8b1f03f96a9de7b09aca3c7f29c02fb01762 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 6 Feb 2016 09:42:48 -0200 Subject: [PATCH 0105/2505] Fix crash after 325cfee72f --- common/lwan-coro.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lwan-coro.c b/common/lwan-coro.c index 2e9b28937..99973bbbd 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -174,7 +174,7 @@ coro_run_deferred(coro_t *coro, bool sticky) defer = defer->next; if (sticky) { - defer->func(tmp->data1, tmp->data2); + tmp->func(tmp->data1, tmp->data2); free(tmp); } else if (defer->sticky) { tmp->next = sticked; From 3a6b683e2cb44e3de3e1ba56e8ac19a7c19da417 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 9 Feb 2016 13:16:32 -0200 Subject: [PATCH 0106/2505] Yet another crash fix after 325cfee72f --- common/lwan-coro.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lwan-coro.c b/common/lwan-coro.c index 99973bbbd..e35b2c266 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -176,7 +176,7 @@ coro_run_deferred(coro_t *coro, bool sticky) if (sticky) { tmp->func(tmp->data1, tmp->data2); free(tmp); - } else if (defer->sticky) { + } else if (tmp->sticky) { tmp->next = sticked; sticked = tmp; } From 47ab93efdd115f41b3fabef77979a0a5c4086e5a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 6 Feb 2016 10:33:46 -0200 Subject: [PATCH 0107/2505] Slightly clean up parser_text() by using an auxiliary function --- common/lwan-template.c | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/common/lwan-template.c b/common/lwan-template.c index 38f7dfd84..edc8c6bc5 100644 --- a/common/lwan-template.c +++ b/common/lwan-template.c @@ -785,42 +785,43 @@ static void *parser_meta(struct parser *parser, struct lexeme *lexeme) return unexpected_lexeme(lexeme); } +static strbuf_t *strbuf_from_lexeme(struct parser *parser, struct lexeme *lexeme) +{ + if (parser->template_flags & LWAN_TPL_FLAG_CONST_TEMPLATE) + return strbuf_new_static(lexeme->value.value, lexeme->value.len); + + strbuf_t *buf = strbuf_new_with_size(lexeme->value.len); + if (buf) + strbuf_set(buf, lexeme->value.value, lexeme->value.len); + + return buf; +} + static void *parser_text(struct parser *parser, struct lexeme *lexeme) { if (lexeme->type == LEXEME_LEFT_META) return parser_meta; + if (lexeme->type == LEXEME_TEXT) { if (lexeme->value.len == 1) { emit_chunk(parser, ACTION_APPEND_CHAR, 0, (void *)(uintptr_t)*lexeme->value.value); } else { - strbuf_t *buf; - - if (parser->template_flags & LWAN_TPL_FLAG_CONST_TEMPLATE) { - buf = strbuf_new_static(lexeme->value.value, lexeme->value.len); - if (!buf) - goto no_buf; - } else { - buf = strbuf_new_with_size(lexeme->value.len); - if (!buf) - goto no_buf; - - strbuf_set(buf, lexeme->value.value, lexeme->value.len); - } + strbuf_t *buf = strbuf_from_lexeme(parser, lexeme); + if (!buf) + return error_lexeme(lexeme, "Out of memory"); emit_chunk(parser, ACTION_APPEND, 0, buf); } parser->tpl->minimum_size += lexeme->value.len; return parser_text; } + if (lexeme->type == LEXEME_EOF) { emit_chunk(parser, ACTION_LAST, 0, NULL); return NULL; } return unexpected_lexeme(lexeme); - -no_buf: - return error_lexeme(lexeme, "Out of memory"); } void From ec27c4d342c0c070f12e1e39ffb0120717576ae4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 9 Feb 2016 13:42:09 -0200 Subject: [PATCH 0108/2505] Fix possible buffer overrun in get_config_path() --- common/lwan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lwan.c b/common/lwan.c index 3cb24df5f..9b73da304 100644 --- a/common/lwan.c +++ b/common/lwan.c @@ -362,7 +362,7 @@ const char *get_config_path(char *path_buf) ssize_t path_len; path_len = readlink("/proc/self/exe", buffer, PATH_MAX); - if (path_len < 0) { + if (path_len < 0 || path_len >= PATH_MAX) { lwan_status_perror("readlink"); goto out; } From e2a2f720325d90b4c6e3c6c641e0ed4338295f4e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 9 Feb 2016 13:43:25 -0200 Subject: [PATCH 0109/2505] NULL-check call to coro_malloc() in test_proxy() --- lwan/main.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lwan/main.c b/lwan/main.c index 5f292aa03..f95aeb81a 100644 --- a/lwan/main.c +++ b/lwan/main.c @@ -99,9 +99,11 @@ test_proxy(lwan_request_t *request, { lwan_key_value_t *headers = coro_malloc(request->conn->coro, sizeof(*headers) * 2); if (UNLIKELY(!headers)) - return HTTP_INTERNAL_ERROR; + return HTTP_INTERNAL_ERROR; char *buffer = coro_malloc(request->conn->coro, INET6_ADDRSTRLEN); + if (UNLIKELY(!buffer)) + return HTTP_INTERNAL_ERROR; headers[0].key = "X-Proxy"; headers[0].value = (char*) lwan_request_get_remote_address(request, buffer); From da6e9b5765662c15ff432e0da1afdbff6c4e1b7b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 9 Feb 2016 18:44:41 -0200 Subject: [PATCH 0110/2505] Fix heap buffer overflow in url_decode() Found with libFuzzer. One of the crashing inputs: {0x38, 0x25, 0x27}. --- common/lwan-request.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index 06309e9cc..5c97bb7c0 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -305,11 +305,9 @@ decode_hex_digit(char ch) static ALWAYS_INLINE bool is_hex_digit(char ch) { - unsigned char c = (unsigned char)ch; - - return ((c - '0') <= ('9' - '0')) - || ((c - 'a') <= ('f' - 'a')) - || ((c - 'A') <= ('F' - 'A')); + return (ch >= '0' && ch <= '0') || + (ch >= 'a' && ch <= 'f') || + (ch >= 'A' && ch <= 'F'); } static size_t From 6574cc37142eae26e2e903a11d1e8586797540e8 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 10 Feb 2016 14:40:44 -0200 Subject: [PATCH 0111/2505] When spawning coro, conn flags are always the same No need to bitwise OR and AND the connection flags, just attribute them directly. --- common/lwan-thread.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/common/lwan-thread.c b/common/lwan-thread.c index 358c98b39..01190b4b2 100644 --- a/common/lwan-thread.c +++ b/common/lwan-thread.c @@ -303,10 +303,9 @@ spawn_coro(lwan_connection_t *conn, assert(!(conn->flags & CONN_SHOULD_RESUME_CORO)); conn->coro = coro_new(switcher, process_request_coro, conn); + conn->flags = CONN_IS_ALIVE | CONN_SHOULD_RESUME_CORO; death_queue_insert(dq, conn); - conn->flags |= (CONN_IS_ALIVE | CONN_SHOULD_RESUME_CORO); - conn->flags &= ~CONN_WRITE_EVENTS; } static lwan_connection_t * From dfe95a531493ee08ab31121da3293c4212172428 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 11 Feb 2016 01:07:13 -0200 Subject: [PATCH 0112/2505] Restore previous coro_collect_garbage() behavior This fix the behavior where Lwan would quickly run out of memory, specially when running Lua handlers. --- common/lwan-coro.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/common/lwan-coro.c b/common/lwan-coro.c index e35b2c266..97510be46 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -173,12 +173,13 @@ coro_run_deferred(coro_t *coro, bool sticky) coro_defer_t *tmp = defer; defer = defer->next; - if (sticky) { - tmp->func(tmp->data1, tmp->data2); - free(tmp); - } else if (tmp->sticky) { + + if (!sticky && tmp->sticky) { tmp->next = sticked; sticked = tmp; + } else { + tmp->func(tmp->data1, tmp->data2); + free(tmp); } } From 0a4af6feb3ba1a7a7d6aa122456dd78c4a29853b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 11 Feb 2016 23:20:44 -0200 Subject: [PATCH 0113/2505] Ensure value_lookup() won't call bsearch() with a NULL base pointer --- common/lwan-request.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index 5c97bb7c0..becf168b0 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -1058,10 +1058,15 @@ compare_key_value(const void *a, const void *b) static inline void * value_lookup(const void *base, size_t len, const char *key) { - lwan_key_value_t *entry, k = { .key = (char *)key }; + if (LIKELY(base)) { + lwan_key_value_t k = { .key = (char *)key }; + lwan_key_value_t *entry = bsearch(&k, base, len, sizeof(k), compare_key_value); - entry = bsearch(&k, base, len, sizeof(k), compare_key_value); - return LIKELY(entry) ? entry->value : NULL; + if (LIKELY(entry)) + return entry->value; + } + + return NULL; } const char * From afcbb560ff8d4b1fedb86894b38131ebe1ee4e0d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 15 Feb 2016 23:35:53 -0200 Subject: [PATCH 0114/2505] Pass immediate binding and read only GOT flags to the linker (Regardless if it's a debug or a release build.) Also, ensure that -malign-data and -fno-asynchronous-unwind-tables are actually passed to the compiler. --- CMakeLists.txt | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d398170f..5f21ca7a4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,25 +59,25 @@ endif() set(C_FLAGS_REL "-mtune=native") include(CheckCCompilerFlag) -if (${CMAKE_BUILD_TYPE} MATCHES "Rel") - check_c_compiler_flag(-Wl,-z,now HAS_GOT_PROTECTION1) - if (HAS_GOT_PROTECTION1) - set(C_FLAGS_REL "${CFLAGS_REL} -Wl,-z,now") - endif () +check_c_compiler_flag(-Wl,-z,now HAS_IMMEDIATE_BINDING) +if (HAS_IMMEDIATE_BINDING) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,now") +endif () - check_c_compiler_flag(-Wl,-z,relro HAS_GOT_PROTECTION2) - if (HAS_GOT_PROTECTION2) - set(C_FLAGS_REL "${CFLAGS_REL} -Wl,-z,relro") - endif () +check_c_compiler_flag(-Wl,-z,relro HAS_READ_ONLY_GOT) +if (HAS_READ_ONLY_GOT) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,relro") +endif () +if (${CMAKE_BUILD_TYPE} MATCHES "Rel") check_c_compiler_flag(-malign-data=cacheline HAS_MALIGN_DATA_CACHELINE) if (HAS_MALIGN_DATA_CACHELINE) - set(C_FLAGS_REL "${CFLAGS_REL} -malign-data=cacheline") + set(C_FLAGS_REL "${C_FLAGS_REL} -malign-data=cacheline") endif () check_c_compiler_flag(-fno-asynchronous-unwind-tables HAS_ASYNC_UNWIND_TABLES) if (HAS_ASYNC_UNWIND_TABLES) - set(C_FLAGS_REL "${CFLAGS_REL} -fno-asynchronous-unwind-tables") + set(C_FLAGS_REL "${C_FLAGS_REL} -fno-asynchronous-unwind-tables") endif () check_c_compiler_flag(-flto HAS_LTO) From 0aa3e03676306cb4159ed4214ba2beaf58d7b8e7 Mon Sep 17 00:00:00 2001 From: "Leandro A. F. Pereira" Date: Wed, 17 Feb 2016 22:56:51 -0200 Subject: [PATCH 0115/2505] Update links in README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 96150c76f..26c358915 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,7 @@ been seen in the wild. *Help build this list!* * [An experimental version of Node.js using Lwan](https://github.com/raadad/node-lwan) as its HTTP server is maintained by [@raadad](https://github.com/raadad). * The beginnings of a C++11 [web framework](https://github.com/vileda/wfpp) based on Lwan written by [@vileda](https://github.com/vileda). +* A more complete C++14 [web framework](https://github.com/matt-42/silicon) by [@matt-42](https://github.com/matt-42) offers Lwan as one of its backends. * A [word ladder sample program](https://github.com/sjnam/lwan-sgb-ladders) by [@sjnam](https://github.com/sjnam). [Demo](http://tbcoe.ddns.net/sgb/ladders?start=chaos&goal=order). * A [Shodan search](https://www.shodan.io/search?query=server%3A+lwan) listing some brave souls that expose Lwan to the public internet. @@ -162,6 +163,7 @@ Some other distribution channels were made available as well: * A `Dockerfile` is maintained by [@jaxgeller](https://github.com/jaxgeller), and is [available from the Docker registry](https://registry.hub.docker.com/u/jaxgeller/lwan/). * A buildpack for Heroku is maintained by [@bherrera](https://github.com/bherrera), and is [available from its repo](https://github.com/bherrera/heroku-buildpack-lwan). * Lwan is also available as a package in [Biicode](http://docs.biicode.com/c++/examples/lwan.html). +* User packages for [Arch Linux](https://aur.archlinux.org/packages/lwan-git/) also [Ubuntu](https://launchpad.net/lwan-unofficial). Lwan has been also used as a benchmark: @@ -175,6 +177,7 @@ Not really third-party, but alas: * The [author's blog](http://tia.mat.br). * The [project's webpage](http://lwan.ws). + Build status ------------ From 2906a4c10af2b93996dec016ecf1d30b4af328bd Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 20 Feb 2016 14:23:04 -0200 Subject: [PATCH 0116/2505] Generate a `testrunner` binary only to run the unit tests. This ensures that the default Lwan server binary does not have things like `/hello`, `/chunked`, `/beacon`, and so on. Also, ensures that the proxy setting support is disabled by default. --- CMakeLists.txt | 1 + lwan.conf | 47 +------ lwan/main.c | 141 +-------------------- testrunner/CMakeLists.txt | 8 ++ testrunner/main.c | 258 ++++++++++++++++++++++++++++++++++++++ tools/testsuite.py | 2 +- 6 files changed, 272 insertions(+), 185 deletions(-) create mode 100644 testrunner/CMakeLists.txt create mode 100644 testrunner/main.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f21ca7a4..a5e87b40e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -125,5 +125,6 @@ add_subdirectory(common) include_directories(common) add_subdirectory(lwan) +add_subdirectory(testrunner) add_subdirectory(freegeoip) add_subdirectory(techempower) diff --git a/lwan.conf b/lwan.conf index a552198e5..9ca48d738 100644 --- a/lwan.conf +++ b/lwan.conf @@ -14,53 +14,10 @@ expires = 1M 1w # Number of I/O threads. Default (0) is number of online CPUs. threads = 0 -# This flag is enabled here so that the automated tests can be executed -# properly, but should be disabled unless absolutely needed (an example -# would be haproxy). -proxy_protocol = true +# Disable HAProxy's PROXY protocol by default. Only enable if needed. +proxy_protocol = false listener *:8080 { - prefix /hello { - handler = hello_world - } - prefix /proxy { - handler = test_proxy - } - prefix /chunked { - handler = test_chunked_encoding - } - prefix /sse { - handler = test_server_sent_event - } - prefix /beacon { - handler = gif_beacon - } - prefix /favicon.ico { - handler = gif_beacon - } - redirect /elsewhere { - to = http://lwan.ws - } - prefix /admin { - handler = hello_world - authorization basic { - realm = Administration Page - password file = htpasswd - } - } - lua /lua { - default type = text/html - script file = test.lua - cache period = 30s - } - rewrite /pattern { - pattern foo/(%d+)(%a)(%d+) { - redirect to = /hello?name=pre%2middle%3othermiddle%1post - } - pattern bar/(%d+)/test { - rewrite as = /hello?name=rewritten%1 - } - } serve_files / { path = ./wwwroot diff --git a/lwan/main.c b/lwan/main.c index f95aeb81a..b6a1c4d27 100644 --- a/lwan/main.c +++ b/lwan/main.c @@ -32,143 +32,6 @@ enum args { ARGS_SERVE_FILES }; -lwan_http_status_t -gif_beacon(lwan_request_t *request __attribute__((unused)), - lwan_response_t *response, - void *data __attribute__((unused))) -{ - /* - * 1x1 transparent GIF image generated with tinygif - * http://www.perlmonks.org/?node_id=7974 - */ - static const unsigned char gif_beacon_data[] = { - 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x01, 0x00, 0x01, 0x00, 0x90, - 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xF9, 0x04, - 0x05, 0x10, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x01, 0x00, 0x00, 0x02, 0x02, 0x04, 0x01 - }; - - response->mime_type = "image/gif"; - strbuf_set_static(response->buffer, (char*)gif_beacon_data, sizeof(gif_beacon_data)); - - return HTTP_OK; -} - -lwan_http_status_t -test_chunked_encoding(lwan_request_t *request, - lwan_response_t *response, - void *data __attribute__((unused))) -{ - int i; - - response->mime_type = "text/plain"; - - strbuf_printf(response->buffer, "Testing chunked encoding! First chunk\n"); - lwan_response_send_chunk(request); - - for (i = 0; i <= 10; i++) { - strbuf_printf(response->buffer, "*This is chunk %d*\n", i); - lwan_response_send_chunk(request); - } - - strbuf_printf(response->buffer, "Last chunk\n"); - lwan_response_send_chunk(request); - - return HTTP_OK; -} - -lwan_http_status_t -test_server_sent_event(lwan_request_t *request, - lwan_response_t *response, - void *data __attribute__((unused))) -{ - int i; - - for (i = 0; i <= 10; i++) { - strbuf_printf(response->buffer, "Current value is %d", i); - lwan_response_send_event(request, "currval"); - } - - return HTTP_OK; -} - -lwan_http_status_t -test_proxy(lwan_request_t *request, - lwan_response_t *response, - void *data __attribute__((unused))) -{ - lwan_key_value_t *headers = coro_malloc(request->conn->coro, sizeof(*headers) * 2); - if (UNLIKELY(!headers)) - return HTTP_INTERNAL_ERROR; - - char *buffer = coro_malloc(request->conn->coro, INET6_ADDRSTRLEN); - if (UNLIKELY(!buffer)) - return HTTP_INTERNAL_ERROR; - - headers[0].key = "X-Proxy"; - headers[0].value = (char*) lwan_request_get_remote_address(request, buffer); - headers[1].key = NULL; - headers[1].value = NULL; - - response->headers = headers; - - return HTTP_OK; -} - -lwan_http_status_t -hello_world(lwan_request_t *request, - lwan_response_t *response, - void *data __attribute__((unused))) -{ - static lwan_key_value_t headers[] = { - { .key = "X-The-Answer-To-The-Universal-Question", .value = "42" }, - { NULL, NULL } - }; - response->headers = headers; - response->mime_type = "text/plain"; - - const char *name = lwan_request_get_query_param(request, "name"); - if (name) - strbuf_printf(response->buffer, "Hello, %s!", name); - else - strbuf_set_static(response->buffer, "Hello, world!", sizeof("Hello, world!") -1); - - const char *dump_vars = lwan_request_get_query_param(request, "dump_vars"); - if (!dump_vars) - goto end; - - if (request->cookies.base) { - strbuf_append_str(response->buffer, "\n\nCookies\n", 0); - strbuf_append_str(response->buffer, "-------\n\n", 0); - - lwan_key_value_t *qs = request->cookies.base; - for (; qs->key; qs++) - strbuf_append_printf(response->buffer, - "Key = \"%s\"; Value = \"%s\"\n", qs->key, qs->value); - } - - strbuf_append_str(response->buffer, "\n\nQuery String Variables\n", 0); - strbuf_append_str(response->buffer, "----------------------\n\n", 0); - - lwan_key_value_t *qs = request->query_params.base; - for (; qs->key; qs++) - strbuf_append_printf(response->buffer, - "Key = \"%s\"; Value = \"%s\"\n", qs->key, qs->value); - - if (!(request->flags & REQUEST_METHOD_POST)) - goto end; - - strbuf_append_str(response->buffer, "\n\nPOST data\n", 0); - strbuf_append_str(response->buffer, "---------\n\n", 0); - - for (qs = request->post_data.base; qs->key; qs++) - strbuf_append_printf(response->buffer, - "Key = \"%s\"; Value = \"%s\"\n", qs->key, qs->value); - -end: - return HTTP_OK; -} - static enum args parse_args(int argc, char *argv[], lwan_config_t *config, char *root) { @@ -202,9 +65,9 @@ parse_args(int argc, char *argv[], lwan_config_t *config, char *root) printf("Usage: %s [--root /path/to/root/dir] [--listener addr:port]\n", argv[0]); printf("\t[--config]\n"); printf("Serve files through HTTP.\n\n"); - printf("Defaults to listening on all interfaces, port 8080, serving current directory.\n\n"); + printf("Defaults to listening on %s, serving from ./wwwroot.\n\n", config->listener); printf("Options:\n"); - printf("\t-r, --root Path to serve files from (default: current dir).\n"); + printf("\t-r, --root Path to serve files from (default: ./wwwroot).\n"); printf("\t-l, --listener Listener (default: %s).\n", config->listener); printf("\t-h, --help This.\n"); printf("\n"); diff --git a/testrunner/CMakeLists.txt b/testrunner/CMakeLists.txt new file mode 100644 index 000000000..8267574e1 --- /dev/null +++ b/testrunner/CMakeLists.txt @@ -0,0 +1,8 @@ +add_executable(testrunner main.c) + +target_link_libraries(testrunner + -Wl,-whole-archive lwan-common -Wl,-no-whole-archive + ${CMAKE_DL_LIBS} + ${ADDITIONAL_LIBRARIES} +) + diff --git a/testrunner/main.c b/testrunner/main.c new file mode 100644 index 000000000..f95aeb81a --- /dev/null +++ b/testrunner/main.c @@ -0,0 +1,258 @@ +/* + * lwan - simple web server + * Copyright (c) 2012 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include "lwan.h" +#include "lwan-serve-files.h" + +enum args { + ARGS_FAILED, + ARGS_USE_CONFIG, + ARGS_SERVE_FILES +}; + +lwan_http_status_t +gif_beacon(lwan_request_t *request __attribute__((unused)), + lwan_response_t *response, + void *data __attribute__((unused))) +{ + /* + * 1x1 transparent GIF image generated with tinygif + * http://www.perlmonks.org/?node_id=7974 + */ + static const unsigned char gif_beacon_data[] = { + 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x01, 0x00, 0x01, 0x00, 0x90, + 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xF9, 0x04, + 0x05, 0x10, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x01, 0x00, 0x00, 0x02, 0x02, 0x04, 0x01 + }; + + response->mime_type = "image/gif"; + strbuf_set_static(response->buffer, (char*)gif_beacon_data, sizeof(gif_beacon_data)); + + return HTTP_OK; +} + +lwan_http_status_t +test_chunked_encoding(lwan_request_t *request, + lwan_response_t *response, + void *data __attribute__((unused))) +{ + int i; + + response->mime_type = "text/plain"; + + strbuf_printf(response->buffer, "Testing chunked encoding! First chunk\n"); + lwan_response_send_chunk(request); + + for (i = 0; i <= 10; i++) { + strbuf_printf(response->buffer, "*This is chunk %d*\n", i); + lwan_response_send_chunk(request); + } + + strbuf_printf(response->buffer, "Last chunk\n"); + lwan_response_send_chunk(request); + + return HTTP_OK; +} + +lwan_http_status_t +test_server_sent_event(lwan_request_t *request, + lwan_response_t *response, + void *data __attribute__((unused))) +{ + int i; + + for (i = 0; i <= 10; i++) { + strbuf_printf(response->buffer, "Current value is %d", i); + lwan_response_send_event(request, "currval"); + } + + return HTTP_OK; +} + +lwan_http_status_t +test_proxy(lwan_request_t *request, + lwan_response_t *response, + void *data __attribute__((unused))) +{ + lwan_key_value_t *headers = coro_malloc(request->conn->coro, sizeof(*headers) * 2); + if (UNLIKELY(!headers)) + return HTTP_INTERNAL_ERROR; + + char *buffer = coro_malloc(request->conn->coro, INET6_ADDRSTRLEN); + if (UNLIKELY(!buffer)) + return HTTP_INTERNAL_ERROR; + + headers[0].key = "X-Proxy"; + headers[0].value = (char*) lwan_request_get_remote_address(request, buffer); + headers[1].key = NULL; + headers[1].value = NULL; + + response->headers = headers; + + return HTTP_OK; +} + +lwan_http_status_t +hello_world(lwan_request_t *request, + lwan_response_t *response, + void *data __attribute__((unused))) +{ + static lwan_key_value_t headers[] = { + { .key = "X-The-Answer-To-The-Universal-Question", .value = "42" }, + { NULL, NULL } + }; + response->headers = headers; + response->mime_type = "text/plain"; + + const char *name = lwan_request_get_query_param(request, "name"); + if (name) + strbuf_printf(response->buffer, "Hello, %s!", name); + else + strbuf_set_static(response->buffer, "Hello, world!", sizeof("Hello, world!") -1); + + const char *dump_vars = lwan_request_get_query_param(request, "dump_vars"); + if (!dump_vars) + goto end; + + if (request->cookies.base) { + strbuf_append_str(response->buffer, "\n\nCookies\n", 0); + strbuf_append_str(response->buffer, "-------\n\n", 0); + + lwan_key_value_t *qs = request->cookies.base; + for (; qs->key; qs++) + strbuf_append_printf(response->buffer, + "Key = \"%s\"; Value = \"%s\"\n", qs->key, qs->value); + } + + strbuf_append_str(response->buffer, "\n\nQuery String Variables\n", 0); + strbuf_append_str(response->buffer, "----------------------\n\n", 0); + + lwan_key_value_t *qs = request->query_params.base; + for (; qs->key; qs++) + strbuf_append_printf(response->buffer, + "Key = \"%s\"; Value = \"%s\"\n", qs->key, qs->value); + + if (!(request->flags & REQUEST_METHOD_POST)) + goto end; + + strbuf_append_str(response->buffer, "\n\nPOST data\n", 0); + strbuf_append_str(response->buffer, "---------\n\n", 0); + + for (qs = request->post_data.base; qs->key; qs++) + strbuf_append_printf(response->buffer, + "Key = \"%s\"; Value = \"%s\"\n", qs->key, qs->value); + +end: + return HTTP_OK; +} + +static enum args +parse_args(int argc, char *argv[], lwan_config_t *config, char *root) +{ + static const struct option opts[] = { + { .name = "root", .has_arg = 1, .val = 'r' }, + { .name = "listen", .has_arg = 1, .val = 'l' }, + { .name = "help", .val = 'h' }, + { } + }; + int c, optidx = 0; + enum args result = ARGS_USE_CONFIG; + + while ((c = getopt_long(argc, argv, "hr:l:", opts, &optidx)) != -1) { + switch (c) { + case 'l': + free(config->listener); + config->listener = strdup(optarg); + result = ARGS_SERVE_FILES; + break; + + case 'r': + memcpy(root, optarg, strnlen(optarg, PATH_MAX - 1) + 1); + result = ARGS_SERVE_FILES; + break; + + default: + printf("Run %s --help for usage information.\n", argv[0]); + return ARGS_FAILED; + + case 'h': + printf("Usage: %s [--root /path/to/root/dir] [--listener addr:port]\n", argv[0]); + printf("\t[--config]\n"); + printf("Serve files through HTTP.\n\n"); + printf("Defaults to listening on all interfaces, port 8080, serving current directory.\n\n"); + printf("Options:\n"); + printf("\t-r, --root Path to serve files from (default: current dir).\n"); + printf("\t-l, --listener Listener (default: %s).\n", config->listener); + printf("\t-h, --help This.\n"); + printf("\n"); + printf("Examples:\n"); + printf(" Serve system-wide documentation: %s -r /usr/share/doc\n", argv[0]); + printf(" Serve on a different port: %s -l '*:1337'\n", argv[0]); + printf("\n"); + printf("Report bugs at .\n"); + return ARGS_FAILED; + } + } + + return result; +} + +int +main(int argc, char *argv[]) +{ + lwan_t l; + lwan_config_t c; + char root[PATH_MAX]; + + if (!getcwd(root, PATH_MAX)) + return 1; + + c = *lwan_get_default_config(); + c.listener = strdup("*:8080"); + + switch (parse_args(argc, argv, &c, root)) { + case ARGS_SERVE_FILES: + lwan_status_info("Serving files from %s", root); + lwan_init_with_config(&l, &c); + + const lwan_url_map_t map[] = { + { .prefix = "/", SERVE_FILES(root) }, + { } + }; + lwan_set_url_map(&l, map); + break; + case ARGS_USE_CONFIG: + lwan_init(&l); + break; + case ARGS_FAILED: + return EXIT_FAILURE; + } + + lwan_main_loop(&l); + lwan_shutdown(&l); + + return EXIT_SUCCESS; +} diff --git a/tools/testsuite.py b/tools/testsuite.py index 097bb0276..37f1a0af4 100755 --- a/tools/testsuite.py +++ b/tools/testsuite.py @@ -12,7 +12,7 @@ import os import re -LWAN_PATH = './build/lwan/lwan' +LWAN_PATH = './build/testrunner/testrunner' for arg in sys.argv[1:]: if not arg.startswith('-') and os.path.exists(arg): LWAN_PATH = arg From 0b1b0533946e77572a95daf01a02f76919476927 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 21 Feb 2016 13:17:32 -0300 Subject: [PATCH 0117/2505] Add forgotten testrunner.conf --- testrunner.conf | 72 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 testrunner.conf diff --git a/testrunner.conf b/testrunner.conf new file mode 100644 index 000000000..a552198e5 --- /dev/null +++ b/testrunner.conf @@ -0,0 +1,72 @@ +# Timeout in seconds to keep a connection alive. +keep_alive_timeout = 15 + +# Set to true to not print any debugging messages. (Only effective in +# release builds.) +quiet = false + +# Set SO_REUSEPORT=1 in the master socket. +reuse_port = false + +# Value of "Expires" header. Default is 1 month and 1 week. +expires = 1M 1w + +# Number of I/O threads. Default (0) is number of online CPUs. +threads = 0 + +# This flag is enabled here so that the automated tests can be executed +# properly, but should be disabled unless absolutely needed (an example +# would be haproxy). +proxy_protocol = true + +listener *:8080 { + prefix /hello { + handler = hello_world + } + prefix /proxy { + handler = test_proxy + } + prefix /chunked { + handler = test_chunked_encoding + } + prefix /sse { + handler = test_server_sent_event + } + prefix /beacon { + handler = gif_beacon + } + prefix /favicon.ico { + handler = gif_beacon + } + redirect /elsewhere { + to = http://lwan.ws + } + prefix /admin { + handler = hello_world + authorization basic { + realm = Administration Page + password file = htpasswd + } + } + lua /lua { + default type = text/html + script file = test.lua + cache period = 30s + } + rewrite /pattern { + pattern foo/(%d+)(%a)(%d+) { + redirect to = /hello?name=pre%2middle%3othermiddle%1post + } + pattern bar/(%d+)/test { + rewrite as = /hello?name=rewritten%1 + } + } + serve_files / { + path = ./wwwroot + + # When requesting for file.ext, look for a smaller/newer file.ext.gz, + # and serve that instead if `Accept-Encoding: gzip` is in the + # request headers. + serve precompressed files = true + } +} From 6f3e97a41e8d9524dbafc7fd6d6ac18e158a44d7 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 21 Feb 2016 14:27:34 -0300 Subject: [PATCH 0118/2505] No need for testrunner to have command line arguments --- testrunner/main.c | 88 ++--------------------------------------------- 1 file changed, 2 insertions(+), 86 deletions(-) diff --git a/testrunner/main.c b/testrunner/main.c index f95aeb81a..55224fc97 100644 --- a/testrunner/main.c +++ b/testrunner/main.c @@ -18,20 +18,12 @@ */ #define _GNU_SOURCE -#include #include #include -#include #include "lwan.h" #include "lwan-serve-files.h" -enum args { - ARGS_FAILED, - ARGS_USE_CONFIG, - ARGS_SERVE_FILES -}; - lwan_http_status_t gif_beacon(lwan_request_t *request __attribute__((unused)), lwan_response_t *response, @@ -169,88 +161,12 @@ hello_world(lwan_request_t *request, return HTTP_OK; } -static enum args -parse_args(int argc, char *argv[], lwan_config_t *config, char *root) -{ - static const struct option opts[] = { - { .name = "root", .has_arg = 1, .val = 'r' }, - { .name = "listen", .has_arg = 1, .val = 'l' }, - { .name = "help", .val = 'h' }, - { } - }; - int c, optidx = 0; - enum args result = ARGS_USE_CONFIG; - - while ((c = getopt_long(argc, argv, "hr:l:", opts, &optidx)) != -1) { - switch (c) { - case 'l': - free(config->listener); - config->listener = strdup(optarg); - result = ARGS_SERVE_FILES; - break; - - case 'r': - memcpy(root, optarg, strnlen(optarg, PATH_MAX - 1) + 1); - result = ARGS_SERVE_FILES; - break; - - default: - printf("Run %s --help for usage information.\n", argv[0]); - return ARGS_FAILED; - - case 'h': - printf("Usage: %s [--root /path/to/root/dir] [--listener addr:port]\n", argv[0]); - printf("\t[--config]\n"); - printf("Serve files through HTTP.\n\n"); - printf("Defaults to listening on all interfaces, port 8080, serving current directory.\n\n"); - printf("Options:\n"); - printf("\t-r, --root Path to serve files from (default: current dir).\n"); - printf("\t-l, --listener Listener (default: %s).\n", config->listener); - printf("\t-h, --help This.\n"); - printf("\n"); - printf("Examples:\n"); - printf(" Serve system-wide documentation: %s -r /usr/share/doc\n", argv[0]); - printf(" Serve on a different port: %s -l '*:1337'\n", argv[0]); - printf("\n"); - printf("Report bugs at .\n"); - return ARGS_FAILED; - } - } - - return result; -} - int -main(int argc, char *argv[]) +main() { lwan_t l; - lwan_config_t c; - char root[PATH_MAX]; - - if (!getcwd(root, PATH_MAX)) - return 1; - - c = *lwan_get_default_config(); - c.listener = strdup("*:8080"); - - switch (parse_args(argc, argv, &c, root)) { - case ARGS_SERVE_FILES: - lwan_status_info("Serving files from %s", root); - lwan_init_with_config(&l, &c); - - const lwan_url_map_t map[] = { - { .prefix = "/", SERVE_FILES(root) }, - { } - }; - lwan_set_url_map(&l, map); - break; - case ARGS_USE_CONFIG: - lwan_init(&l); - break; - case ARGS_FAILED: - return EXIT_FAILURE; - } + lwan_init(&l); lwan_main_loop(&l); lwan_shutdown(&l); From 2799b44478d9952776bb7a1f5b02b25541582d08 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 21 Feb 2016 20:43:28 -0300 Subject: [PATCH 0119/2505] fstatat() will never return > 0 --- common/lwan-serve-files.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lwan-serve-files.c b/common/lwan-serve-files.c index c73f8cb2d..42e349eb0 100644 --- a/common/lwan-serve-files.c +++ b/common/lwan-serve-files.c @@ -383,7 +383,7 @@ sendfile_init(file_cache_entry_t *ce, goto only_uncompressed; int ret = fstatat(priv->root.fd, sd->compressed.filename, &compressed_st, 0); - if (LIKELY(ret >= 0 && compressed_st.st_mtime >= st->st_mtime && + if (LIKELY(!ret && compressed_st.st_mtime >= st->st_mtime && is_compression_worthy((size_t)compressed_st.st_size, (size_t)st->st_size))) { sd->compressed.size = (size_t)compressed_st.st_size; } else { From 84726a9b1a83b93d6e0b58acd569fe76c7aabf0e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 21 Feb 2016 20:46:34 -0300 Subject: [PATCH 0120/2505] parse_bool() should accept 0 as false and anything > 0 as true --- common/lwan-config.c | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/common/lwan-config.c b/common/lwan-config.c index 191ca0708..f0aeaf3c3 100644 --- a/common/lwan-config.c +++ b/common/lwan-config.c @@ -62,22 +62,6 @@ unsigned int parse_time_period(const char *str, unsigned int default_value) return total ? total : default_value; } -bool parse_bool(const char *value, bool default_value) -{ - if (!value) - return default_value; - - if (!strcmp(value, "true") || !strcmp(value, "1") - || !strcmp(value, "on") || !strcmp(value, "yes")) - return true; - - if (!strcmp(value, "false") || !strcmp(value, "0") - || !strcmp(value, "off") || !strcmp(value, "no")) - return false; - - return default_value; -} - long parse_long(const char *value, long default_value) { char *endptr; @@ -105,6 +89,28 @@ int parse_int(const char *value, int default_value) return (int)long_value; } +bool parse_bool(const char *value, bool default_value) +{ + int int_value; + + if (!value) + return default_value; + + if (!strcmp(value, "true") || !strcmp(value, "on") + || !strcmp(value, "yes")) + return true; + + if (!strcmp(value, "false") || !strcmp(value, "off") + || !strcmp(value, "no")) + return false; + + int_value = parse_int(value, -1); + if (int_value < 0) + return default_value; + + return int_value != 0; +} + bool config_error(config_t *conf, const char *fmt, ...) { va_list values; From 53d4cd3007b2f79ac159012e8894be2f6ae76207 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 22 Feb 2016 20:28:45 -0300 Subject: [PATCH 0121/2505] Do not schedule client if accept4() fails with unhandled error --- common/lwan.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/lwan.c b/common/lwan.c index 9b73da304..cf0f17478 100644 --- a/common/lwan.c +++ b/common/lwan.c @@ -671,8 +671,8 @@ lwan_main_loop(lwan_t *l) } lwan_status_perror("accept"); + } else { + schedule_client(l, client_fd); } - - schedule_client(l, client_fd); } } From af26c13b65d8e11c439d98f911dc7e22dc87d320 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 22 Feb 2016 20:32:08 -0300 Subject: [PATCH 0122/2505] Correctly indent parse_args() in main.c For some unknown reason it was indented with 2 spaces instead of 4. --- lwan/main.c | 74 ++++++++++++++++++++++++++--------------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/lwan/main.c b/lwan/main.c index b6a1c4d27..5b931ac73 100644 --- a/lwan/main.c +++ b/lwan/main.c @@ -36,48 +36,48 @@ static enum args parse_args(int argc, char *argv[], lwan_config_t *config, char *root) { static const struct option opts[] = { - { .name = "root", .has_arg = 1, .val = 'r' }, - { .name = "listen", .has_arg = 1, .val = 'l' }, - { .name = "help", .val = 'h' }, - { } + { .name = "root", .has_arg = 1, .val = 'r' }, + { .name = "listen", .has_arg = 1, .val = 'l' }, + { .name = "help", .val = 'h' }, + { } }; int c, optidx = 0; enum args result = ARGS_USE_CONFIG; while ((c = getopt_long(argc, argv, "hr:l:", opts, &optidx)) != -1) { - switch (c) { - case 'l': - free(config->listener); - config->listener = strdup(optarg); - result = ARGS_SERVE_FILES; - break; - - case 'r': - memcpy(root, optarg, strnlen(optarg, PATH_MAX - 1) + 1); - result = ARGS_SERVE_FILES; - break; - - default: - printf("Run %s --help for usage information.\n", argv[0]); - return ARGS_FAILED; - - case 'h': - printf("Usage: %s [--root /path/to/root/dir] [--listener addr:port]\n", argv[0]); - printf("\t[--config]\n"); - printf("Serve files through HTTP.\n\n"); - printf("Defaults to listening on %s, serving from ./wwwroot.\n\n", config->listener); - printf("Options:\n"); - printf("\t-r, --root Path to serve files from (default: ./wwwroot).\n"); - printf("\t-l, --listener Listener (default: %s).\n", config->listener); - printf("\t-h, --help This.\n"); - printf("\n"); - printf("Examples:\n"); - printf(" Serve system-wide documentation: %s -r /usr/share/doc\n", argv[0]); - printf(" Serve on a different port: %s -l '*:1337'\n", argv[0]); - printf("\n"); - printf("Report bugs at .\n"); - return ARGS_FAILED; - } + switch (c) { + case 'l': + free(config->listener); + config->listener = strdup(optarg); + result = ARGS_SERVE_FILES; + break; + + case 'r': + memcpy(root, optarg, strnlen(optarg, PATH_MAX - 1) + 1); + result = ARGS_SERVE_FILES; + break; + + default: + printf("Run %s --help for usage information.\n", argv[0]); + return ARGS_FAILED; + + case 'h': + printf("Usage: %s [--root /path/to/root/dir] [--listener addr:port]\n", argv[0]); + printf("\t[--config]\n"); + printf("Serve files through HTTP.\n\n"); + printf("Defaults to listening on %s, serving from ./wwwroot.\n\n", config->listener); + printf("Options:\n"); + printf("\t-r, --root Path to serve files from (default: ./wwwroot).\n"); + printf("\t-l, --listener Listener (default: %s).\n", config->listener); + printf("\t-h, --help This.\n"); + printf("\n"); + printf("Examples:\n"); + printf(" Serve system-wide documentation: %s -r /usr/share/doc\n", argv[0]); + printf(" Serve on a different port: %s -l '*:1337'\n", argv[0]); + printf("\n"); + printf("Report bugs at .\n"); + return ARGS_FAILED; + } } return result; From c473d5d5ad8b7397399c83d2108c6711a5defbce Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 22 Feb 2016 20:36:59 -0300 Subject: [PATCH 0123/2505] More indentation fixes in main.c --- lwan/main.c | 98 ++++++++++++++++++++++++++--------------------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/lwan/main.c b/lwan/main.c index 5b931ac73..9621bed91 100644 --- a/lwan/main.c +++ b/lwan/main.c @@ -27,60 +27,60 @@ #include "lwan-serve-files.h" enum args { - ARGS_FAILED, - ARGS_USE_CONFIG, - ARGS_SERVE_FILES + ARGS_FAILED, + ARGS_USE_CONFIG, + ARGS_SERVE_FILES }; static enum args parse_args(int argc, char *argv[], lwan_config_t *config, char *root) { - static const struct option opts[] = { - { .name = "root", .has_arg = 1, .val = 'r' }, - { .name = "listen", .has_arg = 1, .val = 'l' }, - { .name = "help", .val = 'h' }, - { } - }; - int c, optidx = 0; - enum args result = ARGS_USE_CONFIG; - - while ((c = getopt_long(argc, argv, "hr:l:", opts, &optidx)) != -1) { - switch (c) { - case 'l': - free(config->listener); - config->listener = strdup(optarg); - result = ARGS_SERVE_FILES; - break; - - case 'r': - memcpy(root, optarg, strnlen(optarg, PATH_MAX - 1) + 1); - result = ARGS_SERVE_FILES; - break; - - default: - printf("Run %s --help for usage information.\n", argv[0]); - return ARGS_FAILED; - - case 'h': - printf("Usage: %s [--root /path/to/root/dir] [--listener addr:port]\n", argv[0]); - printf("\t[--config]\n"); - printf("Serve files through HTTP.\n\n"); - printf("Defaults to listening on %s, serving from ./wwwroot.\n\n", config->listener); - printf("Options:\n"); - printf("\t-r, --root Path to serve files from (default: ./wwwroot).\n"); - printf("\t-l, --listener Listener (default: %s).\n", config->listener); - printf("\t-h, --help This.\n"); - printf("\n"); - printf("Examples:\n"); - printf(" Serve system-wide documentation: %s -r /usr/share/doc\n", argv[0]); - printf(" Serve on a different port: %s -l '*:1337'\n", argv[0]); - printf("\n"); - printf("Report bugs at .\n"); - return ARGS_FAILED; - } - } - - return result; + static const struct option opts[] = { + { .name = "root", .has_arg = 1, .val = 'r' }, + { .name = "listen", .has_arg = 1, .val = 'l' }, + { .name = "help", .val = 'h' }, + { } + }; + int c, optidx = 0; + enum args result = ARGS_USE_CONFIG; + + while ((c = getopt_long(argc, argv, "hr:l:", opts, &optidx)) != -1) { + switch (c) { + case 'l': + free(config->listener); + config->listener = strdup(optarg); + result = ARGS_SERVE_FILES; + break; + + case 'r': + memcpy(root, optarg, strnlen(optarg, PATH_MAX - 1) + 1); + result = ARGS_SERVE_FILES; + break; + + case 'h': + printf("Usage: %s [--root /path/to/root/dir] [--listener addr:port]\n", argv[0]); + printf("\t[--config]\n"); + printf("Serve files through HTTP.\n\n"); + printf("Defaults to listening on %s, serving from ./wwwroot.\n\n", config->listener); + printf("Options:\n"); + printf("\t-r, --root Path to serve files from (default: ./wwwroot).\n"); + printf("\t-l, --listener Listener (default: %s).\n", config->listener); + printf("\t-h, --help This.\n"); + printf("\n"); + printf("Examples:\n"); + printf(" Serve system-wide documentation: %s -r /usr/share/doc\n", argv[0]); + printf(" Serve on a different port: %s -l '*:1337'\n", argv[0]); + printf("\n"); + printf("Report bugs at .\n"); + return ARGS_FAILED; + + default: + printf("Run %s --help for usage information.\n", argv[0]); + return ARGS_FAILED; + } + } + + return result; } int From aaef85a0f71370a7d80ba188a5ef450fd2b232b2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 23 Feb 2016 08:49:30 -0300 Subject: [PATCH 0124/2505] Synchronize patterns.c with current version from OpenBSD httpd Original commit message: httpd patterns double free issue and diff from Alexander Schrijver alex at flupzor nl ok reyk@ --- common/patterns.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/common/patterns.c b/common/patterns.c index 93a2b9ecc..a67456c41 100644 --- a/common/patterns.c +++ b/common/patterns.c @@ -1,4 +1,4 @@ -/* $OpenBSD: patterns.c,v 1.3 2015/06/26 10:07:48 semarie Exp $ */ +/* $OpenBSD: patterns.c,v 1.5 2016/02/14 18:20:59 semarie Exp $ */ /* * Copyright (c) 2015 Reyk Floeter @@ -26,7 +26,7 @@ /* * Derived from Lua 5.3.1: - * $Id: patterns.c,v 1.3 2015/06/26 10:07:48 semarie Exp $ + * $Id: patterns.c,v 1.5 2016/02/14 18:20:59 semarie Exp $ * Standard library for string operations and pattern-matching */ @@ -693,5 +693,6 @@ str_match_free(struct str_match *m) for (i = 0; i < m->sm_nmatch; i++) free(m->sm_match[i]); free(m->sm_match); + m->sm_match = NULL; m->sm_nmatch = 0; } From 9b91a9c947d53937bbcb86b079f16a9094c41110 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 23 Feb 2016 08:57:30 -0300 Subject: [PATCH 0125/2505] Add some sanity checks to file server - Do not try to serve non-regular files (FIFOs, pipes, device nodes, sockets, etc). - If index_html_path is a directory, don't try serving it as a file. --- common/lwan-serve-files.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/common/lwan-serve-files.c b/common/lwan-serve-files.c index 42e349eb0..6ef8c14e5 100644 --- a/common/lwan-serve-files.c +++ b/common/lwan-serve-files.c @@ -473,6 +473,11 @@ get_funcs(serve_files_priv_t *priv, const char *key, char *full_path, return &dirlist_funcs; } + if (UNLIKELY(S_ISDIR(st->st_mode))) { + /* The index file is a directory. Shouldn't happen. */ + return NULL; + } + /* If it does, we want its full path. */ /* FIXME: Use strlcpy() here instead of calling strlen()? */ @@ -483,6 +488,9 @@ get_funcs(serve_files_priv_t *priv, const char *key, char *full_path, full_path[priv->root.path_len] = '/'; strncpy(full_path + priv->root.path_len + 1, index_html_path, PATH_MAX - priv->root.path_len - 1); + } else if (UNLIKELY(!S_ISREG(st->st_mode))) { + /* Only serve regular files. */ + return NULL; } /* It's not a directory: choose the fastest way to serve the file From 6baf295736ad9539832ec664df21bda90432b1f0 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 23 Feb 2016 09:02:45 -0300 Subject: [PATCH 0126/2505] Add ability to disable automatic directory indexing --- common/lwan-serve-files.c | 12 ++++++++++-- common/lwan-serve-files.h | 4 +++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/common/lwan-serve-files.c b/common/lwan-serve-files.c index 6ef8c14e5..30ae6870f 100644 --- a/common/lwan-serve-files.c +++ b/common/lwan-serve-files.c @@ -64,6 +64,7 @@ struct serve_files_priv_t_ { lwan_tpl_t *directory_list_tpl; bool serve_precompressed_files; + bool auto_index; }; struct cache_funcs_t_ { @@ -469,8 +470,13 @@ get_funcs(serve_files_priv_t *priv, const char *key, char *full_path, if (UNLIKELY(errno != ENOENT)) return NULL; - /* If it doesn't, we want to generate a directory list. */ - return &dirlist_funcs; + if (LIKELY(priv->auto_index)) { + /* If it doesn't, we want to generate a directory list. */ + return &dirlist_funcs; + } + + /* Auto index is disabled. */ + return NULL; } if (UNLIKELY(S_ISDIR(st->st_mode))) { @@ -693,6 +699,7 @@ serve_files_init(void *args) priv->open_mode = open_mode; priv->index_html = settings->index_html ? settings->index_html : "index.html"; priv->serve_precompressed_files = settings->serve_precompressed_files; + priv->auto_index = settings->auto_index; return priv; @@ -716,6 +723,7 @@ serve_files_init_from_hash(const struct hash *hash) .index_html = hash_find(hash, "index_path"), .serve_precompressed_files = parse_bool(hash_find(hash, "serve_precompressed_files"), true), + .auto_index = parse_bool(hash_find(hash, "auto_index"), true), .directory_list_template = hash_find(hash, "directory_list_template") }; return serve_files_init(&settings); diff --git a/common/lwan-serve-files.h b/common/lwan-serve-files.h index 491226c53..15315dbd2 100644 --- a/common/lwan-serve-files.h +++ b/common/lwan-serve-files.h @@ -30,6 +30,7 @@ struct lwan_serve_files_settings_t { const char *index_html; const char *directory_list_template; bool serve_precompressed_files; + bool auto_index; }; #define SERVE_FILES_SETTINGS(root_path_, index_html_, serve_precompressed_files_) \ @@ -38,7 +39,8 @@ struct lwan_serve_files_settings_t { .root_path = root_path_, \ .index_html = index_html_, \ .serve_precompressed_files = serve_precompressed_files_, \ - .directory_list_template = NULL \ + .directory_list_template = NULL, \ + .auto_index = true \ }}), \ .flags = (lwan_handler_flags_t)0 From 3545cdb5205b8e324fc9eac22c428baf35698852 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 27 Feb 2016 08:58:11 -0300 Subject: [PATCH 0127/2505] Fix buffer overflow in parse_key_values() --- common/lwan-request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index becf168b0..542aaed99 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -384,7 +384,7 @@ parse_key_values(lwan_request_t *request, kvs[values].value = value; values++; - } while (ptr && values < n_elements); + } while (ptr && values < (n_elements - 1)); kvs[values].key = kvs[values].value = NULL; From 322b6bddf16513f86d62c91b2b313b19ebf293b4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 27 Feb 2016 09:20:36 -0300 Subject: [PATCH 0128/2505] Try to keep the file descriptor open when serving files with lwan_sendfile() Since sendfile() takes an offset parameter, it's possible to keep only one reference to a file in the cache instead of a copy of the full file path. This should speed up file serving even more. As a bonus: * Give out the correct error message for HEAD requests if the access to the file has been denied. * Keeping the file open and using fstat() on it will prevent some possible TOCTOU-style vulnerabilities. Inspired by the filecache from H2O. --- common/lwan-serve-files.c | 88 +++++++++++++++++++++++---------------- 1 file changed, 51 insertions(+), 37 deletions(-) diff --git a/common/lwan-serve-files.c b/common/lwan-serve-files.c index 30ae6870f..360895085 100644 --- a/common/lwan-serve-files.c +++ b/common/lwan-serve-files.c @@ -88,7 +88,7 @@ struct mmap_cache_data_t_ { struct sendfile_cache_data_t_ { struct { - char *filename; + int fd; size_t size; } compressed, uncompressed; }; @@ -369,8 +369,10 @@ sendfile_init(file_cache_entry_t *ce, const char *full_path, struct stat *st) { + char gzpath[PATH_MAX]; sendfile_cache_data_t *sd = (sendfile_cache_data_t *)(ce + 1); struct stat compressed_st; + int ret; ce->mime_type = lwan_determine_mime_type_for_file_name( full_path + priv->root.path_len); @@ -379,27 +381,47 @@ sendfile_init(file_cache_entry_t *ce, goto only_uncompressed; /* Try to serve a compressed file using sendfile() if $FILENAME.gz exists */ - int len = asprintf(&sd->compressed.filename, "%s.gz", full_path + priv->root.path_len + 1); - if (UNLIKELY(len < 0 || len >= PATH_MAX)) + ret = snprintf(gzpath, PATH_MAX, "%s.gz", full_path + priv->root.path_len + 1); + if (UNLIKELY(ret < 0 || ret >= PATH_MAX)) + goto only_uncompressed; + + sd->compressed.fd = openat(priv->root.fd, gzpath, priv->open_mode); + if (UNLIKELY(sd->compressed.fd < 0)) goto only_uncompressed; - int ret = fstatat(priv->root.fd, sd->compressed.filename, &compressed_st, 0); + ret = fstat(sd->compressed.fd, &compressed_st); if (LIKELY(!ret && compressed_st.st_mtime >= st->st_mtime && is_compression_worthy((size_t)compressed_st.st_size, (size_t)st->st_size))) { sd->compressed.size = (size_t)compressed_st.st_size; } else { - free(sd->compressed.filename); + close(sd->compressed.fd); only_uncompressed: - sd->compressed.filename = NULL; + sd->compressed.fd = -1; sd->compressed.size = 0; } - /* Regardless of the existence of $FILENAME.gz, store the full path */ + /* Regardless of the existence of $FILENAME.gz, keep the uncompressed file open */ sd->uncompressed.size = (size_t)st->st_size; - sd->uncompressed.filename = strdup(full_path + priv->root.path_len + 1); - if (UNLIKELY(!sd->uncompressed.filename)) { - free(sd->compressed.filename); + sd->uncompressed.fd = openat(priv->root.fd, full_path + priv->root.path_len + 1, priv->open_mode); + if (UNLIKELY(sd->uncompressed.fd < 0)) { + int openat_errno = errno; + + close(sd->compressed.fd); + + switch (openat_errno) { + case ENFILE: + case EMFILE: + case EACCES: + /* These errors should produce responses other than 404, so store errno as the + * file descriptor. */ + + sd->uncompressed.fd = -openat_errno; + sd->compressed.fd = -1; + sd->compressed.size = 0; + return true; + } + return false; } @@ -578,8 +600,10 @@ sendfile_free(void *data) { sendfile_cache_data_t *sd = data; - free(sd->compressed.filename); - free(sd->uncompressed.filename); + if (sd->compressed.fd >= 0) + close(sd->compressed.fd); + if (sd->uncompressed.fd >= 0) + close(sd->uncompressed.fd); } static void @@ -839,15 +863,15 @@ sendfile_serve(lwan_request_t *request, void *data) lwan_http_status_t return_status; off_t from, to; const char *compressed; - char *filename; size_t size; + int fd; if (sd->compressed.size && (request->flags & REQUEST_ACCEPT_GZIP)) { from = 0; to = (off_t)sd->compressed.size; compressed = compression_gzip; - filename = sd->compressed.filename; + fd = sd->compressed.fd; size = sd->compressed.size; return_status = HTTP_OK; @@ -857,9 +881,20 @@ sendfile_serve(lwan_request_t *request, void *data) return HTTP_RANGE_UNSATISFIABLE; compressed = compression_none; - filename = sd->uncompressed.filename; + fd = sd->uncompressed.fd; size = sd->uncompressed.size; } + if (UNLIKELY(fd < 0)) { + switch (-fd) { + case EACCES: + return HTTP_FORBIDDEN; + case EMFILE: + case ENFILE: + return HTTP_UNAVAILABLE; + default: + return HTTP_INTERNAL_ERROR; + } + } if (client_has_fresh_content(request, fce->last_modified.integer)) return_status = HTTP_NOT_MODIFIED; @@ -872,28 +907,7 @@ sendfile_serve(lwan_request_t *request, void *data) if (request->flags & REQUEST_METHOD_HEAD || return_status == HTTP_NOT_MODIFIED) { lwan_write(request, headers, header_len); } else { - serve_files_priv_t *priv = request->response.stream.priv; - /* - * lwan_openat() will yield from the coroutine if openat() - * can't open the file due to not having free file descriptors - * around. This will happen just a handful of times. - * The file will be automatically closed whenever this - * coroutine is freed. - */ - int file_fd = lwan_openat(request, priv->root.fd, filename, priv->open_mode); - if (UNLIKELY(file_fd < 0)) { - switch (file_fd) { - case -EACCES: - return HTTP_FORBIDDEN; - case -ENFILE: - return HTTP_UNAVAILABLE; - default: - return HTTP_NOT_FOUND; - } - } - - lwan_sendfile(request, file_fd, from, (size_t)to, - headers, header_len); + lwan_sendfile(request, fd, from, (size_t)to, headers, header_len); } return return_status; From 47541d5b74ed2cc688f7c84bf617a0fabeaae487 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 27 Feb 2016 09:30:54 -0300 Subject: [PATCH 0129/2505] Call initgroups() right after calling setgid() --- common/lwan-straitjacket.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/lwan-straitjacket.c b/common/lwan-straitjacket.c index 61c04bce6..fd11bfe46 100644 --- a/common/lwan-straitjacket.c +++ b/common/lwan-straitjacket.c @@ -19,6 +19,7 @@ #define _GNU_SOURCE #include +#include #include #include #include @@ -76,6 +77,8 @@ static bool switch_to_user(uid_t uid, gid_t gid, const char *username) if (setgid(gid) < 0) return false; + if (initgroups(username, gid) < 0) + return false; if (setuid(uid) < 0) return false; From 9e120d69d81e8396b579306cf5adc1beec2f9abf Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 27 Feb 2016 10:30:49 -0300 Subject: [PATCH 0130/2505] Remove coro_collect_garbage() and related code This proved to be an experiment that did not yield lower latencies as I thought it would yield. It also added complexity that's not required. With the file server keeping files open when sendfile() is used, the motivation for this thing to exist is now moot. --- common/lwan-coro.c | 56 ++++++++------------------------------------ common/lwan-coro.h | 3 +-- common/lwan-thread.c | 8 +------ 3 files changed, 12 insertions(+), 55 deletions(-) diff --git a/common/lwan-coro.c b/common/lwan-coro.c index 97510be46..95e19fa3f 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -46,7 +46,6 @@ struct coro_defer_t_ { defer_func func; void *data1; void *data2; - bool sticky; }; struct coro_t_ { @@ -147,49 +146,17 @@ coro_entry_point(coro_t *coro, coro_function_t func) coro_yield(coro, return_value); } -static coro_defer_t * -reverse(coro_defer_t *root) -{ - coro_defer_t *new = NULL; - - while (root) { - coro_defer_t *next = root->next; - - root->next = new; - new = root; - root = next; - } - - return new; -} - static void -coro_run_deferred(coro_t *coro, bool sticky) +coro_run_deferred(coro_t *coro) { - coro_defer_t *defer = coro->defer; - coro_defer_t *sticked = NULL; + while (coro->defer) { + coro_defer_t *tmp = coro->defer; - while (defer) { - coro_defer_t *tmp = defer; + coro->defer = coro->defer->next; - defer = defer->next; - - if (!sticky && tmp->sticky) { - tmp->next = sticked; - sticked = tmp; - } else { - tmp->func(tmp->data1, tmp->data2); - free(tmp); - } + tmp->func(tmp->data1, tmp->data2); + free(tmp); } - - coro->defer = reverse(sticked); -} - -void -coro_collect_garbage(coro_t *coro) -{ - coro_run_deferred(coro, false); } void @@ -200,8 +167,7 @@ coro_reset(coro_t *coro, coro_function_t func, void *data) coro->ended = false; coro->data = data; - if (coro->defer) - coro_run_deferred(coro, true); + coro_run_deferred(coro); #if defined(__x86_64__) coro->context[6 /* RDI */] = (uintptr_t) coro; @@ -310,7 +276,7 @@ coro_free(coro_t *coro) #if !defined(NDEBUG) && defined(USE_VALGRIND) VALGRIND_STACK_DEREGISTER(coro->vg_stack_id); #endif - coro_run_deferred(coro, true); + coro_run_deferred(coro); free(coro); } @@ -328,7 +294,6 @@ coro_defer_any(coro_t *coro, defer_func func, void *data1, void *data2) defer->func = func; defer->data1 = data1; defer->data2 = data2; - defer->sticky = false; coro->defer = defer; } @@ -346,7 +311,7 @@ coro_defer2(coro_t *coro, void (*func)(void *data1, void *data2), } void * -coro_malloc_full(coro_t *coro, size_t size, bool sticky, void (*destroy_func)()) +coro_malloc_full(coro_t *coro, size_t size, void (*destroy_func)()) { coro_defer_t *defer = malloc(sizeof(*defer) + size); if (UNLIKELY(!defer)) @@ -356,7 +321,6 @@ coro_malloc_full(coro_t *coro, size_t size, bool sticky, void (*destroy_func)()) defer->func = destroy_func; defer->data1 = defer + 1; defer->data2 = NULL; - defer->sticky = sticky; coro->defer = defer; @@ -370,7 +334,7 @@ static void nothing() inline void * coro_malloc(coro_t *coro, size_t size) { - return coro_malloc_full(coro, size, false, nothing); + return coro_malloc_full(coro, size, nothing); } char * diff --git a/common/lwan-coro.h b/common/lwan-coro.h index 6b7f03da5..7a14cfcbe 100644 --- a/common/lwan-coro.h +++ b/common/lwan-coro.h @@ -55,10 +55,9 @@ void *coro_get_data(coro_t *coro); void coro_defer(coro_t *coro, void (*func)(void *data), void *data); void coro_defer2(coro_t *coro, void (*func)(void *data1, void *data2), void *data1, void *data2); -void coro_collect_garbage(coro_t *coro); void *coro_malloc(coro_t *coro, size_t sz); -void *coro_malloc_full(coro_t *coro, size_t size, bool sticky, void (*destroy_func)()); +void *coro_malloc_full(coro_t *coro, size_t size, void (*destroy_func)()); char *coro_strdup(coro_t *coro, const char *str); char *coro_printf(coro_t *coro, const char *fmt, ...); diff --git a/common/lwan-thread.c b/common/lwan-thread.c index 01190b4b2..ddb973abc 100644 --- a/common/lwan-thread.c +++ b/common/lwan-thread.c @@ -147,7 +147,7 @@ static int process_request_coro(coro_t *coro) { const lwan_request_flags_t flags_filter = REQUEST_PROXIED; - strbuf_t *strbuf = coro_malloc_full(coro, sizeof(*strbuf), true, strbuf_free); + strbuf_t *strbuf = coro_malloc_full(coro, sizeof(*strbuf), strbuf_free); lwan_connection_t *conn = coro_get_data(coro); lwan_t *lwan = conn->thread->lwan; int fd = lwan_connection_get_fd(lwan, conn); @@ -160,7 +160,6 @@ process_request_coro(coro_t *coro) lwan_request_flags_t flags = lwan->config.proxy_protocol ? REQUEST_ALLOW_PROXY_REQS : 0; lwan_proxy_t proxy; - int gc_counter = CORO_GC_THRESHOLD; if (UNLIKELY(!strbuf)) return CONN_CORO_ABORT; @@ -181,11 +180,6 @@ process_request_coro(coro_t *coro) assert(conn->flags & CONN_IS_ALIVE); next_request = lwan_process_request(lwan, &request, &buffer, next_request); - if (!gc_counter--) { - coro_collect_garbage(coro); - gc_counter = CORO_GC_THRESHOLD; - } - coro_yield(coro, CONN_CORO_MAY_RESUME); if (UNLIKELY(!strbuf_reset_length(strbuf))) From e54c3ff4932ce5ca871dd659fee5ff457622fade Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 29 Feb 2016 00:21:57 -0300 Subject: [PATCH 0131/2505] If openat() fails for compressed files, give the correct error message --- common/lwan-serve-files.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/common/lwan-serve-files.c b/common/lwan-serve-files.c index 360895085..b776433c1 100644 --- a/common/lwan-serve-files.c +++ b/common/lwan-serve-files.c @@ -416,8 +416,7 @@ sendfile_init(file_cache_entry_t *ce, /* These errors should produce responses other than 404, so store errno as the * file descriptor. */ - sd->uncompressed.fd = -openat_errno; - sd->compressed.fd = -1; + sd->uncompressed.fd = sd->compressed.fd = -openat_errno; sd->compressed.size = 0; return true; } From d6ab83ba3e1664fa620c8b3c953a13e3d1412660 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 2 Mar 2016 22:24:30 -0300 Subject: [PATCH 0132/2505] Remove unused CORO_GC_THRESHOLD constant --- common/lwan-thread.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/common/lwan-thread.c b/common/lwan-thread.c index ddb973abc..1f74b2409 100644 --- a/common/lwan-thread.c +++ b/common/lwan-thread.c @@ -35,8 +35,6 @@ #include "lwan-private.h" -#define CORO_GC_THRESHOLD 16 - struct death_queue_t { const lwan_t *lwan; lwan_connection_t *conns; From 673ccc2ccbb217d32461644746ea1fa272022b91 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 2 Mar 2016 22:24:45 -0300 Subject: [PATCH 0133/2505] Fix invalid call to hash_free() when using user-defined url_map with data ptrs Should fix #126. --- common/lwan.c | 4 ++-- common/lwan.h | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/common/lwan.c b/common/lwan.c index cf0f17478..9b4aa7245 100644 --- a/common/lwan.c +++ b/common/lwan.c @@ -131,7 +131,7 @@ static void destroy_urlmap(void *data) const lwan_module_t *module = url_map->module; if (module->shutdown) module->shutdown(url_map->data); - } else if (url_map->data) { + } else if (url_map->data && url_map->flags & HANDLER_DATA_IS_HASH_TABLE) { hash_free(url_map->data); } @@ -271,7 +271,7 @@ static void parse_listener_prefix(config_t *c, config_line_t *l, lwan_t *lwan, if (handler) { url_map.handler = handler; - url_map.flags |= HANDLER_PARSE_MASK; + url_map.flags |= HANDLER_PARSE_MASK | HANDLER_DATA_IS_HASH_TABLE; url_map.data = hash; url_map.module = NULL; diff --git a/common/lwan.h b/common/lwan.h index 10f63265b..093c1fa10 100644 --- a/common/lwan.h +++ b/common/lwan.h @@ -142,6 +142,7 @@ typedef enum { HANDLER_REMOVE_LEADING_SLASH = 1<<6, HANDLER_CAN_REWRITE_URL = 1<<7, HANDLER_PARSE_COOKIES = 1<<8, + HANDLER_DATA_IS_HASH_TABLE = 1<<9, HANDLER_PARSE_MASK = 1<<0 | 1<<1 | 1<<2 | 1<<3 | 1<<4 | 1<<8 } lwan_handler_flags_t; From 7b2796c039b51b98fbab26a409e4c3e214dd67b3 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 2 Mar 2016 23:15:29 -0300 Subject: [PATCH 0134/2505] Fix reading of POST requests After refactoring the read function many times, I forgot to add the terminating NUL before the finalizer could return FINALIZER_DONE. Use memrchr() instead of strrchr(), and give the buffer length as the parameter to find the post data separator. Closes #127. --- common/lwan-request.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index 542aaed99..0d580c22a 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -803,7 +803,8 @@ static lwan_read_finalizer_t read_request_finalizer(size_t total_read, return FINALIZER_DONE; if (get_http_method(helper->buffer->value) == REQUEST_METHOD_POST) { - char *post_data_separator = strrchr(helper->buffer->value, '\n'); + char *post_data_separator = memrchr(helper->buffer->value, '\n', + helper->buffer->len); if (post_data_separator) { if (LIKELY(!memcmp(post_data_separator - 3, "\r\n\r", 3))) return FINALIZER_DONE; From ff24c53c03f32f47089b87bc7e8976b6896e8d15 Mon Sep 17 00:00:00 2001 From: Kubilay Kocak Date: Sat, 5 Mar 2016 01:20:50 +1100 Subject: [PATCH 0135/2505] Add FreeBSD package install names & instructions --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 26c358915..42c57b2c2 100644 --- a/README.md +++ b/README.md @@ -88,9 +88,10 @@ The build system will look for these libraries and enable/link if available. - [jemalloc](http://www.canonware.com/jemalloc) - [Valgrind](http://valgrind.org) -### Common distribution package names +### Common operating system package names - ArchLinux: `pacman -S cmake python zlib sqlite luajit libmariadbclient gperftools valgrind` + - FreeBSD: `pkg install cmake pkgconf python27 sqlite3 lua51` - Ubuntu 14: `apt-get update && apt-get install git cmake zlib1g-dev pkg-config lua5.1-dev libsqlite3-dev libmysqlclient-dev` ### Build commands From c0169d1c638d5746fd4937ef07ada500669f3db5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 4 Mar 2016 21:39:34 -0300 Subject: [PATCH 0136/2505] Remove `_t` prefix from internal structs in serve-files module Also, do not use typedef for internal structs in this file. --- common/lwan-serve-files.c | 192 +++++++++++++++++--------------------- 1 file changed, 87 insertions(+), 105 deletions(-) diff --git a/common/lwan-serve-files.c b/common/lwan-serve-files.c index b776433c1..d9adf638c 100644 --- a/common/lwan-serve-files.c +++ b/common/lwan-serve-files.c @@ -41,15 +41,9 @@ static const char *compression_none = NULL; static const char *compression_gzip = "gzip"; static const char *compression_deflate = "deflate"; -typedef struct serve_files_priv_t_ serve_files_priv_t; -typedef struct file_cache_entry_t_ file_cache_entry_t; -typedef struct cache_funcs_t_ cache_funcs_t; -typedef struct mmap_cache_data_t_ mmap_cache_data_t; -typedef struct sendfile_cache_data_t_ sendfile_cache_data_t; -typedef struct dir_list_cache_data_t_ dir_list_cache_data_t; -typedef struct redir_cache_data_t_ redir_cache_data_t; - -struct serve_files_priv_t_ { +struct file_cache_entry; + +struct serve_files_priv { struct cache_t *cache; struct { @@ -67,18 +61,16 @@ struct serve_files_priv_t_ { bool auto_index; }; -struct cache_funcs_t_ { +struct cache_funcs { lwan_http_status_t (*serve)(lwan_request_t *request, void *data); - bool (*init)(file_cache_entry_t *ce, - serve_files_priv_t *priv, - const char *full_path, - struct stat *st); + bool (*init)(struct file_cache_entry *ce, struct serve_files_priv *priv, + const char *full_path, struct stat *st); void (*free)(void *data); size_t struct_size; }; -struct mmap_cache_data_t_ { +struct mmap_cache_data { struct { void *contents; /* zlib expects unsigned longs instead of size_t */ @@ -86,22 +78,22 @@ struct mmap_cache_data_t_ { } compressed, uncompressed; }; -struct sendfile_cache_data_t_ { +struct sendfile_cache_data { struct { int fd; size_t size; } compressed, uncompressed; }; -struct dir_list_cache_data_t_ { +struct dir_list_cache_data { strbuf_t *rendered; }; -struct redir_cache_data_t_ { +struct redir_cache_data { char *redir_to; }; -struct file_cache_entry_t_ { +struct file_cache_entry { struct cache_entry_t base; struct { @@ -110,10 +102,10 @@ struct file_cache_entry_t_ { } last_modified; const char *mime_type; - const cache_funcs_t *funcs; + const struct cache_funcs *funcs; }; -struct file_list_t { +struct file_list { const char *full_path; const char *rel_path; struct { @@ -131,63 +123,65 @@ struct file_list_t { static int directory_list_generator(coro_t *coro); -static bool mmap_init(file_cache_entry_t *ce, serve_files_priv_t *priv, - const char *full_path, struct stat *st); +static bool mmap_init(struct file_cache_entry *ce, + struct serve_files_priv *priv, const char *full_path, struct stat *st); static void mmap_free(void *data); static lwan_http_status_t mmap_serve(lwan_request_t *request, void *data); -static bool sendfile_init(file_cache_entry_t *ce, serve_files_priv_t *priv, - const char *full_path, struct stat *st); + +static bool sendfile_init(struct file_cache_entry *ce, + struct serve_files_priv *priv, const char *full_path, struct stat *st); static void sendfile_free(void *data); static lwan_http_status_t sendfile_serve(lwan_request_t *request, void *data); -static bool dirlist_init(file_cache_entry_t *ce, serve_files_priv_t *priv, - const char *full_path, struct stat *st); + +static bool dirlist_init(struct file_cache_entry *ce, + struct serve_files_priv *priv, const char *full_path, struct stat *st); static void dirlist_free(void *data); static lwan_http_status_t dirlist_serve(lwan_request_t *request, void *data); -static bool redir_init(file_cache_entry_t *ce, serve_files_priv_t *priv, - const char *full_path, struct stat *st); + +static bool redir_init(struct file_cache_entry *ce, + struct serve_files_priv *priv, const char *full_path, struct stat *st); static void redir_free(void *data); static lwan_http_status_t redir_serve(lwan_request_t *request, void *data); - -static const cache_funcs_t mmap_funcs = { +static const struct cache_funcs mmap_funcs = { .init = mmap_init, .free = mmap_free, .serve = mmap_serve, - .struct_size = sizeof(mmap_cache_data_t) + .struct_size = sizeof(struct mmap_cache_data) }; -static const cache_funcs_t sendfile_funcs = { +static const struct cache_funcs sendfile_funcs = { .init = sendfile_init, .free = sendfile_free, .serve = sendfile_serve, - .struct_size = sizeof(sendfile_cache_data_t) + .struct_size = sizeof(struct sendfile_cache_data) }; -static const cache_funcs_t dirlist_funcs = { +static const struct cache_funcs dirlist_funcs = { .init = dirlist_init, .free = dirlist_free, .serve = dirlist_serve, - .struct_size = sizeof(dir_list_cache_data_t) + .struct_size = sizeof(struct dir_list_cache_data) }; -static const cache_funcs_t redir_funcs = { +static const struct cache_funcs redir_funcs = { .init = redir_init, .free = redir_free, .serve = redir_serve, - .struct_size = sizeof(redir_cache_data_t) + .struct_size = sizeof(struct redir_cache_data) }; static const lwan_var_descriptor_t file_list_desc[] = { - TPL_VAR_STR_ESCAPE(struct file_list_t, full_path), - TPL_VAR_STR_ESCAPE(struct file_list_t, rel_path), - TPL_VAR_SEQUENCE(struct file_list_t, file_list, directory_list_generator, ( + TPL_VAR_STR_ESCAPE(struct file_list, full_path), + TPL_VAR_STR_ESCAPE(struct file_list, rel_path), + TPL_VAR_SEQUENCE(struct file_list, file_list, directory_list_generator, ( (const lwan_var_descriptor_t[]) { - TPL_VAR_STR(struct file_list_t, file_list.icon), - TPL_VAR_STR(struct file_list_t, file_list.icon_alt), - TPL_VAR_STR(struct file_list_t, file_list.name), - TPL_VAR_STR(struct file_list_t, file_list.type), - TPL_VAR_INT(struct file_list_t, file_list.size), - TPL_VAR_STR(struct file_list_t, file_list.unit), + TPL_VAR_STR(struct file_list, file_list.icon), + TPL_VAR_STR(struct file_list, file_list.icon_alt), + TPL_VAR_STR(struct file_list, file_list.name), + TPL_VAR_STR(struct file_list, file_list.type), + TPL_VAR_INT(struct file_list, file_list.size), + TPL_VAR_STR(struct file_list, file_list.unit), TPL_VAR_SENTINEL } )), @@ -233,7 +227,7 @@ directory_list_generator(coro_t *coro) { DIR *dir; struct dirent entry, *buffer; - struct file_list_t *fl = coro_get_data(coro); + struct file_list *fl = coro_get_data(coro); int fd; dir = opendir(fl->full_path); @@ -302,7 +296,7 @@ is_compression_worthy(const size_t compressed_sz, const size_t uncompressed_sz) } static void -compress_cached_entry(mmap_cache_data_t *md) +compress_cached_entry(struct mmap_cache_data *md) { md->compressed.size = compressBound(md->uncompressed.size); @@ -324,12 +318,10 @@ compress_cached_entry(mmap_cache_data_t *md) } static bool -mmap_init(file_cache_entry_t *ce, - serve_files_priv_t *priv, - const char *full_path, - struct stat *st) +mmap_init(struct file_cache_entry *ce, struct serve_files_priv *priv, + const char *full_path, struct stat *st) { - mmap_cache_data_t *md = (mmap_cache_data_t *)(ce + 1); + struct mmap_cache_data *md = (struct mmap_cache_data *)(ce + 1); int file_fd; bool success; @@ -364,13 +356,11 @@ mmap_init(file_cache_entry_t *ce, } static bool -sendfile_init(file_cache_entry_t *ce, - serve_files_priv_t *priv, - const char *full_path, - struct stat *st) +sendfile_init(struct file_cache_entry *ce, struct serve_files_priv *priv, + const char *full_path, struct stat *st) { char gzpath[PATH_MAX]; - sendfile_cache_data_t *sd = (sendfile_cache_data_t *)(ce + 1); + struct sendfile_cache_data *sd = (struct sendfile_cache_data *)(ce + 1); struct stat compressed_st; int ret; @@ -428,13 +418,11 @@ sendfile_init(file_cache_entry_t *ce, } static bool -dirlist_init(file_cache_entry_t *ce, - serve_files_priv_t *priv, - const char *full_path, - struct stat *st __attribute__((unused))) +dirlist_init(struct file_cache_entry *ce, struct serve_files_priv *priv, + const char *full_path, struct stat *st __attribute__((unused))) { - dir_list_cache_data_t *dd = (dir_list_cache_data_t *)(ce + 1); - struct file_list_t vars = { + struct dir_list_cache_data *dd = (struct dir_list_cache_data *)(ce + 1); + struct file_list vars = { .full_path = full_path, .rel_path = full_path + priv->root.path_len }; @@ -446,12 +434,10 @@ dirlist_init(file_cache_entry_t *ce, } static bool -redir_init(file_cache_entry_t *ce, - serve_files_priv_t *priv, - const char *full_path, - struct stat *st __attribute__((unused))) +redir_init(struct file_cache_entry *ce, struct serve_files_priv *priv, + const char *full_path, struct stat *st __attribute__((unused))) { - redir_cache_data_t *rd = (redir_cache_data_t *)(ce + 1); + struct redir_cache_data *rd = (struct redir_cache_data *)(ce + 1); if (asprintf(&rd->redir_to, "%s/", full_path + priv->root.path_len) < 0) return false; @@ -460,8 +446,8 @@ redir_init(file_cache_entry_t *ce, return true; } -static const cache_funcs_t * -get_funcs(serve_files_priv_t *priv, const char *key, char *full_path, +static const struct cache_funcs * +get_funcs(struct serve_files_priv *priv, const char *key, char *full_path, struct stat *st) { char index_html_path_buf[PATH_MAX]; @@ -528,11 +514,11 @@ get_funcs(serve_files_priv_t *priv, const char *key, char *full_path, return &sendfile_funcs; } -static file_cache_entry_t * -create_cache_entry_from_funcs(serve_files_priv_t *priv, const char *full_path, - struct stat *st, const cache_funcs_t *funcs) +static struct file_cache_entry * +create_cache_entry_from_funcs(struct serve_files_priv *priv, + const char *full_path, struct stat *st, const struct cache_funcs *funcs) { - file_cache_entry_t *fce; + struct file_cache_entry *fce; fce = malloc(sizeof(*fce) + funcs->struct_size); if (UNLIKELY(!fce)) @@ -555,10 +541,10 @@ static struct cache_entry_t * create_cache_entry(const char *key, void *context) { const mode_t world_readable = S_IRUSR | S_IRGRP | S_IROTH; - serve_files_priv_t *priv = context; - file_cache_entry_t *fce; + struct serve_files_priv *priv = context; + struct file_cache_entry *fce; struct stat st; - const cache_funcs_t *funcs; + const struct cache_funcs *funcs; char full_path[PATH_MAX]; if (UNLIKELY(!realpathat2(priv->root.fd, priv->root.path, @@ -588,7 +574,7 @@ create_cache_entry(const char *key, void *context) static void mmap_free(void *data) { - mmap_cache_data_t *md = data; + struct mmap_cache_data *md = data; munmap(md->uncompressed.contents, md->uncompressed.size); free(md->compressed.contents); @@ -597,7 +583,7 @@ mmap_free(void *data) static void sendfile_free(void *data) { - sendfile_cache_data_t *sd = data; + struct sendfile_cache_data *sd = data; if (sd->compressed.fd >= 0) close(sd->compressed.fd); @@ -608,7 +594,7 @@ sendfile_free(void *data) static void dirlist_free(void *data) { - dir_list_cache_data_t *dd = data; + struct dir_list_cache_data *dd = data; strbuf_free(dd->rendered); } @@ -616,7 +602,7 @@ dirlist_free(void *data) static void redir_free(void *data) { - redir_cache_data_t *rd = data; + struct redir_cache_data *rd = data; free(rd->redir_to); } @@ -624,7 +610,7 @@ redir_free(void *data) static void destroy_cache_entry(struct cache_entry_t *entry, void *context __attribute__((unused))) { - file_cache_entry_t *fce = (file_cache_entry_t *)entry; + struct file_cache_entry *fce = (struct file_cache_entry *)entry; fce->funcs->free(fce + 1); free(fce); @@ -668,7 +654,7 @@ serve_files_init(void *args) struct lwan_serve_files_settings_t *settings = args; char *canonical_root; int root_fd; - serve_files_priv_t *priv; + struct serve_files_priv *priv; int open_mode; if (!settings->root_path) { @@ -755,7 +741,7 @@ serve_files_init_from_hash(const struct hash *hash) static void serve_files_shutdown(void *data) { - serve_files_priv_t *priv = data; + struct serve_files_priv *priv = data; if (!priv) { lwan_status_warning("Nothing to shutdown"); @@ -776,13 +762,9 @@ client_has_fresh_content(lwan_request_t *request, time_t mtime) } static size_t -prepare_headers(lwan_request_t *request, - lwan_http_status_t return_status, - file_cache_entry_t *fce, - size_t size, - const char *compression_type, - char *header_buf, - size_t header_buf_size) +prepare_headers(lwan_request_t *request, lwan_http_status_t return_status, + struct file_cache_entry *fce, size_t size, const char *compression_type, + char *header_buf, size_t header_buf_size) { lwan_key_value_t headers[3] = { [0] = { .key = "Last-Modified", .value = fce->last_modified.string }, @@ -855,8 +837,8 @@ compute_range(lwan_request_t *request, off_t *from, off_t *to, off_t size) static lwan_http_status_t sendfile_serve(lwan_request_t *request, void *data) { - file_cache_entry_t *fce = data; - sendfile_cache_data_t *sd = (sendfile_cache_data_t *)(fce + 1); + struct file_cache_entry *fce = data; + struct sendfile_cache_data *sd = (struct sendfile_cache_data *)(fce + 1); char headers[DEFAULT_BUFFER_SIZE]; size_t header_len; lwan_http_status_t return_status; @@ -913,8 +895,8 @@ sendfile_serve(lwan_request_t *request, void *data) } static lwan_http_status_t -serve_contents_and_size(lwan_request_t *request, file_cache_entry_t *fce, - const char *compression_type, void *contents, size_t size) +serve_contents_and_size(lwan_request_t *request, struct file_cache_entry *fce, + const char *compression_type, void *contents, size_t size) { char headers[DEFAULT_BUFFER_SIZE]; size_t header_len; @@ -946,8 +928,8 @@ serve_contents_and_size(lwan_request_t *request, file_cache_entry_t *fce, static lwan_http_status_t mmap_serve(lwan_request_t *request, void *data) { - file_cache_entry_t *fce = data; - mmap_cache_data_t *md = (mmap_cache_data_t *)(fce + 1); + struct file_cache_entry *fce = data; + struct mmap_cache_data *md = (struct mmap_cache_data *)(fce + 1); void *contents; size_t size; const char *compressed; @@ -968,8 +950,8 @@ mmap_serve(lwan_request_t *request, void *data) static lwan_http_status_t dirlist_serve(lwan_request_t *request, void *data) { - file_cache_entry_t *fce = data; - dir_list_cache_data_t *dd = (dir_list_cache_data_t *)(fce + 1); + struct file_cache_entry *fce = data; + struct dir_list_cache_data *dd = (struct dir_list_cache_data *)(fce + 1); return serve_contents_and_size(request, fce, compression_none, strbuf_get_buffer(dd->rendered), strbuf_get_length(dd->rendered)); @@ -978,8 +960,8 @@ dirlist_serve(lwan_request_t *request, void *data) static lwan_http_status_t redir_serve(lwan_request_t *request, void *data) { - file_cache_entry_t *fce = data; - redir_cache_data_t *rd = (redir_cache_data_t *)(fce + 1); + struct file_cache_entry *fce = data; + struct redir_cache_data *rd = (struct redir_cache_data *)(fce + 1); char header_buf[DEFAULT_BUFFER_SIZE]; size_t header_buf_size; lwan_key_value_t headers[2] = { @@ -1008,7 +990,7 @@ static lwan_http_status_t serve_files_handle_cb(lwan_request_t *request, lwan_response_t *response, void *data) { lwan_http_status_t return_status = HTTP_NOT_FOUND; - serve_files_priv_t *priv = data; + struct serve_files_priv *priv = data; struct cache_entry_t *ce; if (UNLIKELY(!priv)) { @@ -1019,7 +1001,7 @@ serve_files_handle_cb(lwan_request_t *request, lwan_response_t *response, void * ce = cache_coro_get_and_ref_entry(priv->cache, request->conn->coro, request->url.value); if (LIKELY(ce)) { - file_cache_entry_t *fce = (file_cache_entry_t *)ce; + struct file_cache_entry *fce = (struct file_cache_entry *)ce; response->mime_type = fce->mime_type; response->stream.callback = fce->funcs->serve; response->stream.data = ce; From 1ebfabe608666b983053d8c38830cb000a51dd09 Mon Sep 17 00:00:00 2001 From: Matthieu Garrigues Date: Sat, 5 Mar 2016 12:09:47 +0100 Subject: [PATCH 0137/2505] Fix is_hex_digit to properly recognize number digits. --- common/lwan-request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index 0d580c22a..b39bf544c 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -305,7 +305,7 @@ decode_hex_digit(char ch) static ALWAYS_INLINE bool is_hex_digit(char ch) { - return (ch >= '0' && ch <= '0') || + return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'); } From f83d26a4a9a5f1daecdbf7775e5bc83276b43361 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 6 Mar 2016 22:31:39 -0300 Subject: [PATCH 0138/2505] No need to have `const char *name` in struct `lwan_module_t` anymore --- common/lwan-lua.c | 1 - common/lwan-redirect.c | 1 - common/lwan-rewrite.c | 1 - common/lwan-serve-files.c | 4 +--- common/lwan.c | 2 +- common/lwan.h | 1 - 6 files changed, 2 insertions(+), 8 deletions(-) diff --git a/common/lwan-lua.c b/common/lwan-lua.c index 5c9a56b96..d283922f1 100644 --- a/common/lwan-lua.c +++ b/common/lwan-lua.c @@ -455,7 +455,6 @@ static void *lua_init_from_hash(const struct hash *hash) const lwan_module_t *lwan_module_lua(void) { static const lwan_module_t lua_module = { - .name = "lua", .init = lua_init, .init_from_hash = lua_init_from_hash, .shutdown = lua_shutdown, diff --git a/common/lwan-redirect.c b/common/lwan-redirect.c index 6ff542b18..cb28bb418 100644 --- a/common/lwan-redirect.c +++ b/common/lwan-redirect.c @@ -62,7 +62,6 @@ static void *redirect_init_from_hash(const struct hash *hash) const lwan_module_t *lwan_module_redirect(void) { static const lwan_module_t redirect_module = { - .name = "redirect", .init = redirect_init, .init_from_hash = redirect_init_from_hash, .shutdown = free, diff --git a/common/lwan-rewrite.c b/common/lwan-rewrite.c index f3bc72ed8..4ea00e64b 100644 --- a/common/lwan-rewrite.c +++ b/common/lwan-rewrite.c @@ -300,7 +300,6 @@ const lwan_module_t * lwan_module_rewrite(void) { static const lwan_module_t rewrite_module = { - .name = "rewrite", .init = module_init, .init_from_hash = module_init_from_hash, .parse_conf = module_parse_conf, diff --git a/common/lwan-serve-files.c b/common/lwan-serve-files.c index d9adf638c..1c5197708 100644 --- a/common/lwan-serve-files.c +++ b/common/lwan-serve-files.c @@ -62,8 +62,7 @@ struct serve_files_priv { }; struct cache_funcs { - lwan_http_status_t (*serve)(lwan_request_t *request, - void *data); + lwan_http_status_t (*serve)(lwan_request_t *request, void *data); bool (*init)(struct file_cache_entry *ce, struct serve_files_priv *priv, const char *full_path, struct stat *st); void (*free)(void *data); @@ -1018,7 +1017,6 @@ serve_files_handle_cb(lwan_request_t *request, lwan_response_t *response, void * const lwan_module_t *lwan_module_serve_files(void) { static const lwan_module_t serve_files = { - .name = "serve_files", .init = serve_files_init, .init_from_hash = serve_files_init_from_hash, .shutdown = serve_files_shutdown, diff --git a/common/lwan.c b/common/lwan.c index 9b4aa7245..8dea408f1 100644 --- a/common/lwan.c +++ b/common/lwan.c @@ -117,7 +117,7 @@ static const lwan_module_t *lwan_module_find(lwan_t *l, const char *name) } lwan_status_debug("Module \"%s\" registered", name); - hash_add(l->module_registry, module->name, module); + hash_add(l->module_registry, name, module); } return module; diff --git a/common/lwan.h b/common/lwan.h index 093c1fa10..22276471c 100644 --- a/common/lwan.h +++ b/common/lwan.h @@ -241,7 +241,6 @@ struct lwan_request_t_ { }; struct lwan_module_t_ { - const char *name; void *(*init)(void *args); void *(*init_from_hash)(const struct hash *hash); void (*shutdown)(void *data); From be0fdec86df525aa637644a2c2ae88c2f8e1f4e2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 7 Mar 2016 08:41:32 -0300 Subject: [PATCH 0139/2505] Always test for S_ISREG() in serve-files::get_funcs() Check if serving a regular file even if it's not a directory at first since the S_ISDIR() will fall if index_html_file exists. This slightly simplifies the logic in this function. --- common/lwan-serve-files.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/common/lwan-serve-files.c b/common/lwan-serve-files.c index 1c5197708..ced06d74b 100644 --- a/common/lwan-serve-files.c +++ b/common/lwan-serve-files.c @@ -485,11 +485,6 @@ get_funcs(struct serve_files_priv *priv, const char *key, char *full_path, return NULL; } - if (UNLIKELY(S_ISDIR(st->st_mode))) { - /* The index file is a directory. Shouldn't happen. */ - return NULL; - } - /* If it does, we want its full path. */ /* FIXME: Use strlcpy() here instead of calling strlen()? */ @@ -500,11 +495,12 @@ get_funcs(struct serve_files_priv *priv, const char *key, char *full_path, full_path[priv->root.path_len] = '/'; strncpy(full_path + priv->root.path_len + 1, index_html_path, PATH_MAX - priv->root.path_len - 1); - } else if (UNLIKELY(!S_ISREG(st->st_mode))) { - /* Only serve regular files. */ - return NULL; } + /* Only serve regular files. */ + if (UNLIKELY(!S_ISREG(st->st_mode))) + return NULL; + /* It's not a directory: choose the fastest way to serve the file * judging by its size. */ if (st->st_size < 16384) From 7448ae000cedbac6956e12470bec0068e0b1d406 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 11 Mar 2016 21:20:26 -0300 Subject: [PATCH 0140/2505] Also test if index_html is world-readable before serving --- common/lwan-serve-files.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/common/lwan-serve-files.c b/common/lwan-serve-files.c index ced06d74b..4f3e22fbd 100644 --- a/common/lwan-serve-files.c +++ b/common/lwan-serve-files.c @@ -445,6 +445,14 @@ redir_init(struct file_cache_entry *ce, struct serve_files_priv *priv, return true; } +static bool +is_world_readable(mode_t mode) +{ + const mode_t world_readable = S_IRUSR | S_IRGRP | S_IROTH; + + return (mode & world_readable) == world_readable; +} + static const struct cache_funcs * get_funcs(struct serve_files_priv *priv, const char *key, char *full_path, struct stat *st) @@ -501,6 +509,10 @@ get_funcs(struct serve_files_priv *priv, const char *key, char *full_path, if (UNLIKELY(!S_ISREG(st->st_mode))) return NULL; + /* Only serve world readable files. */ + if (UNLIKELY(!is_world_readable(st->st_mode))) + return NULL; + /* It's not a directory: choose the fastest way to serve the file * judging by its size. */ if (st->st_size < 16384) @@ -535,7 +547,6 @@ create_cache_entry_from_funcs(struct serve_files_priv *priv, static struct cache_entry_t * create_cache_entry(const char *key, void *context) { - const mode_t world_readable = S_IRUSR | S_IRGRP | S_IROTH; struct serve_files_priv *priv = context; struct file_cache_entry *fce; struct stat st; @@ -546,7 +557,7 @@ create_cache_entry(const char *key, void *context) key, full_path, &st))) return NULL; - if (UNLIKELY((st.st_mode & world_readable) != world_readable)) + if (UNLIKELY(!is_world_readable(st.st_mode))) return NULL; if (UNLIKELY(strncmp(full_path, priv->root.path, priv->root.path_len))) From f54fd5838b75fe3cad7be7f88ed33d0116b14fe7 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 13 Mar 2016 13:23:03 -0300 Subject: [PATCH 0141/2505] Use an assert rather than LIKELY() to NULL check coro in coro_get_data() --- common/lwan-coro.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/common/lwan-coro.c b/common/lwan-coro.c index 95e19fa3f..bbe0dd2e0 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -220,7 +220,9 @@ coro_new(coro_switcher_t *switcher, coro_function_t function, void *data) ALWAYS_INLINE void * coro_get_data(coro_t *coro) { - return LIKELY(coro) ? coro->data : NULL; + assert(coro); + + return coro->data; } ALWAYS_INLINE int From 3803b0faac076719b8a4b463a5a4650c7f8e9de8 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 13 Mar 2016 13:23:37 -0300 Subject: [PATCH 0142/2505] Only call is_world_readable() once --- common/lwan-serve-files.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/lwan-serve-files.c b/common/lwan-serve-files.c index 4f3e22fbd..dc7cfc02a 100644 --- a/common/lwan-serve-files.c +++ b/common/lwan-serve-files.c @@ -493,6 +493,10 @@ get_funcs(struct serve_files_priv *priv, const char *key, char *full_path, return NULL; } + /* Only serve world-readable indexes. */ + if (UNLIKELY(!is_world_readable(st->st_mode))) + return NULL; + /* If it does, we want its full path. */ /* FIXME: Use strlcpy() here instead of calling strlen()? */ @@ -509,10 +513,6 @@ get_funcs(struct serve_files_priv *priv, const char *key, char *full_path, if (UNLIKELY(!S_ISREG(st->st_mode))) return NULL; - /* Only serve world readable files. */ - if (UNLIKELY(!is_world_readable(st->st_mode))) - return NULL; - /* It's not a directory: choose the fastest way to serve the file * judging by its size. */ if (st->st_size < 16384) From 8151df00a0f07514619605d4881c32fa93ace1d9 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 13 Mar 2016 21:09:10 -0300 Subject: [PATCH 0143/2505] Fix off-by-one indent in lwan-tables.c --- common/lwan-tables.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lwan-tables.c b/common/lwan-tables.c index 120534ff2..1b363998b 100644 --- a/common/lwan-tables.c +++ b/common/lwan-tables.c @@ -98,7 +98,7 @@ lwan_determine_mime_type_for_file_name(const char *file_name) case EXT_JPG: return "image/jpeg"; case EXT_JS: - return "application/javascript"; + return "application/javascript"; case EXT_PNG: return "image/png"; case EXT_TXT: From 04658c6baf5e66954fc43bf740692d7d786d2f5a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 13 Mar 2016 21:09:28 -0300 Subject: [PATCH 0144/2505] Handle hash_add() error in template::symtab_push() --- common/lwan-template.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/common/lwan-template.c b/common/lwan-template.c index edc8c6bc5..753324cda 100644 --- a/common/lwan-template.c +++ b/common/lwan-template.c @@ -207,6 +207,7 @@ static int symtab_push(struct parser *parser, const lwan_var_descriptor_t *descriptor) { struct symtab *tab; + int r; if (!descriptor) return -ENODEV; @@ -217,17 +218,28 @@ symtab_push(struct parser *parser, const lwan_var_descriptor_t *descriptor) tab->hash = hash_str_new(NULL, NULL); if (!tab->hash) { - free(tab); - return -ENOMEM; + r = -ENOMEM; + goto hash_new_err; } tab->next = parser->symtab; parser->symtab = tab; - for (; descriptor->name; descriptor++) - hash_add(parser->symtab->hash, descriptor->name, descriptor); + for (; descriptor->name; descriptor++) { + r = hash_add(parser->symtab->hash, descriptor->name, descriptor); + + if (r < 0) + goto hash_add_err; + } return 0; + +hash_add_err: + hash_free(tab->hash); +hash_new_err: + free(tab); + + return r; } static void From 6dad66a167b5984b7cd81548660246b98c09c9a8 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 13 Mar 2016 21:12:50 -0300 Subject: [PATCH 0145/2505] Assert that ring_buffer population is smaller than buffer size In practice, population_count shouldn't go over 3 lexemes since the parser will most likely consume all of them before the assertion fails. Assert just to document the invariant. --- common/lwan-template.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/lwan-template.c b/common/lwan-template.c index 753324cda..88746a302 100644 --- a/common/lwan-template.c +++ b/common/lwan-template.c @@ -256,6 +256,8 @@ symtab_pop(struct parser *parser) static void emit_lexeme(struct lexer *lexer, struct lexeme *lexeme) { + assert(lexer->ring_buffer.population < N_ELEMENTS(lexer->ring_buffer.lexemes)); + lexer->ring_buffer.lexemes[lexer->ring_buffer.last] = *lexeme; lexer->ring_buffer.last = (lexer->ring_buffer.last + 1) % N_ELEMENTS(lexer->ring_buffer.lexemes); lexer->ring_buffer.population++; From 970226042af555e08198dab0b23715e1926c8cb7 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 19 Mar 2016 11:55:01 -0300 Subject: [PATCH 0146/2505] Use a lookup table for HTTP response codes This should reduce the amount of comparisons performed in the fast path (at most two, rather than the at least two due to the binary search GCC and Clang generates for the switch statement). Idea stolen from libreactor. --- common/lwan-tables.c | 60 ++++++++++++++++++-------------------------- 1 file changed, 25 insertions(+), 35 deletions(-) diff --git a/common/lwan-tables.c b/common/lwan-tables.c index 1b363998b..bd6ca1b4a 100644 --- a/common/lwan-tables.c +++ b/common/lwan-tables.c @@ -121,41 +121,31 @@ lwan_determine_mime_type_for_file_name(const char *file_name) const char * lwan_http_status_as_string_with_code(lwan_http_status_t status) { - switch (status) { - case HTTP_OK: - return "200 OK"; - case HTTP_PARTIAL_CONTENT: - return "206 Partial content"; - case HTTP_MOVED_PERMANENTLY: - return "301 Moved permanently"; - case HTTP_NOT_MODIFIED: - return "304 Not modified"; - case HTTP_BAD_REQUEST: - return "400 Bad request"; - case HTTP_NOT_AUTHORIZED: - return "401 Not authorized"; - case HTTP_FORBIDDEN: - return "403 Forbidden"; - case HTTP_NOT_FOUND: - return "404 Not found"; - case HTTP_NOT_ALLOWED: - return "405 Not allowed"; - case HTTP_TIMEOUT: - return "408 Request timeout"; - case HTTP_TOO_LARGE: - return "413 Request too large"; - case HTTP_RANGE_UNSATISFIABLE: - return "416 Requested range unsatisfiable"; - case HTTP_I_AM_A_TEAPOT: - return "418 I'm a teapot"; - case HTTP_INTERNAL_ERROR: - return "500 Internal server error"; - case HTTP_NOT_IMPLEMENTED: - return "501 Not implemented"; - case HTTP_UNAVAILABLE: - return "503 Service unavailable"; - } - return "999 Invalid"; + const char *ret; + +#define RESP(code,description) [code] = #code " " description + static const char *responses[] = { + RESP(200, "OK"), + RESP(206, "Partial content"), + RESP(301, "Moved permanently"), + RESP(304, "Not modified"), + RESP(400, "Bad request"), + RESP(401, "Not authorized"), + RESP(403, "Forbidden"), + RESP(404, "Not found"), + RESP(405, "Not allowed"), + RESP(408, "Request timeout"), + RESP(413, "Request too large"), + RESP(416, "Requested range unsatisfiable"), + RESP(418, "I'm a teapot"), + RESP(500, "Internal server error"), + RESP(501, "Not implemented"), + RESP(503, "Service unavailable") + }; +#undef RESP + + ret = LIKELY(status < N_ELEMENTS(responses)) ? responses[status] : NULL; + return LIKELY(ret) ? ret : "999 Invalid"; } ALWAYS_INLINE const char * From cccdbb34476568cae69147b2b6848cef54c3bdce Mon Sep 17 00:00:00 2001 From: ReadmeCritic Date: Tue, 29 Mar 2016 07:31:15 -0700 Subject: [PATCH 0147/2505] Update README URLs based on HTTP redirects --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 42c57b2c2..0bf4b59f9 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Features include: - systemd socket activation - IPv6 ready -The [web site](http://lwan.ws) has more details, including a FAQ about the name of the project and security concerns. +The [web site](https://lwan.ws/) has more details, including a FAQ about the name of the project and security concerns. Performance ----------- @@ -73,8 +73,8 @@ Before installing Lwan, ensure all dependencies are installed. All of them are c ### Required dependencies - - [CMake](http://cmake.org), at least version 2.8 - - [Python](http://python.org), at least version 2.6 (3.x is OK) + - [CMake](https://cmake.org/), at least version 2.8 + - [Python](https://www.python.org/), at least version 2.6 (3.x is OK) - [ZLib](http://zlib.net) ### Optional dependencies @@ -84,8 +84,8 @@ The build system will look for these libraries and enable/link if available. - [SQLite 3](http://sqlite.org) - [Lua 5.1](http://www.lua.org) or [LuaJIT 2.0](http://luajit.org) - Client libraries for either [MySQL](https://dev.mysql.com) or [MariaDB](https://mariadb.org) - - [TCMalloc](https://code.google.com/p/gperftools/) - - [jemalloc](http://www.canonware.com/jemalloc) + - [TCMalloc](https://github.com/gperftools/gperftools) + - [jemalloc](http://www.canonware.com/jemalloc/) - [Valgrind](http://valgrind.org) ### Common operating system package names @@ -161,7 +161,7 @@ been seen in the wild. *Help build this list!* Some other distribution channels were made available as well: -* A `Dockerfile` is maintained by [@jaxgeller](https://github.com/jaxgeller), and is [available from the Docker registry](https://registry.hub.docker.com/u/jaxgeller/lwan/). +* A `Dockerfile` is maintained by [@jaxgeller](https://github.com/jaxgeller), and is [available from the Docker registry](https://hub.docker.com/r/jaxgeller/lwan/). * A buildpack for Heroku is maintained by [@bherrera](https://github.com/bherrera), and is [available from its repo](https://github.com/bherrera/heroku-buildpack-lwan). * Lwan is also available as a package in [Biicode](http://docs.biicode.com/c++/examples/lwan.html). * User packages for [Arch Linux](https://aur.archlinux.org/packages/lwan-git/) also [Ubuntu](https://launchpad.net/lwan-unofficial). @@ -184,6 +184,6 @@ Build status | Release | Debug | Static Analysis | Unit Tests | |---------|-------|-----------------|------------| -| ![release](http://buildbot.lwan.ws/buildstatusimage?builder=release&number=-1 "Release") | ![debug](http://buildbot.lwan.ws/buildstatusimage?builder=debug&number=-1 "Debug") | ![clang](http://buildbot.lwan.ws/buildstatusimage?builder=clang-analyze&number=-1 "Clang") ![coverity](https://scan.coverity.com/projects/375/badge.svg)| ![tests](http://buildbot.lwan.ws/buildstatusimage?builder=unit-tests&number=-1 "Tests") -| [Waterfall](http://buildbot.lwan.ws/waterfall?show=release) | [Waterfall](http://buildbot.lwan.ws/waterfall?show=debug) | [Waterfall](http://buildbot.lwan.ws/waterfall?show=clang-analyze) - [Reports](http://buildbot.lwan.ws/sa/) | [Waterfall](http://buildbot.lwan.ws/waterfall?show=unit-tests) | +| ![release](https://buildbot.lwan.ws/buildstatusimage?builder=release&number=-1 "Release") | ![debug](https://buildbot.lwan.ws/buildstatusimage?builder=debug&number=-1 "Debug") | ![clang](https://buildbot.lwan.ws/buildstatusimage?builder=clang-analyze&number=-1 "Clang") ![coverity](https://scan.coverity.com/projects/375/badge.svg)| ![tests](https://buildbot.lwan.ws/buildstatusimage?builder=unit-tests&number=-1 "Tests") +| [Waterfall](https://buildbot.lwan.ws/waterfall?show=release) | [Waterfall](https://buildbot.lwan.ws/waterfall?show=debug) | [Waterfall](https://buildbot.lwan.ws/waterfall?show=clang-analyze) - [Reports](https://buildbot.lwan.ws/sa/) | [Waterfall](https://buildbot.lwan.ws/waterfall?show=unit-tests) | From 192fe8c89cbecc16aabeae41c8dc1a7f2404a262 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 9 Apr 2016 20:07:43 -0300 Subject: [PATCH 0148/2505] Allow obtaining the request body/content-type Closes #136. --- common/lwan-request.c | 7 +++++-- common/lwan.h | 2 ++ testrunner.conf | 3 +++ testrunner/main.c | 32 ++++++++++++++++++++++++++++++++ tools/testsuite.py | 7 +++++++ 5 files changed, 49 insertions(+), 2 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index b39bf544c..2b731061f 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -51,10 +51,10 @@ struct request_parser_helper { lwan_value_t query_string; lwan_value_t fragment; lwan_value_t content_length; - lwan_value_t post_data; + lwan_value_t authorization; + lwan_value_t post_data; lwan_value_t content_type; - lwan_value_t authorization; int urls_rewritten; char connection; @@ -419,6 +419,9 @@ parse_post_data(lwan_request_t *request, struct request_parser_helper *helper) { static const char content_type[] = "application/x-www-form-urlencoded"; + request->header.body = &helper->post_data; + request->header.content_type = &helper->content_type; + if (helper->content_type.len != sizeof(content_type) - 1) return; if (UNLIKELY(strcmp(helper->content_type.value, content_type))) diff --git a/common/lwan.h b/common/lwan.h index 22276471c..0c2827dd6 100644 --- a/common/lwan.h +++ b/common/lwan.h @@ -236,6 +236,8 @@ struct lwan_request_t_ { off_t from; off_t to; } range; + lwan_value_t *body; + lwan_value_t *content_type; } header; lwan_response_t response; }; diff --git a/testrunner.conf b/testrunner.conf index a552198e5..3d9e517cd 100644 --- a/testrunner.conf +++ b/testrunner.conf @@ -38,6 +38,9 @@ listener *:8080 { prefix /favicon.ico { handler = gif_beacon } + prefix /post { + handler = test_post + } redirect /elsewhere { to = http://lwan.ws } diff --git a/testrunner/main.c b/testrunner/main.c index 55224fc97..d62752455 100644 --- a/testrunner/main.c +++ b/testrunner/main.c @@ -107,6 +107,38 @@ test_proxy(lwan_request_t *request, return HTTP_OK; } +lwan_http_status_t +test_post(lwan_request_t *request, lwan_response_t *response, + void *data __attribute__((unused))) +{ + static const char type[] = "application/json"; + static const char request_body[] = "{\"will-it-blend\": true}"; + static const char response_body[] = "{\"did-it-blend\": \"oh-hell-yeah\"}"; + + if (!request->header.content_type) + return HTTP_BAD_REQUEST; + if (!request->header.content_type->value) + return HTTP_BAD_REQUEST; + if (request->header.content_type->len != sizeof(type) - 1) + return HTTP_BAD_REQUEST; + if (memcmp(request->header.content_type->value, type, sizeof(type) - 1) != 0) + return HTTP_BAD_REQUEST; + + if (!request->header.body) + return HTTP_BAD_REQUEST; + if (!request->header.body->value) + return HTTP_BAD_REQUEST; + if (request->header.body->len != sizeof(request_body) - 1) + return HTTP_BAD_REQUEST; + if (memcmp(request->header.body->value, request_body, sizeof(request_body) - 1) != 0) + return HTTP_BAD_REQUEST; + + response->mime_type = type; + strbuf_set_static(response->buffer, response_body, sizeof(response_body) -1); + + return HTTP_OK; +} + lwan_http_status_t hello_world(lwan_request_t *request, lwan_response_t *response, diff --git a/tools/testsuite.py b/tools/testsuite.py index 37f1a0af4..dbca74991 100755 --- a/tools/testsuite.py +++ b/tools/testsuite.py @@ -60,6 +60,13 @@ def assertResponsePlain(self, request, status_code=200): self.assertHttpResponseValid(request, status_code, 'text/plain') +class TestPost(LwanTest): + def test_bat(self): + r = requests.post('http://127.0.0.1:8080/post', json={'will-it-blend': True}) + self.assertHttpResponseValid(r, 200, 'application/json') + self.assertEqual(r.json(), {'did-it-blend': 'oh-hell-yeah'}) + + class TestFileServing(LwanTest): def test_mime_type_is_correct(self): table = ( From f65c8c263d2e10853b412b66254d956454977ff6 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 18 Apr 2016 21:40:14 -0300 Subject: [PATCH 0149/2505] For fregeoip sample, group mime-type and compiled template in a struct --- freegeoip/freegeoip.c | 61 +++++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/freegeoip/freegeoip.c b/freegeoip/freegeoip.c index c3ec3a4af..b7f5d54df 100644 --- a/freegeoip/freegeoip.c +++ b/freegeoip/freegeoip.c @@ -52,6 +52,11 @@ struct ip_info_t { const char *callback; }; +struct template_mime_t { + lwan_tpl_t *tpl; + const char *mime_type; +}; + static const lwan_var_descriptor_t template_descriptor[] = { TPL_VAR_STR(struct ip_info_t, country.code), TPL_VAR_STR(struct ip_info_t, country.name), @@ -179,10 +184,6 @@ struct query_limit_t { static struct cache_t *query_limit; #endif - -static lwan_tpl_t *json_template = NULL; -static lwan_tpl_t *xml_template = NULL; -static lwan_tpl_t *csv_template = NULL; static struct cache_t *cache = NULL; static sqlite3 *db = NULL; @@ -356,8 +357,8 @@ templated_output(lwan_request_t *request, lwan_response_t *response, void *data) { + const struct template_mime_t *tm = data; const char *ip_address; - lwan_tpl_t *tpl = data; struct ip_info_t *info; char ip_address_buf[INET6_ADDRSTRLEN]; @@ -374,23 +375,30 @@ templated_output(lwan_request_t *request, if (UNLIKELY(!info)) return HTTP_NOT_FOUND; - if (tpl == json_template) - response->mime_type = "application/json; charset=UTF-8"; - else if (tpl == xml_template) - response->mime_type = "application/xml; charset=UTF-8"; - else - response->mime_type = "text/plain; charset=UTF-8"; - const char *callback = lwan_request_get_query_param(request, "callback"); struct ip_info_t info_with_callback = *info; info_with_callback.callback = callback; - lwan_tpl_apply_with_buffer(tpl, response->buffer, + lwan_tpl_apply_with_buffer(tm->tpl, response->buffer, &info_with_callback); + response->mime_type = tm->mime_type; return HTTP_OK; } +static struct template_mime_t +compile_template(const char *template, const char *mime_type) +{ + lwan_tpl_t *tpl = lwan_tpl_compile_string(template, template_descriptor); + + if (!tpl) { + lwan_status_critical("Could not compile template for mime-type %s", + mime_type); + } + + return (struct template_mime_t) { .tpl = tpl, .mime_type = mime_type }; +} + int main(void) { @@ -398,15 +406,12 @@ main(void) lwan_init(&l); - json_template = lwan_tpl_compile_string(json_template_str, template_descriptor); - if (!json_template) - lwan_status_critical("Could not compile JSON template"); - xml_template = lwan_tpl_compile_string(xml_template_str, template_descriptor); - if (!xml_template) - lwan_status_critical("Could not compile XML template"); - csv_template = lwan_tpl_compile_string(csv_template_str, template_descriptor); - if (!csv_template) - lwan_status_critical("Could not compile CSV template"); + struct template_mime_t json_tpl = compile_template(json_template_str, + "application/json; charset=UTF-8"); + struct template_mime_t xml_tpl = compile_template(csv_template_str, + "application/csv; charset=UTF-8"); + struct template_mime_t csv_tpl = compile_template(xml_template_str, + "text/plain; charset=UTF-8"); int result = sqlite3_open_v2("./db/ipdb.sqlite", &db, SQLITE_OPEN_READONLY, NULL); @@ -425,9 +430,9 @@ main(void) #endif const lwan_url_map_t default_map[] = { - { .prefix = "/json/", .handler = templated_output, .data = json_template }, - { .prefix = "/xml/", .handler = templated_output, .data = xml_template }, - { .prefix = "/csv/", .handler = templated_output, .data = csv_template }, + { .prefix = "/json/", .handler = templated_output, .data = &json_tpl }, + { .prefix = "/xml/", .handler = templated_output, .data = &xml_tpl }, + { .prefix = "/csv/", .handler = templated_output, .data = &csv_tpl }, { .prefix = "/", SERVE_FILES("./static") }, { .prefix = NULL } }; @@ -436,9 +441,9 @@ main(void) lwan_main_loop(&l); lwan_shutdown(&l); - lwan_tpl_free(csv_template); - lwan_tpl_free(xml_template); - lwan_tpl_free(json_template); + lwan_tpl_free(json_tpl.tpl); + lwan_tpl_free(xml_tpl.tpl); + lwan_tpl_free(csv_tpl.tpl); #if QUERIES_PER_HOUR != 0 cache_destroy(query_limit); #endif From df8047a229bb8901e446c82df751975433bb3634 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 20 Apr 2016 21:40:21 -0300 Subject: [PATCH 0150/2505] Fix segmented sendfile() wrapper on Linux Spotted by @ewewukek. Closes #140. --- common/lwan-io-wrappers.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/common/lwan-io-wrappers.c b/common/lwan-io-wrappers.c index e27a382d5..9cf1897ba 100644 --- a/common/lwan-io-wrappers.c +++ b/common/lwan-io-wrappers.c @@ -178,7 +178,6 @@ void lwan_sendfile(lwan_request_t *request, int in_fd, off_t offset, size_t count, const char *header, size_t header_len) { - size_t total_written = 0; size_t to_be_written = count; lwan_send(request, header, header_len, MSG_MORE); @@ -198,8 +197,7 @@ lwan_sendfile(lwan_request_t *request, int in_fd, off_t offset, size_t count, } } - total_written += (size_t)written; - to_be_written -= (size_t)count; + to_be_written -= (size_t)written; coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); } while (to_be_written > 0); From c5a83589ebde9e5bd9b461b2f8f6087ab0c42bc6 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 23 Apr 2016 10:42:28 -0300 Subject: [PATCH 0151/2505] No need to set prev/next pointers of a connection removed from DQ --- common/lwan-thread.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/common/lwan-thread.c b/common/lwan-thread.c index 1f74b2409..357e2327b 100644 --- a/common/lwan-thread.c +++ b/common/lwan-thread.c @@ -76,10 +76,6 @@ static void death_queue_remove(struct death_queue_t *dq, lwan_connection_t *next = death_queue_idx_to_node(dq, node->next); next->prev = node->prev; prev->next = node->next; - - /* FIXME: This shouldn't be required; there may be a bug somewhere - * that manifests if lots of chunked encoding requests are performed. */ - node->next = node->prev = -1; } static bool death_queue_empty(struct death_queue_t *dq) From 2053965a78d28a51fa466bac8ae24b6159f34234 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 21 Apr 2016 17:02:52 -0300 Subject: [PATCH 0152/2505] pthread_timedjoin_np() is also available under Linux --- common/lwan-thread.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/common/lwan-thread.c b/common/lwan-thread.c index 357e2327b..d67378275 100644 --- a/common/lwan-thread.c +++ b/common/lwan-thread.c @@ -475,12 +475,8 @@ lwan_thread_shutdown(lwan_t *l) lwan_thread_t *t = &l->thread.threads[i]; lwan_status_debug("Waiting for thread %d to finish", i); -#ifdef __FreeBSD__ pthread_timedjoin_np(l->thread.threads[i].self, NULL, &(const struct timespec) { .tv_sec = 1 }); -#else - pthread_join(l->thread.threads[i].self, NULL); -#endif lwan_status_debug("Closing pipe (%d, %d)", t->pipe_fd[0], t->pipe_fd[1]); From c778453f2faaa02904c9cedf73e0fb7abd1b52ea Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 9 Apr 2016 22:55:57 -0300 Subject: [PATCH 0153/2505] Generate and install pkg-config file --- CMakeLists.txt | 20 ++++++++++++++++++++ lwan.pc.cmake | 8 ++++++++ 2 files changed, 28 insertions(+) create mode 100644 lwan.pc.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index a5e87b40e..f00464f4a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -128,3 +128,23 @@ add_subdirectory(lwan) add_subdirectory(testrunner) add_subdirectory(freegeoip) add_subdirectory(techempower) + +set(PKG_CONFIG_REQUIRES ${ADDITIONAL_LIBRARIES}) +set(PKG_CONFIG_LIBDIR "\${prefix}/lib") +set(PKG_CONFIG_INCLUDEDIR "\${prefix}/include/lwan") +set(PKG_CONFIG_LIBS "-L\${libdir} -llwan-common") +set(PKG_CONFIG_CFLAGS "-I\${includedir}") + +execute_process( + COMMAND git log -1 --format=%h + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE PROJECT_VERSION + OUTPUT_STRIP_TRAILING_WHITESPACE +) + +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/lwan.pc.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc" +) + +install(FILES "${CMAKE_BINARY_DIR}/${PROJECT_NAME}.pc" DESTINATION lib/pkgconfig) diff --git a/lwan.pc.cmake b/lwan.pc.cmake new file mode 100644 index 000000000..2f62eff83 --- /dev/null +++ b/lwan.pc.cmake @@ -0,0 +1,8 @@ +Name: ${PROJECT_NAME} +Version: ${PROJECT_VERSION} +Requires: ${PKG_CONFIG_REQUIRES} +prefix=${CMAKE_INSTALL_PREFIX} +includedir=${PKG_CONFIG_INCLUDEDIR} +libdir=${PKG_CONFIG_LIBDIR} +Libs: ${PKG_CONFIG_LIBS} +Cflags: ${PKG_CONFIG_CFLAGS} From a74b05f83e176eb46656a00fbe92ab4cad2e88e9 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 10 Apr 2016 10:45:24 -0300 Subject: [PATCH 0154/2505] Move compatibility functions to missing.[ch] This is part of #132, where @LampmanYao implemented memrchr() and made Lwan build (but still not run) on OS X. I've added accept4() and pipe2(), implemented around their older counterparts with calls to fcntl(), and clock_gettime(), which is currently just a naive shim on top of time(). Moving these implementations to a file like so helps keeping the main code cleaner and with fewer --- CMakeLists.txt | 6 +- common/CMakeLists.txt | 30 +++++++-- common/lwan-cache.c | 6 -- common/lwan-private.h | 50 --------------- common/lwan.c | 2 +- common/lwan.h | 1 + common/missing.c | 143 ++++++++++++++++++++++++++++++++++++++++++ common/missing.h | 106 +++++++++++++++++++++++++++++++ 8 files changed, 280 insertions(+), 64 deletions(-) create mode 100644 common/missing.c create mode 100644 common/missing.h diff --git a/CMakeLists.txt b/CMakeLists.txt index f00464f4a..0700a7857 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,10 +35,10 @@ include(CheckFunctionExists) set(CMAKE_EXTRA_INCLUDE_FILES time.h) check_function_exists(clock_gettime HAS_CLOCK_GETTIME) if (HAS_CLOCK_GETTIME) - message(STATUS "libc has clock_gettime(). Good.") -else () + add_definitions("-DHAS_CLOCK_GETTIME") +elseif (${CMAKE_SYSTEM_NAME} MATCHES "Linux") + add_definitions("-DHAS_CLOCK_GETTIME") list(APPEND ADDITIONAL_LIBRARIES rt) - message(STATUS "No clock_gettime() in libc. Linking with -lrt.") endif () diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index fa97013f6..b3a4c7fa3 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -28,9 +28,9 @@ set(SOURCES realpathat.c strbuf.c sd-daemon.c + missing.c ) - include(CheckIncludeFiles) check_include_files(sys/epoll.h HAVE_EPOLL_H) if (NOT HAVE_EPOLL_H) @@ -50,6 +50,28 @@ if (HAS_MEMPCPY) add_definitions(-DHAS_MEMPCPY) endif () +check_function_exists(memrchr HAS_MEMRCHR) +if (HAS_MEMRCHR) + add_definitions(-DHAS_MEMRCHR) +endif () + +set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) +check_function_exists(pthread_timedjoin_np HAS_TIMEDJOIN) +if (HAS_TIMEDJOIN) + add_definitions(-DHAS_TIMEDJOIN) +endif () + +set(CMAKE_EXTRA_INCLUDE_FILES unistd.h) +check_function_exists(pipe2 HAS_PIPE2) +if (HAS_PIPE2) + add_definitions(-DHAS_PIPE2) +endif () + +set(CMAKE_EXTRA_INCLUDE_FILES sys/types.h sys/socket.h) +check_function_exists(accept4 HAS_ACCEPT4) +if (HAS_ACCEPT4) + add_definitions(-DHAS_ACCEPT4) +endif () include(FindPkgConfig) foreach (pc_file luajit lua lua51 lua5.1 lua-5.1) @@ -93,6 +115,6 @@ set(ADDITIONAL_LIBRARIES ${ADDITIONAL_LIBRARIES} PARENT_SCOPE) INSTALL(TARGETS lwan-common DESTINATION "lib" ) -INSTALL(FILES lwan.h lwan-coro.h lwan-trie.h lwan-status.h strbuf.h hash.h lwan-template.h lwan-serve-files.h lwan-config.h - DESTINATION "include/lwan" -) +INSTALL(FILES lwan.h lwan-coro.h lwan-trie.h lwan-status.h strbuf.h hash.h + lwan-template.h lwan-serve-files.h lwan-config.h missing.h + DESTINATION "include/lwan" ) diff --git a/common/lwan-cache.c b/common/lwan-cache.c index 42685a9da..b3d068df1 100644 --- a/common/lwan-cache.c +++ b/common/lwan-cache.c @@ -60,9 +60,7 @@ struct cache_t { struct { time_t time_to_live; -#ifndef __MACH__ clockid_t clock_id; -#endif } settings; unsigned flags; @@ -92,12 +90,8 @@ static clockid_t detect_fastest_monotonic_clock(void) static ALWAYS_INLINE void clock_monotonic_gettime(struct cache_t *cache, struct timespec *ts) { -#ifdef __MACH__ - ts->tv_sec = time(NULL); -#else if (UNLIKELY(clock_gettime(cache->settings.clock_id, ts) < 0)) lwan_status_perror("clock_gettime"); -#endif } struct cache_t *cache_create(CreateEntryCallback create_entry_cb, diff --git a/common/lwan-private.h b/common/lwan-private.h index a1d1cddcc..00a54ba51 100644 --- a/common/lwan-private.h +++ b/common/lwan-private.h @@ -46,53 +46,3 @@ char *lwan_process_request(lwan_t *l, lwan_request_t *request, lwan_value_t *buffer, char *next_request); void lwan_straitjacket_enforce(config_t *c, config_line_t *l); - -#undef static_assert -#if HAVE_STATIC_ASSERT -#define static_assert(expr, msg) _Static_assert(expr, msg) -#else -#define static_assert(expr, msg) -#endif - -#define strndupa_impl(s, l) ({ \ - char *strndupa_tmp_s = alloca(l + 1); \ - strndupa_tmp_s[l] = '\0'; \ - memcpy(strndupa_tmp_s, s, l); \ -}) - -#ifndef strndupa -#define strndupa(s, l) strndupa_impl((s), strnlen((s), (l))) -#endif - -#ifndef strdupa -#define strdupa(s) strndupa((s), strlen(s)) -#endif - -#ifndef HAS_RAWMEMCHR -#define rawmemchr(s, c) memchr((s), (c), SIZE_MAX) -#endif - -#ifndef HAS_MEMPCPY -static inline void * -mempcpy(void *dest, const void *src, size_t len) -{ - char *p = memcpy(dest, src, len); - return p + len; -} -#endif - -#ifndef MSG_MORE -#define MSG_MORE 0 -#endif - -#ifndef O_NOATIME -#define O_NOATIME 0 -#endif - -#ifndef O_PATH -#define O_PATH 0 -#endif - -#ifndef SOCK_CLOEXEC -#define SOCK_CLOEXEC 0 -#endif diff --git a/common/lwan.c b/common/lwan.c index 8dea408f1..fecc87e9a 100644 --- a/common/lwan.c +++ b/common/lwan.c @@ -479,7 +479,7 @@ setup_open_file_count_limits(void) if (r.rlim_max != r.rlim_cur) { if (r.rlim_max == RLIM_INFINITY) - r.rlim_cur *= 8; + r.rlim_cur = OPEN_MAX; else if (r.rlim_cur < r.rlim_max) r.rlim_cur = r.rlim_max; if (setrlimit(RLIMIT_NOFILE, &r) < 0) diff --git a/common/lwan.h b/common/lwan.h index 0c2827dd6..aa6b2b217 100644 --- a/common/lwan.h +++ b/common/lwan.h @@ -35,6 +35,7 @@ extern "C" { #include "lwan-status.h" #include "lwan-trie.h" #include "strbuf.h" +#include "missing.h" #define DEFAULT_BUFFER_SIZE 4096 #define DEFAULT_HEADERS_SIZE 512 diff --git a/common/missing.c b/common/missing.c new file mode 100644 index 000000000..09535adc4 --- /dev/null +++ b/common/missing.c @@ -0,0 +1,143 @@ +/* + * lwan - simple web server + * Copyright (c) 2012 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include "missing.h" + +#ifndef HAS_MEMPCPY +void * +mempcpy(void *dest, const void *src, size_t len) +{ + char *p = memcpy(dest, src, len); + return p + len; +} +#endif + +#ifndef HAS_MEMRCHR +void * +memrchr(const void *s, int c, size_t n) +{ + const unsigned char *cp; + unsigned char *p = (unsigned char *)s; + unsigned char chr = c; + + if (n != 0) { + cp = p + n; + do { + if (*(--cp) == chr) + return cp; + } while (--n != 0); + } + + return NULL; +} +#endif + +#ifndef HAS_PIPE2 +int +pipe2(int pipefd[2], int flags) +{ + int r; + + r = pipe(pipefd); + if (r < 0) + return r; + + if (fcntl(pipefd[0], F_SETFL, flags) < 0 || fcntl(pipefd[1], F_SETFL, flags) < 0) { + int saved_errno = errno; + + close(pipefd[0]); + close(pipefd[1]); + + errno = saved_errno; + return -1; + } + + return 0; +} +#endif + +#ifndef HAS_ACCEPT4 +int +accept4(int sock, struct sockaddr *addr, socklen_t *addrlen, int flags) +{ + int fd = accept(sock, addr, addrlen); + int newflags = 0; + + if (fd < 0) + return fd; + + if (flags & SOCK_NONBLOCK) { + newflags |= FD_NONBLOCK; + flags &= ~SOCK_NONBLOCK; + } + if (flags & SOCK_CLOEXEC) { + newflags |= FD_CLOEXEC; + flags &= ~SOCK_CLOEXEC; + } + if (flags) { + errno = -EINVAL; + return -1; + } + + if (fcntl(fd, F_SETFL, newflags) < 0) { + int saved_errno = errno; + + close(fd); + + errno = saved_errno; + return -1; + } + + return fd; +} +#endif + +#ifndef HAS_CLOCK_GETTIME +int +clock_gettime(clockid_t clk_id, struct timespec *ts) +{ + switch (clk_id) { + case CLOCK_MONOTONIC: + case CLOCK_MONOTONIC_COARSE: + /* FIXME: time() isn't monotonic */ + ts->tv_sec = time(NULL); + ts->tv_nsec = 0; + return 0; + } + + errno = EINVAL; + return -1; +} +#endif + +#ifndef HAS_TIMEDJOIN +int +pthread_timedjoin_np(pthread_t thread, void **retval, + const struct timespec *abstime __attribute__((unused))) +{ + return pthread_join(thread, retval); +} +#endif diff --git a/common/missing.h b/common/missing.h new file mode 100644 index 000000000..f25341ea8 --- /dev/null +++ b/common/missing.h @@ -0,0 +1,106 @@ +/* + * lwan - simple web server + * Copyright (c) 2012 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include + +#undef static_assert +#if HAVE_STATIC_ASSERT +#define static_assert(expr, msg) _Static_assert(expr, msg) +#else +#define static_assert(expr, msg) +#endif + +#define strndupa_impl(s, l) ({ \ + char *strndupa_tmp_s = alloca(l + 1); \ + strndupa_tmp_s[l] = '\0'; \ + memcpy(strndupa_tmp_s, s, l); \ +}) + +#ifndef strndupa +#define strndupa(s, l) strndupa_impl((s), strnlen((s), (l))) +#endif + +#ifndef strdupa +#define strdupa(s) strndupa((s), strlen(s)) +#endif + +#ifndef HAS_RAWMEMCHR +#define rawmemchr(s, c) memchr((s), (c), SIZE_MAX) +#endif + +#ifndef HAS_MEMPCPY +void *mempcpy(void *dest, const void *src, size_t len); +#endif + +#ifndef HAS_MEMRCHR +void *memrchr(const void *s, int c, size_t n); +#endif + +#ifndef HAS_PIPE2 +int pipe2(int pipefd[2], int flags); +#endif + +#ifndef HAS_ACCEPT4 +int accept4(int sock, struct sockaddr *addr, socklen_t *addrlen, int flags); +#endif + +#ifndef HAS_CLOCK_GETTIME +typedef int clockid_t; +int clock_gettime(clockid_t clk_id, struct timespec *ts); + +# ifndef CLOCK_MONOTONIC_COARSE +# define CLOCK_MONOTONIC_COARSE 0 +# endif + +#ifndef HAS_TIMEDJOIN +#include +int pthread_timedjoin_np(pthread_t thread, void **retval, const struct timespec *abstime); +#endif + +# ifndef CLOCK_MONOTONIC +# define CLOCK_MONOTONIC 1 +# endif +#endif + +#ifndef MSG_MORE +#define MSG_MORE 0 +#endif + +#ifndef O_NOATIME +#define O_NOATIME 0 +#endif + +#ifndef O_PATH +#define O_PATH 0 +#endif + +#ifndef SOCK_CLOEXEC +#define SOCK_CLOEXEC 0 +#endif + +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif + +#ifndef OPEN_MAX +#define OPEN_MAX 65535 +#endif + From 451641e799e194efde476e15af5275bb47e2d57c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 24 Apr 2016 15:47:24 -0300 Subject: [PATCH 0155/2505] Simplify epoll_ctl() implementation for BSDs --- common/epoll-bsd.c | 58 +++++++++++++++++----------------------------- 1 file changed, 21 insertions(+), 37 deletions(-) diff --git a/common/epoll-bsd.c b/common/epoll-bsd.c index 8834c1857..3cc66cdd9 100644 --- a/common/epoll-bsd.c +++ b/common/epoll-bsd.c @@ -34,40 +34,6 @@ epoll_create1(int flags __attribute__((unused))) return kqueue(); } -static struct kevent -add_or_mod(int fd, struct epoll_event *event) -{ - struct kevent ev; - int events = 0; - int flags = EV_ADD; - - if (event->events & EPOLLIN) - events = EVFILT_READ; - else if (event->events & EPOLLOUT) - events = EVFILT_WRITE; - - if (event->events & EPOLLONESHOT) - flags |= EV_ONESHOT; - if (event->events & EPOLLRDHUP) - flags |= EV_EOF; - if (event->events & EPOLLERR) - flags |= EV_ERROR; - - EV_SET(&ev, fd, events, flags, 0, 0, event->data.ptr); - - return ev; -} - -static struct kevent -del(int fd, struct epoll_event *event __attribute__((unused))) -{ - struct kevent ev; - - EV_SET(&ev, fd, 0, EV_DELETE, 0, 0, 0); - - return ev; -} - int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) { @@ -75,12 +41,30 @@ epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) switch (op) { case EPOLL_CTL_ADD: - case EPOLL_CTL_MOD: - ev = add_or_mod(fd, event); + case EPOLL_CTL_MOD: { + int events = 0; + int flags = EV_ADD; + + if (event->events & EPOLLIN) + events = EVFILT_READ; + if (event->events & EPOLLOUT) + events = EVFILT_WRITE; + + if (event->events & EPOLLONESHOT) + flags |= EV_ONESHOT; + if (event->events & EPOLLRDHUP) + flags |= EV_EOF; + if (event->events & EPOLLERR) + flags |= EV_ERROR; + + EV_SET(&ev, fd, events, flags, 0, 0, event->data.ptr); break; + } + case EPOLL_CTL_DEL: - ev = del(fd, event); + EV_SET(&ev, fd, 0, EV_DELETE, 0, 0, 0); break; + default: errno = EINVAL; return -1; From 70fcfde6f931fcfa8821e35c4ac22dc31a6374ca Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 24 Apr 2016 18:17:31 -0300 Subject: [PATCH 0156/2505] Fall back find_next_power_of_two() to not use compiler intrinsic --- common/strbuf.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/common/strbuf.c b/common/strbuf.c index c33e703b5..8f7f98e52 100644 --- a/common/strbuf.c +++ b/common/strbuf.c @@ -43,17 +43,18 @@ find_next_power_of_two(size_t number) } else if (sizeof(size_t) == sizeof(unsigned long long)) { return 1ULL << (size_bits - __builtin_clzll((unsigned long long)number)); } else { - __builtin_unreachable(); + (void)size_bits; } -#else +#endif + number--; number |= number >> 1; number |= number >> 2; number |= number >> 4; number |= number >> 8; number |= number >> 16; + return number + 1; -#endif } static ALWAYS_INLINE size_t From 7c1417f423fe026698bad3872143748ce90e9d37 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 24 Apr 2016 18:31:34 -0300 Subject: [PATCH 0157/2505] Use a table to store character properties (is hex, is space, etc) Should reduce slightly the number of branches at the expense of having a small table. could be used, but it's not clear if isxdigit() or isspace() looks up locale information. --- common/CMakeLists.txt | 4 ++-- common/lwan-private.h | 4 ++++ common/lwan-request.c | 28 +++------------------------ common/lwan-tables.c | 44 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 27 deletions(-) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index b3a4c7fa3..40708af00 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -22,13 +22,13 @@ set(SOURCES lwan-template.c lwan-thread.c lwan-trie.c + missing.c murmur3.c patterns.c reallocarray.c realpathat.c - strbuf.c sd-daemon.c - missing.c + strbuf.c ) include(CheckIncludeFiles) diff --git a/common/lwan-private.h b/common/lwan-private.h index 00a54ba51..b19d6d922 100644 --- a/common/lwan-private.h +++ b/common/lwan-private.h @@ -46,3 +46,7 @@ char *lwan_process_request(lwan_t *l, lwan_request_t *request, lwan_value_t *buffer, char *next_request); void lwan_straitjacket_enforce(config_t *c, config_line_t *l); + +uint8_t lwan_char_isspace(char ch) __attribute__((pure)); +uint8_t lwan_char_isxdigit(char ch) __attribute__((pure)); + diff --git a/common/lwan-request.c b/common/lwan-request.c index 2b731061f..d17f2bc05 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -32,6 +32,7 @@ #include "lwan.h" #include "lwan-config.h" #include "lwan-http-authorize.h" +#include "lwan-private.h" typedef enum { FINALIZER_DONE, @@ -87,9 +88,6 @@ union proxy_protocol_header { }; static char decode_hex_digit(char ch) __attribute__((pure)); -static bool is_hex_digit(char ch) __attribute__((pure)); -static uint32_t has_zero_byte(uint32_t n) __attribute__((pure)); -static uint32_t is_space(char ch) __attribute__((pure)); static char *ignore_leading_whitespace(char *buffer) __attribute__((pure)); static lwan_request_flags_t get_http_method(const char *buffer) __attribute__((pure)); @@ -302,14 +300,6 @@ decode_hex_digit(char ch) return (char)((ch <= '9') ? ch - '0' : (ch & 7) + 9); } -static ALWAYS_INLINE bool -is_hex_digit(char ch) -{ - return (ch >= '0' && ch <= '9') || - (ch >= 'a' && ch <= 'f') || - (ch >= 'A' && ch <= 'F'); -} - static size_t url_decode(char *str) { @@ -318,7 +308,7 @@ url_decode(char *str) char *ch, *decoded; for (decoded = ch = str; *ch; ch++) { - if (*ch == '%' && LIKELY(is_hex_digit(ch[1]) && is_hex_digit(ch[2]))) { + if (*ch == '%' && LIKELY(lwan_char_isxdigit(ch[1]) && lwan_char_isxdigit(ch[2]))) { char tmp = (char)(decode_hex_digit(ch[1]) << 4 | decode_hex_digit(ch[2])); if (UNLIKELY(!tmp)) return 0; @@ -680,22 +670,10 @@ parse_accept_encoding(lwan_request_t *request, struct request_parser_helper *hel } } -static ALWAYS_INLINE uint32_t -has_zero_byte(uint32_t n) -{ - return ((n - 0x01010101U) & ~n) & 0x80808080U; -} - -static ALWAYS_INLINE uint32_t -is_space(char ch) -{ - return has_zero_byte((0x1010101U * (uint32_t)ch) ^ 0x090a0d20U); -} - static ALWAYS_INLINE char * ignore_leading_whitespace(char *buffer) { - while (*buffer && is_space(*buffer)) + while (*buffer && lwan_char_isspace(*buffer)) buffer++; return buffer; } diff --git a/common/lwan-tables.c b/common/lwan-tables.c index bd6ca1b4a..13a582f7c 100644 --- a/common/lwan-tables.c +++ b/common/lwan-tables.c @@ -193,3 +193,47 @@ lwan_http_status_as_descriptive_string(lwan_http_status_t status) } return "Invalid"; } + +enum { + CHAR_PROP_SPACE = 1<<0, + CHAR_PROP_HEX = 1<<1 +}; + +static const uint8_t char_prop_tbl[256] = { + [' '] = CHAR_PROP_SPACE, + ['\t'] = CHAR_PROP_SPACE, + ['\n'] = CHAR_PROP_SPACE, + ['\r'] = CHAR_PROP_SPACE, + ['0'] = CHAR_PROP_HEX, + ['1'] = CHAR_PROP_HEX, + ['2'] = CHAR_PROP_HEX, + ['3'] = CHAR_PROP_HEX, + ['4'] = CHAR_PROP_HEX, + ['5'] = CHAR_PROP_HEX, + ['6'] = CHAR_PROP_HEX, + ['7'] = CHAR_PROP_HEX, + ['8'] = CHAR_PROP_HEX, + ['9'] = CHAR_PROP_HEX, + ['a'] = CHAR_PROP_HEX, + ['b'] = CHAR_PROP_HEX, + ['c'] = CHAR_PROP_HEX, + ['d'] = CHAR_PROP_HEX, + ['e'] = CHAR_PROP_HEX, + ['f'] = CHAR_PROP_HEX, + ['A'] = CHAR_PROP_HEX, + ['B'] = CHAR_PROP_HEX, + ['C'] = CHAR_PROP_HEX, + ['D'] = CHAR_PROP_HEX, + ['E'] = CHAR_PROP_HEX, + ['F'] = CHAR_PROP_HEX, +}; + +ALWAYS_INLINE uint8_t lwan_char_isspace(char ch) +{ + return char_prop_tbl[(int)ch] & CHAR_PROP_SPACE; +} + +ALWAYS_INLINE uint8_t lwan_char_isxdigit(char ch) +{ + return char_prop_tbl[(int)ch] & CHAR_PROP_HEX; +} From 8ad0d9fb9eeed2089b80e6b393cc2ab2e085d31e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 24 Apr 2016 21:33:18 -0300 Subject: [PATCH 0158/2505] Remove build dependency on Python Rewrite generate-mime-types-table in C. Python 2.6+ is still required to execute test suite. If Zopfli is installed, use it to compress the mime types table; otherwise, fall back to zlib. --- README.md | 8 +- common/CMakeLists.txt | 21 ++- tools/generate-mime-types-table.py | 79 ---------- tools/mimegen.c | 236 +++++++++++++++++++++++++++++ 4 files changed, 254 insertions(+), 90 deletions(-) delete mode 100755 tools/generate-mime-types-table.py create mode 100644 tools/mimegen.c diff --git a/README.md b/README.md index 0bf4b59f9..7c1649f6f 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,6 @@ Before installing Lwan, ensure all dependencies are installed. All of them are c ### Required dependencies - [CMake](https://cmake.org/), at least version 2.8 - - [Python](https://www.python.org/), at least version 2.6 (3.x is OK) - [ZLib](http://zlib.net) ### Optional dependencies @@ -87,12 +86,13 @@ The build system will look for these libraries and enable/link if available. - [TCMalloc](https://github.com/gperftools/gperftools) - [jemalloc](http://www.canonware.com/jemalloc/) - [Valgrind](http://valgrind.org) + - To run test suite, [Python](https://www.python.org/) (2.6+) with Requests module is required ### Common operating system package names - - ArchLinux: `pacman -S cmake python zlib sqlite luajit libmariadbclient gperftools valgrind` - - FreeBSD: `pkg install cmake pkgconf python27 sqlite3 lua51` - - Ubuntu 14: `apt-get update && apt-get install git cmake zlib1g-dev pkg-config lua5.1-dev libsqlite3-dev libmysqlclient-dev` + - ArchLinux: `pacman -S cmake zlib sqlite luajit libmariadbclient gperftools valgrind` + - FreeBSD: `pkg install cmake pkgconf sqlite3 lua51` + - Ubuntu 14+: `apt-get update && apt-get install git cmake zlib1g-dev pkg-config lua5.1-dev libsqlite3-dev libmysqlclient-dev` ### Build commands diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 40708af00..a30a31cee 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -94,17 +94,24 @@ else () message(STATUS "Building with Lua support using ${LUA_LIBRARIES}") endif () -find_package(PythonInterp) -if (NOT PYTHONINTERP_FOUND) - message(FATAL_ERROR "Could not find Python interpreter") -endif () - add_library(lwan-common STATIC ${SOURCES}) +add_executable(mimegen ../tools/mimegen.c hash.c murmur3.c reallocarray.c) + +find_library(ZOPFLI_LIBRARY NAMES zopfli PATHS /usr/lib /usr/local/lib) +if (ZOPFLI_LIBRARY) + message(STATUS "Using Zopfli (${ZOPFLI_LIBRARY}) for mimegen") + target_link_libraries(mimegen ${ZOPFLI_LIBRARY}) + add_definitions(-DHAVE_ZOPFLI) +else () + message(STATUS "Using zlib (${ZLIB_LIBRARIES}) for mimegen") + target_link_libraries(mimegen ${ZLIB_LIBRARIES}) +endif () + add_custom_command( OUTPUT ${CMAKE_BINARY_DIR}/mime-types.h - COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tools/generate-mime-types-table.py ${CMAKE_SOURCE_DIR}/tools/mime.types > ${CMAKE_BINARY_DIR}/mime-types.h - DEPENDS ${CMAKE_SOURCE_DIR}/tools/mime.types ${CMAKE_SOURCE_DIR}/tools/generate-mime-types-table.py + COMMAND ${CMAKE_BINARY_DIR}/common/mimegen ${CMAKE_SOURCE_DIR}/tools/mime.types > ${CMAKE_BINARY_DIR}/mime-types.h + DEPENDS ${CMAKE_SOURCE_DIR}/tools/mime.types mimegen ) add_custom_target(generate_mime_types_table DEPENDS ${CMAKE_BINARY_DIR}/mime-types.h) add_dependencies(lwan-common generate_mime_types_table) diff --git a/tools/generate-mime-types-table.py b/tools/generate-mime-types-table.py deleted file mode 100755 index 3b45ec951..000000000 --- a/tools/generate-mime-types-table.py +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/python - -import sys -import struct -try: - import zopfli as zlib - zlib.compress -except: - try: - from zopfli import zopfli as zlib - zlib.compress - except: - import zlib -from operator import itemgetter - -def to_bytes(s): - try: - return bytes(s, 'ascii') - except TypeError: - return bytes(s) - -def pack_string(s): - return struct.pack('%dsb' % len(s), to_bytes(s), 0) - -known_exts = set() -types = [] -for l in open(sys.argv[1]): - l = l.strip() - if l.startswith('#'): - continue - if '#' in l: - l = l.split('#')[0].strip() - try: - last_tab = l.rindex('\t') - except ValueError: - continue - mime_type = l[:last_tab].strip() - for extension in l[last_tab:].split(): - if not extension in known_exts: - known_exts.add(extension) - types.append((mime_type, extension)) - -out = b'' -entries = 0 -for typ, ext in sorted(types, key = itemgetter(1)): - entries += 1 - out += pack_string(ext) - out += pack_string(typ) - -compressed_out = zlib.compress(out, 9) - -print('#pragma once') -print('/* Auto generated from generate-mime-types-table.py, do not modify */') - -print('#define MIME_UNCOMPRESSED_LEN %d' % len(out)) -print('#define MIME_COMPRESSED_LEN %d' % len(compressed_out)) -print('#define MIME_ENTRIES %d' % entries) - -print('struct mime_entry {') -print(' const char *extension;') -print(' const char *type;') -print('};') - -print('static const unsigned char mime_entries_compressed[] = {') -line = [] -for index, b in enumerate(compressed_out): - if index > 0 and index % 13 == 0: - print(' '.join(line)) - line = [] - - if isinstance(b, str): - b = ord(b) - - line.append('0x%x,' % b) - -if line: - print(' '.join(line)) - -print('};') diff --git a/tools/mimegen.c b/tools/mimegen.c new file mode 100644 index 000000000..746c21a31 --- /dev/null +++ b/tools/mimegen.c @@ -0,0 +1,236 @@ +/* + * lwan - simple web server + * Copyright (c) 2016 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_ZOPFLI +#include +#else +#include +#endif + +#include "../common/hash.h" + +struct output { + char *ptr; + size_t used, capacity; +}; + +static int output_append(struct output *output, const char *str) +{ + size_t str_len = strlen(str) + 1; + size_t total_size = output->used + str_len; + + if (total_size >= output->capacity) { + char *tmp; + + output->capacity *= 2; + + tmp = realloc(output->ptr, output->capacity); + if (!tmp) + return -errno; + + output->ptr = tmp; + } + + memcpy(output->ptr + output->used, str, str_len); + output->used = total_size; + + return 0; +} + +static int compare_ext(const void *a, const void *b) +{ + const char **exta = (const char **)a; + const char **extb = (const char **)b; + + return strcmp(*exta, *extb); +} + +static char *strend(char *str, char ch) +{ + str = strchr(str, ch); + if (str) { + *str = '\0'; + return str + 1; + } + return NULL; +} + +static char *compress_output(const struct output *output, size_t *outlen) +{ + char *compressed; + +#ifdef HAVE_ZOPFLI + ZopfliOptions opts; + + ZopfliInitOptions(&opts); + ZopfliCompress(&opts, ZOPFLI_FORMAT_ZLIB, + (const unsigned char *)output->ptr, output->used, + (unsigned char **)&compressed, outlen); +#else + *outlen = compressBound((uLong)output->used); + compressed = malloc(*outlen); + if (!compressed) { + fprintf(stderr, "Could not allocate memory for compressed data\n"); + exit(1); + } + if (compress2((Bytef *)compressed, outlen, (const Bytef *)output->ptr, output->used, 9) != Z_OK) { + fprintf(stderr, "Could not compress data\n"); + exit(1); + } +#endif + fprintf(stderr, "Uncompressed: %zu bytes; compressed: %zu bytes (compressed %2fx)\n", + output->used, *outlen, (double)output->used / (double)*outlen); + + return compressed; +} + +int main(int argc, char *argv[]) +{ + FILE *fp; + char buffer[256]; + struct output output = { .capacity = 1024 }; + size_t compressed_size; + char *compressed, *ext; + struct hash *ext_mime; + struct hash_iter iter; + const char **exts, *key; + size_t i; + + if (argc < 2) { + fprintf(stderr, "Usage: %s /path/to/mime.types\n", argv[0]); + return 1; + } + + fp = fopen(argv[1], "re"); + if (!fp) { + fprintf(stderr, "Could not open %s: %s\n", argv[1], strerror(errno)); + return 1; + } + + ext_mime = hash_str_new(free, free); + if (!ext_mime) { + fprintf(stderr, "Could not allocate hash table\n"); + return 1; + } + + while (fgets(buffer, sizeof(buffer), fp)) { + char *start = buffer, *end, *tab, *mime_type; + + while (*start && isspace(*start)) /* Strip spaces at the start. */ + start++; + if (*start == '#') /* Ignore commented-out lines. */ + continue; + + strend(start, '\n'); /* Strip line endings. */ + strend(start, '#'); /* Strip comments from the middle. */ + tab = strend(start, '\t'); + if (!tab) /* Find mime-type/extension separator. */ + continue; + + mime_type = start; + while (*tab && *tab == '\t') /* Find first extension. */ + tab++; + + for (ext = tab; *ext; ext += end - ext + 1) { + char *k, *v; + int r; + + end = strchr(ext, ' '); /* Stop at next extension. */ + if (!end) + end = strchr(ext, '\0'); /* If not found, find last extension. */ + *end = '\0'; + + k = strdup(ext); + v = strdup(mime_type); + + if (!k || !v) { + fprintf(stderr, "Could not allocate memory\n"); + return 1; + } + + r = hash_add_unique(ext_mime, k, v); + if (r < 0 && r != -EEXIST) { + fprintf(stderr, "Could not add extension to hash table\n"); + return 1; + } + } + } + + /* Get sorted list of extensions. */ + exts = calloc(hash_get_count(ext_mime), sizeof(char *)); + if (!exts) { + fprintf(stderr, "Could not allocate extension array\n"); + return 1; + } + hash_iter_init(ext_mime, &iter); + for (i = 0; hash_iter_next(&iter, (const void **)&key, NULL); i++) + exts[i] = key; + qsort(exts, hash_get_count(ext_mime), sizeof(char *), compare_ext); + + /* Generate uncompressed blob. */ + output.ptr = malloc(output.capacity); + if (!output.ptr) { + fprintf(stderr, "Could not allocate temporary memory\n"); + return 1; + } + for (i = 0; i < hash_get_count(ext_mime); i++) { + if (output_append(&output, exts[i]) < 0) { + fprintf(stderr, "Could not append to output\n"); + return 1; + } + if (output_append(&output, hash_find(ext_mime, exts[i])) < 0) { + fprintf(stderr, "Could not append to output\n"); + return 1; + } + } + + /* Compress blob. */ + compressed = compress_output(&output, &compressed_size); + if (!compressed) { + fprintf(stderr, "Could not compress data\n"); + return 1; + } + + /* Print output. */ + printf("#pragma once\n"); + printf("#define MIME_UNCOMPRESSED_LEN %zu\n", output.used); + printf("#define MIME_COMPRESSED_LEN %lu\n", compressed_size); + printf("#define MIME_ENTRIES %d\n", hash_get_count(ext_mime)); + printf("struct mime_entry { const char *extension; const char *type; };\n"); + printf("static const unsigned char mime_entries_compressed[] = {\n"); + for (i = 1; compressed_size; compressed_size--, i++) + printf("0x%x,%c", compressed[i - 1] & 0xff, " \n"[i % 13 == 0]); + printf("};\n"); + + free(compressed); + free(output.ptr); + free(exts); + hash_free(ext_mime); + fclose(fp); + + return 0; +} From e7f9ecd213d306a5d891758b5a556e3d72a04105 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 1 May 2016 09:04:13 -0300 Subject: [PATCH 0159/2505] Ensure size passed to alligned_alloc() is multiple of alignment --- common/lwan.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/common/lwan.c b/common/lwan.c index fecc87e9a..329f42374 100644 --- a/common/lwan.c +++ b/common/lwan.c @@ -489,24 +489,34 @@ setup_open_file_count_limits(void) return r.rlim_cur; } +#if defined(_ISOC11_SOURCE) +static inline size_t +align_to_size(size_t value, size_t alignment) +{ + return (value + alignment - 1) & ~(alignment - 1); +} + static void allocate_connections(lwan_t *l, size_t max_open_files) { -#if defined(_ISOC11_SOURCE) const size_t sz = max_open_files * sizeof(lwan_connection_t); - l->conns = aligned_alloc(64, sz); + l->conns = aligned_alloc(64, align_to_size(sz, 64)); if (!l->conns) lwan_status_critical_perror("aligned_alloc"); memset(l->conns, 0, sz); +} #else +static void +allocate_connections(lwan_t *l, size_t max_open_files) +{ l->conns = calloc(max_open_files, sizeof(lwan_connection_t)); if (!l->conns) lwan_status_critical_perror("calloc"); -#endif } +#endif static unsigned short int get_number_of_cpus(void) From adf76630d831f810bf1e094fb405c30a8be9f8ea Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 1 May 2016 20:59:39 -0300 Subject: [PATCH 0160/2505] Do not reset CMAKE_C_FLAGS --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0700a7857..f814596aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,7 +24,6 @@ check_c_source_compiles("int main(void) { unsigned long long p; (void)__builtin_ if (HAVE_BUILTIN_MUL_OVERFLOW) add_definitions("-DHAVE_BUILTIN_MUL_OVERFLOW") endif () - check_c_source_compiles("int main(void) { _Static_assert(1, \"\"); }" HAVE_STATIC_ASSERT) if (HAVE_STATIC_ASSERT) add_definitions("-DHAVE_STATIC_ASSERT") @@ -56,6 +55,7 @@ else () endif() endif() + set(C_FLAGS_REL "-mtune=native") include(CheckCCompilerFlag) @@ -115,7 +115,7 @@ else () message(STATUS "Valgrind headers not found -- disabling valgrind support") endif() -set(CMAKE_C_FLAGS "-Wall -Wextra -Wshadow -Wconversion -std=gnu11") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wshadow -Wconversion -std=gnu11") set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${C_FLAGS_REL}") set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} ${C_FLAGS_REL}") set(CMAKE_C_FLAGS_MINSIZEREL "${CMAKE_C_FLAGS_MINSIZEREL} ${C_FLAGS_REL}") From 0733ee1d20badbe5007637820b2de245e862c3d6 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 1 May 2016 21:07:14 -0300 Subject: [PATCH 0161/2505] Wait for all IO threads to start before accepting clients Use a pthread_barrier to synchronize the threads; n+1 threads will wait on the barrier (where n = number of IO threads, 1 = main thread). --- common/lwan-thread.c | 18 ++++++++++++++++-- common/lwan.h | 1 + 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/common/lwan-thread.c b/common/lwan-thread.c index d67378275..a01a1a503 100644 --- a/common/lwan-thread.c +++ b/common/lwan-thread.c @@ -336,6 +336,9 @@ thread_io_loop(void *data) death_queue_init(&dq, lwan); + pthread_barrier_wait(t->barrier); + t->barrier = NULL; + for (;;) { switch (n_fds = epoll_wait(epoll_fd, events, max_events, death_queue_epoll_timeout(&dq))) { @@ -384,12 +387,13 @@ thread_io_loop(void *data) } static void -create_thread(lwan_t *l, lwan_thread_t *thread) +create_thread(lwan_t *l, lwan_thread_t *thread, pthread_barrier_t *barrier) { pthread_attr_t attr; memset(thread, 0, sizeof(*thread)); thread->lwan = l; + thread->barrier = barrier; if ((thread->epoll_fd = epoll_create1(EPOLL_CLOEXEC)) < 0) lwan_status_critical_perror("epoll_create"); @@ -430,6 +434,11 @@ lwan_thread_add_client(lwan_thread_t *t, int fd) void lwan_thread_init(lwan_t *l) { + pthread_barrier_t barrier; + + if (pthread_barrier_init(&barrier, NULL, (unsigned)l->thread.count + 1)) + lwan_status_critical("Could not create barrier"); + lwan_status_debug("Initializing threads"); l->thread.threads = calloc((size_t)l->thread.count, sizeof(lwan_thread_t)); @@ -437,7 +446,12 @@ lwan_thread_init(lwan_t *l) lwan_status_critical("Could not allocate memory for threads"); for (short i = 0; i < l->thread.count; i++) - create_thread(l, &l->thread.threads[i]); + create_thread(l, &l->thread.threads[i], &barrier); + + pthread_barrier_wait(&barrier); + pthread_barrier_destroy(&barrier); + + lwan_status_debug("IO threads created and ready to serve"); } void diff --git a/common/lwan.h b/common/lwan.h index aa6b2b217..b0b4d5ff2 100644 --- a/common/lwan.h +++ b/common/lwan.h @@ -280,6 +280,7 @@ struct lwan_thread_t_ { int epoll_fd; int pipe_fd[2]; pthread_t self; + pthread_barrier_t *barrier; }; struct lwan_config_t_ { From 13df4267e1a0ba658346f863c1fc5ce9e2cda63c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 2 May 2016 08:41:52 -0300 Subject: [PATCH 0162/2505] Fix segfault in mimegen when using Zopfli In FreeBSD with Clang 3.7.1 this worked, but was crashing with a recent GCC in Linux. The Zopfli documentation does not mention that outsize must be initialized to 0. --- tools/mimegen.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/mimegen.c b/tools/mimegen.c index 746c21a31..ae39d205c 100644 --- a/tools/mimegen.c +++ b/tools/mimegen.c @@ -86,6 +86,8 @@ static char *compress_output(const struct output *output, size_t *outlen) #ifdef HAVE_ZOPFLI ZopfliOptions opts; + *outlen = 0; + ZopfliInitOptions(&opts); ZopfliCompress(&opts, ZOPFLI_FORMAT_ZLIB, (const unsigned char *)output->ptr, output->used, From 10f2ec2a9e8c81262af237d1de5fd781bb8cc9d6 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 2 May 2016 08:44:20 -0300 Subject: [PATCH 0163/2505] Fix memory leaks in mimegen Memory leaks in this program aren't much of a problem, as it's a short-living utility. However, if building with ASAN, the build process will be aborted. --- tools/mimegen.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tools/mimegen.c b/tools/mimegen.c index ae39d205c..a7f090171 100644 --- a/tools/mimegen.c +++ b/tools/mimegen.c @@ -175,9 +175,14 @@ int main(int argc, char *argv[]) } r = hash_add_unique(ext_mime, k, v); - if (r < 0 && r != -EEXIST) { - fprintf(stderr, "Could not add extension to hash table\n"); - return 1; + if (r < 0) { + free(k); + free(v); + + if (r != -EEXIST) { + fprintf(stderr, "Could not add extension to hash table\n"); + return 1; + } } } } From fc7f6dfa5fcec6f8406118b91925933606a97f6f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 3 May 2016 19:41:37 -0300 Subject: [PATCH 0164/2505] Swap CSV and XML templates in freegeoip sample application Spotted by Frank Schusdziarra. --- freegeoip/freegeoip.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freegeoip/freegeoip.c b/freegeoip/freegeoip.c index b7f5d54df..b9239ee66 100644 --- a/freegeoip/freegeoip.c +++ b/freegeoip/freegeoip.c @@ -408,9 +408,9 @@ main(void) struct template_mime_t json_tpl = compile_template(json_template_str, "application/json; charset=UTF-8"); - struct template_mime_t xml_tpl = compile_template(csv_template_str, + struct template_mime_t csv_tpl = compile_template(csv_template_str, "application/csv; charset=UTF-8"); - struct template_mime_t csv_tpl = compile_template(xml_template_str, + struct template_mime_t xml_tpl = compile_template(xml_template_str, "text/plain; charset=UTF-8"); int result = sqlite3_open_v2("./db/ipdb.sqlite", &db, From bf2f6a5fa59fd1376c985c8a533d7a197e71150f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 16 May 2016 07:59:01 -0300 Subject: [PATCH 0165/2505] Use O_NONBLOCK and O_CLOEXEC. Closes #142. --- common/missing.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/missing.c b/common/missing.c index 09535adc4..eb5ade3bb 100644 --- a/common/missing.c +++ b/common/missing.c @@ -90,11 +90,11 @@ accept4(int sock, struct sockaddr *addr, socklen_t *addrlen, int flags) return fd; if (flags & SOCK_NONBLOCK) { - newflags |= FD_NONBLOCK; + newflags |= O_NONBLOCK; flags &= ~SOCK_NONBLOCK; } if (flags & SOCK_CLOEXEC) { - newflags |= FD_CLOEXEC; + newflags |= O_CLOEXEC; flags &= ~SOCK_CLOEXEC; } if (flags) { From 09bfc3127604f07d52e8f87ace66b3b6c718b2d4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 21 May 2016 19:14:46 -0300 Subject: [PATCH 0166/2505] key_value_compare_qsort_key() and compare_key_value() are the same function --- common/lwan-request.c | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index d17f2bc05..67ce60667 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -326,7 +326,7 @@ url_decode(char *str) } static int -key_value_compare_qsort_key(const void *a, const void *b) +key_value_compare(const void *a, const void *b) { return strcmp(((lwan_key_value_t *)a)->key, ((lwan_key_value_t *)b)->key); } @@ -378,7 +378,7 @@ parse_key_values(lwan_request_t *request, kvs[values].key = kvs[values].value = NULL; - qsort(kvs, values, sizeof(lwan_key_value_t), key_value_compare_qsort_key); + qsort(kvs, values, sizeof(lwan_key_value_t), key_value_compare); *base = kvs; *len = values; } @@ -1028,22 +1028,14 @@ lwan_process_request(lwan_t *l, lwan_request_t *request, return helper.next_request; } -static int -compare_key_value(const void *a, const void *b) -{ - const lwan_key_value_t *kva = a; - const lwan_key_value_t *kvb = b; - - return strcmp(kva->key, kvb->key); -} - static inline void * value_lookup(const void *base, size_t len, const char *key) { if (LIKELY(base)) { lwan_key_value_t k = { .key = (char *)key }; - lwan_key_value_t *entry = bsearch(&k, base, len, sizeof(k), compare_key_value); + lwan_key_value_t *entry; + entry = bsearch(&k, base, len, sizeof(k), key_value_compare); if (LIKELY(entry)) return entry->value; } From f005d76a0a382a0db235d337eea25c9b99e0b858 Mon Sep 17 00:00:00 2001 From: lampman Date: Tue, 24 May 2016 22:04:32 -0300 Subject: [PATCH 0167/2505] Show proper in error messages while parsing isolated config section Closes #141. --- common/lwan-config.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/lwan-config.c b/common/lwan-config.c index f0aeaf3c3..b46e07d53 100644 --- a/common/lwan-config.c +++ b/common/lwan-config.c @@ -243,6 +243,7 @@ bool config_isolate_section(config_t *current_conf, { long startpos, endpos; bool r = false; + int origin_line = current_conf->line; *isolated = *current_conf; @@ -256,6 +257,7 @@ bool config_isolate_section(config_t *current_conf, return false; if (!find_section_end(current_conf, current_line, 0)) goto resetpos; + current_conf->line = origin_line; endpos = ftell(current_conf->file); if (endpos < 0) goto resetpos; From 1a3c9d7da8d000ea65f600613a3c304e38e04eb0 Mon Sep 17 00:00:00 2001 From: lampman Date: Wed, 25 May 2016 23:09:01 +0800 Subject: [PATCH 0168/2505] Build on Mac OSX - Implemente pthread_barrier_t related APIs on mac osx - Change some options when linking. Because of those options of the linker on mac osx is different from on linux. - Change coro_swapcontext() definition. The original definition has `symbol not found` issue on mac osx when linking. --- CMakeLists.txt | 34 ++++++++++++++++++------ common/CMakeLists.txt | 1 - common/lwan-coro.c | 28 ++++++++++---------- common/lwan-thread.c | 2 ++ common/missing.c | 53 ++++++++++++++++++++++++++++++++++++-- common/missing.h | 17 ++++++++++++ freegeoip/CMakeLists.txt | 2 +- lwan/CMakeLists.txt | 3 +-- techempower/CMakeLists.txt | 2 +- testrunner/CMakeLists.txt | 3 +-- 10 files changed, 114 insertions(+), 31 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f814596aa..28e29be74 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,8 +29,13 @@ if (HAVE_STATIC_ASSERT) add_definitions("-DHAVE_STATIC_ASSERT") endif () - include(CheckFunctionExists) +set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) +check_function_exists(pthread_barrier_init HAS_PTHREADBARRIER) +if (HAS_PTHREADBARRIER) + add_definitions(-DHAS_PTHREADBARRIER) +endif () + set(CMAKE_EXTRA_INCLUDE_FILES time.h) check_function_exists(clock_gettime HAS_CLOCK_GETTIME) if (HAS_CLOCK_GETTIME) @@ -59,14 +64,21 @@ endif() set(C_FLAGS_REL "-mtune=native") include(CheckCCompilerFlag) -check_c_compiler_flag(-Wl,-z,now HAS_IMMEDIATE_BINDING) -if (HAS_IMMEDIATE_BINDING) - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,now") -endif () +if (CMAKE_COMPILER_IS_GNUCC) + check_c_compiler_flag(-Wl,-z,now HAS_IMMEDIATE_BINDING) + if (HAS_IMMEDIATE_BINDING) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,now") + endif () -check_c_compiler_flag(-Wl,-z,relro HAS_READ_ONLY_GOT) -if (HAS_READ_ONLY_GOT) - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,relro") + check_c_compiler_flag(-Wl,-z,relro HAS_READ_ONLY_GOT) + if (HAS_READ_ONLY_GOT) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,relro") + endif () +else () + check_c_compiler_flag(-Wl,-bind_at_load HAS_IMMEDIATE_BINDING) + if (HAS_IMMEDIATE_BINDING) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-bind_at_load") + endif () endif () if (${CMAKE_BUILD_TYPE} MATCHES "Rel") @@ -124,6 +136,12 @@ add_definitions("-D_FILE_OFFSET_BITS=64") add_subdirectory(common) include_directories(common) + +set(LWAN_COMMON_LIBS lwan-common) +if (CMAKE_COMPILER_IS_GNUCC) + set(LWAN_COMMON_LIBS -Wl,-whole-archive ${LWAN_COMMON_LIBS} -Wl,-no-whole-archive) +endif () + add_subdirectory(lwan) add_subdirectory(testrunner) add_subdirectory(freegeoip) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index a30a31cee..26d823566 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -37,7 +37,6 @@ if (NOT HAVE_EPOLL_H) list(APPEND SOURCES epoll-bsd.c) endif () - include(CheckFunctionExists) set(CMAKE_EXTRA_INCLUDE_FILES string.h) check_function_exists(rawmemchr HAS_RAWMEMCHR) diff --git a/common/lwan-coro.c b/common/lwan-coro.c index bbe0dd2e0..1dfa49f78 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -76,13 +76,10 @@ static void coro_entry_point(coro_t *data, coro_function_t func); * -- Leandro */ #if defined(__x86_64__) -void coro_swapcontext(coro_context_t *current, coro_context_t *other) - __attribute__((noinline)); - asm( - ".text\n\t" - ".p2align 4\n\t" - ".globl coro_swapcontext\n\t" - "coro_swapcontext:\n\t" +void __attribute__((noinline)) +coro_swapcontext(coro_context_t *current, coro_context_t *other) +{ + asm volatile( "mov %rbx,0(%rdi)\n\t" "mov %rbp,8(%rdi)\n\t" "mov %r12,16(%rdi)\n\t" @@ -106,14 +103,14 @@ void coro_swapcontext(coro_context_t *current, coro_context_t *other) "mov 64(%rsi),%rcx\n\t" "mov 56(%rsi),%rsi\n\t" "jmp *%rcx\n\t"); + (void)current; + (void)other; +} #elif defined(__i386__) -void coro_swapcontext(coro_context_t *current, coro_context_t *other) - __attribute__((noinline)); - asm( - ".text\n\t" - ".p2align 16\n\t" - ".globl coro_swapcontext\n\t" - "coro_swapcontext:\n\t" +void __attribute__((noinline)) +coro_swapcontext(coro_context_t *current, coro_context_t *other) +{ + asm volatile( "movl 0x4(%esp),%eax\n\t" "movl %ecx,0x1c(%eax)\n\t" /* ECX */ "movl %ebx,0x0(%eax)\n\t" /* EBX */ @@ -134,6 +131,9 @@ void coro_swapcontext(coro_context_t *current, coro_context_t *other) "movl 0xc(%eax),%ebp\n\t" /* EBP */ "movl 0x1c(%eax),%ecx\n\t" /* ECX */ "ret\n\t"); + (void)current; + (void)other; +} #else #define coro_swapcontext(cur,oth) swapcontext(cur, oth) #endif diff --git a/common/lwan-thread.c b/common/lwan-thread.c index a01a1a503..efc4eb439 100644 --- a/common/lwan-thread.c +++ b/common/lwan-thread.c @@ -29,6 +29,8 @@ #ifdef __FreeBSD__ #include #include "epoll-bsd.h" +#elif __APPLE__ +#include "epoll-bsd.h" #elif __linux__ #include #endif diff --git a/common/missing.c b/common/missing.c index eb5ade3bb..d5a664cbd 100644 --- a/common/missing.c +++ b/common/missing.c @@ -26,12 +26,61 @@ #include "missing.h" +#ifndef HAS_PTHREADBARRIER +#define PTHREAD_BARRIER_SERIAL_THREAD -1 +int +pthread_barrier_init(pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restrict attr, unsigned int count) { + if (count == 0) { + return -1; + } + + barrier->count = count; + barrier->in = 0; + + if (pthread_mutex_init(&barrier->mutex, NULL) < 0) + return -1; + + if (pthread_cond_init(&barrier->cond, NULL) < 0) { + pthread_mutex_destroy(&barrier->mutex); + return -1; + } + + return 0; +} + +int +pthread_barrier_destroy(pthread_barrier_t *barrier) +{ + pthread_mutex_destroy(&barrier->mutex); + pthread_cond_destroy(&barrier->cond); + barrier->in = 0; + return 0; +} + +int +pthread_barrier_wait(pthread_barrier_t *barrier) +{ + pthread_mutex_lock(&barrier->mutex); + __sync_fetch_and_add(&barrier->in, 1); + if (barrier->in >= barrier->count) { + barrier->in = 0; + pthread_cond_broadcast(&barrier->cond); + pthread_mutex_unlock(&barrier->mutex); + return PTHREAD_BARRIER_SERIAL_THREAD; + } + + pthread_cond_wait(&barrier->cond, &barrier->mutex); + pthread_mutex_unlock(&barrier->mutex); + return 0; +} +#endif + #ifndef HAS_MEMPCPY void * mempcpy(void *dest, const void *src, size_t len) { - char *p = memcpy(dest, src, len); - return p + len; + char *p = memcpy(dest, src, len); + return p + len; } #endif diff --git a/common/missing.h b/common/missing.h index f25341ea8..09201a7b0 100644 --- a/common/missing.h +++ b/common/missing.h @@ -46,6 +46,20 @@ #define rawmemchr(s, c) memchr((s), (c), SIZE_MAX) #endif +#ifndef HAS_PTHREADBARRIER +typedef int pthread_barrierattr_t; +typedef struct pthread_barrier { + unsigned int count; + unsigned int in; + pthread_mutex_t mutex; + pthread_cond_t cond; +} pthread_barrier_t; + +int pthread_barrier_init(pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restrict attr, unsigned int count); +int pthread_barrier_destroy(pthread_barrier_t *barrier); +int pthread_barrier_wait(pthread_barrier_t *barrier); +#endif + #ifndef HAS_MEMPCPY void *mempcpy(void *dest, const void *src, size_t len); #endif @@ -104,3 +118,6 @@ int pthread_timedjoin_np(pthread_t thread, void **retval, const struct timespec #define OPEN_MAX 65535 #endif +#ifndef SOCK_NONBLOCK +#define SOCK_NONBLOCK 00004000 +#endif diff --git a/freegeoip/CMakeLists.txt b/freegeoip/CMakeLists.txt index 2a211616d..6192ec618 100644 --- a/freegeoip/CMakeLists.txt +++ b/freegeoip/CMakeLists.txt @@ -7,7 +7,7 @@ if (SQLITE_FOUND) ) target_link_libraries(freegeoip - -Wl,-whole-archive lwan-common -Wl,-no-whole-archive + ${LWAN_COMMON_LIBS} ${CMAKE_DL_LIBS} ${ADDITIONAL_LIBRARIES} ${SQLITE_LIBRARIES} diff --git a/lwan/CMakeLists.txt b/lwan/CMakeLists.txt index 0b3c342ff..eb1530acc 100644 --- a/lwan/CMakeLists.txt +++ b/lwan/CMakeLists.txt @@ -1,8 +1,7 @@ add_executable(lwan main.c) target_link_libraries(lwan - -Wl,-whole-archive lwan-common -Wl,-no-whole-archive + ${LWAN_COMMON_LIBS} ${CMAKE_DL_LIBS} ${ADDITIONAL_LIBRARIES} ) - diff --git a/techempower/CMakeLists.txt b/techempower/CMakeLists.txt index ad572b128..2fc81462e 100644 --- a/techempower/CMakeLists.txt +++ b/techempower/CMakeLists.txt @@ -27,7 +27,7 @@ if (MYSQL_INCLUDE_DIR AND SQLITE_FOUND) ) target_link_libraries(techempower - -Wl,-whole-archive lwan-common -Wl,-no-whole-archive + ${LWAN_COMMON_LIBS} ${CMAKE_DL_LIBS} ${ADDITIONAL_LIBRARIES} ${SQLITE_LIBRARIES} diff --git a/testrunner/CMakeLists.txt b/testrunner/CMakeLists.txt index 8267574e1..6bb35085c 100644 --- a/testrunner/CMakeLists.txt +++ b/testrunner/CMakeLists.txt @@ -1,8 +1,7 @@ add_executable(testrunner main.c) target_link_libraries(testrunner - -Wl,-whole-archive lwan-common -Wl,-no-whole-archive + ${LWAN_COMMON_LIBS} ${CMAKE_DL_LIBS} ${ADDITIONAL_LIBRARIES} ) - From 3d6557cf7d9273edffdd358c2fcf2d1bfb1b8267 Mon Sep 17 00:00:00 2001 From: lampman Date: Fri, 27 May 2016 07:51:26 -0300 Subject: [PATCH 0169/2505] Fix core dump on Linux when switching coroutines Patch sent in #145. --- common/lwan-coro.c | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/common/lwan-coro.c b/common/lwan-coro.c index 1dfa49f78..7c8a310ea 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -77,9 +77,17 @@ static void coro_entry_point(coro_t *data, coro_function_t func); */ #if defined(__x86_64__) void __attribute__((noinline)) -coro_swapcontext(coro_context_t *current, coro_context_t *other) -{ - asm volatile( +coro_swapcontext(coro_context_t *current, coro_context_t *other); + asm( + ".text\n\t" + ".p2align 4\n\t" +#if defined(__APPLE__) + ".globl _coro_swapcontext\n\t" + "_coro_swapcontext:\n\t" +#else + ".globl coro_swapcontext\n\t" + "coro_swapcontext:\n\t" +#endif "mov %rbx,0(%rdi)\n\t" "mov %rbp,8(%rdi)\n\t" "mov %r12,16(%rdi)\n\t" @@ -103,14 +111,19 @@ coro_swapcontext(coro_context_t *current, coro_context_t *other) "mov 64(%rsi),%rcx\n\t" "mov 56(%rsi),%rsi\n\t" "jmp *%rcx\n\t"); - (void)current; - (void)other; -} #elif defined(__i386__) void __attribute__((noinline)) -coro_swapcontext(coro_context_t *current, coro_context_t *other) -{ - asm volatile( +coro_swapcontext(coro_context_t *current, coro_context_t *other); + asm( + ".text\n\t" + ".p2align 16\n\t" +#if defined(__APPLE__) + ".globl _coro_swapcontext\n\t" + "_coro_swapcontext:\n\t" +#else + ".globl coro_swapcontext\n\t" + "coro_swapcontext:\n\t" +#endif "movl 0x4(%esp),%eax\n\t" "movl %ecx,0x1c(%eax)\n\t" /* ECX */ "movl %ebx,0x0(%eax)\n\t" /* EBX */ @@ -131,9 +144,6 @@ coro_swapcontext(coro_context_t *current, coro_context_t *other) "movl 0xc(%eax),%ebp\n\t" /* EBP */ "movl 0x1c(%eax),%ecx\n\t" /* ECX */ "ret\n\t"); - (void)current; - (void)other; -} #else #define coro_swapcontext(cur,oth) swapcontext(cur, oth) #endif From ae0f87d980a74ef9fdbdfd57538fbf96e3bffd7c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 27 May 2016 08:40:40 -0300 Subject: [PATCH 0170/2505] Update README.md --- README.md | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 7c1649f6f..2521d02c0 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -lwan Web Server +Lwan Web Server =============== Lwan is a **high-performance** & **scalable** web server for glibc/Linux platforms. -In development for almost 3 years, Lwan was until now a personal research +In development for almost 4 years, Lwan was until now a personal research effort that focused mostly on building a **solid infrastructure** for a lightweight and speedy web server: @@ -43,7 +43,7 @@ Performance ----------- It can achieve good performance, yielding about **320000 requests/second** -on a Core i7 laptop for requests without disk access. +on a Core i7 laptop for requests without disk access, and without pipelining. When disk I/O is required, for files up to 16KiB, it yields about **290000 requests/second**; for larger files, this drops to **185000 @@ -61,10 +61,9 @@ Portability Although it uses [epoll](https://en.wikipedia.org/wiki/Epoll) and the Linux variant of sendfile(), it is fairly portable to other event-based pollers, like [kqueue](https://en.wikipedia.org/wiki/Kqueue). -An old version of lwan has been [successfully ported to -FreeBSD](https://github.com/rakuco/lwan/tree/kqueue-port). Eventually, -some event library such as [libev](http://libev.schmorp.de) or -[libevent](http://libevent.org) will be used to aid in portability. + +Porting for FreeBSD and OS X is a work in progress. It builds and runs, +but crashes after a few requests. Building -------- @@ -90,6 +89,12 @@ The build system will look for these libraries and enable/link if available. ### Common operating system package names +#### Minimum to build + - ArchLinux: `pacman -S cmake zlib` + - FreeBSD: `pkg install cmake pkgconf` + - Ubuntu 14+: `apt-get update && apt-get install git cmake zlib1g-dev pkg-config` + +#### Build all examples - ArchLinux: `pacman -S cmake zlib sqlite luajit libmariadbclient gperftools valgrind` - FreeBSD: `pkg install cmake pkgconf sqlite3 lua51` - Ubuntu 14+: `apt-get update && apt-get install git cmake zlib1g-dev pkg-config lua5.1-dev libsqlite3-dev libmysqlclient-dev` @@ -116,8 +121,7 @@ The default build (i.e. not passing `-DCMAKE_BUILD_TYPE=Release`) will build a version suitable for debugging purposes. This version can be used under Valgrind, is built with Undefined Behavior Sanitizer, and includes debugging messages that are stripped in the release version. Debugging messages are -printed while holding a mutex, and are printed for each and every request; -so do not use this version for benchmarking purposes. +printed while holding a mutex, and are printed for each and every request. Running ------- @@ -126,11 +130,8 @@ Set up the server by editing the provided `lwan.conf`; the format is very simple and should be self-explanatory. Configuration files are loaded from the current directory. If no changes -are made to this file, running lwan will serve static files located in -the `./wwwroot` directory, and also provide a `Hello, World!` handler (which -serves as an example of how to use some of its internal APIs). - -Lwan will listen on port 8080 on all interfaces. +are made to this file, running Lwan will serve static files located in +the `./wwwroot` directory. Lwan will listen on port 8080 on all interfaces. Lwan will detect the number of CPUs, will increase the maximum number of open file descriptors and generally try its best to autodetect reasonable @@ -164,7 +165,7 @@ Some other distribution channels were made available as well: * A `Dockerfile` is maintained by [@jaxgeller](https://github.com/jaxgeller), and is [available from the Docker registry](https://hub.docker.com/r/jaxgeller/lwan/). * A buildpack for Heroku is maintained by [@bherrera](https://github.com/bherrera), and is [available from its repo](https://github.com/bherrera/heroku-buildpack-lwan). * Lwan is also available as a package in [Biicode](http://docs.biicode.com/c++/examples/lwan.html). -* User packages for [Arch Linux](https://aur.archlinux.org/packages/lwan-git/) also [Ubuntu](https://launchpad.net/lwan-unofficial). +* User packages for [Arch Linux](https://aur.archlinux.org/packages/lwan-git/) and [Ubuntu](https://launchpad.net/lwan-unofficial). Lwan has been also used as a benchmark: From 9a469d2056c58b62b83271996b42e763fb65b377 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 27 May 2016 21:17:15 -0300 Subject: [PATCH 0171/2505] Move declaration of pthread_timedjoin_np() in missing.h It should be outside the declaration for clock_gettime() stuff. --- common/missing.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/common/missing.h b/common/missing.h index 09201a7b0..4a3864cf9 100644 --- a/common/missing.h +++ b/common/missing.h @@ -84,16 +84,16 @@ int clock_gettime(clockid_t clk_id, struct timespec *ts); # define CLOCK_MONOTONIC_COARSE 0 # endif -#ifndef HAS_TIMEDJOIN -#include -int pthread_timedjoin_np(pthread_t thread, void **retval, const struct timespec *abstime); -#endif - # ifndef CLOCK_MONOTONIC # define CLOCK_MONOTONIC 1 # endif #endif +#ifndef HAS_TIMEDJOIN +#include +int pthread_timedjoin_np(pthread_t thread, void **retval, const struct timespec *abstime); +#endif + #ifndef MSG_MORE #define MSG_MORE 0 #endif From d739582eb6be632f1d3df65f0ad48de1ad3a496e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 27 May 2016 21:17:53 -0300 Subject: [PATCH 0172/2505] Do not repeat mime-type information in compressed table This will cut down 27 entries in the table. Not huge savings, but, hey, this is trivial. --- tools/mimegen.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tools/mimegen.c b/tools/mimegen.c index a7f090171..428db79e8 100644 --- a/tools/mimegen.c +++ b/tools/mimegen.c @@ -110,6 +110,27 @@ static char *compress_output(const struct output *output, size_t *outlen) return compressed; } +static bool is_builtin_mime_type(const char *mime) +{ + /* These are the mime types supported by Lwan without having to perform + * a bsearch(). application/octet-stream is the fallback. */ + if (!strcmp(mime, "application/octet-stream")) + return true; + if (!strcmp(mime, "application/javascript")) + return true; + if (!strcmp(mime, "image/jpeg")) + return true; + if (!strcmp(mime, "image/png")) + return true; + if (!strcmp(mime, "text/html")) + return true; + if (!strcmp(mime, "text/css")) + return true; + if (!strcmp(mime, "text/plain")) + return true; + return false; +} + int main(int argc, char *argv[]) { FILE *fp; @@ -166,6 +187,9 @@ int main(int argc, char *argv[]) end = strchr(ext, '\0'); /* If not found, find last extension. */ *end = '\0'; + if (is_builtin_mime_type(mime_type)) + continue; + k = strdup(ext); v = strdup(mime_type); From a4403fd435a6cde7bf35645e8a7369d366b7efea Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 28 May 2016 10:30:45 -0300 Subject: [PATCH 0173/2505] Update README.md --- README.md | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2521d02c0..1a5e7f7b8 100644 --- a/README.md +++ b/README.md @@ -101,13 +101,45 @@ The build system will look for these libraries and enable/link if available. ### Build commands +#### Clone the repository + ~$ git clone git://github.com/lpereira/lwan ~$ cd lwan + +#### Create the build directory + ~/lwan$ mkdir build ~/lwan$ cd build + +#### Select build type + +Selecting a *release* version (no debugging symbols, messages, enable some +optimizations, etc): + ~/lwan/build$ cmake .. -DCMAKE_BUILD_TYPE=Release + +If you'd like to enable optimiations but still use a debugger, use this instead: + + ~/lwan/build$ cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo + +To disable optimizations and build a more debugging-friendly version: + + ~/lwan/build$ cmake .. -DCMAKE_BUILD_TYPE=Debug + +#### Build Lwan + ~/lwan/build$ make +This will generate a few binaries: + + - `lwan/lwan`: The main Lwan executable. May be executed with `--help` for guidance. + - `testrunner/testrunner`: Contains code to execute the test suite. + - `freegeoip/freegeoip`: FreeGeoIP sample implementation. Requires SQLite. + - `techempower/techempower`: Code for the Techempower Web Framework benchmark. Requires SQLite and MySQL libraries. + - `common/mimegen`: Builds the extension-MIME type table. Used during build process. + +#### Remarks + It is important to build outside of the tree; in-tree builds *are not supported*. Passing `-DCMAKE_BUILD_TYPE=Release` will enable some compiler @@ -121,7 +153,7 @@ The default build (i.e. not passing `-DCMAKE_BUILD_TYPE=Release`) will build a version suitable for debugging purposes. This version can be used under Valgrind, is built with Undefined Behavior Sanitizer, and includes debugging messages that are stripped in the release version. Debugging messages are -printed while holding a mutex, and are printed for each and every request. +printed for each and every request. Running ------- From 281120f7368e5cb8bb5ff431f3ff092bb200c5cb Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 28 May 2016 19:55:07 -0300 Subject: [PATCH 0174/2505] Set prev/next pointers of a connection removed from DQ Reverts c5a83589. The bug is still there somewhere and is easy to reproduce after a few dozen million pipelined requests. Commit found using git bisect. --- common/lwan-thread.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/common/lwan-thread.c b/common/lwan-thread.c index efc4eb439..aceba9c68 100644 --- a/common/lwan-thread.c +++ b/common/lwan-thread.c @@ -78,6 +78,10 @@ static void death_queue_remove(struct death_queue_t *dq, lwan_connection_t *next = death_queue_idx_to_node(dq, node->next); next->prev = node->prev; prev->next = node->next; + + /* FIXME: This shouldn't be required; there may be a bug somewhere when + * a few million requests are attended to. */ + node->next = node->prev = -1; } static bool death_queue_empty(struct death_queue_t *dq) From 016224b2c40ad74f4cec92d35c3c850520dcb098 Mon Sep 17 00:00:00 2001 From: lampman Date: Mon, 30 May 2016 18:12:59 +0800 Subject: [PATCH 0175/2505] Use EV_CLEAR flag when add event to kqueue It fixes the `"UNKNOWN (null) HTTP/1.1" 408 text/html` problem. --- common/epoll-bsd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/epoll-bsd.c b/common/epoll-bsd.c index 3cc66cdd9..e81406fee 100644 --- a/common/epoll-bsd.c +++ b/common/epoll-bsd.c @@ -43,7 +43,7 @@ epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) case EPOLL_CTL_ADD: case EPOLL_CTL_MOD: { int events = 0; - int flags = EV_ADD; + int flags = EV_ADD | EV_CLEAR; if (event->events & EPOLLIN) events = EVFILT_READ; From 7aa8315c71b688f2f6c633527a9f058f6e721ec5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 30 May 2016 07:41:02 -0300 Subject: [PATCH 0176/2505] Fix build on FreeBSD with Clang --- CMakeLists.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 28e29be74..7e06ab5b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,7 +64,12 @@ endif() set(C_FLAGS_REL "-mtune=native") include(CheckCCompilerFlag) -if (CMAKE_COMPILER_IS_GNUCC) +if (APPLE) + check_c_compiler_flag(-Wl,-bind_at_load HAS_IMMEDIATE_BINDING) + if (HAS_IMMEDIATE_BINDING) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-bind_at_load") + endif () +else () check_c_compiler_flag(-Wl,-z,now HAS_IMMEDIATE_BINDING) if (HAS_IMMEDIATE_BINDING) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,now") @@ -74,11 +79,6 @@ if (CMAKE_COMPILER_IS_GNUCC) if (HAS_READ_ONLY_GOT) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,relro") endif () -else () - check_c_compiler_flag(-Wl,-bind_at_load HAS_IMMEDIATE_BINDING) - if (HAS_IMMEDIATE_BINDING) - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-bind_at_load") - endif () endif () if (${CMAKE_BUILD_TYPE} MATCHES "Rel") From 8742611e4657a92e0778e56b71eaafea3c1c303a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 30 May 2016 07:47:33 -0300 Subject: [PATCH 0177/2505] Only set EV_CLEAR if EPOLLET is set --- common/epoll-bsd.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/common/epoll-bsd.c b/common/epoll-bsd.c index e81406fee..9288f2d74 100644 --- a/common/epoll-bsd.c +++ b/common/epoll-bsd.c @@ -43,7 +43,7 @@ epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) case EPOLL_CTL_ADD: case EPOLL_CTL_MOD: { int events = 0; - int flags = EV_ADD | EV_CLEAR; + int flags = EV_ADD; if (event->events & EPOLLIN) events = EVFILT_READ; @@ -56,6 +56,8 @@ epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) flags |= EV_EOF; if (event->events & EPOLLERR) flags |= EV_ERROR; + if (event->events & EPOLLET) + flags |= EV_CLEAR; EV_SET(&ev, fd, events, flags, 0, 0, event->data.ptr); break; From f4162366a53190edbd9bab93882010a5fd1e530a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 30 May 2016 08:13:53 -0300 Subject: [PATCH 0178/2505] Always set EV_CLEAR, regardless if EPOLLET is set --- common/epoll-bsd.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/epoll-bsd.c b/common/epoll-bsd.c index 9288f2d74..285638470 100644 --- a/common/epoll-bsd.c +++ b/common/epoll-bsd.c @@ -43,7 +43,9 @@ epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) case EPOLL_CTL_ADD: case EPOLL_CTL_MOD: { int events = 0; - int flags = EV_ADD; + /* EV_CLEAR should be set only if EPOLLET is there, but Lwan doesn't + * always set EPOLLET. In the meantime, force EV_CLEAR every time. */ + int flags = EV_ADD | EV_CLEAR; if (event->events & EPOLLIN) events = EVFILT_READ; @@ -56,8 +58,6 @@ epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) flags |= EV_EOF; if (event->events & EPOLLERR) flags |= EV_ERROR; - if (event->events & EPOLLET) - flags |= EV_CLEAR; EV_SET(&ev, fd, events, flags, 0, 0, event->data.ptr); break; From 9d5006d120dadd07eb05baa5fb2054932c58195f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 30 May 2016 20:54:17 -0300 Subject: [PATCH 0179/2505] Fix debug builds with asan/ubsan with older GCC versions Closes #147. Patch by Mario Fischer. --- CMakeLists.txt | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e06ab5b0..1576c6961 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -112,9 +112,43 @@ else () option(ASAN "Build with address sanitizer" OFF) if (UBSAN) - set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fsanitize=undefined") + # Set -Werror to catch "argument unused during compilation" warnings + set(CMAKE_REQUIRED_FLAGS "-Werror -fsanitize=undefined") # Also needs to be a link flag for test to pass + check_c_compiler_flag("-fsanitize=undefined" HAVE_FLAG_SANITIZE_UNDEFINED) + + set(CMAKE_REQUIRED_FLAGS "-Werror -fcatch-undefined-behavior") # Also needs to be a link flag for test to pass + check_c_compiler_flag("-fcatch-undefined-behavior" HAVE_FLAG_CATCH_UNDEFINED_BEHAVIOR) + + unset(CMAKE_REQUIRED_FLAGS) + + if(HAVE_FLAG_SANITIZE_UNDEFINED) + message(STATUS "Build with newer undefined behavior sanitizer") + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fsanitize=undefined") + elseif(HAVE_FLAG_CATCH_UNDEFINED_BEHAVIOR) + message(STATUS "Build with older undefined behavior sanitizer") + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fcatch-undefined-behavior") + else() + message(STATUS "Build without undefined behavior sanitizer") + endif() elseif (ASAN) - set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fsanitize=address") + # Set -Werror to catch "argument unused during compilation" warnings + set(CMAKE_REQUIRED_FLAGS "-Werror -faddress-sanitizer") # Also needs to be a link flag for test to pass + check_c_compiler_flag("-faddress-sanitizer" HAVE_FLAG_ADDRESS_SANITIZER) + + set(CMAKE_REQUIRED_FLAGS "-Werror -fsanitize=address") # Also needs to be a link flag for test to pass + check_c_compiler_flag("-fsanitize=address" HAVE_FLAG_SANITIZE_ADDRESS) + + unset(CMAKE_REQUIRED_FLAGS) + + if(HAVE_FLAG_SANITIZE_ADDRESS) + message(STATUS "Build with newer address sanitizer") + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fsanitize=address") + elseif(HAVE_FLAG_ADDRESS_SANITIZER) + message(STATUS "Build with older address sanitizer") + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -faddress-sanitizer") + else() + message(STATUS "Build without address sanitizer") + endif() endif () endif () From c26d2af544d989df8292024a55bce257c8d434d3 Mon Sep 17 00:00:00 2001 From: Daker Fernandes Pinheiro Date: Thu, 2 Jun 2016 02:06:22 -0300 Subject: [PATCH 0180/2505] Adding UNLIKELY on match reaching max recursion level. --- common/patterns.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/patterns.c b/common/patterns.c index a67456c41..6e74d2e1d 100644 --- a/common/patterns.c +++ b/common/patterns.c @@ -322,7 +322,7 @@ match(struct match_state *ms, const char *s, const char *p) const char *ep, *res; char previous; - if (ms->matchdepth-- == 0) { + if (UNLIKELY(ms->matchdepth-- == 0)) { match_error(ms, "pattern too complex"); return (NULL); } From 7bf3914dc01973d8d9c53a984372db49c45c637c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 2 Jun 2016 08:53:06 -0300 Subject: [PATCH 0181/2505] Include "lwan.h" for the UNLIKELY() macro --- common/patterns.c | 1 + 1 file changed, 1 insertion(+) diff --git a/common/patterns.c b/common/patterns.c index 6e74d2e1d..cbd6dfaea 100644 --- a/common/patterns.c +++ b/common/patterns.c @@ -38,6 +38,7 @@ #include #include "patterns.h" +#include "lwan.h" #define uchar(c) ((unsigned char)(c)) /* macro to 'unsign' a char */ #define CAP_UNFINISHED (-1) From e593b83eea633d53e9074e53bc324a0992304020 Mon Sep 17 00:00:00 2001 From: halosghost Date: Wed, 1 Jun 2016 20:10:47 -0500 Subject: [PATCH 0182/2505] Add a dynamically-linked library --- common/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 26d823566..94ca5d49d 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -93,7 +93,8 @@ else () message(STATUS "Building with Lua support using ${LUA_LIBRARIES}") endif () -add_library(lwan-common STATIC ${SOURCES}) +add_library(lwan-common SHARED ${SOURCES}) +add_library(lwan-common_static STATIC ${SOURCES}) add_executable(mimegen ../tools/mimegen.c hash.c murmur3.c reallocarray.c) From c1c5ea775173c166d3581a34ff03afd85cf5ba82 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 5 Jun 2016 09:50:54 -0300 Subject: [PATCH 0183/2505] Fix shared and static library generation This creates two libraries: lwan-static and lwan-shared. Target properties are set so that, whenever installed, they're installed as liblwan.a and liblwan.so respectively. --- CMakeLists.txt | 2 +- common/CMakeLists.txt | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1576c6961..b8f401336 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -171,7 +171,7 @@ add_subdirectory(common) include_directories(common) -set(LWAN_COMMON_LIBS lwan-common) +set(LWAN_COMMON_LIBS lwan-static) if (CMAKE_COMPILER_IS_GNUCC) set(LWAN_COMMON_LIBS -Wl,-whole-archive ${LWAN_COMMON_LIBS} -Wl,-no-whole-archive) endif () diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 94ca5d49d..5c2733b68 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -93,8 +93,16 @@ else () message(STATUS "Building with Lua support using ${LUA_LIBRARIES}") endif () -add_library(lwan-common SHARED ${SOURCES}) -add_library(lwan-common_static STATIC ${SOURCES}) +add_library(lwan-static STATIC ${SOURCES}) +set_target_properties(lwan-static PROPERTIES + OUTPUT_NAME lwan CLEAN_DIRECT_OUTPUT 1) + +# Can't call add_library() without source files. Create an empty .c file, +# then link with the static library just recently built. +file(WRITE "${CMAKE_BINARY_DIR}/empty.c" "") +add_library(lwan-shared SHARED "${CMAKE_BINARY_DIR}/empty.c") +set_target_properties(lwan-shared PROPERTIES + OUTPUT_NAME lwan CLEAN_DIRECT_OUTPUT 1) add_executable(mimegen ../tools/mimegen.c hash.c murmur3.c reallocarray.c) @@ -114,12 +122,12 @@ add_custom_command( DEPENDS ${CMAKE_SOURCE_DIR}/tools/mime.types mimegen ) add_custom_target(generate_mime_types_table DEPENDS ${CMAKE_BINARY_DIR}/mime-types.h) -add_dependencies(lwan-common generate_mime_types_table) +add_dependencies(lwan-static generate_mime_types_table) include_directories(${CMAKE_BINARY_DIR}) set(ADDITIONAL_LIBRARIES ${ADDITIONAL_LIBRARIES} PARENT_SCOPE) -INSTALL(TARGETS lwan-common +INSTALL(TARGETS lwan-static lwan-shared DESTINATION "lib" ) INSTALL(FILES lwan.h lwan-coro.h lwan-trie.h lwan-status.h strbuf.h hash.h From 8229911654e1ab0ad8b1fdfdb49dc81185dddefb Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 5 Jun 2016 09:51:55 -0300 Subject: [PATCH 0184/2505] Fix .pc file generation Generate a proper .pc file so that pkg-config can be used to find where Lwan was installed and give the correct linking information. --- CMakeLists.txt | 8 ++++++-- common/CMakeLists.txt | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b8f401336..d58f5ebf8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -181,10 +181,14 @@ add_subdirectory(testrunner) add_subdirectory(freegeoip) add_subdirectory(techempower) -set(PKG_CONFIG_REQUIRES ${ADDITIONAL_LIBRARIES}) +set(PKG_CONFIG_REQUIRES "") set(PKG_CONFIG_LIBDIR "\${prefix}/lib") set(PKG_CONFIG_INCLUDEDIR "\${prefix}/include/lwan") -set(PKG_CONFIG_LIBS "-L\${libdir} -llwan-common") + +string (REPLACE ";" " " ADDITIONAL_LIBRARIES_STR "${ADDITIONAL_LIBRARIES}") +set(PKG_CONFIG_LIBS "-L\${libdir} -llwan ${ADDITIONAL_LIBRARIES_STR}") +unset(ADDITIONAL_LIBRARIES_STR) + set(PKG_CONFIG_CFLAGS "-I\${includedir}") execute_process( diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 5c2733b68..06a468d1f 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -80,7 +80,7 @@ foreach (pc_file luajit lua lua51 lua5.1 lua-5.1) pkg_check_modules(LUA ${pc_file}>=5.1.0 ${pc_file}<=5.1.999) endif () if (LUA_FOUND) - list(APPEND ADDITIONAL_LIBRARIES ${LUA_LIBRARIES}) + list(APPEND ADDITIONAL_LIBRARIES "-l${LUA_LIBRARIES}") list(APPEND SOURCES lwan-lua.c) include_directories(${LUA_INCLUDE_DIRS}) add_definitions(-DHAVE_LUA) From 1055244e00760631b20db3923e8b9f1d209b880f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 5 Jun 2016 10:17:40 -0300 Subject: [PATCH 0185/2505] Build libs with -fPIC on x86-64 and export symbols from static lib --- CMakeLists.txt | 7 +++---- common/CMakeLists.txt | 4 ++++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d58f5ebf8..4a54cce91 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -167,15 +167,14 @@ set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} ${C_FLAGS_REL} set(CMAKE_C_FLAGS_MINSIZEREL "${CMAKE_C_FLAGS_MINSIZEREL} ${C_FLAGS_REL}") add_definitions("-D_FILE_OFFSET_BITS=64") -add_subdirectory(common) - -include_directories(common) - set(LWAN_COMMON_LIBS lwan-static) if (CMAKE_COMPILER_IS_GNUCC) set(LWAN_COMMON_LIBS -Wl,-whole-archive ${LWAN_COMMON_LIBS} -Wl,-no-whole-archive) endif () +add_subdirectory(common) +include_directories(common) + add_subdirectory(lwan) add_subdirectory(testrunner) add_subdirectory(freegeoip) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 06a468d1f..71d5738dd 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -96,11 +96,15 @@ endif () add_library(lwan-static STATIC ${SOURCES}) set_target_properties(lwan-static PROPERTIES OUTPUT_NAME lwan CLEAN_DIRECT_OUTPUT 1) +if (CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64") + set_target_properties(lwan-static PROPERTIES COMPILE_FLAGS "-fPIC") +endif() # Can't call add_library() without source files. Create an empty .c file, # then link with the static library just recently built. file(WRITE "${CMAKE_BINARY_DIR}/empty.c" "") add_library(lwan-shared SHARED "${CMAKE_BINARY_DIR}/empty.c") +target_link_libraries(lwan-shared ${LWAN_COMMON_LIBS}) set_target_properties(lwan-shared PROPERTIES OUTPUT_NAME lwan CLEAN_DIRECT_OUTPUT 1) From b1515c2d23ed25ff7b9950196824949b20e1f45e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 5 Jun 2016 12:14:18 -0300 Subject: [PATCH 0186/2505] Coalesce kqueue events into a single epoll event This should bring the BSD epoll implementation on top of kqueue closer to what Lwan expects an epoll implementation to be. This fixes the assertion failure that happens in FreeBSD, but Lwan is still crashing. --- common/epoll-bsd.c | 52 ++++++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/common/epoll-bsd.c b/common/epoll-bsd.c index 285638470..1866a2d01 100644 --- a/common/epoll-bsd.c +++ b/common/epoll-bsd.c @@ -87,25 +87,19 @@ to_timespec(struct timespec *t, int ms) return t; } -static int -kevent_compare(const void *a, const void *b) -{ - const struct kevent *ka = a; - const struct kevent *kb = b; - - if (ka->flags & (EV_ERROR | EV_EOF) || kb->flags & (EV_ERROR | EV_EOF)) - return 1; - return (ka > kb) - (ka < kb); -} - int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) { struct epoll_event *ev = events; struct kevent evs[maxevents]; struct timespec tmspec; + struct hash *coalesce; int i, r; + coalesce = hash_int_new(NULL, NULL); + if (!coalesce) + return -errno; + r = kevent(epfd, NULL, 0, evs, maxevents, to_timespec(&tmspec, timeout)); if (UNLIKELY(r < 0)) { if (errno != EINTR) @@ -113,25 +107,39 @@ epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) goto out; } - qsort(evs, (size_t)r, sizeof(struct kevent), kevent_compare); - - for (i = 0; i < r; i++, ev++) { + for (i = 0; i < r; i++) { struct kevent *kev = &evs[i]; - - ev->events = 0; - ev->data.ptr = kev->udata; + uint32_t mask = (uint32_t)(uintptr_t)hash_find(coalesce, + (void*)(intptr_t)evs[i].ident); if (kev->flags & EV_ERROR) - ev->events |= EPOLLERR; + mask |= EPOLLERR; if (kev->flags & EV_EOF) - ev->events |= EPOLLRDHUP; + mask |= EPOLLRDHUP; if (kev->filter == EVFILT_READ) - ev->events |= EPOLLIN; + mask |= EPOLLIN; else if (kev->filter == EVFILT_WRITE) - ev->events |= EPOLLOUT; + mask |= EPOLLOUT; + + hash_add(coalesce, (void*)(intptr_t)evs[i].ident, (void *)(uintptr_t)mask); + } + + for (i = 0; i < r; i++) { + void *maskptr = hash_find(coalesce, (void*)(intptr_t)evs[i].ident); + + if (maskptr) { + struct kevent *kev = &evs[i]; + + hash_del(coalesce, (void*)(intptr_t)evs[i].ident); + + ev->data.ptr = kev->udata; + ev->events = (uint32_t)(uintptr_t)maskptr; + ev++; + } } out: - return r; + hash_free(coalesce); + return (int)(intptr_t)(ev - events); } From d24e0d3e7cbab2352b74b8243210d87693002e59 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 5 Jun 2016 14:56:59 -0300 Subject: [PATCH 0187/2505] Add URL for new AUR package --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1a5e7f7b8..c7e467a8f 100644 --- a/README.md +++ b/README.md @@ -197,7 +197,7 @@ Some other distribution channels were made available as well: * A `Dockerfile` is maintained by [@jaxgeller](https://github.com/jaxgeller), and is [available from the Docker registry](https://hub.docker.com/r/jaxgeller/lwan/). * A buildpack for Heroku is maintained by [@bherrera](https://github.com/bherrera), and is [available from its repo](https://github.com/bherrera/heroku-buildpack-lwan). * Lwan is also available as a package in [Biicode](http://docs.biicode.com/c++/examples/lwan.html). -* User packages for [Arch Linux](https://aur.archlinux.org/packages/lwan-git/) and [Ubuntu](https://launchpad.net/lwan-unofficial). +* User packages for [Arch Linux](https://aur.archlinux.org/packages/liblwan-git/) (newer version), [Arch Linux](https://aur.archlinux.org/packages/lwan-git/) (older version) and [Ubuntu](https://launchpad.net/lwan-unofficial). Lwan has been also used as a benchmark: From 4bfe436c8a96c2699dfff897c0877ce31ab34f9b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 5 Jun 2016 15:01:27 -0300 Subject: [PATCH 0188/2505] Add rule to install Lwan binary on "make install" --- lwan/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lwan/CMakeLists.txt b/lwan/CMakeLists.txt index eb1530acc..947174ef8 100644 --- a/lwan/CMakeLists.txt +++ b/lwan/CMakeLists.txt @@ -5,3 +5,5 @@ target_link_libraries(lwan ${CMAKE_DL_LIBS} ${ADDITIONAL_LIBRARIES} ) + +INSTALL(TARGETS lwan DESTINATION "bin") From 27276774294207b8fd2b7f2fcc42f0161b59ffaa Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 5 Jun 2016 19:33:53 -0300 Subject: [PATCH 0189/2505] Do not mask kevent() errors --- common/epoll-bsd.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/common/epoll-bsd.c b/common/epoll-bsd.c index 1866a2d01..cbecec02f 100644 --- a/common/epoll-bsd.c +++ b/common/epoll-bsd.c @@ -96,16 +96,13 @@ epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) struct hash *coalesce; int i, r; + r = kevent(epfd, NULL, 0, evs, maxevents, to_timespec(&tmspec, timeout)); + if (UNLIKELY(r < 0)) + return -1; + coalesce = hash_int_new(NULL, NULL); if (!coalesce) - return -errno; - - r = kevent(epfd, NULL, 0, evs, maxevents, to_timespec(&tmspec, timeout)); - if (UNLIKELY(r < 0)) { - if (errno != EINTR) - lwan_status_perror("kevent"); - goto out; - } + return -1; for (i = 0; i < r; i++) { struct kevent *kev = &evs[i]; @@ -139,7 +136,6 @@ epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) } } -out: hash_free(coalesce); return (int)(intptr_t)(ev - events); } From 16fd5eeab6a04051c907591ac2de4280387beab4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 5 Jun 2016 22:42:48 -0300 Subject: [PATCH 0190/2505] Check if mime_type is built-in only once per MIME type --- tools/mimegen.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/mimegen.c b/tools/mimegen.c index 428db79e8..c66d05c16 100644 --- a/tools/mimegen.c +++ b/tools/mimegen.c @@ -175,6 +175,9 @@ int main(int argc, char *argv[]) continue; mime_type = start; + if (is_builtin_mime_type(mime_type)) + continue; + while (*tab && *tab == '\t') /* Find first extension. */ tab++; @@ -187,9 +190,6 @@ int main(int argc, char *argv[]) end = strchr(ext, '\0'); /* If not found, find last extension. */ *end = '\0'; - if (is_builtin_mime_type(mime_type)) - continue; - k = strdup(ext); v = strdup(mime_type); From 0c69ede1bbe942c8fddbb273007f1549e053fcef Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 6 Jun 2016 08:22:19 -0300 Subject: [PATCH 0191/2505] Add Description field to lwan.pc It's common for pkg-config to dislike .pc files without a Description field. --- CMakeLists.txt | 4 ++++ lwan.pc.cmake | 1 + 2 files changed, 5 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4a54cce91..0ddce6b1f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,10 @@ project(lwan C) cmake_minimum_required(VERSION 2.8) +set(PROJECT_DESCRIPTION "Scalable, high performance, experimental web server") + +message(STATUS "Running CMake for ${PROJECT_NAME} (${PROJECT_DESCRIPTION})") + if (NOT CMAKE_BUILD_TYPE) message(STATUS "No build type selected, defaulting to Debug") set(CMAKE_BUILD_TYPE "Debug") diff --git a/lwan.pc.cmake b/lwan.pc.cmake index 2f62eff83..99bec5744 100644 --- a/lwan.pc.cmake +++ b/lwan.pc.cmake @@ -1,4 +1,5 @@ Name: ${PROJECT_NAME} +Description: ${PROJECT_DESCRIPTION} Version: ${PROJECT_VERSION} Requires: ${PKG_CONFIG_REQUIRES} prefix=${CMAKE_INSTALL_PREFIX} From 4875184e1757c2faed2af24492bca9ee186df77a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 9 Jun 2016 22:19:59 -0300 Subject: [PATCH 0192/2505] Increase coroutine stack size for non-glibc targets This makes Lwan not crash under FreeBSD. --- README.md | 6 ++++-- common/lwan-coro.c | 6 +++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c7e467a8f..a4f2c8d80 100644 --- a/README.md +++ b/README.md @@ -62,8 +62,10 @@ Although it uses [epoll](https://en.wikipedia.org/wiki/Epoll) and the Linux variant of sendfile(), it is fairly portable to other event-based pollers, like [kqueue](https://en.wikipedia.org/wiki/Kqueue). -Porting for FreeBSD and OS X is a work in progress. It builds and runs, -but crashes after a few requests. +Porting for FreeBSD and OS X is a work in progress. It works on +FreeBSD, but the module registry can't find any module and/or handlers +(so it's essentially only serves 404 pages at the moment). Help to fix +this is appreciated. Building -------- diff --git a/common/lwan-coro.c b/common/lwan-coro.c index 7c8a310ea..5138f4cbd 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -32,7 +32,11 @@ #include #endif -#define CORO_STACK_MIN ((3 * (PTHREAD_STACK_MIN)) / 2) +#ifdef __GLIBC__ +#define CORO_STACK_MIN ((3 * (PTHREAD_STACK_MIN)) / 2) +#else +#define CORO_STACK_MIN (5 * (PTHREAD_STACK_MIN)) +#endif static_assert(DEFAULT_BUFFER_SIZE < (CORO_STACK_MIN + PTHREAD_STACK_MIN), "Request buffer fits inside coroutine stack"); From 1d3b8a2a686469c74c9a52b3a4d30ca6a18a2344 Mon Sep 17 00:00:00 2001 From: halosghost Date: Sat, 11 Jun 2016 14:01:04 -0500 Subject: [PATCH 0193/2505] =?UTF-8?q?Remove=20liblwan-git=20link=20(will?= =?UTF-8?q?=20be=20merged=20into=20lwan-git=20Soon=E2=84=A2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a4f2c8d80..2263ef30f 100644 --- a/README.md +++ b/README.md @@ -199,7 +199,7 @@ Some other distribution channels were made available as well: * A `Dockerfile` is maintained by [@jaxgeller](https://github.com/jaxgeller), and is [available from the Docker registry](https://hub.docker.com/r/jaxgeller/lwan/). * A buildpack for Heroku is maintained by [@bherrera](https://github.com/bherrera), and is [available from its repo](https://github.com/bherrera/heroku-buildpack-lwan). * Lwan is also available as a package in [Biicode](http://docs.biicode.com/c++/examples/lwan.html). -* User packages for [Arch Linux](https://aur.archlinux.org/packages/liblwan-git/) (newer version), [Arch Linux](https://aur.archlinux.org/packages/lwan-git/) (older version) and [Ubuntu](https://launchpad.net/lwan-unofficial). +* User packages for [Arch Linux](https://aur.archlinux.org/packages/lwan-git/) and [Ubuntu](https://launchpad.net/lwan-unofficial). Lwan has been also used as a benchmark: From f1cd15c1a1bd15791c9f321fdc8c3ddb8d0e3799 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 19 Jun 2016 23:09:07 +0200 Subject: [PATCH 0194/2505] Fix techempower validation when "queries" paramter is omitted --- techempower/techempower.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/techempower/techempower.c b/techempower/techempower.c index ce17021db..61a8ebfb1 100644 --- a/techempower/techempower.c +++ b/techempower/techempower.c @@ -154,15 +154,17 @@ queries(lwan_request_t *request, void *data __attribute__((unused))) { const char *queries_str = lwan_request_get_query_param(request, "queries"); - - if (UNLIKELY(!queries_str)) - return HTTP_BAD_REQUEST; - - long queries = parse_long(queries_str, -1); - if (UNLIKELY(queries <= 0)) + long queries; + + if (LIKELY(queries_str)) { + queries = parse_long(queries_str, -1); + if (UNLIKELY(queries <= 0)) + queries = 1; + else if (UNLIKELY(queries > 500)) + queries = 500; + } else { queries = 1; - else if (UNLIKELY(queries > 500)) - queries = 500; + } struct db_stmt *stmt = db_prepare_stmt(database, random_number_query, sizeof(random_number_query) - 1); From 505afff0f67ff42dd05213edd528ae58a084903a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 23 Jun 2016 15:51:29 +0200 Subject: [PATCH 0195/2505] Work around cross-compilation issues with mimegen This moves the mimegen utility to be an external project, and so it's built and executed on the host. I'm not sure if this is the best approach, specially since it's essentially calls CMake and make again, and on some platforms that might break. But since this is working for me, both on Linux and FreeBSD, I'm pushing this anyway. Also, check if `-mtune=native` is supported by the compiler. GCC documentation says it should do nothing if unsupported by a particular compiler but that doesn't seem to be the case. Doesn't hurt to perform yet another configure-time test. Based on patch by @fischermario. Closes #154. --- CMakeLists.txt | 16 ++++++++++++---- common/CMakeLists.txt | 20 ++++++++++---------- common/hash.c | 1 + common/reallocarray.c | 4 ++-- tools/CMakeLists.txt | 23 +++++++++++++++++++++++ 5 files changed, 48 insertions(+), 16 deletions(-) create mode 100644 tools/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 0ddce6b1f..0f7977b20 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,19 +3,26 @@ cmake_minimum_required(VERSION 2.8) set(PROJECT_DESCRIPTION "Scalable, high performance, experimental web server") + +include(CheckCCompilerFlag) +include(CheckCSourceCompiles) +include(CheckFunctionExists) + + message(STATUS "Running CMake for ${PROJECT_NAME} (${PROJECT_DESCRIPTION})") + if (NOT CMAKE_BUILD_TYPE) message(STATUS "No build type selected, defaulting to Debug") set(CMAKE_BUILD_TYPE "Debug") endif () + find_package(ZLIB REQUIRED) find_package(Threads REQUIRED) set(ADDITIONAL_LIBRARIES ${ZLIB_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) -include(CheckCSourceCompiles) check_c_source_compiles("int main(void) { __builtin_cpu_init(); }" HAVE_BUILTIN_CPU_INIT) if (HAVE_BUILTIN_CPU_INIT) add_definitions("-DHAVE_BUILTIN_CPU_INIT") @@ -33,7 +40,6 @@ if (HAVE_STATIC_ASSERT) add_definitions("-DHAVE_STATIC_ASSERT") endif () -include(CheckFunctionExists) set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) check_function_exists(pthread_barrier_init HAS_PTHREADBARRIER) if (HAS_PTHREADBARRIER) @@ -65,9 +71,11 @@ else () endif() -set(C_FLAGS_REL "-mtune=native") +check_c_compiler_flag(-mtune=native HAS_MTUNE_NATIVE) +if (HAS_MTUNE_NATIVE) + set(C_FLAGS_REL "-mtune=native") +endif() -include(CheckCCompilerFlag) if (APPLE) check_c_compiler_flag(-Wl,-bind_at_load HAS_IMMEDIATE_BINDING) if (HAS_IMMEDIATE_BINDING) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 71d5738dd..149c01e65 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -108,17 +108,17 @@ target_link_libraries(lwan-shared ${LWAN_COMMON_LIBS}) set_target_properties(lwan-shared PROPERTIES OUTPUT_NAME lwan CLEAN_DIRECT_OUTPUT 1) -add_executable(mimegen ../tools/mimegen.c hash.c murmur3.c reallocarray.c) +include(ExternalProject) +ExternalProject_Add( + mimegen + SOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR}/tools + PREFIX ${CMAKE_CURRENT_BINARY_DIR}/tools + CONFIGURE_COMMAND cmake ${CMAKE_SOURCE_DIR}/tools + BUILD_COMMAND make + INSTALL_COMMAND ln -sf ${CMAKE_CURRENT_BINARY_DIR}/tools/src/mimegen-build/mimegen ${CMAKE_CURRENT_BINARY_DIR}/mimegen +) -find_library(ZOPFLI_LIBRARY NAMES zopfli PATHS /usr/lib /usr/local/lib) -if (ZOPFLI_LIBRARY) - message(STATUS "Using Zopfli (${ZOPFLI_LIBRARY}) for mimegen") - target_link_libraries(mimegen ${ZOPFLI_LIBRARY}) - add_definitions(-DHAVE_ZOPFLI) -else () - message(STATUS "Using zlib (${ZLIB_LIBRARIES}) for mimegen") - target_link_libraries(mimegen ${ZLIB_LIBRARIES}) -endif () +add_dependencies(lwan-static mimegen) add_custom_command( OUTPUT ${CMAKE_BINARY_DIR}/mime-types.h diff --git a/common/hash.c b/common/hash.c index 3a9a02a87..20230f4ad 100644 --- a/common/hash.c +++ b/common/hash.c @@ -18,6 +18,7 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#define _GNU_SOURCE #include "hash.h" #include "murmur3.h" #include "reallocarray.h" diff --git a/common/reallocarray.c b/common/reallocarray.c index 5ff26694b..e5646280a 100644 --- a/common/reallocarray.c +++ b/common/reallocarray.c @@ -21,8 +21,6 @@ #include #include -#include "lwan.h" - #if !defined(HAVE_BUILTIN_MUL_OVERFLOW) /* * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX @@ -41,6 +39,8 @@ static inline bool umull_overflow(size_t a, size_t b, size_t *out) #define umull_overflow __builtin_mul_overflow #endif +#define UNLIKELY(expr) __builtin_expect(!!(expr), 0) + void * reallocarray(void *optr, size_t nmemb, size_t size) { diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 000000000..414124cb8 --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 2.8) + +project(mimegen) + +find_package(ZLIB REQUIRED) +set(ADDITIONAL_LIBRARIES ${ZLIB_LIBRARIES}) + +add_executable(mimegen + mimegen.c + ../common/hash.c + ../common/murmur3.c + ../common/reallocarray.c +) + +find_library(ZOPFLI_LIBRARY NAMES zopfli PATHS /usr/lib /usr/local/lib) +if (ZOPFLI_LIBRARY) + message(STATUS "Using Zopfli (${ZOPFLI_LIBRARY}) for mimegen") + target_link_libraries(mimegen ${ZOPFLI_LIBRARY}) + add_definitions(-DHAVE_ZOPFLI) +else () + message(STATUS "Using zlib (${ZLIB_LIBRARIES}) for mimegen") + target_link_libraries(mimegen ${ZLIB_LIBRARIES}) +endif () From 0735a1768c31640d944184e8e23c1f02f24e2414 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 24 Jun 2016 00:59:17 +0200 Subject: [PATCH 0196/2505] Fix dynamic module loading on FreeBSD Specify `-rdynamic` so that `-export-dynamic` is passed to the linker, so that all symbols, not only the used ones, are added to the dynamic symbol table. This allows Lwan to call `dlopen()` on itself to find module symbols. --- CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f7977b20..2e02fd275 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,6 +76,11 @@ if (HAS_MTUNE_NATIVE) set(C_FLAGS_REL "-mtune=native") endif() +check_c_compiler_flag(-rdynamic HAS_RDYNAMIC) +if (HAS_MTUNE_NATIVE) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -rdynamic") +endif() + if (APPLE) check_c_compiler_flag(-Wl,-bind_at_load HAS_IMMEDIATE_BINDING) if (HAS_IMMEDIATE_BINDING) From 406d153c581cc15228fe6c11b97758a25ee1d4a9 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 24 Jun 2016 11:29:10 +0200 Subject: [PATCH 0197/2505] For libraries found with pkg-config, pass LDFLAGS as well This is required when linking with libraries installed in places other than the standard places (such as when installing LuaJIT on FreeBSD via ports). --- common/CMakeLists.txt | 2 +- freegeoip/CMakeLists.txt | 1 + techempower/CMakeLists.txt | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 149c01e65..8fe19d0c6 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -80,7 +80,7 @@ foreach (pc_file luajit lua lua51 lua5.1 lua-5.1) pkg_check_modules(LUA ${pc_file}>=5.1.0 ${pc_file}<=5.1.999) endif () if (LUA_FOUND) - list(APPEND ADDITIONAL_LIBRARIES "-l${LUA_LIBRARIES}") + list(APPEND ADDITIONAL_LIBRARIES "-l${LUA_LIBRARIES} ${LUA_LDFLAGS}") list(APPEND SOURCES lwan-lua.c) include_directories(${LUA_INCLUDE_DIRS}) add_definitions(-DHAVE_LUA) diff --git a/freegeoip/CMakeLists.txt b/freegeoip/CMakeLists.txt index 6192ec618..ea793c813 100644 --- a/freegeoip/CMakeLists.txt +++ b/freegeoip/CMakeLists.txt @@ -11,6 +11,7 @@ if (SQLITE_FOUND) ${CMAKE_DL_LIBS} ${ADDITIONAL_LIBRARIES} ${SQLITE_LIBRARIES} + ${SQLITE_LDFLAGS} ) else () message(STATUS "Freegeoip sample application not being built: SQLite not found") diff --git a/techempower/CMakeLists.txt b/techempower/CMakeLists.txt index 2fc81462e..05290c961 100644 --- a/techempower/CMakeLists.txt +++ b/techempower/CMakeLists.txt @@ -31,6 +31,7 @@ if (MYSQL_INCLUDE_DIR AND SQLITE_FOUND) ${CMAKE_DL_LIBS} ${ADDITIONAL_LIBRARIES} ${SQLITE_LIBRARIES} + ${SQLITE_LDFLAGS} ${MYSQL_LIBRARY} ) endif () From 75074dfad56a1c638069b34512682bac5ee92d27 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 24 Jun 2016 11:34:36 +0200 Subject: [PATCH 0198/2505] Also include_directories(${SQLITE_INCLUDE_DIRS}) --- freegeoip/CMakeLists.txt | 1 + techempower/CMakeLists.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/freegeoip/CMakeLists.txt b/freegeoip/CMakeLists.txt index ea793c813..dc915df54 100644 --- a/freegeoip/CMakeLists.txt +++ b/freegeoip/CMakeLists.txt @@ -13,6 +13,7 @@ if (SQLITE_FOUND) ${SQLITE_LIBRARIES} ${SQLITE_LDFLAGS} ) + include_directories(${SQLITE_INCLUDE_DIRS}) else () message(STATUS "Freegeoip sample application not being built: SQLite not found") endif () diff --git a/techempower/CMakeLists.txt b/techempower/CMakeLists.txt index 05290c961..54092295d 100644 --- a/techempower/CMakeLists.txt +++ b/techempower/CMakeLists.txt @@ -34,6 +34,7 @@ if (MYSQL_INCLUDE_DIR AND SQLITE_FOUND) ${SQLITE_LDFLAGS} ${MYSQL_LIBRARY} ) + include_directories(${SQLITE_INCLUDE_DIRS}) endif () endif () From ff06c209266022a609402dcde80c3216a8d62d9c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 24 Jun 2016 11:40:39 +0200 Subject: [PATCH 0199/2505] No need to set `-std=gnu11` for now No C11 features are being used currently, so not set this flag. Should make it easier to build on FreeBSD. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e02fd275..5c7503a09 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -178,7 +178,7 @@ else () message(STATUS "Valgrind headers not found -- disabling valgrind support") endif() -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wshadow -Wconversion -std=gnu11") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wshadow -Wconversion") set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${C_FLAGS_REL}") set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} ${C_FLAGS_REL}") set(CMAKE_C_FLAGS_MINSIZEREL "${CMAKE_C_FLAGS_MINSIZEREL} ${C_FLAGS_REL}") From 8a444bc0161afc14986f8c2f34d6cc5348f33628 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 28 Jun 2016 08:27:03 +0200 Subject: [PATCH 0200/2505] Force at least -std=gnu99 when building Older compilers, such as GCC 4.8, will default to -std=gnu89, which is not sufficient for certain idioms used in Lwan. Unfortunately, these older compilers cannot use -std=gnu11. Fixes #157. --- CMakeLists.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5c7503a09..004acc830 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,6 +81,11 @@ if (HAS_MTUNE_NATIVE) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -rdynamic") endif() +check_c_compiler_flag(-std=gnu99 HAS_STD_GNU99) +if (NOT HAS_STD_GNU99) + message(FATAL_ERROR "Compiler does not support -std=gnu99. Consider using a newer compiler") +endif() + if (APPLE) check_c_compiler_flag(-Wl,-bind_at_load HAS_IMMEDIATE_BINDING) if (HAS_IMMEDIATE_BINDING) @@ -178,7 +183,7 @@ else () message(STATUS "Valgrind headers not found -- disabling valgrind support") endif() -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wshadow -Wconversion") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wshadow -Wconversion -std=gnu99") set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${C_FLAGS_REL}") set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} ${C_FLAGS_REL}") set(CMAKE_C_FLAGS_MINSIZEREL "${CMAKE_C_FLAGS_MINSIZEREL} ${C_FLAGS_REL}") From 36e987b68636f1cb9499f148a67440ea470cb2ec Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 28 Jun 2016 10:06:40 +0200 Subject: [PATCH 0201/2505] Do not include missing.h from lwan.h Move the #include to lwan-private.h, which is not installed and is only used by implementation files. Should fix #158. --- common/lwan-lua.c | 1 + common/lwan-private.h | 1 + common/lwan.h | 1 - 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/common/lwan-lua.c b/common/lwan-lua.c index d283922f1..9a8efb757 100644 --- a/common/lwan-lua.c +++ b/common/lwan-lua.c @@ -30,6 +30,7 @@ #include "lwan-cache.h" #include "lwan-config.h" #include "lwan-lua.h" +#include "lwan-private.h" static const char *request_metatable_name = "Lwan.Request"; diff --git a/common/lwan-private.h b/common/lwan-private.h index b19d6d922..fb5613ef1 100644 --- a/common/lwan-private.h +++ b/common/lwan-private.h @@ -20,6 +20,7 @@ #pragma once #include "lwan.h" +#include "missing.h" void lwan_response_init(lwan_t *l); void lwan_response_shutdown(lwan_t *l); diff --git a/common/lwan.h b/common/lwan.h index b0b4d5ff2..2812b3641 100644 --- a/common/lwan.h +++ b/common/lwan.h @@ -35,7 +35,6 @@ extern "C" { #include "lwan-status.h" #include "lwan-trie.h" #include "strbuf.h" -#include "missing.h" #define DEFAULT_BUFFER_SIZE 4096 #define DEFAULT_HEADERS_SIZE 512 From e598f1049feebe312c27b485eb4861947e4a1621 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 1 Jul 2016 10:31:32 +0200 Subject: [PATCH 0202/2505] Implement --config command line option This command line option was documented but not implemented. It's useful when installing Lwan and using it with many different configuration options, without inferring the configuration file path from the executable name. Closes #152. --- common/lwan.c | 11 ++++++----- common/lwan.h | 1 + lwan/main.c | 15 +++++++++++++-- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/common/lwan.c b/common/lwan.c index 329f42374..05c124b62 100644 --- a/common/lwan.c +++ b/common/lwan.c @@ -393,15 +393,15 @@ const char *get_config_path(char *path_buf) return "lwan.conf"; } -static bool setup_from_config(lwan_t *lwan) +static bool setup_from_config(lwan_t *lwan, const char *path) { config_t conf; config_line_t line; bool has_listener = false; char path_buf[PATH_MAX]; - const char *path; - path = get_config_path(path_buf); + if (!path) + path = get_config_path(path_buf); lwan_status_info("Loading configuration file: %s", path); if (!lwan_trie_init(&lwan->url_map_trie, destroy_urlmap)) @@ -561,8 +561,8 @@ lwan_init_with_config(lwan_t *l, const lwan_config_t *config) lwan_module_init(l); /* Load the configuration file. */ - if (config == &default_config) { - if (!setup_from_config(l)) + if (config == &default_config || config->config_file_path) { + if (!setup_from_config(l, config->config_file_path)) lwan_status_warning("Could not read config file, using defaults"); /* `quiet` key might have changed value. */ @@ -604,6 +604,7 @@ lwan_shutdown(lwan_t *l) if (l->config.listener != default_config.listener) free(l->config.listener); free(l->config.error_template); + free(l->config.config_file_path); lwan_job_thread_shutdown(); lwan_thread_shutdown(l); diff --git a/common/lwan.h b/common/lwan.h index 2812b3641..e97869370 100644 --- a/common/lwan.h +++ b/common/lwan.h @@ -285,6 +285,7 @@ struct lwan_thread_t_ { struct lwan_config_t_ { char *listener; char *error_template; + char *config_file_path; unsigned short keep_alive_timeout; unsigned int expires; short unsigned int n_threads; diff --git a/lwan/main.c b/lwan/main.c index 9621bed91..1b692fee5 100644 --- a/lwan/main.c +++ b/lwan/main.c @@ -39,13 +39,20 @@ parse_args(int argc, char *argv[], lwan_config_t *config, char *root) { .name = "root", .has_arg = 1, .val = 'r' }, { .name = "listen", .has_arg = 1, .val = 'l' }, { .name = "help", .val = 'h' }, + { .name = "config", .has_arg = 1, .val = 'c' }, { } }; int c, optidx = 0; enum args result = ARGS_USE_CONFIG; - while ((c = getopt_long(argc, argv, "hr:l:", opts, &optidx)) != -1) { + while ((c = getopt_long(argc, argv, "hr:l:c:", opts, &optidx)) != -1) { switch (c) { + case 'c': + free(config->config_file_path); + config->config_file_path = strdup(optarg); + result = ARGS_USE_CONFIG; + break; + case 'l': free(config->listener); config->listener = strdup(optarg); @@ -65,6 +72,7 @@ parse_args(int argc, char *argv[], lwan_config_t *config, char *root) printf("Options:\n"); printf("\t-r, --root Path to serve files from (default: ./wwwroot).\n"); printf("\t-l, --listener Listener (default: %s).\n", config->listener); + printf("\t-c, --config Path to config file path.\n"); printf("\t-h, --help This.\n"); printf("\n"); printf("Examples:\n"); @@ -108,7 +116,10 @@ main(int argc, char *argv[]) lwan_set_url_map(&l, map); break; case ARGS_USE_CONFIG: - lwan_init(&l); + if (c.config_file_path) + lwan_init_with_config(&l, &c); + else + lwan_init(&l); break; case ARGS_FAILED: return EXIT_FAILURE; From b8444ea58ad25ba029a2047722e6740966d985f0 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 1 Jul 2016 14:58:51 +0200 Subject: [PATCH 0203/2505] Fix directory listing when serving files from places other than / If serve_files module is being applied to a prefix other than /, the directory listing would not calculate the relative path correctly. This would make links to files wrongly point to the root directory. --- common/lwan-lua.c | 6 +++--- common/lwan-redirect.c | 6 +++--- common/lwan-rewrite.c | 8 +++++--- common/lwan-serve-files.c | 24 ++++++++++++++++++++---- common/lwan.c | 4 ++-- common/lwan.h | 4 ++-- 6 files changed, 35 insertions(+), 17 deletions(-) diff --git a/common/lwan-lua.c b/common/lwan-lua.c index 9a8efb757..ff5dd20a5 100644 --- a/common/lwan-lua.c +++ b/common/lwan-lua.c @@ -386,7 +386,7 @@ lua_handle_cb(lwan_request_t *request, } } -static void *lua_init(void *data) +static void *lua_init(const char *prefix __attribute__((unused)), void *data) { struct lwan_lua_settings_t *settings = data; struct lwan_lua_priv_t *priv; @@ -443,14 +443,14 @@ static void lua_shutdown(void *data) } } -static void *lua_init_from_hash(const struct hash *hash) +static void *lua_init_from_hash(const char *prefix, const struct hash *hash) { struct lwan_lua_settings_t settings = { .default_type = hash_find(hash, "default_type"), .script_file = hash_find(hash, "script_file"), .cache_period = parse_time_period(hash_find(hash, "cache_period"), 15) }; - return lua_init(&settings); + return lua_init(prefix, &settings); } const lwan_module_t *lwan_module_lua(void) diff --git a/common/lwan-redirect.c b/common/lwan-redirect.c index cb28bb418..4aa89715f 100644 --- a/common/lwan-redirect.c +++ b/common/lwan-redirect.c @@ -45,18 +45,18 @@ redirect_handle_cb(lwan_request_t *request, return HTTP_MOVED_PERMANENTLY; } -static void *redirect_init(void *data) +static void *redirect_init(const char *prefix __attribute__((unused)), void *data) { struct lwan_redirect_settings_t *settings = data; return (settings->to) ? strdup(settings->to) : NULL; } -static void *redirect_init_from_hash(const struct hash *hash) +static void *redirect_init_from_hash(const char *prefix, const struct hash *hash) { struct lwan_redirect_settings_t settings = { .to = hash_find(hash, "to") }; - return redirect_init(&settings); + return redirect_init(prefix, &settings); } const lwan_module_t *lwan_module_redirect(void) diff --git a/common/lwan-rewrite.c b/common/lwan-rewrite.c index 4ea00e64b..83bae4ef7 100644 --- a/common/lwan-rewrite.c +++ b/common/lwan-rewrite.c @@ -176,7 +176,8 @@ module_handle_cb(lwan_request_t *request, } static void * -module_init(void *data __attribute__((unused))) +module_init(const char *prefix __attribute__((unused)), + void *data __attribute__((unused))) { struct private_data *pd = malloc(sizeof(*pd)); @@ -202,9 +203,10 @@ module_shutdown(void *data) } static void * -module_init_from_hash(const struct hash *hash __attribute__((unused))) +module_init_from_hash(const char *prefix, + const struct hash *hash __attribute__((unused))) { - return module_init(NULL); + return module_init(prefix, NULL); } static bool diff --git a/common/lwan-serve-files.c b/common/lwan-serve-files.c index dc7cfc02a..42544024f 100644 --- a/common/lwan-serve-files.c +++ b/common/lwan-serve-files.c @@ -54,6 +54,7 @@ struct serve_files_priv { int open_mode; const char *index_html; + char *prefix; lwan_tpl_t *directory_list_tpl; @@ -416,6 +417,13 @@ sendfile_init(struct file_cache_entry *ce, struct serve_files_priv *priv, return true; } +static const char * +get_rel_path(const char *full_path, struct serve_files_priv *priv) +{ + const char *root_path = full_path + priv->root.path_len; + return !strcmp(root_path, ".") ? root_path : priv->prefix; +} + static bool dirlist_init(struct file_cache_entry *ce, struct serve_files_priv *priv, const char *full_path, struct stat *st __attribute__((unused))) @@ -423,7 +431,7 @@ dirlist_init(struct file_cache_entry *ce, struct serve_files_priv *priv, struct dir_list_cache_data *dd = (struct dir_list_cache_data *)(ce + 1); struct file_list vars = { .full_path = full_path, - .rel_path = full_path + priv->root.path_len + .rel_path = get_rel_path(full_path, priv) }; dd->rendered = lwan_tpl_apply(priv->directory_list_tpl, &vars); @@ -655,7 +663,7 @@ try_open_directory(const char *path, int *open_mode) } static void * -serve_files_init(void *args) +serve_files_init(const char *prefix, void *args) { struct lwan_serve_files_settings_t *settings = args; char *canonical_root; @@ -708,6 +716,12 @@ serve_files_init(void *args) goto out_tpl_compile; } + priv->prefix = strdup(prefix); + if (!priv->prefix) { + lwan_status_error("Could not copy prefix"); + goto out_tpl_prefix_copy; + } + priv->root.path = canonical_root; priv->root.path_len = strlen(canonical_root); priv->root.fd = root_fd; @@ -718,6 +732,7 @@ serve_files_init(void *args) return priv; +out_tpl_prefix_copy: out_tpl_compile: cache_destroy(priv->cache); out_cache_create: @@ -731,7 +746,7 @@ serve_files_init(void *args) } static void * -serve_files_init_from_hash(const struct hash *hash) +serve_files_init_from_hash(const char *prefix, const struct hash *hash) { struct lwan_serve_files_settings_t settings = { .root_path = hash_find(hash, "path"), @@ -741,7 +756,7 @@ serve_files_init_from_hash(const struct hash *hash) .auto_index = parse_bool(hash_find(hash, "auto_index"), true), .directory_list_template = hash_find(hash, "directory_list_template") }; - return serve_files_init(&settings); + return serve_files_init(prefix, &settings); } static void @@ -758,6 +773,7 @@ serve_files_shutdown(void *data) cache_destroy(priv->cache); close(priv->root.fd); free(priv->root.path); + free(priv->prefix); free(priv); } diff --git a/common/lwan.c b/common/lwan.c index 05c124b62..a2c04b68f 100644 --- a/common/lwan.c +++ b/common/lwan.c @@ -277,7 +277,7 @@ static void parse_listener_prefix(config_t *c, config_line_t *l, lwan_t *lwan, hash = NULL; } else if (module && module->init_from_hash && module->handle) { - url_map.data = module->init_from_hash(hash); + url_map.data = module->init_from_hash(prefix, hash); if (isolated.file && module->parse_conf) { if (!module->parse_conf(url_map.data, &isolated)) { config_error(c, "Error from module: %s", @@ -313,7 +313,7 @@ void lwan_set_url_map(lwan_t *l, const lwan_url_map_t *map) continue; if (copy->module && copy->module->init) { - copy->data = copy->module->init(copy->args); + copy->data = copy->module->init(map->prefix, copy->args); copy->flags = copy->module->flags; copy->handler = copy->module->handle; } else { diff --git a/common/lwan.h b/common/lwan.h index e97869370..74f97b7df 100644 --- a/common/lwan.h +++ b/common/lwan.h @@ -243,8 +243,8 @@ struct lwan_request_t_ { }; struct lwan_module_t_ { - void *(*init)(void *args); - void *(*init_from_hash)(const struct hash *hash); + void *(*init)(const char *prefix, void *args); + void *(*init_from_hash)(const char *prefix, const struct hash *hash); void (*shutdown)(void *data); bool (*parse_conf)(void *data, config_t *config); lwan_http_status_t (*handle)(lwan_request_t *request, lwan_response_t *response, void *data); From c5132051cbe7945ccf5acfbdeb4f4cc48aae7f4c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 2 Jul 2016 14:15:19 +0200 Subject: [PATCH 0204/2505] Detect integer overflows when resizing string buffers Suggested by @solardiz. --- common/strbuf.c | 45 ++++++++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/common/strbuf.c b/common/strbuf.c index 8f7f98e52..73f8bc7f4 100644 --- a/common/strbuf.c +++ b/common/strbuf.c @@ -57,6 +57,17 @@ find_next_power_of_two(size_t number) return number + 1; } +static inline size_t align_size(size_t unaligned_size) +{ + const size_t aligned_size = find_next_power_of_two(unaligned_size); + + if (UNLIKELY(unaligned_size >= aligned_size)) + return 0; + + return aligned_size; +} + + static ALWAYS_INLINE size_t max(size_t one, size_t another) { @@ -67,28 +78,33 @@ static bool grow_buffer_if_needed(strbuf_t *s, size_t size) { if (s->flags & STATIC) { - const size_t next_power = find_next_power_of_two(max(size + 1, - s->len.buffer)); - char *buffer = malloc(next_power); - if (!buffer) + const size_t aligned_size = align_size(max(size + 1, s->len.buffer)); + if (UNLIKELY(!aligned_size)) + return false; + + char *buffer = malloc(aligned_size); + if (UNLIKELY(!buffer)) return false; memcpy(buffer, s->value.static_buffer, s->len.buffer); buffer[s->len.buffer + 1] = '\0'; s->flags &= ~STATIC; - s->len.allocated = next_power; + s->len.allocated = aligned_size; s->value.buffer = buffer; return true; } if (UNLIKELY(s->len.allocated < size)) { - const size_t next_power = find_next_power_of_two(size); - char *buffer = realloc(s->value.buffer, next_power + 1); + const size_t aligned_size = align_size(size + 1); + if (UNLIKELY(!aligned_size)) + return false; + + char *buffer = realloc(s->value.buffer, aligned_size); if (UNLIKELY(!buffer)) return false; - s->len.allocated = next_power; + s->len.allocated = aligned_size; s->value.buffer = buffer; } @@ -283,15 +299,18 @@ strbuf_shrink_to(strbuf_t *s, size_t new_size) if (s->flags & STATIC) return true; - size_t next_power_of_two = find_next_power_of_two(new_size); - char *buffer = realloc(s->value.buffer, next_power_of_two + 1); + size_t aligned_size = align_size(new_size + 1); + if (UNLIKELY(!aligned_size)) + return false; + + char *buffer = realloc(s->value.buffer, aligned_size); if (UNLIKELY(!buffer)) return false; s->value.buffer = buffer; - s->len.allocated = next_power_of_two; - if (s->len.buffer > next_power_of_two) { - s->len.buffer = next_power_of_two - 1; + s->len.allocated = aligned_size; + if (s->len.buffer > aligned_size) { + s->len.buffer = aligned_size - 1; s->value.buffer[s->len.buffer + 1] = '\0'; } From 0c057c158a6d65a02d7ace9eb628779d84e82575 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 2 Jul 2016 15:07:14 +0200 Subject: [PATCH 0205/2505] Fix directory listing when serving files from places other than / Really fix b8444ea5. --- common/lwan-serve-files.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lwan-serve-files.c b/common/lwan-serve-files.c index 42544024f..e213b158d 100644 --- a/common/lwan-serve-files.c +++ b/common/lwan-serve-files.c @@ -421,7 +421,7 @@ static const char * get_rel_path(const char *full_path, struct serve_files_priv *priv) { const char *root_path = full_path + priv->root.path_len; - return !strcmp(root_path, ".") ? root_path : priv->prefix; + return *root_path ? root_path : priv->prefix; } static bool From f81880768d8d3bd81dd3e400cba4e95cc10e0859 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 2 Jul 2016 18:19:02 +0200 Subject: [PATCH 0206/2505] Ensure epoll_create1() emulation on BSDs respect flags argument --- common/epoll-bsd.c | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/common/epoll-bsd.c b/common/epoll-bsd.c index cbecec02f..dd7aeeb7e 100644 --- a/common/epoll-bsd.c +++ b/common/epoll-bsd.c @@ -17,21 +17,36 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include -#include -#include #include +#include #include #include +#include +#include +#include #include "epoll-bsd.h" #include "lwan-status.h" #include "lwan.h" int -epoll_create1(int flags __attribute__((unused))) +epoll_create1(int flags) { - return kqueue(); + int fd = kqueue(); + + if (fd < 0) + return -1; + + if (flags & EPOLL_CLOEXEC && fcntl(fd, F_SETFL, O_CLOEXEC) < 0) { + int saved_errno = errno; + + close(fd); + + errno = saved_errno; + return -1; + } + + return fd; } int From 92bb075518e8cf7e534549e48d806d77c72b3326 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 2 Jul 2016 18:45:03 +0200 Subject: [PATCH 0207/2505] Reduce epoll_ctl() chatter by using level-triggered behavior After making a quick benchmark, here are the results for 100k requests with 2k connections: With patch: 2004 calls to epoll_ctl() Without patch: 5183 calls to epoll_ctl() Strace output is a lot nicer with this patch, but I couldn't measure any significant different in throughput as I thought it would. Since all the tests are passing I'm pushing this. I'm not really happy with how the CONN_FLIP_FLAGS thing, but I'll eventually find a better way to do this. Closes #125. --- common/lwan-io-wrappers.c | 6 ++++++ common/lwan-thread.c | 8 +++++++- common/lwan.h | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/common/lwan-io-wrappers.c b/common/lwan-io-wrappers.c index 9cf1897ba..831f957f4 100644 --- a/common/lwan-io-wrappers.c +++ b/common/lwan-io-wrappers.c @@ -52,6 +52,7 @@ lwan_openat(lwan_request_t *request, case EWOULDBLOCK: case EINTR: case ENOMEM: + request->conn->flags |= CONN_FLIP_FLAGS; coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); break; default: @@ -77,6 +78,7 @@ lwan_writev(lwan_request_t *request, struct iovec *iov, int iov_count) switch (errno) { case EAGAIN: case EINTR: + request->conn->flags |= CONN_FLIP_FLAGS; goto try_again; default: goto out; @@ -118,6 +120,7 @@ lwan_write(lwan_request_t *request, const void *buf, size_t count) switch (errno) { case EAGAIN: case EINTR: + request->conn->flags |= CONN_FLIP_FLAGS; goto try_again; default: goto out; @@ -152,6 +155,7 @@ lwan_send(lwan_request_t *request, const void *buf, size_t count, int flags) switch (errno) { case EAGAIN: case EINTR: + request->conn->flags |= CONN_FLIP_FLAGS; goto try_again; default: goto out; @@ -188,6 +192,7 @@ lwan_sendfile(lwan_request_t *request, int in_fd, off_t offset, size_t count, switch (errno) { case EAGAIN: case EINTR: + request->conn->flags |= CONN_FLIP_FLAGS; coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); continue; @@ -229,6 +234,7 @@ lwan_sendfile(lwan_request_t *request, int in_fd, off_t offset, size_t count, case EAGAIN: case EBUSY: case EINTR: + request->conn->flags |= CONN_FLIP_FLAGS; coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); continue; diff --git a/common/lwan-thread.c b/common/lwan-thread.c index aceba9c68..d59d2e079 100644 --- a/common/lwan-thread.c +++ b/common/lwan-thread.c @@ -47,7 +47,7 @@ struct death_queue_t { static const uint32_t events_by_write_flag[] = { EPOLLOUT | EPOLLRDHUP | EPOLLERR, - EPOLLIN | EPOLLRDHUP | EPOLLERR | EPOLLET + EPOLLIN | EPOLLRDHUP | EPOLLERR }; static inline int death_queue_node_to_idx(struct death_queue_t *dq, @@ -207,6 +207,12 @@ resume_coro_if_needed(struct death_queue_t *dq, lwan_connection_t *conn, return; } + if (conn->flags & CONN_FLIP_FLAGS) { + conn->flags &= ~CONN_FLIP_FLAGS; + } else { + return; + } + bool write_events; if (conn->flags & CONN_MUST_READ) { write_events = true; diff --git a/common/lwan.h b/common/lwan.h index 74f97b7df..a2055cc90 100644 --- a/common/lwan.h +++ b/common/lwan.h @@ -170,6 +170,7 @@ typedef enum { CONN_SHOULD_RESUME_CORO = 1<<2, CONN_WRITE_EVENTS = 1<<3, CONN_MUST_READ = 1<<4, + CONN_FLIP_FLAGS = 1<<5, } lwan_connection_flags_t; typedef enum { From c98ceaac5584de629a8860d7ac5f2bf5fd466e4c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 2 Jul 2016 19:10:29 +0200 Subject: [PATCH 0208/2505] Fix MIME Type for .js files Fix provided by Mandar Kulkarni. Closes #159. --- common/lwan-tables.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lwan-tables.c b/common/lwan-tables.c index 13a582f7c..aa4056f5e 100644 --- a/common/lwan-tables.c +++ b/common/lwan-tables.c @@ -87,7 +87,7 @@ lwan_determine_mime_type_for_file_name(const char *file_name) EXT_HTM = MULTICHAR_CONSTANT_L('.','h','t','m'), EXT_CSS = MULTICHAR_CONSTANT_L('.','c','s','s'), EXT_TXT = MULTICHAR_CONSTANT_L('.','t','x','t'), - EXT_JS = MULTICHAR_CONSTANT_L('.','j','s',0), + EXT_JS = MULTICHAR_CONSTANT_L('.','j','s',0x20), }; STRING_SWITCH_L(last_dot) { From de14c34b49d43cee556203b5558a469baabb8f93 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 3 Jul 2016 09:28:03 +0200 Subject: [PATCH 0209/2505] It's unlikely flags will be flipped after a coroutine yields --- common/lwan-thread.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lwan-thread.c b/common/lwan-thread.c index d59d2e079..76e036e3c 100644 --- a/common/lwan-thread.c +++ b/common/lwan-thread.c @@ -207,7 +207,7 @@ resume_coro_if_needed(struct death_queue_t *dq, lwan_connection_t *conn, return; } - if (conn->flags & CONN_FLIP_FLAGS) { + if (UNLIKELY(conn->flags & CONN_FLIP_FLAGS)) { conn->flags &= ~CONN_FLIP_FLAGS; } else { return; From 0990b0ebb4df29d70d9c4557aebd508d28a26565 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 3 Jul 2016 09:28:22 +0200 Subject: [PATCH 0210/2505] Only flip flags on EWOULDBLOCK and EAGAIN errors --- common/lwan-io-wrappers.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/common/lwan-io-wrappers.c b/common/lwan-io-wrappers.c index 831f957f4..779b4da6e 100644 --- a/common/lwan-io-wrappers.c +++ b/common/lwan-io-wrappers.c @@ -47,12 +47,13 @@ lwan_openat(lwan_request_t *request, } switch (errno) { + case EWOULDBLOCK: + request->conn->flags |= CONN_FLIP_FLAGS; + /* Fallthrough */ case EMFILE: case ENFILE: - case EWOULDBLOCK: case EINTR: case ENOMEM: - request->conn->flags |= CONN_FLIP_FLAGS; coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); break; default: @@ -77,8 +78,9 @@ lwan_writev(lwan_request_t *request, struct iovec *iov, int iov_count) switch (errno) { case EAGAIN: - case EINTR: request->conn->flags |= CONN_FLIP_FLAGS; + /* Fallthrough */ + case EINTR: goto try_again; default: goto out; @@ -119,8 +121,9 @@ lwan_write(lwan_request_t *request, const void *buf, size_t count) switch (errno) { case EAGAIN: - case EINTR: request->conn->flags |= CONN_FLIP_FLAGS; + /* Fallthrough */ + case EINTR: goto try_again; default: goto out; @@ -154,8 +157,9 @@ lwan_send(lwan_request_t *request, const void *buf, size_t count, int flags) switch (errno) { case EAGAIN: - case EINTR: request->conn->flags |= CONN_FLIP_FLAGS; + /* Fallthrough */ + case EINTR: goto try_again; default: goto out; @@ -191,8 +195,9 @@ lwan_sendfile(lwan_request_t *request, int in_fd, off_t offset, size_t count, if (written < 0) { switch (errno) { case EAGAIN: - case EINTR: request->conn->flags |= CONN_FLIP_FLAGS; + /* Fallthrough */ + case EINTR: coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); continue; @@ -232,9 +237,10 @@ lwan_sendfile(lwan_request_t *request, int in_fd, off_t offset, size_t count, if (UNLIKELY(r < 0)) { switch (errno) { case EAGAIN: + request->conn->flags |= CONN_FLIP_FLAGS; + /* Fallthrough */ case EBUSY: case EINTR: - request->conn->flags |= CONN_FLIP_FLAGS; coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); continue; From c2e73b0c7d7def3ccfada8bff086b982d622fdea Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 3 Jul 2016 09:31:45 +0200 Subject: [PATCH 0211/2505] EPOLLET is not used in Lwan anymore; update BSD epoll emulation --- common/epoll-bsd.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/epoll-bsd.c b/common/epoll-bsd.c index dd7aeeb7e..f68407b1b 100644 --- a/common/epoll-bsd.c +++ b/common/epoll-bsd.c @@ -58,9 +58,7 @@ epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) case EPOLL_CTL_ADD: case EPOLL_CTL_MOD: { int events = 0; - /* EV_CLEAR should be set only if EPOLLET is there, but Lwan doesn't - * always set EPOLLET. In the meantime, force EV_CLEAR every time. */ - int flags = EV_ADD | EV_CLEAR; + int flags = EV_ADD; if (event->events & EPOLLIN) events = EVFILT_READ; @@ -73,6 +71,8 @@ epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) flags |= EV_EOF; if (event->events & EPOLLERR) flags |= EV_ERROR; + if (event->events & EPOLLET) + flags |= EV_CLEAR; EV_SET(&ev, fd, events, flags, 0, 0, event->data.ptr); break; From 3c8890e616032e67ba912a7c2f4a1e0c1d6e789f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 3 Jul 2016 09:34:19 +0200 Subject: [PATCH 0212/2505] Try to create hash table before calling kevent() Since the hash table creation might fail, call it before calling kevent(). Events can be cleared with the EV_CLEAR flag, and this could miss important events. --- common/epoll-bsd.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/epoll-bsd.c b/common/epoll-bsd.c index f68407b1b..ece81678b 100644 --- a/common/epoll-bsd.c +++ b/common/epoll-bsd.c @@ -111,14 +111,14 @@ epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) struct hash *coalesce; int i, r; - r = kevent(epfd, NULL, 0, evs, maxevents, to_timespec(&tmspec, timeout)); - if (UNLIKELY(r < 0)) - return -1; - coalesce = hash_int_new(NULL, NULL); if (!coalesce) return -1; + r = kevent(epfd, NULL, 0, evs, maxevents, to_timespec(&tmspec, timeout)); + if (UNLIKELY(r < 0)) + return -1; + for (i = 0; i < r; i++) { struct kevent *kev = &evs[i]; uint32_t mask = (uint32_t)(uintptr_t)hash_find(coalesce, From 39ae6ed99e25521ded089cfa7b4013e81c215083 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 3 Jul 2016 14:21:21 +0200 Subject: [PATCH 0213/2505] Revert f81880 to fix build on FreeBSD (My FreeBSD box is quite far away and I can't always build on it.) --- common/epoll-bsd.c | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/common/epoll-bsd.c b/common/epoll-bsd.c index ece81678b..9411c92a5 100644 --- a/common/epoll-bsd.c +++ b/common/epoll-bsd.c @@ -32,21 +32,7 @@ int epoll_create1(int flags) { - int fd = kqueue(); - - if (fd < 0) - return -1; - - if (flags & EPOLL_CLOEXEC && fcntl(fd, F_SETFL, O_CLOEXEC) < 0) { - int saved_errno = errno; - - close(fd); - - errno = saved_errno; - return -1; - } - - return fd; + return kqueue(); } int From 151358f596113fe6fddc8d5b57a304ea03e2aa53 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 3 Jul 2016 13:44:54 -0300 Subject: [PATCH 0214/2505] Fix build on FreeBSD --- common/epoll-bsd.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/common/epoll-bsd.c b/common/epoll-bsd.c index 9411c92a5..725a81170 100644 --- a/common/epoll-bsd.c +++ b/common/epoll-bsd.c @@ -19,18 +19,19 @@ #include #include +#include #include #include +#include #include #include -#include #include "epoll-bsd.h" #include "lwan-status.h" #include "lwan.h" int -epoll_create1(int flags) +epoll_create1(int flags __attribute__((unused))) { return kqueue(); } From 5114928b1a5612453c3523630310cc5d92a7ddd0 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 6 Jul 2016 07:52:29 -0300 Subject: [PATCH 0215/2505] Critical status messages should not call abort() Generating core dumps is not that useful in Release builds, so just call exit(1) instead. Keep calling abort() on Debug builds. --- common/lwan-status.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lwan-status.c b/common/lwan-status.c index 227287c0d..0b061aadc 100644 --- a/common/lwan-status.c +++ b/common/lwan-status.c @@ -202,7 +202,7 @@ status_out(const char *file, const int line, const char *func, status_out(type_, fmt, values); \ va_end(values); \ } \ - if ((type_) & STATUS_CRITICAL) abort(); \ + if ((type_) & STATUS_CRITICAL) exit(1); \ } #else #define IMPLEMENT_FUNCTION(fn_name_, type_) \ From 0f50698b2caf689d512329d4abbfe63abdd8cc32 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 7 Jul 2016 07:07:19 -0300 Subject: [PATCH 0216/2505] Try to fix build on OS X. Maybe help with #160. --- common/int-to-str.c | 2 ++ common/int-to-str.h | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/common/int-to-str.c b/common/int-to-str.c index 526763ee4..875383980 100644 --- a/common/int-to-str.c +++ b/common/int-to-str.c @@ -18,7 +18,9 @@ */ #include + #include "int-to-str.h" +#include "lwan-private.h" ALWAYS_INLINE char * uint_to_string(size_t value, diff --git a/common/int-to-str.h b/common/int-to-str.h index d3b48d80a..c5ec9b22b 100644 --- a/common/int-to-str.h +++ b/common/int-to-str.h @@ -18,7 +18,8 @@ */ #pragma once -#include "lwan.h" +#include +#include #define INT_TO_STR_BUFFER_SIZE (3 * sizeof(size_t) + 1) From c60fca37b2d72e33e66aebac38c9695038554fd2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 7 Jul 2016 07:07:56 -0300 Subject: [PATCH 0217/2505] When using systemd socket activation, allow for "systemd" listeners No need to specity address+port to listen on, since socket will be passed down from systemd itself. Just declare a listener like so: listener systemd { serve_files / { path = /wwwroot } } --- common/lwan-socket.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/common/lwan-socket.c b/common/lwan-socket.c index 530ff7bbe..2597e3389 100644 --- a/common/lwan-socket.c +++ b/common/lwan-socket.c @@ -129,8 +129,15 @@ parse_listener_ipv6(char *listener, char **node, char **port) static sa_family_t parse_listener(char *listener, char **node, char **port) { + if (!strcmp(listener, "systemd")) { + lwan_status_critical("Listener configured to use systemd socket activation, " + "but started outside systemd."); + return AF_UNSPEC; + } + if (*listener == '[') return parse_listener_ipv6(listener, node, port); + return parse_listener_ipv4(listener, node, port); } From adc755d50ec5ce3c7fb77e4dc6dfe0e1007a71bd Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 8 Jul 2016 07:40:21 -0300 Subject: [PATCH 0218/2505] Fix build where pthread_barrier_t is unavailable This should be fixed differently. I'll possibly have to resort to GCC's `#include_next` (which is also supported by Clang) and create a missing directory, with header names the same as the header names in Linux, and pass that directory first. Also, structs that are in the missing.h can't be used in public headers, since missing.h won't be installed. This can be sort of worked around by creating a `lwan-build-options.h` with all the `-DHAS_*` flags passed to GCC, and installing that. Another attempt to help with #160. --- common/epoll-bsd.c | 5 +++-- common/lwan-cache.c | 2 +- common/lwan-coro.c | 3 ++- common/lwan-http-authorize.c | 2 ++ common/lwan-io-wrappers.c | 3 ++- common/lwan-job.c | 3 ++- common/lwan-lua.c | 4 ++-- common/lwan-private.h | 2 +- common/lwan-redirect.c | 3 ++- common/lwan-request.c | 4 ++-- common/lwan-response.c | 3 ++- common/lwan-rewrite.c | 4 ++-- common/lwan-serve-files.c | 4 ++-- common/lwan-socket.c | 4 ++-- common/lwan-status.c | 1 - common/lwan-tables.c | 4 ++-- common/lwan-trie.c | 2 +- common/lwan.c | 1 - common/lwan.h | 2 +- common/missing.c | 4 +++- common/missing.h | 2 ++ common/patterns.c | 3 ++- common/realpathat.c | 1 - common/strbuf.c | 3 ++- 24 files changed, 40 insertions(+), 29 deletions(-) diff --git a/common/epoll-bsd.c b/common/epoll-bsd.c index 725a81170..b08726e8c 100644 --- a/common/epoll-bsd.c +++ b/common/epoll-bsd.c @@ -26,9 +26,10 @@ #include #include -#include "epoll-bsd.h" +#include "lwan-private.h" + #include "lwan-status.h" -#include "lwan.h" +#include "epoll-bsd.h" int epoll_create1(int flags __attribute__((unused))) diff --git a/common/lwan-cache.c b/common/lwan-cache.c index b3d068df1..91479fba5 100644 --- a/common/lwan-cache.c +++ b/common/lwan-cache.c @@ -25,8 +25,8 @@ #include #include -#include "lwan.h" #include "lwan-private.h" + #include "lwan-cache.h" #include "hash.h" diff --git a/common/lwan-coro.c b/common/lwan-coro.c index 5138f4cbd..6a5aa8ff3 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -25,7 +25,8 @@ #include #include -#include "lwan.h" +#include "lwan-private.h" + #include "lwan-coro.h" #ifdef USE_VALGRIND diff --git a/common/lwan-http-authorize.c b/common/lwan-http-authorize.c index d0c81d299..1a84be447 100644 --- a/common/lwan-http-authorize.c +++ b/common/lwan-http-authorize.c @@ -22,6 +22,8 @@ #include #include +#include "lwan-private.h" + #include "base64.h" #include "lwan-cache.h" #include "lwan-config.h" diff --git a/common/lwan-io-wrappers.c b/common/lwan-io-wrappers.c index 779b4da6e..befb2d5fc 100644 --- a/common/lwan-io-wrappers.c +++ b/common/lwan-io-wrappers.c @@ -30,7 +30,8 @@ #include #endif -#include "lwan.h" +#include "lwan-private.h" + #include "lwan-io-wrappers.h" static const int MAX_FAILED_TRIES = 5; diff --git a/common/lwan-job.c b/common/lwan-job.c index cd8f7e57e..91661e8c1 100644 --- a/common/lwan-job.c +++ b/common/lwan-job.c @@ -30,7 +30,8 @@ #include #endif -#include "lwan.h" +#include "lwan-private.h" + #include "lwan-status.h" #include "list.h" diff --git a/common/lwan-lua.c b/common/lwan-lua.c index ff5dd20a5..1c21c2782 100644 --- a/common/lwan-lua.c +++ b/common/lwan-lua.c @@ -26,11 +26,11 @@ #include #include -#include "lwan.h" +#include "lwan-private.h" + #include "lwan-cache.h" #include "lwan-config.h" #include "lwan-lua.h" -#include "lwan-private.h" static const char *request_metatable_name = "Lwan.Request"; diff --git a/common/lwan-private.h b/common/lwan-private.h index fb5613ef1..b534c62c8 100644 --- a/common/lwan-private.h +++ b/common/lwan-private.h @@ -19,8 +19,8 @@ #pragma once -#include "lwan.h" #include "missing.h" +#include "lwan.h" void lwan_response_init(lwan_t *l); void lwan_response_shutdown(lwan_t *l); diff --git a/common/lwan-redirect.c b/common/lwan-redirect.c index 4aa89715f..7b0bade47 100644 --- a/common/lwan-redirect.c +++ b/common/lwan-redirect.c @@ -20,7 +20,8 @@ #include #include -#include "lwan.h" +#include "lwan-private.h" + #include "lwan-redirect.h" static lwan_http_status_t diff --git a/common/lwan-request.c b/common/lwan-request.c index 67ce60667..09e55e40e 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -29,10 +29,10 @@ #include #include -#include "lwan.h" +#include "lwan-private.h" + #include "lwan-config.h" #include "lwan-http-authorize.h" -#include "lwan-private.h" typedef enum { FINALIZER_DONE, diff --git a/common/lwan-response.c b/common/lwan-response.c index 548e96f78..635dd16cf 100644 --- a/common/lwan-response.c +++ b/common/lwan-response.c @@ -24,8 +24,9 @@ #include #include +#include "lwan-private.h" + #include "int-to-str.h" -#include "lwan.h" #include "lwan-io-wrappers.h" #include "lwan-template.h" #include "lwan-private.h" diff --git a/common/lwan-rewrite.c b/common/lwan-rewrite.c index 83bae4ef7..60ae460a6 100644 --- a/common/lwan-rewrite.c +++ b/common/lwan-rewrite.c @@ -23,11 +23,11 @@ #include #include -#include "lwan.h" +#include "lwan-private.h" + #include "lwan-rewrite.h" #include "list.h" #include "patterns.h" -#include "lwan-private.h" struct private_data { struct list_head patterns; diff --git a/common/lwan-serve-files.c b/common/lwan-serve-files.c index e213b158d..6316ad8c2 100644 --- a/common/lwan-serve-files.c +++ b/common/lwan-serve-files.c @@ -27,7 +27,8 @@ #include #include -#include "lwan.h" +#include "lwan-private.h" + #include "lwan-cache.h" #include "lwan-config.h" #include "lwan-io-wrappers.h" @@ -35,7 +36,6 @@ #include "lwan-template.h" #include "realpathat.h" #include "hash.h" -#include "lwan-private.h" static const char *compression_none = NULL; static const char *compression_gzip = "gzip"; diff --git a/common/lwan-socket.c b/common/lwan-socket.c index 2597e3389..c9c9f1a91 100644 --- a/common/lwan-socket.c +++ b/common/lwan-socket.c @@ -29,10 +29,10 @@ #include #include -#include "lwan.h" +#include "lwan-private.h" + #include "sd-daemon.h" #include "int-to-str.h" -#include "lwan-private.h" static int get_backlog_size(void) diff --git a/common/lwan-status.c b/common/lwan-status.c index 0b061aadc..5d47ddf37 100644 --- a/common/lwan-status.c +++ b/common/lwan-status.c @@ -35,7 +35,6 @@ #include #endif -#include "lwan.h" #include "lwan-private.h" typedef enum { diff --git a/common/lwan-tables.c b/common/lwan-tables.c index aa4056f5e..05169b2a7 100644 --- a/common/lwan-tables.c +++ b/common/lwan-tables.c @@ -23,10 +23,10 @@ #include #include -#include "lwan.h" -#include "mime-types.h" #include "lwan-private.h" +#include "mime-types.h" + static unsigned char uncompressed_mime_entries[MIME_UNCOMPRESSED_LEN]; static struct mime_entry mime_entries[MIME_ENTRIES]; static bool mime_entries_initialized = false; diff --git a/common/lwan-trie.c b/common/lwan-trie.c index d943c278a..af163b140 100644 --- a/common/lwan-trie.c +++ b/common/lwan-trie.c @@ -20,7 +20,7 @@ #include #include -#include "lwan.h" +#include "lwan-private.h" bool lwan_trie_init(lwan_trie_t *trie, void (*free_node)(void *data)) diff --git a/common/lwan.c b/common/lwan.c index a2c04b68f..efe77bb42 100644 --- a/common/lwan.c +++ b/common/lwan.c @@ -35,7 +35,6 @@ #include #endif -#include "lwan.h" #include "lwan-private.h" #include "lwan-config.h" diff --git a/common/lwan.h b/common/lwan.h index a2055cc90..e7b0ee43a 100644 --- a/common/lwan.h +++ b/common/lwan.h @@ -280,7 +280,7 @@ struct lwan_thread_t_ { int epoll_fd; int pipe_fd[2]; pthread_t self; - pthread_barrier_t *barrier; + void *barrier; }; struct lwan_config_t_ { diff --git a/common/missing.c b/common/missing.c index d5a664cbd..abfe53b27 100644 --- a/common/missing.c +++ b/common/missing.c @@ -29,7 +29,9 @@ #ifndef HAS_PTHREADBARRIER #define PTHREAD_BARRIER_SERIAL_THREAD -1 int -pthread_barrier_init(pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restrict attr, unsigned int count) { +pthread_barrier_init(pthread_barrier_t *restrict barrier, + const pthread_barrierattr_t *restrict attr __attribute__((unused)), + unsigned int count) { if (count == 0) { return -1; } diff --git a/common/missing.h b/common/missing.h index 4a3864cf9..935465093 100644 --- a/common/missing.h +++ b/common/missing.h @@ -47,6 +47,8 @@ #endif #ifndef HAS_PTHREADBARRIER +#include + typedef int pthread_barrierattr_t; typedef struct pthread_barrier { unsigned int count; diff --git a/common/patterns.c b/common/patterns.c index cbd6dfaea..e83a953e0 100644 --- a/common/patterns.c +++ b/common/patterns.c @@ -37,8 +37,9 @@ #include #include +#include "lwan-private.h" + #include "patterns.h" -#include "lwan.h" #define uchar(c) ((unsigned char)(c)) /* macro to 'unsign' a char */ #define CAP_UNFINISHED (-1) diff --git a/common/realpathat.c b/common/realpathat.c index 200f35615..1d26d38ec 100644 --- a/common/realpathat.c +++ b/common/realpathat.c @@ -32,7 +32,6 @@ #include #include -#include "lwan.h" #include "lwan-private.h" char * diff --git a/common/strbuf.c b/common/strbuf.c index 73f8bc7f4..e9b1a6d75 100644 --- a/common/strbuf.c +++ b/common/strbuf.c @@ -23,7 +23,8 @@ #include #include -#include "lwan.h" +#include "lwan-private.h" + #include "strbuf.h" static const unsigned int STATIC = 1<<0; From 7c34910099dd61f8042e68d62a9c1a2c5003dbfd Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 8 Jul 2016 08:08:31 -0300 Subject: [PATCH 0219/2505] Revert `adc755d50': this broke build on Linux --- common/epoll-bsd.c | 5 ++--- common/lwan-cache.c | 2 +- common/lwan-coro.c | 3 +-- common/lwan-http-authorize.c | 2 -- common/lwan-io-wrappers.c | 3 +-- common/lwan-job.c | 3 +-- common/lwan-lua.c | 4 ++-- common/lwan-private.h | 2 +- common/lwan-redirect.c | 3 +-- common/lwan-request.c | 4 ++-- common/lwan-response.c | 3 +-- common/lwan-rewrite.c | 4 ++-- common/lwan-serve-files.c | 4 ++-- common/lwan-socket.c | 4 ++-- common/lwan-status.c | 1 + common/lwan-tables.c | 4 ++-- common/lwan-trie.c | 2 +- common/lwan.c | 1 + common/lwan.h | 2 +- common/missing.c | 4 +--- common/missing.h | 2 -- common/patterns.c | 3 +-- common/realpathat.c | 1 + common/strbuf.c | 3 +-- 24 files changed, 29 insertions(+), 40 deletions(-) diff --git a/common/epoll-bsd.c b/common/epoll-bsd.c index b08726e8c..725a81170 100644 --- a/common/epoll-bsd.c +++ b/common/epoll-bsd.c @@ -26,10 +26,9 @@ #include #include -#include "lwan-private.h" - -#include "lwan-status.h" #include "epoll-bsd.h" +#include "lwan-status.h" +#include "lwan.h" int epoll_create1(int flags __attribute__((unused))) diff --git a/common/lwan-cache.c b/common/lwan-cache.c index 91479fba5..b3d068df1 100644 --- a/common/lwan-cache.c +++ b/common/lwan-cache.c @@ -25,8 +25,8 @@ #include #include +#include "lwan.h" #include "lwan-private.h" - #include "lwan-cache.h" #include "hash.h" diff --git a/common/lwan-coro.c b/common/lwan-coro.c index 6a5aa8ff3..5138f4cbd 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -25,8 +25,7 @@ #include #include -#include "lwan-private.h" - +#include "lwan.h" #include "lwan-coro.h" #ifdef USE_VALGRIND diff --git a/common/lwan-http-authorize.c b/common/lwan-http-authorize.c index 1a84be447..d0c81d299 100644 --- a/common/lwan-http-authorize.c +++ b/common/lwan-http-authorize.c @@ -22,8 +22,6 @@ #include #include -#include "lwan-private.h" - #include "base64.h" #include "lwan-cache.h" #include "lwan-config.h" diff --git a/common/lwan-io-wrappers.c b/common/lwan-io-wrappers.c index befb2d5fc..779b4da6e 100644 --- a/common/lwan-io-wrappers.c +++ b/common/lwan-io-wrappers.c @@ -30,8 +30,7 @@ #include #endif -#include "lwan-private.h" - +#include "lwan.h" #include "lwan-io-wrappers.h" static const int MAX_FAILED_TRIES = 5; diff --git a/common/lwan-job.c b/common/lwan-job.c index 91661e8c1..cd8f7e57e 100644 --- a/common/lwan-job.c +++ b/common/lwan-job.c @@ -30,8 +30,7 @@ #include #endif -#include "lwan-private.h" - +#include "lwan.h" #include "lwan-status.h" #include "list.h" diff --git a/common/lwan-lua.c b/common/lwan-lua.c index 1c21c2782..ff5dd20a5 100644 --- a/common/lwan-lua.c +++ b/common/lwan-lua.c @@ -26,11 +26,11 @@ #include #include -#include "lwan-private.h" - +#include "lwan.h" #include "lwan-cache.h" #include "lwan-config.h" #include "lwan-lua.h" +#include "lwan-private.h" static const char *request_metatable_name = "Lwan.Request"; diff --git a/common/lwan-private.h b/common/lwan-private.h index b534c62c8..fb5613ef1 100644 --- a/common/lwan-private.h +++ b/common/lwan-private.h @@ -19,8 +19,8 @@ #pragma once -#include "missing.h" #include "lwan.h" +#include "missing.h" void lwan_response_init(lwan_t *l); void lwan_response_shutdown(lwan_t *l); diff --git a/common/lwan-redirect.c b/common/lwan-redirect.c index 7b0bade47..4aa89715f 100644 --- a/common/lwan-redirect.c +++ b/common/lwan-redirect.c @@ -20,8 +20,7 @@ #include #include -#include "lwan-private.h" - +#include "lwan.h" #include "lwan-redirect.h" static lwan_http_status_t diff --git a/common/lwan-request.c b/common/lwan-request.c index 09e55e40e..67ce60667 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -29,10 +29,10 @@ #include #include -#include "lwan-private.h" - +#include "lwan.h" #include "lwan-config.h" #include "lwan-http-authorize.h" +#include "lwan-private.h" typedef enum { FINALIZER_DONE, diff --git a/common/lwan-response.c b/common/lwan-response.c index 635dd16cf..548e96f78 100644 --- a/common/lwan-response.c +++ b/common/lwan-response.c @@ -24,9 +24,8 @@ #include #include -#include "lwan-private.h" - #include "int-to-str.h" +#include "lwan.h" #include "lwan-io-wrappers.h" #include "lwan-template.h" #include "lwan-private.h" diff --git a/common/lwan-rewrite.c b/common/lwan-rewrite.c index 60ae460a6..83bae4ef7 100644 --- a/common/lwan-rewrite.c +++ b/common/lwan-rewrite.c @@ -23,11 +23,11 @@ #include #include -#include "lwan-private.h" - +#include "lwan.h" #include "lwan-rewrite.h" #include "list.h" #include "patterns.h" +#include "lwan-private.h" struct private_data { struct list_head patterns; diff --git a/common/lwan-serve-files.c b/common/lwan-serve-files.c index 6316ad8c2..e213b158d 100644 --- a/common/lwan-serve-files.c +++ b/common/lwan-serve-files.c @@ -27,8 +27,7 @@ #include #include -#include "lwan-private.h" - +#include "lwan.h" #include "lwan-cache.h" #include "lwan-config.h" #include "lwan-io-wrappers.h" @@ -36,6 +35,7 @@ #include "lwan-template.h" #include "realpathat.h" #include "hash.h" +#include "lwan-private.h" static const char *compression_none = NULL; static const char *compression_gzip = "gzip"; diff --git a/common/lwan-socket.c b/common/lwan-socket.c index c9c9f1a91..2597e3389 100644 --- a/common/lwan-socket.c +++ b/common/lwan-socket.c @@ -29,10 +29,10 @@ #include #include -#include "lwan-private.h" - +#include "lwan.h" #include "sd-daemon.h" #include "int-to-str.h" +#include "lwan-private.h" static int get_backlog_size(void) diff --git a/common/lwan-status.c b/common/lwan-status.c index 5d47ddf37..0b061aadc 100644 --- a/common/lwan-status.c +++ b/common/lwan-status.c @@ -35,6 +35,7 @@ #include #endif +#include "lwan.h" #include "lwan-private.h" typedef enum { diff --git a/common/lwan-tables.c b/common/lwan-tables.c index 05169b2a7..aa4056f5e 100644 --- a/common/lwan-tables.c +++ b/common/lwan-tables.c @@ -23,9 +23,9 @@ #include #include -#include "lwan-private.h" - +#include "lwan.h" #include "mime-types.h" +#include "lwan-private.h" static unsigned char uncompressed_mime_entries[MIME_UNCOMPRESSED_LEN]; static struct mime_entry mime_entries[MIME_ENTRIES]; diff --git a/common/lwan-trie.c b/common/lwan-trie.c index af163b140..d943c278a 100644 --- a/common/lwan-trie.c +++ b/common/lwan-trie.c @@ -20,7 +20,7 @@ #include #include -#include "lwan-private.h" +#include "lwan.h" bool lwan_trie_init(lwan_trie_t *trie, void (*free_node)(void *data)) diff --git a/common/lwan.c b/common/lwan.c index efe77bb42..a2c04b68f 100644 --- a/common/lwan.c +++ b/common/lwan.c @@ -35,6 +35,7 @@ #include #endif +#include "lwan.h" #include "lwan-private.h" #include "lwan-config.h" diff --git a/common/lwan.h b/common/lwan.h index e7b0ee43a..a2055cc90 100644 --- a/common/lwan.h +++ b/common/lwan.h @@ -280,7 +280,7 @@ struct lwan_thread_t_ { int epoll_fd; int pipe_fd[2]; pthread_t self; - void *barrier; + pthread_barrier_t *barrier; }; struct lwan_config_t_ { diff --git a/common/missing.c b/common/missing.c index abfe53b27..d5a664cbd 100644 --- a/common/missing.c +++ b/common/missing.c @@ -29,9 +29,7 @@ #ifndef HAS_PTHREADBARRIER #define PTHREAD_BARRIER_SERIAL_THREAD -1 int -pthread_barrier_init(pthread_barrier_t *restrict barrier, - const pthread_barrierattr_t *restrict attr __attribute__((unused)), - unsigned int count) { +pthread_barrier_init(pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restrict attr, unsigned int count) { if (count == 0) { return -1; } diff --git a/common/missing.h b/common/missing.h index 935465093..4a3864cf9 100644 --- a/common/missing.h +++ b/common/missing.h @@ -47,8 +47,6 @@ #endif #ifndef HAS_PTHREADBARRIER -#include - typedef int pthread_barrierattr_t; typedef struct pthread_barrier { unsigned int count; diff --git a/common/patterns.c b/common/patterns.c index e83a953e0..cbd6dfaea 100644 --- a/common/patterns.c +++ b/common/patterns.c @@ -37,9 +37,8 @@ #include #include -#include "lwan-private.h" - #include "patterns.h" +#include "lwan.h" #define uchar(c) ((unsigned char)(c)) /* macro to 'unsign' a char */ #define CAP_UNFINISHED (-1) diff --git a/common/realpathat.c b/common/realpathat.c index 1d26d38ec..200f35615 100644 --- a/common/realpathat.c +++ b/common/realpathat.c @@ -32,6 +32,7 @@ #include #include +#include "lwan.h" #include "lwan-private.h" char * diff --git a/common/strbuf.c b/common/strbuf.c index e9b1a6d75..73f8bc7f4 100644 --- a/common/strbuf.c +++ b/common/strbuf.c @@ -23,8 +23,7 @@ #include #include -#include "lwan-private.h" - +#include "lwan.h" #include "strbuf.h" static const unsigned int STATIC = 1<<0; From cf2500fecc5b618881c1b4f1ea9449774f073f14 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 9 Jul 2016 16:57:42 -0300 Subject: [PATCH 0220/2505] Fix build on OS X. Tested on OS X Yosemite. Closes #160. --- common/lwan-cache.c | 2 -- common/lwan-coro.c | 2 +- common/lwan-io-wrappers.c | 14 +++++++------- common/lwan-straitjacket.c | 5 +++++ common/lwan.h | 2 +- common/missing.c | 8 +++++--- 6 files changed, 19 insertions(+), 14 deletions(-) diff --git a/common/lwan-cache.c b/common/lwan-cache.c index b3d068df1..a1732c1d4 100644 --- a/common/lwan-cache.c +++ b/common/lwan-cache.c @@ -122,9 +122,7 @@ struct cache_t *cache_create(CreateEntryCallback create_entry_cb, cache->cb.destroy_entry = destroy_entry_cb; cache->cb.context = cb_context; -#ifndef __MACH__ cache->settings.clock_id = detect_fastest_monotonic_clock(); -#endif cache->settings.time_to_live = time_to_live; list_head_init(&cache->queue.list); diff --git a/common/lwan-coro.c b/common/lwan-coro.c index 5138f4cbd..2472c29f4 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -25,7 +25,7 @@ #include #include -#include "lwan.h" +#include "lwan-private.h" #include "lwan-coro.h" #ifdef USE_VALGRIND diff --git a/common/lwan-io-wrappers.c b/common/lwan-io-wrappers.c index 779b4da6e..1af03dc94 100644 --- a/common/lwan-io-wrappers.c +++ b/common/lwan-io-wrappers.c @@ -212,7 +212,7 @@ lwan_sendfile(lwan_request_t *request, int in_fd, off_t offset, size_t count, coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); } while (to_be_written > 0); } -#elif defined(__FreeBSD__) +#elif defined(__FreeBSD__) || defined(__APPLE__) void lwan_sendfile(lwan_request_t *request, int in_fd, off_t offset, size_t count, const char *header, size_t header_len) @@ -227,12 +227,16 @@ lwan_sendfile(lwan_request_t *request, int in_fd, off_t offset, size_t count, .hdr_cnt = 1 }; size_t total_written = 0; + off_t sbytes = (off_t)count; do { - off_t sbytes; int r; +#ifdef __APPLE__ + r = sendfile(request->fd, in_fd, offset, &sbytes, &headers, 0); +#else r = sendfile(request->fd, in_fd, offset, count, &headers, &sbytes, SF_MNOWAIT); +#endif if (UNLIKELY(r < 0)) { switch (errno) { @@ -256,9 +260,5 @@ lwan_sendfile(lwan_request_t *request, int in_fd, off_t offset, size_t count, } while (total_written < count); } #else -void -lwan_sendfile(lwan_request_t *request, int in_fd, off_t offset, size_t count, - const char *header, size_t header_len) -{ -} +#error No sendfile() implementation for this platform #endif diff --git a/common/lwan-straitjacket.c b/common/lwan-straitjacket.c index fd11bfe46..de17160a1 100644 --- a/common/lwan-straitjacket.c +++ b/common/lwan-straitjacket.c @@ -77,8 +77,13 @@ static bool switch_to_user(uid_t uid, gid_t gid, const char *username) if (setgid(gid) < 0) return false; +#if defined(__APPLE__) + if (initgroups(username, (int)gid) < 0) + return false; +#else if (initgroups(username, gid) < 0) return false; +#endif if (setuid(uid) < 0) return false; diff --git a/common/lwan.h b/common/lwan.h index a2055cc90..e7b0ee43a 100644 --- a/common/lwan.h +++ b/common/lwan.h @@ -280,7 +280,7 @@ struct lwan_thread_t_ { int epoll_fd; int pipe_fd[2]; pthread_t self; - pthread_barrier_t *barrier; + void *barrier; }; struct lwan_config_t_ { diff --git a/common/missing.c b/common/missing.c index d5a664cbd..6a87c25e7 100644 --- a/common/missing.c +++ b/common/missing.c @@ -29,7 +29,9 @@ #ifndef HAS_PTHREADBARRIER #define PTHREAD_BARRIER_SERIAL_THREAD -1 int -pthread_barrier_init(pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restrict attr, unsigned int count) { +pthread_barrier_init(pthread_barrier_t *restrict barrier, + const pthread_barrierattr_t *restrict attr __attribute__((unused)), + unsigned int count) { if (count == 0) { return -1; } @@ -90,13 +92,13 @@ memrchr(const void *s, int c, size_t n) { const unsigned char *cp; unsigned char *p = (unsigned char *)s; - unsigned char chr = c; + unsigned char chr = (unsigned char)c; if (n != 0) { cp = p + n; do { if (*(--cp) == chr) - return cp; + return (void *)cp; } while (--n != 0); } From 1289baeb09b3ff3d4e870153c1143e29c7d23456 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 10 Jul 2016 09:10:28 -0300 Subject: [PATCH 0221/2505] Add FreeBSD and OS X buildbot badges --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2263ef30f..511c463e1 100644 --- a/README.md +++ b/README.md @@ -217,8 +217,8 @@ Not really third-party, but alas: Build status ------------ -| Release | Debug | Static Analysis | Unit Tests | -|---------|-------|-----------------|------------| -| ![release](https://buildbot.lwan.ws/buildstatusimage?builder=release&number=-1 "Release") | ![debug](https://buildbot.lwan.ws/buildstatusimage?builder=debug&number=-1 "Debug") | ![clang](https://buildbot.lwan.ws/buildstatusimage?builder=clang-analyze&number=-1 "Clang") ![coverity](https://scan.coverity.com/projects/375/badge.svg)| ![tests](https://buildbot.lwan.ws/buildstatusimage?builder=unit-tests&number=-1 "Tests") -| [Waterfall](https://buildbot.lwan.ws/waterfall?show=release) | [Waterfall](https://buildbot.lwan.ws/waterfall?show=debug) | [Waterfall](https://buildbot.lwan.ws/waterfall?show=clang-analyze) - [Reports](https://buildbot.lwan.ws/sa/) | [Waterfall](https://buildbot.lwan.ws/waterfall?show=unit-tests) | - +| Platform | Release | Debug | Static Analysis | Tests | +|----------|---------|-------|-----------------|------------| +| Linux | ![release](https://buildbot.lwan.ws/buildstatusimage?builder=release&number=-1 "Release") | ![debug](https://buildbot.lwan.ws/buildstatusimage?builder=debug&number=-1 "Debug") | ![static-analysis](https://buildbot.lwan.ws/buildstatusimage?builder=clang-analyze&number=-1 "Static Analysis") ![coverity](https://scan.coverity.com/projects/375/badge.svg) [Report history](https://buildbot.lwan.ws/sa/) | ![tests](https://buildbot.lwan.ws/buildstatusimage?builder=unit-tests&number=-1 "Test") | +| FreeBSD | ![freebsd-release](https://buildbot.lwan.ws/buildstatusimage?builder=release-freebsd&number=-1 "Release FreeBSD") | ![freebsd-debug](https://buildbot.lwan.ws/buildstatusimage?builder=debug-freebsd&number=-1 "Debug FreeBSD") | | | +| OS X | ![osx-release](https://buildbot.lwan.ws/buildstatusimage?builder=release-yosemite&number=-1 "Release OS X") | ![osx-debug](https://buildbot.lwan.ws/buildstatusimage?builder=debug-yosemite&number=-1 "Debug OS X") | | | From ead25a06796114862991f708d78b96d215825893 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 10 Jul 2016 09:11:47 -0300 Subject: [PATCH 0222/2505] Get the order of file descriptors in call to sendfile() right --- common/lwan-io-wrappers.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/lwan-io-wrappers.c b/common/lwan-io-wrappers.c index 1af03dc94..b0284b420 100644 --- a/common/lwan-io-wrappers.c +++ b/common/lwan-io-wrappers.c @@ -26,7 +26,7 @@ #if defined(__linux__) #include -#elif defined(__FreeBSD__) +#elif defined(__FreeBSD__) || defined(__APPLE__) #include #endif @@ -233,9 +233,9 @@ lwan_sendfile(lwan_request_t *request, int in_fd, off_t offset, size_t count, int r; #ifdef __APPLE__ - r = sendfile(request->fd, in_fd, offset, &sbytes, &headers, 0); + r = sendfile(in_fd, request->fd, offset, &sbytes, &headers, 0); #else - r = sendfile(request->fd, in_fd, offset, count, &headers, &sbytes, SF_MNOWAIT); + r = sendfile(in_fd, request->fd, offset, count, &headers, &sbytes, SF_MNOWAIT); #endif if (UNLIKELY(r < 0)) { From 58b26940ec1eefc6163a111d9ab0b204ab044e00 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 10 Jul 2016 14:56:31 -0300 Subject: [PATCH 0223/2505] Use #include_next instead of having a missing.h header This should ease portability a little bit without having to remember where to include missing.h. This requires a recent-ish Clang or GCC due to #include_next. --- CMakeLists.txt | 2 + common/CMakeLists.txt | 6 -- common/epoll-bsd.c | 143 ------------------------------------ common/epoll-bsd.h | 35 --------- common/lwan-private.h | 1 - common/lwan-response.c | 3 +- common/lwan-serve-files.c | 1 + common/lwan-thread.c | 8 -- common/missing.c | 126 ++++++++++++++++++++++++++++++- common/missing.h | 123 ------------------------------- common/missing/assert.h | 32 ++++++++ common/missing/fcntl.h | 33 +++++++++ common/missing/limits.h | 33 +++++++++ common/missing/pthread.h | 47 ++++++++++++ common/missing/string.h | 51 +++++++++++++ common/missing/sys/epoll.h | 60 +++++++++++++++ common/missing/sys/socket.h | 41 +++++++++++ common/missing/time.h | 40 ++++++++++ common/missing/unistd.h | 29 ++++++++ 19 files changed, 496 insertions(+), 318 deletions(-) delete mode 100644 common/epoll-bsd.c delete mode 100644 common/missing.h create mode 100644 common/missing/assert.h create mode 100644 common/missing/fcntl.h create mode 100644 common/missing/limits.h create mode 100644 common/missing/pthread.h create mode 100644 common/missing/string.h create mode 100644 common/missing/sys/epoll.h create mode 100644 common/missing/sys/socket.h create mode 100644 common/missing/time.h create mode 100644 common/missing/unistd.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 004acc830..f197787d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -194,6 +194,8 @@ if (CMAKE_COMPILER_IS_GNUCC) set(LWAN_COMMON_LIBS -Wl,-whole-archive ${LWAN_COMMON_LIBS} -Wl,-no-whole-archive) endif () +include_directories(BEFORE common/missing) + add_subdirectory(common) include_directories(common) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 8fe19d0c6..bb0e9de30 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -31,12 +31,6 @@ set(SOURCES strbuf.c ) -include(CheckIncludeFiles) -check_include_files(sys/epoll.h HAVE_EPOLL_H) -if (NOT HAVE_EPOLL_H) - list(APPEND SOURCES epoll-bsd.c) -endif () - include(CheckFunctionExists) set(CMAKE_EXTRA_INCLUDE_FILES string.h) check_function_exists(rawmemchr HAS_RAWMEMCHR) diff --git a/common/epoll-bsd.c b/common/epoll-bsd.c deleted file mode 100644 index 725a81170..000000000 --- a/common/epoll-bsd.c +++ /dev/null @@ -1,143 +0,0 @@ -/* - * lwan - simple web server - * Copyright (c) 2016 Leandro A. F. Pereira - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "epoll-bsd.h" -#include "lwan-status.h" -#include "lwan.h" - -int -epoll_create1(int flags __attribute__((unused))) -{ - return kqueue(); -} - -int -epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) -{ - struct kevent ev; - - switch (op) { - case EPOLL_CTL_ADD: - case EPOLL_CTL_MOD: { - int events = 0; - int flags = EV_ADD; - - if (event->events & EPOLLIN) - events = EVFILT_READ; - if (event->events & EPOLLOUT) - events = EVFILT_WRITE; - - if (event->events & EPOLLONESHOT) - flags |= EV_ONESHOT; - if (event->events & EPOLLRDHUP) - flags |= EV_EOF; - if (event->events & EPOLLERR) - flags |= EV_ERROR; - if (event->events & EPOLLET) - flags |= EV_CLEAR; - - EV_SET(&ev, fd, events, flags, 0, 0, event->data.ptr); - break; - } - - case EPOLL_CTL_DEL: - EV_SET(&ev, fd, 0, EV_DELETE, 0, 0, 0); - break; - - default: - errno = EINVAL; - return -1; - } - - return kevent(epfd, &ev, 1, NULL, 0, NULL); -} - -static struct timespec * -to_timespec(struct timespec *t, int ms) -{ - if (ms < 0) - return NULL; - - t->tv_sec = ms / 1000; - t->tv_nsec = (ms % 1000) * 1000000; - - return t; -} - -int -epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) -{ - struct epoll_event *ev = events; - struct kevent evs[maxevents]; - struct timespec tmspec; - struct hash *coalesce; - int i, r; - - coalesce = hash_int_new(NULL, NULL); - if (!coalesce) - return -1; - - r = kevent(epfd, NULL, 0, evs, maxevents, to_timespec(&tmspec, timeout)); - if (UNLIKELY(r < 0)) - return -1; - - for (i = 0; i < r; i++) { - struct kevent *kev = &evs[i]; - uint32_t mask = (uint32_t)(uintptr_t)hash_find(coalesce, - (void*)(intptr_t)evs[i].ident); - - if (kev->flags & EV_ERROR) - mask |= EPOLLERR; - if (kev->flags & EV_EOF) - mask |= EPOLLRDHUP; - - if (kev->filter == EVFILT_READ) - mask |= EPOLLIN; - else if (kev->filter == EVFILT_WRITE) - mask |= EPOLLOUT; - - hash_add(coalesce, (void*)(intptr_t)evs[i].ident, (void *)(uintptr_t)mask); - } - - for (i = 0; i < r; i++) { - void *maskptr = hash_find(coalesce, (void*)(intptr_t)evs[i].ident); - - if (maskptr) { - struct kevent *kev = &evs[i]; - - hash_del(coalesce, (void*)(intptr_t)evs[i].ident); - - ev->data.ptr = kev->udata; - ev->events = (uint32_t)(uintptr_t)maskptr; - ev++; - } - } - - hash_free(coalesce); - return (int)(intptr_t)(ev - events); -} diff --git a/common/epoll-bsd.h b/common/epoll-bsd.h index 6a06e40b6..ad198b4ca 100644 --- a/common/epoll-bsd.h +++ b/common/epoll-bsd.h @@ -19,38 +19,3 @@ #pragma once -#include - -enum { - EPOLLIN = 1<<0, - EPOLLOUT = 1<<1, - EPOLLONESHOT = 1<<2, - EPOLLRDHUP = 1<<3, - EPOLLERR = 1<<4, - EPOLLET = 1<<5, - EPOLLHUP = EPOLLRDHUP -} epoll_event_flag; - -enum { - EPOLL_CTL_ADD, - EPOLL_CTL_MOD, - EPOLL_CTL_DEL -} epoll_op; - -enum { - EPOLL_CLOEXEC = 1<<0 -} epoll_create_flags; - -struct epoll_event { - uint32_t events; - union { - void *ptr; - int fd; - uint32_t u32; - uint64_t u64; - } data; -}; - -int epoll_create1(int flags); -int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); -int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); diff --git a/common/lwan-private.h b/common/lwan-private.h index fb5613ef1..b19d6d922 100644 --- a/common/lwan-private.h +++ b/common/lwan-private.h @@ -20,7 +20,6 @@ #pragma once #include "lwan.h" -#include "missing.h" void lwan_response_init(lwan_t *l); void lwan_response_shutdown(lwan_t *l); diff --git a/common/lwan-response.c b/common/lwan-response.c index 548e96f78..e88bd7a75 100644 --- a/common/lwan-response.c +++ b/common/lwan-response.c @@ -19,10 +19,11 @@ #define _GNU_SOURCE #include +#include #include +#include #include #include -#include #include "int-to-str.h" #include "lwan.h" diff --git a/common/lwan-serve-files.c b/common/lwan-serve-files.c index e213b158d..46e9d81e9 100644 --- a/common/lwan-serve-files.c +++ b/common/lwan-serve-files.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include diff --git a/common/lwan-thread.c b/common/lwan-thread.c index 76e036e3c..5053bf9dd 100644 --- a/common/lwan-thread.c +++ b/common/lwan-thread.c @@ -25,15 +25,7 @@ #include #include #include - -#ifdef __FreeBSD__ -#include -#include "epoll-bsd.h" -#elif __APPLE__ -#include "epoll-bsd.h" -#elif __linux__ #include -#endif #include "lwan-private.h" diff --git a/common/missing.c b/common/missing.c index 6a87c25e7..42d00178f 100644 --- a/common/missing.c +++ b/common/missing.c @@ -19,12 +19,14 @@ #include #include +#include #include +#include #include #include #include -#include "missing.h" +#include "lwan.h" #ifndef HAS_PTHREADBARRIER #define PTHREAD_BARRIER_SERIAL_THREAD -1 @@ -172,7 +174,9 @@ clock_gettime(clockid_t clk_id, struct timespec *ts) { switch (clk_id) { case CLOCK_MONOTONIC: +#ifndef __APPLE__ case CLOCK_MONOTONIC_COARSE: +#endif /* FIXME: time() isn't monotonic */ ts->tv_sec = time(NULL); ts->tv_nsec = 0; @@ -192,3 +196,123 @@ pthread_timedjoin_np(pthread_t thread, void **retval, return pthread_join(thread, retval); } #endif + +#if defined(__FreeBSD__) || defined(__APPLE__) +#include +#include +#include + +#include "hash.h" + +int +epoll_create1(int flags __attribute__((unused))) +{ + return kqueue(); +} + +int +epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) +{ + struct kevent ev; + + switch (op) { + case EPOLL_CTL_ADD: + case EPOLL_CTL_MOD: { + int events = 0; + int flags = EV_ADD; + + if (event->events & EPOLLIN) + events = EVFILT_READ; + if (event->events & EPOLLOUT) + events = EVFILT_WRITE; + + if (event->events & EPOLLONESHOT) + flags |= EV_ONESHOT; + if (event->events & EPOLLRDHUP) + flags |= EV_EOF; + if (event->events & EPOLLERR) + flags |= EV_ERROR; + if (event->events & EPOLLET) + flags |= EV_CLEAR; + + EV_SET(&ev, fd, events, flags, 0, 0, event->data.ptr); + break; + } + + case EPOLL_CTL_DEL: + EV_SET(&ev, fd, 0, EV_DELETE, 0, 0, 0); + break; + + default: + errno = EINVAL; + return -1; + } + + return kevent(epfd, &ev, 1, NULL, 0, NULL); +} + +static struct timespec * +to_timespec(struct timespec *t, int ms) +{ + if (ms < 0) + return NULL; + + t->tv_sec = ms / 1000; + t->tv_nsec = (ms % 1000) * 1000000; + + return t; +} + +int +epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) +{ + struct epoll_event *ev = events; + struct kevent evs[maxevents]; + struct timespec tmspec; + struct hash *coalesce; + int i, r; + + coalesce = hash_int_new(NULL, NULL); + if (!coalesce) + return -1; + + r = kevent(epfd, NULL, 0, evs, maxevents, to_timespec(&tmspec, timeout)); + if (UNLIKELY(r < 0)) + return -1; + + for (i = 0; i < r; i++) { + struct kevent *kev = &evs[i]; + uint32_t mask = (uint32_t)(uintptr_t)hash_find(coalesce, + (void*)(intptr_t)evs[i].ident); + + if (kev->flags & EV_ERROR) + mask |= EPOLLERR; + if (kev->flags & EV_EOF) + mask |= EPOLLRDHUP; + + if (kev->filter == EVFILT_READ) + mask |= EPOLLIN; + else if (kev->filter == EVFILT_WRITE) + mask |= EPOLLOUT; + + hash_add(coalesce, (void*)(intptr_t)evs[i].ident, (void *)(uintptr_t)mask); + } + + for (i = 0; i < r; i++) { + void *maskptr = hash_find(coalesce, (void*)(intptr_t)evs[i].ident); + + if (maskptr) { + struct kevent *kev = &evs[i]; + + hash_del(coalesce, (void*)(intptr_t)evs[i].ident); + + ev->data.ptr = kev->udata; + ev->events = (uint32_t)(uintptr_t)maskptr; + ev++; + } + } + + hash_free(coalesce); + return (int)(intptr_t)(ev - events); +} +#endif diff --git a/common/missing.h b/common/missing.h deleted file mode 100644 index 4a3864cf9..000000000 --- a/common/missing.h +++ /dev/null @@ -1,123 +0,0 @@ -/* - * lwan - simple web server - * Copyright (c) 2012 Leandro A. F. Pereira - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#pragma once - -#include - -#undef static_assert -#if HAVE_STATIC_ASSERT -#define static_assert(expr, msg) _Static_assert(expr, msg) -#else -#define static_assert(expr, msg) -#endif - -#define strndupa_impl(s, l) ({ \ - char *strndupa_tmp_s = alloca(l + 1); \ - strndupa_tmp_s[l] = '\0'; \ - memcpy(strndupa_tmp_s, s, l); \ -}) - -#ifndef strndupa -#define strndupa(s, l) strndupa_impl((s), strnlen((s), (l))) -#endif - -#ifndef strdupa -#define strdupa(s) strndupa((s), strlen(s)) -#endif - -#ifndef HAS_RAWMEMCHR -#define rawmemchr(s, c) memchr((s), (c), SIZE_MAX) -#endif - -#ifndef HAS_PTHREADBARRIER -typedef int pthread_barrierattr_t; -typedef struct pthread_barrier { - unsigned int count; - unsigned int in; - pthread_mutex_t mutex; - pthread_cond_t cond; -} pthread_barrier_t; - -int pthread_barrier_init(pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restrict attr, unsigned int count); -int pthread_barrier_destroy(pthread_barrier_t *barrier); -int pthread_barrier_wait(pthread_barrier_t *barrier); -#endif - -#ifndef HAS_MEMPCPY -void *mempcpy(void *dest, const void *src, size_t len); -#endif - -#ifndef HAS_MEMRCHR -void *memrchr(const void *s, int c, size_t n); -#endif - -#ifndef HAS_PIPE2 -int pipe2(int pipefd[2], int flags); -#endif - -#ifndef HAS_ACCEPT4 -int accept4(int sock, struct sockaddr *addr, socklen_t *addrlen, int flags); -#endif - -#ifndef HAS_CLOCK_GETTIME -typedef int clockid_t; -int clock_gettime(clockid_t clk_id, struct timespec *ts); - -# ifndef CLOCK_MONOTONIC_COARSE -# define CLOCK_MONOTONIC_COARSE 0 -# endif - -# ifndef CLOCK_MONOTONIC -# define CLOCK_MONOTONIC 1 -# endif -#endif - -#ifndef HAS_TIMEDJOIN -#include -int pthread_timedjoin_np(pthread_t thread, void **retval, const struct timespec *abstime); -#endif - -#ifndef MSG_MORE -#define MSG_MORE 0 -#endif - -#ifndef O_NOATIME -#define O_NOATIME 0 -#endif - -#ifndef O_PATH -#define O_PATH 0 -#endif - -#ifndef SOCK_CLOEXEC -#define SOCK_CLOEXEC 0 -#endif - -#ifndef PATH_MAX -#define PATH_MAX 4096 -#endif - -#ifndef OPEN_MAX -#define OPEN_MAX 65535 -#endif - -#ifndef SOCK_NONBLOCK -#define SOCK_NONBLOCK 00004000 -#endif diff --git a/common/missing/assert.h b/common/missing/assert.h new file mode 100644 index 000000000..b0879cc7a --- /dev/null +++ b/common/missing/assert.h @@ -0,0 +1,32 @@ +/* + * lwan - simple web server + * Copyright (c) 2012 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include_next + +#ifndef MISSING_ASSERT_H +#define MISSING_ASSERT_H + +#undef static_assert +#if HAVE_STATIC_ASSERT +# define static_assert(expr, msg) _Static_assert(expr, msg) +#else +# define static_assert(expr, msg) +#endif + +#endif /* MISSING_ASSERT_H */ diff --git a/common/missing/fcntl.h b/common/missing/fcntl.h new file mode 100644 index 000000000..394d77b72 --- /dev/null +++ b/common/missing/fcntl.h @@ -0,0 +1,33 @@ +/* + * lwan - simple web server + * Copyright (c) 2012 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include_next + +#ifndef MISSING_FCNTL_H +#define MISSING_FCNTL_H + +#ifndef O_NOATIME +# define O_NOATIME 0 +#endif + +#ifndef O_PATH +# define O_PATH 0 +#endif + +#endif /* MISSING_FCNTL_H */ diff --git a/common/missing/limits.h b/common/missing/limits.h new file mode 100644 index 000000000..61ffc27b1 --- /dev/null +++ b/common/missing/limits.h @@ -0,0 +1,33 @@ +/* + * lwan - simple web server + * Copyright (c) 2012 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include_next + +#ifndef MISSING_LIMITS_H +#define MISSING_LIMITS_H + +#ifndef PATH_MAX +# define PATH_MAX 4096 +#endif + +#ifndef OPEN_MAX +# define OPEN_MAX 65535 +#endif + +#endif /* MISSING_LIMITS_H */ diff --git a/common/missing/pthread.h b/common/missing/pthread.h new file mode 100644 index 000000000..f04ae0818 --- /dev/null +++ b/common/missing/pthread.h @@ -0,0 +1,47 @@ +/* + * lwan - simple web server + * Copyright (c) 2012 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include_next + +#ifndef MISSING_PTHREAD_H +#define MISSING_PTHREAD_H + +#ifndef HAS_PTHREADBARRIER +typedef int pthread_barrierattr_t; +typedef struct pthread_barrier { + unsigned int count; + unsigned int in; + pthread_mutex_t mutex; + pthread_cond_t cond; +} pthread_barrier_t; + +int pthread_barrier_init(pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restrict attr, unsigned int count); +int pthread_barrier_destroy(pthread_barrier_t *barrier); +int pthread_barrier_wait(pthread_barrier_t *barrier); +#endif + +#ifndef HAS_TIMEDJOIN +int pthread_timedjoin_np(pthread_t thread, void **retval, const struct timespec *abstime); +#endif + +#ifdef __FreeBSD__ +# include +#endif + +#endif /* MISSING_PTHREAD_H */ diff --git a/common/missing/string.h b/common/missing/string.h new file mode 100644 index 000000000..ab7fc6acf --- /dev/null +++ b/common/missing/string.h @@ -0,0 +1,51 @@ +/* + * lwan - simple web server + * Copyright (c) 2012 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include_next + +#ifndef MISSING_STRING_H +#define MISSING_STRING_H + +#define strndupa_impl(s, l) ({ \ + char *strndupa_tmp_s = alloca(l + 1); \ + strndupa_tmp_s[l] = '\0'; \ + memcpy(strndupa_tmp_s, s, l); \ +}) + +#ifndef strndupa +#define strndupa(s, l) strndupa_impl((s), strnlen((s), (l))) +#endif + +#ifndef strdupa +#define strdupa(s) strndupa((s), strlen(s)) +#endif + +#ifndef HAS_RAWMEMCHR +#define rawmemchr(s, c) memchr((s), (c), SIZE_MAX) +#endif + +#ifndef HAS_MEMPCPY +void *mempcpy(void *dest, const void *src, size_t len); +#endif + +#ifndef HAS_MEMRCHR +void *memrchr(const void *s, int c, size_t n); +#endif + +#endif /* MISSING_STRING_H */ diff --git a/common/missing/sys/epoll.h b/common/missing/sys/epoll.h new file mode 100644 index 000000000..71e3d8bb2 --- /dev/null +++ b/common/missing/sys/epoll.h @@ -0,0 +1,60 @@ +/* + * lwan - simple web server + * Copyright (c) 2012 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#if defined(__FreeBSD__) || defined(__APPLE__) +#pragma once +#include + +enum { + EPOLLIN = 1<<0, + EPOLLOUT = 1<<1, + EPOLLONESHOT = 1<<2, + EPOLLRDHUP = 1<<3, + EPOLLERR = 1<<4, + EPOLLET = 1<<5, + EPOLLHUP = EPOLLRDHUP +} epoll_event_flag; + +enum { + EPOLL_CTL_ADD, + EPOLL_CTL_MOD, + EPOLL_CTL_DEL +} epoll_op; + +enum { + EPOLL_CLOEXEC = 1<<0 +} epoll_create_flags; + +struct epoll_event { + uint32_t events; + union { + void *ptr; + int fd; + uint32_t u32; + uint64_t u64; + } data; +}; + +int epoll_create1(int flags); +int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); +int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); + +#else +#include_next +#endif diff --git a/common/missing/sys/socket.h b/common/missing/sys/socket.h new file mode 100644 index 000000000..5de33db80 --- /dev/null +++ b/common/missing/sys/socket.h @@ -0,0 +1,41 @@ +/* + * lwan - simple web server + * Copyright (c) 2012 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include_next + +#ifndef MISSING_SYS_SOCKET_H +#define MISSING_SYS_SOCKET_H + +#ifndef MSG_MORE +# define MSG_MORE 0 +#endif + +#ifndef SOCK_CLOEXEC +# define SOCK_CLOEXEC 0 +#endif + +#ifndef SOCK_NONBLOCK +# define SOCK_NONBLOCK 00004000 +#endif + +#ifndef HAS_ACCEPT4 +int accept4(int sock, struct sockaddr *addr, socklen_t *addrlen, int flags); +#endif + +#endif /* MISSING_SYS_SOCKET_H */ diff --git a/common/missing/time.h b/common/missing/time.h new file mode 100644 index 000000000..334d4e19f --- /dev/null +++ b/common/missing/time.h @@ -0,0 +1,40 @@ +/* + * lwan - simple web server + * Copyright (c) 2012 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include_next + +#ifndef MISSING_TIME_H +#define MISSING_TIME_H + +#ifndef HAS_CLOCK_GETTIME +typedef int clockid_t; +int clock_gettime(clockid_t clk_id, struct timespec *ts); + +# ifndef __APPLE__ +# ifndef CLOCK_MONOTONIC_COARSE +# define CLOCK_MONOTONIC_COARSE 0 +# endif +# endif + +# ifndef CLOCK_MONOTONIC +# define CLOCK_MONOTONIC 1 +# endif +#endif + +#endif /* MISSING_TIME_H */ diff --git a/common/missing/unistd.h b/common/missing/unistd.h new file mode 100644 index 000000000..9622cd3a8 --- /dev/null +++ b/common/missing/unistd.h @@ -0,0 +1,29 @@ +/* + * lwan - simple web server + * Copyright (c) 2012 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include_next + +#ifndef MISSING_UNISTD_H +#define MISSING_UNISTD_H + +#ifndef HAS_PIPE2 +int pipe2(int pipefd[2], int flags); +#endif + +#endif /* MISSING_UNISTD_H */ From eb295ea29668d70a5d05c8705f6a8ff2c68028ad Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 10 Jul 2016 17:28:47 -0300 Subject: [PATCH 0224/2505] Use __sync_add_and_fetch() inside pthread_barrier_wait() impl. Instead of using __sync_fetch_and_add(), call __sync_add_and_fetch() directly and compare with the counter in the barrier. --- common/missing.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/common/missing.c b/common/missing.c index 42d00178f..2b9d03c1b 100644 --- a/common/missing.c +++ b/common/missing.c @@ -65,8 +65,7 @@ int pthread_barrier_wait(pthread_barrier_t *barrier) { pthread_mutex_lock(&barrier->mutex); - __sync_fetch_and_add(&barrier->in, 1); - if (barrier->in >= barrier->count) { + if (__sync_add_and_fetch(&barrier->in, 1) >= barrier->count) { barrier->in = 0; pthread_cond_broadcast(&barrier->cond); pthread_mutex_unlock(&barrier->mutex); From f90432b9f4677ef8e7a421cf0a8df19d4dd93111 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 10 Jul 2016 17:38:54 -0300 Subject: [PATCH 0225/2505] Organize #include directives Do not include lwan.h and , as the former include the latter. Always include after the system headers before other Lwan headers. Blank after the #include directive. Remove #include for FreeBSD targets, as includes that automatically now. Do not include conditionally for sendfile(), do that inside . --- common/hash.c | 8 ++++---- common/int-to-str.c | 3 ++- common/list.c | 1 + common/lwan-cache.c | 2 +- common/lwan-config.c | 1 + common/lwan-coro.c | 1 + common/lwan-io-wrappers.c | 5 ----- common/lwan-job.c | 4 ---- common/lwan-lua.c | 4 ++-- common/lwan-request.c | 4 ++-- common/lwan-response.c | 4 ++-- common/lwan-rewrite.c | 4 ++-- common/lwan-serve-files.c | 4 ++-- common/lwan-socket.c | 4 ++-- common/lwan-status.c | 1 - common/lwan-straitjacket.c | 3 ++- common/lwan-tables.c | 4 ++-- common/lwan-template.c | 3 ++- common/lwan.c | 1 - common/missing/sys/sendfile.h | 29 +++++++++++++++++++++++++++++ common/murmur3.c | 3 ++- common/realpathat.c | 1 - 22 files changed, 59 insertions(+), 35 deletions(-) create mode 100644 common/missing/sys/sendfile.h diff --git a/common/hash.c b/common/hash.c index 20230f4ad..b62bf933b 100644 --- a/common/hash.c +++ b/common/hash.c @@ -19,10 +19,6 @@ */ #define _GNU_SOURCE -#include "hash.h" -#include "murmur3.h" -#include "reallocarray.h" - #include #include #include @@ -34,6 +30,10 @@ #include #include +#include "hash.h" +#include "murmur3.h" +#include "reallocarray.h" + enum { n_buckets = 512, steps = 64, diff --git a/common/int-to-str.c b/common/int-to-str.c index 875383980..356bb059b 100644 --- a/common/int-to-str.c +++ b/common/int-to-str.c @@ -19,9 +19,10 @@ #include -#include "int-to-str.h" #include "lwan-private.h" +#include "int-to-str.h" + ALWAYS_INLINE char * uint_to_string(size_t value, char dst[static INT_TO_STR_BUFFER_SIZE], diff --git a/common/list.c b/common/list.c index 9aa6009aa..eba6eafb0 100644 --- a/common/list.c +++ b/common/list.c @@ -29,6 +29,7 @@ #include #include + #include "list.h" static void *corrupt(const char *abortstr, diff --git a/common/lwan-cache.c b/common/lwan-cache.c index a1732c1d4..efa1a1e20 100644 --- a/common/lwan-cache.c +++ b/common/lwan-cache.c @@ -25,8 +25,8 @@ #include #include -#include "lwan.h" #include "lwan-private.h" + #include "lwan-cache.h" #include "hash.h" diff --git a/common/lwan-config.c b/common/lwan-config.c index b46e07d53..5c3073994 100644 --- a/common/lwan-config.c +++ b/common/lwan-config.c @@ -29,6 +29,7 @@ #include #include "lwan-private.h" + #include "lwan-config.h" #include "lwan-status.h" #include "hash.h" diff --git a/common/lwan-coro.c b/common/lwan-coro.c index 2472c29f4..6a5aa8ff3 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -26,6 +26,7 @@ #include #include "lwan-private.h" + #include "lwan-coro.h" #ifdef USE_VALGRIND diff --git a/common/lwan-io-wrappers.c b/common/lwan-io-wrappers.c index b0284b420..fddbd8a8c 100644 --- a/common/lwan-io-wrappers.c +++ b/common/lwan-io-wrappers.c @@ -23,12 +23,7 @@ #include #include #include - -#if defined(__linux__) #include -#elif defined(__FreeBSD__) || defined(__APPLE__) -#include -#endif #include "lwan.h" #include "lwan-io-wrappers.h" diff --git a/common/lwan-job.c b/common/lwan-job.c index cd8f7e57e..3cda9a741 100644 --- a/common/lwan-job.c +++ b/common/lwan-job.c @@ -26,10 +26,6 @@ #include #include -#ifdef __FreeBSD__ -#include -#endif - #include "lwan.h" #include "lwan-status.h" #include "list.h" diff --git a/common/lwan-lua.c b/common/lwan-lua.c index ff5dd20a5..1c21c2782 100644 --- a/common/lwan-lua.c +++ b/common/lwan-lua.c @@ -26,11 +26,11 @@ #include #include -#include "lwan.h" +#include "lwan-private.h" + #include "lwan-cache.h" #include "lwan-config.h" #include "lwan-lua.h" -#include "lwan-private.h" static const char *request_metatable_name = "Lwan.Request"; diff --git a/common/lwan-request.c b/common/lwan-request.c index 67ce60667..09e55e40e 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -29,10 +29,10 @@ #include #include -#include "lwan.h" +#include "lwan-private.h" + #include "lwan-config.h" #include "lwan-http-authorize.h" -#include "lwan-private.h" typedef enum { FINALIZER_DONE, diff --git a/common/lwan-response.c b/common/lwan-response.c index e88bd7a75..85b7b2f9c 100644 --- a/common/lwan-response.c +++ b/common/lwan-response.c @@ -25,11 +25,11 @@ #include #include +#include "lwan-private.h" + #include "int-to-str.h" -#include "lwan.h" #include "lwan-io-wrappers.h" #include "lwan-template.h" -#include "lwan-private.h" static lwan_tpl_t *error_template = NULL; diff --git a/common/lwan-rewrite.c b/common/lwan-rewrite.c index 83bae4ef7..60ae460a6 100644 --- a/common/lwan-rewrite.c +++ b/common/lwan-rewrite.c @@ -23,11 +23,11 @@ #include #include -#include "lwan.h" +#include "lwan-private.h" + #include "lwan-rewrite.h" #include "list.h" #include "patterns.h" -#include "lwan-private.h" struct private_data { struct list_head patterns; diff --git a/common/lwan-serve-files.c b/common/lwan-serve-files.c index 46e9d81e9..5b722c73e 100644 --- a/common/lwan-serve-files.c +++ b/common/lwan-serve-files.c @@ -28,7 +28,8 @@ #include #include -#include "lwan.h" +#include "lwan-private.h" + #include "lwan-cache.h" #include "lwan-config.h" #include "lwan-io-wrappers.h" @@ -36,7 +37,6 @@ #include "lwan-template.h" #include "realpathat.h" #include "hash.h" -#include "lwan-private.h" static const char *compression_none = NULL; static const char *compression_gzip = "gzip"; diff --git a/common/lwan-socket.c b/common/lwan-socket.c index 2597e3389..c9c9f1a91 100644 --- a/common/lwan-socket.c +++ b/common/lwan-socket.c @@ -29,10 +29,10 @@ #include #include -#include "lwan.h" +#include "lwan-private.h" + #include "sd-daemon.h" #include "int-to-str.h" -#include "lwan-private.h" static int get_backlog_size(void) diff --git a/common/lwan-status.c b/common/lwan-status.c index 0b061aadc..5d47ddf37 100644 --- a/common/lwan-status.c +++ b/common/lwan-status.c @@ -35,7 +35,6 @@ #include #endif -#include "lwan.h" #include "lwan-private.h" typedef enum { diff --git a/common/lwan-straitjacket.c b/common/lwan-straitjacket.c index de17160a1..651b120cb 100644 --- a/common/lwan-straitjacket.c +++ b/common/lwan-straitjacket.c @@ -26,9 +26,10 @@ #include #include +#include "lwan-private.h" + #include "lwan-config.h" #include "lwan-status.h" -#include "lwan-private.h" static bool get_user_uid_gid(const char *user, uid_t *uid, gid_t *gid) { diff --git a/common/lwan-tables.c b/common/lwan-tables.c index aa4056f5e..05169b2a7 100644 --- a/common/lwan-tables.c +++ b/common/lwan-tables.c @@ -23,10 +23,10 @@ #include #include -#include "lwan.h" -#include "mime-types.h" #include "lwan-private.h" +#include "mime-types.h" + static unsigned char uncompressed_mime_entries[MIME_UNCOMPRESSED_LEN]; static struct mime_entry mime_entries[MIME_ENTRIES]; static bool mime_entries_initialized = false; diff --git a/common/lwan-template.c b/common/lwan-template.c index 88746a302..a8bad6fb1 100644 --- a/common/lwan-template.c +++ b/common/lwan-template.c @@ -37,13 +37,14 @@ #include #include +#include "lwan-private.h" + #include "hash.h" #include "int-to-str.h" #include "list.h" #include "lwan-template.h" #include "strbuf.h" #include "reallocarray.h" -#include "lwan-private.h" enum action { ACTION_APPEND, diff --git a/common/lwan.c b/common/lwan.c index a2c04b68f..efe77bb42 100644 --- a/common/lwan.c +++ b/common/lwan.c @@ -35,7 +35,6 @@ #include #endif -#include "lwan.h" #include "lwan-private.h" #include "lwan-config.h" diff --git a/common/missing/sys/sendfile.h b/common/missing/sys/sendfile.h new file mode 100644 index 000000000..a371d8920 --- /dev/null +++ b/common/missing/sys/sendfile.h @@ -0,0 +1,29 @@ +/* + * lwan - simple web server + * Copyright (c) 2012 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include_next + +#ifndef MISSING_SENDFILE_H +#define MISSING_SENDFILE_H + +#if defined(__FreeBSD__) || defined(__APPLE__) +# include +#endif + +#endif /* MISSING_SENDFILE_H */ diff --git a/common/murmur3.c b/common/murmur3.c index ac45322b6..970f635da 100644 --- a/common/murmur3.c +++ b/common/murmur3.c @@ -7,10 +7,11 @@ // compile and run any of them on any platform, but your performance with the // non-native version will be less than optimal. -#include "murmur3.h" #include #include +#include "murmur3.h" + //----------------------------------------------------------------------------- // Platform-specific functions and macros diff --git a/common/realpathat.c b/common/realpathat.c index 200f35615..1d26d38ec 100644 --- a/common/realpathat.c +++ b/common/realpathat.c @@ -32,7 +32,6 @@ #include #include -#include "lwan.h" #include "lwan-private.h" char * From 98cc1b8e25f59d3722c5734220b1f607fd9e9b63 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 10 Jul 2016 17:50:36 -0300 Subject: [PATCH 0226/2505] Fix build on OS X and FreeBSD --- common/missing/sys/sendfile.h | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/common/missing/sys/sendfile.h b/common/missing/sys/sendfile.h index a371d8920..2d5a03304 100644 --- a/common/missing/sys/sendfile.h +++ b/common/missing/sys/sendfile.h @@ -17,13 +17,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include_next - -#ifndef MISSING_SENDFILE_H -#define MISSING_SENDFILE_H - #if defined(__FreeBSD__) || defined(__APPLE__) # include +#else +# include_next #endif - -#endif /* MISSING_SENDFILE_H */ From da8b310462ada0d56ba2be1dcf1666bb8ae89436 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 11 Jul 2016 20:31:34 -0300 Subject: [PATCH 0227/2505] Remove two pairs of #ifdef __APPLE__ in clock_gettime() impl. --- common/missing.c | 2 -- common/missing/time.h | 6 ++---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/common/missing.c b/common/missing.c index 2b9d03c1b..b6a0b971d 100644 --- a/common/missing.c +++ b/common/missing.c @@ -173,9 +173,7 @@ clock_gettime(clockid_t clk_id, struct timespec *ts) { switch (clk_id) { case CLOCK_MONOTONIC: -#ifndef __APPLE__ case CLOCK_MONOTONIC_COARSE: -#endif /* FIXME: time() isn't monotonic */ ts->tv_sec = time(NULL); ts->tv_nsec = 0; diff --git a/common/missing/time.h b/common/missing/time.h index 334d4e19f..adbbb411c 100644 --- a/common/missing/time.h +++ b/common/missing/time.h @@ -26,10 +26,8 @@ typedef int clockid_t; int clock_gettime(clockid_t clk_id, struct timespec *ts); -# ifndef __APPLE__ -# ifndef CLOCK_MONOTONIC_COARSE -# define CLOCK_MONOTONIC_COARSE 0 -# endif +# ifndef CLOCK_MONOTONIC_COARSE +# define CLOCK_MONOTONIC_COARSE 0 # endif # ifndef CLOCK_MONOTONIC From 6309723e783147fd7a2fba81a35838676608ce81 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 11 Jul 2016 20:38:58 -0300 Subject: [PATCH 0228/2505] Fix possible stack overflow in lwan-lua::get_handler_function() --- common/lwan-lua.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/common/lwan-lua.c b/common/lwan-lua.c index 1c21c2782..a47fabcad 100644 --- a/common/lwan-lua.c +++ b/common/lwan-lua.c @@ -288,18 +288,19 @@ static ALWAYS_INLINE const char *get_handle_prefix(lwan_request_t *request, size static bool get_handler_function(lua_State *L, lwan_request_t *request) { + char handler_name[128]; size_t handle_prefix_len; const char *handle_prefix = get_handle_prefix(request, &handle_prefix_len); + if (UNLIKELY(!handle_prefix)) return false; - - char handler_name[128]; - char *method_name = mempcpy(handler_name, handle_prefix, handle_prefix_len); + if (UNLIKELY(request->url.len >= sizeof(handler_name) - handle_prefix_len)) + return false; char *url; size_t url_len; if (request->url.len) { - url = strdupa(request->url.value); + url = strndupa(request->url.value, request->url.len); for (char *c = url; *c; c++) { if (*c == '/') { *c = '\0'; @@ -316,6 +317,8 @@ static bool get_handler_function(lua_State *L, lwan_request_t *request) if (UNLIKELY((handle_prefix_len + url_len + 1) > sizeof(handler_name))) return false; + + char *method_name = mempcpy(handler_name, handle_prefix, handle_prefix_len); memcpy(method_name - 1, url, url_len + 1); lua_getglobal(L, handler_name); From fbedd412afe35c42b8a4dec397b56794a40b7772 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 11 Jul 2016 20:58:03 -0300 Subject: [PATCH 0229/2505] Use posix_memalign() to not depend on C11's aligned_alloc() --- common/CMakeLists.txt | 5 +++++ common/lwan.c | 14 +------------- common/missing.c | 1 + 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index bb0e9de30..592b90557 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -48,6 +48,11 @@ if (HAS_MEMRCHR) add_definitions(-DHAS_MEMRCHR) endif () +check_function_exists(posix_memalign HAS_POSIX_MEMALIGN) +if (HAS_POSIX_MEMALIGN) + add_definitions(-DHAS_POSIX_MEMALIGN) +endif () + set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) check_function_exists(pthread_timedjoin_np HAS_TIMEDJOIN) if (HAS_TIMEDJOIN) diff --git a/common/lwan.c b/common/lwan.c index efe77bb42..9b97d540c 100644 --- a/common/lwan.c +++ b/common/lwan.c @@ -488,7 +488,6 @@ setup_open_file_count_limits(void) return r.rlim_cur; } -#if defined(_ISOC11_SOURCE) static inline size_t align_to_size(size_t value, size_t alignment) { @@ -500,22 +499,11 @@ allocate_connections(lwan_t *l, size_t max_open_files) { const size_t sz = max_open_files * sizeof(lwan_connection_t); - l->conns = aligned_alloc(64, align_to_size(sz, 64)); - if (!l->conns) + if (posix_memalign((void **)&l->conns, 64, align_to_size(sz, 64))) lwan_status_critical_perror("aligned_alloc"); memset(l->conns, 0, sz); } -#else -static void -allocate_connections(lwan_t *l, size_t max_open_files) -{ - l->conns = calloc(max_open_files, sizeof(lwan_connection_t)); - - if (!l->conns) - lwan_status_critical_perror("calloc"); -} -#endif static unsigned short int get_number_of_cpus(void) diff --git a/common/missing.c b/common/missing.c index b6a0b971d..5dadf4c20 100644 --- a/common/missing.c +++ b/common/missing.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include From b0e010666d7c4d5de1d44efbc6db3a44cdfa1c56 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 11 Jul 2016 21:55:26 -0300 Subject: [PATCH 0230/2505] Remove empty epoll-bsd.h Contents moved to missing/sys/epoll.h. --- common/epoll-bsd.h | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 common/epoll-bsd.h diff --git a/common/epoll-bsd.h b/common/epoll-bsd.h deleted file mode 100644 index ad198b4ca..000000000 --- a/common/epoll-bsd.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - * lwan - simple web server - * Copyright (c) 2016 Leandro A. F. Pereira - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#pragma once - From e52ea8f7c41a86541a8beb4b19b95171a046de8a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 12 Jul 2016 08:01:54 -0300 Subject: [PATCH 0231/2505] No need to check if posix_memalign() is available It's available in all supported platforms. --- common/CMakeLists.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 592b90557..bb0e9de30 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -48,11 +48,6 @@ if (HAS_MEMRCHR) add_definitions(-DHAS_MEMRCHR) endif () -check_function_exists(posix_memalign HAS_POSIX_MEMALIGN) -if (HAS_POSIX_MEMALIGN) - add_definitions(-DHAS_POSIX_MEMALIGN) -endif () - set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) check_function_exists(pthread_timedjoin_np HAS_TIMEDJOIN) if (HAS_TIMEDJOIN) From c0f873527a94be9f71af2cf9707db963862ccbb3 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 12 Jul 2016 08:51:33 -0300 Subject: [PATCH 0232/2505] Use proc_pidpath() on OS X to get the executable name --- common/lwan.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/common/lwan.c b/common/lwan.c index 9b97d540c..475d7f33a 100644 --- a/common/lwan.c +++ b/common/lwan.c @@ -33,6 +33,8 @@ #if defined(__FreeBSD__) #include +#elif defined(__APPLE__) +#include #endif #include "lwan-private.h" @@ -354,8 +356,6 @@ static void parse_listener(config_t *c, config_line_t *l, lwan_t *lwan) const char *get_config_path(char *path_buf) { char buffer[PATH_MAX]; - char *path = NULL; - int ret; #if defined(__linux__) ssize_t path_len; @@ -370,19 +370,21 @@ const char *get_config_path(char *path_buf) size_t path_len = PATH_MAX; int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; - ret = sysctl(mib, N_ELEMENTS(mib), buffer, &path_len, NULL, 0); - if (ret < 0) { + if (sysctl(mib, N_ELEMENTS(mib), buffer, &path_len, NULL, 0) < 0) { lwan_status_perror("sysctl"); goto out; } +#elif defined(__APPLE__) + if (proc_pidpath(getpid(), buffer, sizeof(buffer)) < 0) + goto out; #else goto out; #endif - path = strrchr(buffer, '/'); + char *path = strrchr(buffer, '/'); if (!path) goto out; - ret = snprintf(path_buf, PATH_MAX, "%s.conf", path + 1); + int ret = snprintf(path_buf, PATH_MAX, "%s.conf", path + 1); if (ret < 0 || ret >= PATH_MAX) goto out; From b81cec6c1eb5ba0b08e6529ed83adafa5ea01e07 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 12 Jul 2016 09:01:04 -0300 Subject: [PATCH 0233/2505] Provide proc_pidpath() for FreeBSD and Linux Removes some #ifdef chains inside lwan.c. --- common/lwan.c | 32 +++----------------------------- common/missing.c | 38 ++++++++++++++++++++++++++++++++++++++ common/missing/libproc.h | 27 +++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 29 deletions(-) create mode 100644 common/missing/libproc.h diff --git a/common/lwan.c b/common/lwan.c index 475d7f33a..a16262146 100644 --- a/common/lwan.c +++ b/common/lwan.c @@ -22,20 +22,15 @@ #include #include #include +#include #include #include #include #include #include -#include -#include #include - -#if defined(__FreeBSD__) -#include -#elif defined(__APPLE__) -#include -#endif +#include +#include #include "lwan-private.h" @@ -357,29 +352,8 @@ const char *get_config_path(char *path_buf) { char buffer[PATH_MAX]; -#if defined(__linux__) - ssize_t path_len; - - path_len = readlink("/proc/self/exe", buffer, PATH_MAX); - if (path_len < 0 || path_len >= PATH_MAX) { - lwan_status_perror("readlink"); - goto out; - } - buffer[path_len] = '\0'; -#elif defined(__FreeBSD__) - size_t path_len = PATH_MAX; - int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; - - if (sysctl(mib, N_ELEMENTS(mib), buffer, &path_len, NULL, 0) < 0) { - lwan_status_perror("sysctl"); - goto out; - } -#elif defined(__APPLE__) if (proc_pidpath(getpid(), buffer, sizeof(buffer)) < 0) goto out; -#else - goto out; -#endif char *path = strrchr(buffer, '/'); if (!path) diff --git a/common/missing.c b/common/missing.c index 5dadf4c20..94797002a 100644 --- a/common/missing.c +++ b/common/missing.c @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -314,3 +315,40 @@ epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) return (int)(intptr_t)(ev - events); } #endif + +#ifndef __APPLE__ + +#ifdef __FreeBSD__ +#include +#endif + +int +proc_pidpath(pid_t pid, void *buffer, size_t buffersize) +{ + if (getpid() != pid) { + errno = EINVAL; + return -1; + } + +#ifdef __linux__ + ssize_t path_len; + + path_len = readlink("/proc/self/exe", buffer, buffersize); + if (path_len < 0 || path_len >= (ssize_t)buffersize) { + errno = EOVERFLOW; + return -1; + } + ((char *)buffer)[path_len] = '\0'; +#elif __FreeBSD__ + size_t path_len = buffersize; + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; + + if (sysctl(mib, N_ELEMENTS(mib), buffer, &path_len, NULL, 0) < 0) + return -1; +#else + errno = ENOSYS; + return -1; +#endif + return 0; +} +#endif diff --git a/common/missing/libproc.h b/common/missing/libproc.h new file mode 100644 index 000000000..f5ccb236a --- /dev/null +++ b/common/missing/libproc.h @@ -0,0 +1,27 @@ +/* + * lwan - simple web server + * Copyright (c) 2012 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#if defined(__APPLE__) +#include_next +#else +#include +#include + +int proc_pidpath(pid_t pid, void *buffer, size_t buffersize); +#endif From f08d8aa9b2920b5958b9dbb964beec0b97bddfb1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 12 Jul 2016 20:02:37 -0300 Subject: [PATCH 0234/2505] Plug key memory leak in hash table When freeing a hash table, if no callback function was provided to free a value, but a callback function was provided to free a key, the latter function wouldn't be called. --- common/hash.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/common/hash.c b/common/hash.c index b62bf933b..28329457d 100644 --- a/common/hash.c +++ b/common/hash.c @@ -211,15 +211,14 @@ void hash_free(struct hash *hash) bucket = hash->buckets; bucket_end = bucket + n_buckets; for (; bucket < bucket_end; bucket++) { - if (hash->free_value) { - struct hash_entry *entry, *entry_end; - entry = bucket->entries; - entry_end = entry + bucket->used; - for (; entry < entry_end; entry++) { + struct hash_entry *entry, *entry_end; + entry = bucket->entries; + entry_end = entry + bucket->used; + for (; entry < entry_end; entry++) { + if (hash->free_value) hash->free_value((void *)entry->value); - if (hash->free_key) - hash->free_key((void *)entry->key); - } + if (hash->free_key) + hash->free_key((void *)entry->key); } free(bucket->entries); } From 2f01bd0d5a8d9c8a107b114af9593818a6561975 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 12 Jul 2016 20:03:45 -0300 Subject: [PATCH 0235/2505] Duplicate names in module registry The module registry hash table was storing pointer to a memory region within the stack of a call frame that would be long gone whenever the hash table would be freed or consulted. --- common/lwan.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/lwan.c b/common/lwan.c index a16262146..980f59105 100644 --- a/common/lwan.c +++ b/common/lwan.c @@ -58,7 +58,7 @@ static void lwan_module_init(lwan_t *l) { if (!l->module_registry) { lwan_status_debug("Initializing module registry"); - l->module_registry = hash_str_new(NULL, NULL); + l->module_registry = hash_str_new(free, NULL); } } @@ -113,7 +113,7 @@ static const lwan_module_t *lwan_module_find(lwan_t *l, const char *name) } lwan_status_debug("Module \"%s\" registered", name); - hash_add(l->module_registry, name, module); + hash_add(l->module_registry, strdup(name), module); } return module; From 9e0e0b10717d727df5d95d8fd10fb1408c257bcf Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 13 Jul 2016 08:45:58 -0300 Subject: [PATCH 0236/2505] Set errno properly in proc_pidpath() impl. for Linux and FreeBSD --- common/missing.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/common/missing.c b/common/missing.c index 94797002a..12b863a22 100644 --- a/common/missing.c +++ b/common/missing.c @@ -326,7 +326,7 @@ int proc_pidpath(pid_t pid, void *buffer, size_t buffersize) { if (getpid() != pid) { - errno = EINVAL; + errno = EACCES; return -1; } @@ -334,7 +334,9 @@ proc_pidpath(pid_t pid, void *buffer, size_t buffersize) ssize_t path_len; path_len = readlink("/proc/self/exe", buffer, buffersize); - if (path_len < 0 || path_len >= (ssize_t)buffersize) { + if (path_len < 0) + return -1; + if (path_len >= (ssize_t)buffersize) { errno = EOVERFLOW; return -1; } From 5acc014dd62582e2bfc0521891f03969313807cb Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 16 Jul 2016 16:47:40 -0300 Subject: [PATCH 0237/2505] Reduce branching when deleting items from hash table Indirect function calls are always performed there anyway, so define a no-op function that's assigned to free_key and free_value in case those functions are not specified by the user. --- common/hash.c | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/common/hash.c b/common/hash.c index 28329457d..e215a48f5 100644 --- a/common/hash.c +++ b/common/hash.c @@ -152,6 +152,10 @@ static inline int hash_int_key_cmp(const void *k1, const void *k2) return (a > b) - (a < b); } +static void no_op(void *arg __attribute__((unused))) +{ +} + static struct hash *hash_internal_new( unsigned (*hash_value)(const void *key), int (*key_compare)(const void *k1, const void *k2), @@ -164,8 +168,8 @@ static struct hash *hash_internal_new( return NULL; hash->hash_value = hash_value; hash->key_compare = key_compare; - hash->free_value = free_value; - hash->free_key = free_key; + hash->free_value = free_value ? free_value : no_op; + hash->free_key = free_key ? free_key : no_op; return hash; } @@ -174,8 +178,8 @@ struct hash *hash_int_new(void (*free_key)(void *value), { return hash_internal_new(hash_int, hash_int_key_cmp, - free_key, - free_value); + free_key ? free_key : no_op, + free_value ? free_value : no_op); } struct hash *hash_str_new(void (*free_key)(void *value), @@ -197,8 +201,8 @@ struct hash *hash_str_new(void (*free_key)(void *value), return hash_internal_new( hash_func, (int (*)(const void *, const void *))strcmp, - free_key, - free_value); + free_key ? free_key : no_op, + free_value ? free_value : no_op); } void hash_free(struct hash *hash) @@ -215,10 +219,8 @@ void hash_free(struct hash *hash) entry = bucket->entries; entry_end = entry + bucket->used; for (; entry < entry_end; entry++) { - if (hash->free_value) - hash->free_value((void *)entry->value); - if (hash->free_key) - hash->free_key((void *)entry->key); + hash->free_value((void *)entry->value); + hash->free_key((void *)entry->key); } free(bucket->entries); } @@ -254,10 +256,8 @@ int hash_add(struct hash *hash, const void *key, const void *value) continue; if (hash->key_compare(key, entry->key) != 0) continue; - if (hash->free_value) - hash->free_value((void *)entry->value); - if (hash->free_key) - hash->free_key((void *)entry->key); + hash->free_value((void *)entry->value); + hash->free_key((void *)entry->key); entry->key = key; entry->value = value; @@ -348,10 +348,8 @@ int hash_del(struct hash *hash, const void *key) if (entry == NULL) return -ENOENT; - if (hash->free_value) - hash->free_value((void *)entry->value); - if (hash->free_key) - hash->free_key((void *)entry->key); + hash->free_value((void *)entry->value); + hash->free_key((void *)entry->key); entry_end = bucket->entries + bucket->used; memmove(entry, entry + 1, From 05358b3bcfb29fc028898d8bae78d5b1d49a9d6b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 12 Jul 2016 22:09:32 -0300 Subject: [PATCH 0238/2505] Fix race condition when shutting down server --- common/CMakeLists.txt | 6 ----- common/lwan-thread.c | 54 ++++++++++++++++++++++++++++++---------- common/missing.c | 9 ------- common/missing/pthread.h | 8 ------ 4 files changed, 41 insertions(+), 36 deletions(-) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index bb0e9de30..51e37e5a0 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -48,12 +48,6 @@ if (HAS_MEMRCHR) add_definitions(-DHAS_MEMRCHR) endif () -set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) -check_function_exists(pthread_timedjoin_np HAS_TIMEDJOIN) -if (HAS_TIMEDJOIN) - add_definitions(-DHAS_TIMEDJOIN) -endif () - set(CMAKE_EXTRA_INCLUDE_FILES unistd.h) check_function_exists(pipe2 HAS_PIPE2) if (HAS_PIPE2) diff --git a/common/lwan-thread.c b/common/lwan-thread.c index 5053bf9dd..72114bb59 100644 --- a/common/lwan-thread.c +++ b/common/lwan-thread.c @@ -300,13 +300,22 @@ spawn_coro(lwan_connection_t *conn, death_queue_insert(dq, conn); } -static lwan_connection_t * -grab_and_watch_client(int epoll_fd, int pipe_fd, lwan_connection_t *conns) +static int +grab_command(int pipe_fd) { - int fd; - if (UNLIKELY(read(pipe_fd, &fd, sizeof(int)) != sizeof(int))) - return NULL; + int cmd; + ssize_t sz = read(pipe_fd, &cmd, sizeof(cmd)); + + if (UNLIKELY(sz < 0)) + return -1; + if (UNLIKELY((size_t)sz < sizeof(cmd))) + return -2; + return cmd; +} +static lwan_connection_t * +watch_client(int epoll_fd, int fd, lwan_connection_t *conns) +{ struct epoll_event event = { .events = events_by_write_flag[1], .data.ptr = &conns[fd] @@ -363,11 +372,18 @@ thread_io_loop(void *data) lwan_connection_t *conn; if (!ep_event->data.ptr) { - conn = grab_and_watch_client(epoll_fd, read_pipe_fd, conns); - if (UNLIKELY(!conn)) + int cmd = grab_command(read_pipe_fd); + if (LIKELY(cmd >= 0)) { + conn = watch_client(epoll_fd, cmd, conns); + spawn_coro(conn, &switcher, &dq); + } else if (UNLIKELY(cmd == -1)) { continue; - - spawn_coro(conn, &switcher, &dq); + } else if (UNLIKELY(cmd == -2)) { + goto epoll_fd_closed; + } else { + lwan_status_debug("Unknown command received, ignored"); + continue; + } } else { conn = ep_event->data.ptr; if (UNLIKELY(ep_event->events & (EPOLLRDHUP | EPOLLHUP))) { @@ -384,6 +400,9 @@ thread_io_loop(void *data) } epoll_fd_closed: + assert(t->barrier); + pthread_barrier_wait(t->barrier); + death_queue_kill_all(&dq); free(events); @@ -461,13 +480,20 @@ lwan_thread_init(lwan_t *l) void lwan_thread_shutdown(lwan_t *l) { + pthread_barrier_t barrier; + lwan_status_debug("Shutting down threads"); + if (pthread_barrier_init(&barrier, NULL, (unsigned)l->thread.count + 1)) + lwan_status_critical("Could not create barrier"); + for (int i = l->thread.count - 1; i >= 0; i--) { lwan_thread_t *t = &l->thread.threads[i]; char less_than_int = 0; ssize_t r; + t->barrier = &barrier; + lwan_status_debug("Closing epoll for thread %d (fd=%d)", i, t->epoll_fd); @@ -489,17 +515,19 @@ lwan_thread_shutdown(lwan_t *l) } } + pthread_barrier_wait(&barrier); + pthread_barrier_destroy(&barrier); + for (int i = l->thread.count - 1; i >= 0; i--) { lwan_thread_t *t = &l->thread.threads[i]; - lwan_status_debug("Waiting for thread %d to finish", i); - pthread_timedjoin_np(l->thread.threads[i].self, NULL, - &(const struct timespec) { .tv_sec = 1 }); - lwan_status_debug("Closing pipe (%d, %d)", t->pipe_fd[0], t->pipe_fd[1]); close(t->pipe_fd[0]); close(t->pipe_fd[1]); + + lwan_status_debug("Waiting for thread %d to finish", i); + pthread_join(l->thread.threads[i].self, NULL); } free(l->thread.threads); diff --git a/common/missing.c b/common/missing.c index 12b863a22..461521ffa 100644 --- a/common/missing.c +++ b/common/missing.c @@ -187,15 +187,6 @@ clock_gettime(clockid_t clk_id, struct timespec *ts) } #endif -#ifndef HAS_TIMEDJOIN -int -pthread_timedjoin_np(pthread_t thread, void **retval, - const struct timespec *abstime __attribute__((unused))) -{ - return pthread_join(thread, retval); -} -#endif - #if defined(__FreeBSD__) || defined(__APPLE__) #include #include diff --git a/common/missing/pthread.h b/common/missing/pthread.h index f04ae0818..46e3c819e 100644 --- a/common/missing/pthread.h +++ b/common/missing/pthread.h @@ -36,12 +36,4 @@ int pthread_barrier_destroy(pthread_barrier_t *barrier); int pthread_barrier_wait(pthread_barrier_t *barrier); #endif -#ifndef HAS_TIMEDJOIN -int pthread_timedjoin_np(pthread_t thread, void **retval, const struct timespec *abstime); -#endif - -#ifdef __FreeBSD__ -# include -#endif - #endif /* MISSING_PTHREAD_H */ From bd23756625a38c109b7eeaea9b6f9f9dcd1513a7 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 17 Jul 2016 07:57:57 -0300 Subject: [PATCH 0239/2505] Exit if `-r` argument exceeds PATH_MAX length Spotted by Mandar Kulkarni. --- lwan/main.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lwan/main.c b/lwan/main.c index 1b692fee5..ccfead2b5 100644 --- a/lwan/main.c +++ b/lwan/main.c @@ -59,10 +59,18 @@ parse_args(int argc, char *argv[], lwan_config_t *config, char *root) result = ARGS_SERVE_FILES; break; - case 'r': - memcpy(root, optarg, strnlen(optarg, PATH_MAX - 1) + 1); + case 'r': { + size_t len = strlen(optarg); + + if (len >= PATH_MAX) { + fprintf(stderr, "Root path length exeeds %d characters\n", PATH_MAX); + return ARGS_FAILED; + } + + memcpy(root, optarg, len + 1); result = ARGS_SERVE_FILES; break; + } case 'h': printf("Usage: %s [--root /path/to/root/dir] [--listener addr:port]\n", argv[0]); From 59371f7d01864764dbc4f3d3b1f7e2c3636e301e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 17 Jul 2016 10:06:55 -0300 Subject: [PATCH 0240/2505] Use edge-triggered events again This broke pipelined requests, and I don't want to debug this right now. It doesn't seem that difficult to fix, though, but reverting this will be better for the 0.0.1 release. --- common/lwan-io-wrappers.c | 12 ------------ common/lwan-thread.c | 8 +------- common/lwan.h | 1 - common/missing.c | 6 +++--- 4 files changed, 4 insertions(+), 23 deletions(-) diff --git a/common/lwan-io-wrappers.c b/common/lwan-io-wrappers.c index fddbd8a8c..b2a601c55 100644 --- a/common/lwan-io-wrappers.c +++ b/common/lwan-io-wrappers.c @@ -43,8 +43,6 @@ lwan_openat(lwan_request_t *request, switch (errno) { case EWOULDBLOCK: - request->conn->flags |= CONN_FLIP_FLAGS; - /* Fallthrough */ case EMFILE: case ENFILE: case EINTR: @@ -73,8 +71,6 @@ lwan_writev(lwan_request_t *request, struct iovec *iov, int iov_count) switch (errno) { case EAGAIN: - request->conn->flags |= CONN_FLIP_FLAGS; - /* Fallthrough */ case EINTR: goto try_again; default: @@ -116,8 +112,6 @@ lwan_write(lwan_request_t *request, const void *buf, size_t count) switch (errno) { case EAGAIN: - request->conn->flags |= CONN_FLIP_FLAGS; - /* Fallthrough */ case EINTR: goto try_again; default: @@ -152,8 +146,6 @@ lwan_send(lwan_request_t *request, const void *buf, size_t count, int flags) switch (errno) { case EAGAIN: - request->conn->flags |= CONN_FLIP_FLAGS; - /* Fallthrough */ case EINTR: goto try_again; default: @@ -190,8 +182,6 @@ lwan_sendfile(lwan_request_t *request, int in_fd, off_t offset, size_t count, if (written < 0) { switch (errno) { case EAGAIN: - request->conn->flags |= CONN_FLIP_FLAGS; - /* Fallthrough */ case EINTR: coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); continue; @@ -236,8 +226,6 @@ lwan_sendfile(lwan_request_t *request, int in_fd, off_t offset, size_t count, if (UNLIKELY(r < 0)) { switch (errno) { case EAGAIN: - request->conn->flags |= CONN_FLIP_FLAGS; - /* Fallthrough */ case EBUSY: case EINTR: coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); diff --git a/common/lwan-thread.c b/common/lwan-thread.c index 72114bb59..ba4ece03a 100644 --- a/common/lwan-thread.c +++ b/common/lwan-thread.c @@ -39,7 +39,7 @@ struct death_queue_t { static const uint32_t events_by_write_flag[] = { EPOLLOUT | EPOLLRDHUP | EPOLLERR, - EPOLLIN | EPOLLRDHUP | EPOLLERR + EPOLLIN | EPOLLRDHUP | EPOLLERR | EPOLLET }; static inline int death_queue_node_to_idx(struct death_queue_t *dq, @@ -199,12 +199,6 @@ resume_coro_if_needed(struct death_queue_t *dq, lwan_connection_t *conn, return; } - if (UNLIKELY(conn->flags & CONN_FLIP_FLAGS)) { - conn->flags &= ~CONN_FLIP_FLAGS; - } else { - return; - } - bool write_events; if (conn->flags & CONN_MUST_READ) { write_events = true; diff --git a/common/lwan.h b/common/lwan.h index e7b0ee43a..4d34480d0 100644 --- a/common/lwan.h +++ b/common/lwan.h @@ -170,7 +170,6 @@ typedef enum { CONN_SHOULD_RESUME_CORO = 1<<2, CONN_WRITE_EVENTS = 1<<3, CONN_MUST_READ = 1<<4, - CONN_FLIP_FLAGS = 1<<5, } lwan_connection_flags_t; typedef enum { diff --git a/common/missing.c b/common/missing.c index 461521ffa..ee8d539bb 100644 --- a/common/missing.c +++ b/common/missing.c @@ -209,7 +209,9 @@ epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) case EPOLL_CTL_ADD: case EPOLL_CTL_MOD: { int events = 0; - int flags = EV_ADD; + /* EV_CLEAR should be set only if EPOLLET is there, but Lwan doesn't + * always set EPOLLET. In the meantime, force EV_CLEAR every time. */ + int flags = EV_ADD | EV_CLEAR; if (event->events & EPOLLIN) events = EVFILT_READ; @@ -222,8 +224,6 @@ epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) flags |= EV_EOF; if (event->events & EPOLLERR) flags |= EV_ERROR; - if (event->events & EPOLLET) - flags |= EV_CLEAR; EV_SET(&ev, fd, events, flags, 0, 0, event->data.ptr); break; From bf300fc31960577c1fa5adcc414e950a8c303077 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 17 Jul 2016 16:00:06 -0300 Subject: [PATCH 0241/2505] Mark mimegen project as a pure C project Avoids having to have a C++ compiler installed and checked during build. --- tools/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 414124cb8..bd84683be 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -1,3 +1,4 @@ +project(lwan-tools C) cmake_minimum_required(VERSION 2.8) project(mimegen) From b36cc7524b662317edb22834f6b598c0f5613a75 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 17 Jul 2016 22:27:21 -0300 Subject: [PATCH 0242/2505] Correctly attribute substitute memrchr() This function has been contributed by someone else but they forgot to give the proper credit. By looking for alternative implementations, found one that's essentially the same, down to variable names. I'll have to be more careful in accepting contributions in the future. --- common/missing.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/common/missing.c b/common/missing.c index ee8d539bb..750a5ffe9 100644 --- a/common/missing.c +++ b/common/missing.c @@ -90,6 +90,23 @@ mempcpy(void *dest, const void *src, size_t len) #endif #ifndef HAS_MEMRCHR +/* + * Function based from memrchr.c + * Copyright (c) 2007 Todd C. Miller + * + * 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. + */ + void * memrchr(const void *s, int c, size_t n) { From 7c65fb94705b89ed5f13c3cc20cf284dbf036411 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 17 Jul 2016 22:32:39 -0300 Subject: [PATCH 0243/2505] Use memchr() to implement memrchr() if it's missing --- common/missing.c | 36 ++++++++---------------------------- 1 file changed, 8 insertions(+), 28 deletions(-) diff --git a/common/missing.c b/common/missing.c index 750a5ffe9..d7b960d22 100644 --- a/common/missing.c +++ b/common/missing.c @@ -90,39 +90,19 @@ mempcpy(void *dest, const void *src, size_t len) #endif #ifndef HAS_MEMRCHR -/* - * Function based from memrchr.c - * Copyright (c) 2007 Todd C. Miller - * - * 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. - */ - void * memrchr(const void *s, int c, size_t n) { - const unsigned char *cp; - unsigned char *p = (unsigned char *)s; - unsigned char chr = (unsigned char)c; - - if (n != 0) { - cp = p + n; - do { - if (*(--cp) == chr) - return (void *)cp; - } while (--n != 0); + const char *end = (const char *)s + n + 1; + const char *prev = NULL; + + for (const char *cur = s; cur <= end; prev = cur++) { + cur = (const char *)memchr(cur, c, end - cur); + if (!cur) + break; } - return NULL; + return (void *)prev; } #endif From ab94d2e6c19e2a539bfa165c208212ffb6a6ef79 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 19 Jul 2016 20:45:54 -0300 Subject: [PATCH 0244/2505] Mark get_config_path() as static --- common/lwan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lwan.c b/common/lwan.c index 980f59105..5e996e6f2 100644 --- a/common/lwan.c +++ b/common/lwan.c @@ -348,7 +348,7 @@ static void parse_listener(config_t *c, config_line_t *l, lwan_t *lwan) config_error(c, "Expecting section end while parsing listener"); } -const char *get_config_path(char *path_buf) +static const char *get_config_path(char *path_buf) { char buffer[PATH_MAX]; From 33a7ea69c95d6359e8c94de3fc9cab22ccf43e8b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 20 Jul 2016 21:10:37 -0300 Subject: [PATCH 0245/2505] Ensure compressed file descriptor is valid before closing --- common/lwan-serve-files.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/lwan-serve-files.c b/common/lwan-serve-files.c index 5b722c73e..a2b6753d0 100644 --- a/common/lwan-serve-files.c +++ b/common/lwan-serve-files.c @@ -398,7 +398,8 @@ sendfile_init(struct file_cache_entry *ce, struct serve_files_priv *priv, if (UNLIKELY(sd->uncompressed.fd < 0)) { int openat_errno = errno; - close(sd->compressed.fd); + if (sd->compressed.fd >= 0) + close(sd->compressed.fd); switch (openat_errno) { case ENFILE: From e89c5a4e2c0356919ac9baaa72452e6da444680d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 20 Jul 2016 21:11:53 -0300 Subject: [PATCH 0246/2505] Provide gettid() implementation for all supported platforms --- common/lwan-status.c | 24 +----------------------- common/missing.c | 36 ++++++++++++++++++++++++++++++++++++ common/missing/sys/types.h | 27 +++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 23 deletions(-) create mode 100644 common/missing/sys/types.h diff --git a/common/lwan-status.c b/common/lwan-status.c index 5d47ddf37..590320c0f 100644 --- a/common/lwan-status.c +++ b/common/lwan-status.c @@ -25,14 +25,10 @@ #include #include #include +#include #ifndef NDEBUG #include -#include -#endif - -#ifdef __FreeBSD__ -#include #endif #include "lwan-private.h" @@ -109,24 +105,6 @@ strerror_thunk_r(int error_number, char *buffer, size_t len) #endif } -#ifndef NDEBUG -static inline long -gettid(void) -{ - long tid; - -#if defined(__linux__) - tid = syscall(SYS_gettid); -#elif (__FreeBSD__) - thr_self(&tid); -#else - tid = (long)pthread_self(); -#endif - - return tid; -} -#endif - static void #ifdef NDEBUG status_out_msg(lwan_status_type_t type, const char *msg, size_t msg_len) diff --git a/common/missing.c b/common/missing.c index d7b960d22..59706a546 100644 --- a/common/missing.c +++ b/common/missing.c @@ -342,3 +342,39 @@ proc_pidpath(pid_t pid, void *buffer, size_t buffersize) return 0; } #endif + +#if defined(__linux__) +#include + +long +gettid(void) +{ + return syscall(SYS_gettid); +} +#elif defined(__FreeBSD__) +#include + +long +gettid(void) +{ + long ret; + + thr_self(&ret); + + return ret; +} +#elif defined(__APPLE__) +#include + +long +gettid(void) +{ + return syscall(SYS_thread_selfid); +} +#else +long +gettid(void) +{ + return (long)pthread_self(); +} +#endif diff --git a/common/missing/sys/types.h b/common/missing/sys/types.h new file mode 100644 index 000000000..f178de8ad --- /dev/null +++ b/common/missing/sys/types.h @@ -0,0 +1,27 @@ +/* + * lwan - simple web server + * Copyright (c) 2012 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include_next + +#ifndef MISSING_SYS_TYPES_H +#define MISSING_SYS_TYPES_H + +long gettid(void); + +#endif From a53e2b13f0fbb613be2c2ecb1a345b7b5ab1ae77 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 21 Jul 2016 09:00:21 -0300 Subject: [PATCH 0247/2505] Do not divide by zero if compress_output() fails A failure on this program isn't that much of a problem, since it's executed only during the build process. --- tools/mimegen.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/mimegen.c b/tools/mimegen.c index c66d05c16..3673ef2ec 100644 --- a/tools/mimegen.c +++ b/tools/mimegen.c @@ -104,6 +104,9 @@ static char *compress_output(const struct output *output, size_t *outlen) exit(1); } #endif + if (!*outlen) + return NULL; + fprintf(stderr, "Uncompressed: %zu bytes; compressed: %zu bytes (compressed %2fx)\n", output->used, *outlen, (double)output->used / (double)*outlen); From f587e51f8a99d077968f59d7f34748861afaa4c3 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 21 Jul 2016 09:02:34 -0300 Subject: [PATCH 0248/2505] Check for the barrier presence by using an if rather than assert() I'm still not happy with the thread shutdown thing. --- common/lwan-thread.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/lwan-thread.c b/common/lwan-thread.c index ba4ece03a..7cdd1b140 100644 --- a/common/lwan-thread.c +++ b/common/lwan-thread.c @@ -394,8 +394,8 @@ thread_io_loop(void *data) } epoll_fd_closed: - assert(t->barrier); - pthread_barrier_wait(t->barrier); + if (t->barrier) + pthread_barrier_wait(t->barrier); death_queue_kill_all(&dq); free(events); From f6c68c772bcbd9f2ed43a311993248daabc8a6bb Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 21 Jul 2016 09:10:55 -0300 Subject: [PATCH 0249/2505] Check maximum fd number in sd_listen_fds() using getrlimit() This fix should probably be sent upstream to systemd. --- common/sd-daemon.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/common/sd-daemon.c b/common/sd-daemon.c index b630bb1f8..651439b65 100644 --- a/common/sd-daemon.c +++ b/common/sd-daemon.c @@ -21,10 +21,13 @@ ***/ #define _GNU_SOURCE +#include #include #include +#include #include #include +#include #include #include #include @@ -32,6 +35,20 @@ #include "sd-daemon.h" +static rlim_t +get_max_fd(void) +{ + struct rlimit r; + + if (getrlimit(RLIMIT_NOFILE, &r) < 0) + return INT_MAX; + + if (r.rlim_max == RLIM_INFINITY) + return INT_MAX; + + return r.rlim_max; +} + int sd_listen_fds(int unset_environment) { int r, fd; const char *e; @@ -82,6 +99,11 @@ int sd_listen_fds(int unset_environment) { goto finish; } + if (l > get_max_fd() - SD_LISTEN_FDS_START) { + r = -EINVAL; + goto finish; + } + for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + (int) l; fd ++) { int flags; From 27bb188aca88e12594678c0aa1252c237ddc907c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 23 Jul 2016 10:41:09 -0300 Subject: [PATCH 0250/2505] Allow multiline strings in configuration files This uses a syntax similar to Python's multiline string, where three single quotes (''') are used to mark the beginning and end of the string. The only limitation is that the opening quotes must be at the end of a line, and the closing quotes must be by themselves in the last line. For example: section name { something = ''' this is a multiline string ''' } --- common/lwan-config.c | 110 ++++++++++++++++++++++++++++++------------- common/lwan-config.h | 3 ++ 2 files changed, 81 insertions(+), 32 deletions(-) diff --git a/common/lwan-config.c b/common/lwan-config.c index 5c3073994..ed1edab54 100644 --- a/common/lwan-config.c +++ b/common/lwan-config.c @@ -18,6 +18,7 @@ */ #define _GNU_SOURCE +#include #include #include #include @@ -194,7 +195,53 @@ static char *replace_space_with_underscore(char *line) return line; } -static bool parse_line(char *line, config_line_t *l, char *equal) +static bool config_fgets(config_t *conf, char *buffer, size_t buffer_len) +{ + assert(buffer_len <= INT_MAX); + if (!fgets(buffer, (int)buffer_len, conf->file)) + return false; + + if (conf->isolated.end > 0) { + long curpos = ftell(conf->file); + if (curpos < 0) { + config_error(conf, "Could not obtain file position"); + return false; + } + if (curpos >= conf->isolated.end) + return false; + } + conf->line++; + + return true; +} + +static bool parse_multiline(config_t *c, config_line_t *l) +{ + char buffer[1024]; + + if (!strbuf_reset_length(c->strbuf)) { + config_error(c, "Could not allocate multiline string"); + return false; + } + + while (config_fgets(c, buffer, sizeof(buffer))) { + char *tmp = remove_trailing_spaces(remove_leading_spaces(buffer)); + if (!strcmp(tmp, "'''")) { + l->line.value = strbuf_get_buffer(c->strbuf); + return true; + } + + if (!strbuf_append_str(c->strbuf, buffer, 0)) { + config_error(c, "Could not append to multiline string"); + return false; + } + } + + config_error(c, "EOF while scanning for end of multiline string"); + return false; +} + +static bool parse_line(config_t *c, char *line, config_line_t *l, char *equal) { *equal = '\0'; line = remove_leading_spaces(line); @@ -204,7 +251,10 @@ static bool parse_line(char *line, config_line_t *l, char *equal) l->line.value = remove_leading_spaces(equal + 1); l->type = CONFIG_LINE_TYPE_LINE; - return true; + if (strcmp(l->line.value, "'''")) + return true; + + return parse_multiline(c, l); } static bool find_section_end(config_t *config, config_line_t *line, int recursion_level) @@ -288,24 +338,15 @@ bool config_read_line(config_t *conf, config_line_t *l) if (conf->error_message) return false; -retry: - if (!fgets(l->buffer, sizeof(l->buffer), conf->file)) - return false; - - if (conf->isolated.end > 0) { - long curpos = ftell(conf->file); - if (curpos < 0) { - config_error(conf, "Could not obtain file position"); + do { + if (!config_fgets(conf, l->buffer, sizeof(l->buffer))) return false; - } - if (curpos >= conf->isolated.end) - return false; - } - conf->line++; - line = remove_comments(l->buffer); - line = remove_leading_spaces(line); - line = remove_trailing_spaces(line); + line = remove_comments(l->buffer); + line = remove_leading_spaces(line); + line = remove_trailing_spaces(line); + } while (*line == '\0'); + line_end = find_line_end(line); if (*line_end == '{') { @@ -313,14 +354,12 @@ bool config_read_line(config_t *conf, config_line_t *l) config_error(conf, "Malformed section opening"); return false; } - } else if (*line == '\0') { - goto retry; } else if (*line == '}' && line == line_end) { l->type = CONFIG_LINE_TYPE_SECTION_END; } else { char *equal = strchr(line, '='); if (equal) { - if (!parse_line(line, l, equal)) { + if (!parse_line(conf, line, l, equal)) { config_error(conf, "Malformed key=value line"); return false; } @@ -335,27 +374,34 @@ bool config_read_line(config_t *conf, config_line_t *l) bool config_open(config_t *conf, const char *path) { - if (!conf) - return false; - if (!path) - return false; + if (!conf || !path) + goto error_no_strbuf; + + conf->strbuf = strbuf_new(); + if (!conf->strbuf) + goto error_no_strbuf; conf->file = fopen(path, "re"); if (!conf->file) - return false; + goto error_no_file; conf->path = strdup(path); - if (!conf->path) { - fclose(conf->file); - conf->file = NULL; - return false; - } + if (!conf->path) + goto error_no_path; conf->isolated.end = -1; conf->line = 0; conf->error_message = NULL; return true; + +error_no_path: + fclose(conf->file); +error_no_file: + strbuf_free(conf->strbuf); +error_no_strbuf: + conf->file = NULL; + return false; } void config_close(config_t *conf) @@ -367,5 +413,5 @@ void config_close(config_t *conf) fclose(conf->file); free(conf->path); free(conf->error_message); + strbuf_free(conf->strbuf); } - diff --git a/common/lwan-config.h b/common/lwan-config.h index 0942dd8fd..fe9dc9b62 100644 --- a/common/lwan-config.h +++ b/common/lwan-config.h @@ -29,6 +29,8 @@ #include #include +#include "strbuf.h" + typedef struct config_t_ config_t; typedef struct config_line_t_ config_line_t; @@ -41,6 +43,7 @@ typedef enum { struct config_t_ { FILE *file; char *path, *error_message; + strbuf_t *strbuf; struct { long end; } isolated; From c733944e4ef66d6845b7097b4cca37d8623c9bd1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 23 Jul 2016 10:44:07 -0300 Subject: [PATCH 0251/2505] Use multiline configuration strings in the Lua module --- common/lwan-lua.c | 48 ++++++++++++++++++++++++++++++----------------- common/lwan-lua.h | 1 + testrunner.conf | 9 +++++++++ 3 files changed, 41 insertions(+), 17 deletions(-) diff --git a/common/lwan-lua.c b/common/lwan-lua.c index a47fabcad..69e6096d3 100644 --- a/common/lwan-lua.c +++ b/common/lwan-lua.c @@ -37,6 +37,7 @@ static const char *request_metatable_name = "Lwan.Request"; struct lwan_lua_priv_t { char *default_type; char *script_file; + char *script; pthread_key_t cache_key; unsigned cache_period; }; @@ -223,8 +224,13 @@ static struct cache_entry_t *state_create(const char *key __attribute__((unused) luaL_register(state->L, NULL, lwan_request_meta_regs); lua_setfield(state->L, -1, "__index"); - if (UNLIKELY(luaL_dofile(state->L, priv->script_file) != 0)) { - lwan_status_error("Error opening Lua script %s", lua_tostring(state->L, -1)); + if (priv->script_file) { + if (UNLIKELY(luaL_dofile(state->L, priv->script_file) != 0)) { + lwan_status_error("Error opening Lua script %s", lua_tostring(state->L, -1)); + goto close_lua_state; + } + } else if (UNLIKELY(luaL_dostring(state->L, priv->script) != 0)) { + lwan_status_error("Error evaluating Lua script %s", lua_tostring(state->L, -1)); goto close_lua_state; } @@ -394,7 +400,7 @@ static void *lua_init(const char *prefix __attribute__((unused)), void *data) struct lwan_lua_settings_t *settings = data; struct lwan_lua_priv_t *priv; - priv = malloc(sizeof(*priv)); + priv = calloc(1, sizeof(*priv)); if (!priv) { lwan_status_error("Could not allocate memory for private Lua struct"); return NULL; @@ -404,33 +410,39 @@ static void *lua_init(const char *prefix __attribute__((unused)), void *data) settings->default_type ? settings->default_type : "text/plain"); if (!priv->default_type) { lwan_status_perror("strdup"); - goto out_no_default_type; + goto error; } - if (!settings->script_file) { - lwan_status_error("No Lua script file provided"); - goto out_no_script_file; - } - priv->script_file = strdup(settings->script_file); - if (!priv->script_file) { - lwan_status_perror("strdup"); - goto out_no_script_file; + if (settings->script) { + priv->script = strdup(settings->script); + if (!priv->script) { + lwan_status_perror("strdup"); + goto error; + } + } else if (settings->script_file) { + priv->script_file = strdup(settings->script_file); + if (!priv->script_file) { + lwan_status_perror("strdup"); + goto error; + } + } else { + lwan_status_error("No Lua script_file or script provided"); + goto error; } if (pthread_key_create(&priv->cache_key, NULL)) { lwan_status_perror("pthread_key_create"); - goto out_key_create; + goto error; } priv->cache_period = settings->cache_period; return priv; -out_key_create: +error: free(priv->script_file); -out_no_script_file: free(priv->default_type); -out_no_default_type: + free(priv->script); free(priv); return NULL; } @@ -442,6 +454,7 @@ static void lua_shutdown(void *data) pthread_key_delete(priv->cache_key); free(priv->default_type); free(priv->script_file); + free(priv->script); free(priv); } } @@ -451,7 +464,8 @@ static void *lua_init_from_hash(const char *prefix, const struct hash *hash) struct lwan_lua_settings_t settings = { .default_type = hash_find(hash, "default_type"), .script_file = hash_find(hash, "script_file"), - .cache_period = parse_time_period(hash_find(hash, "cache_period"), 15) + .cache_period = parse_time_period(hash_find(hash, "cache_period"), 15), + .script = hash_find(hash, "script") }; return lua_init(prefix, &settings); } diff --git a/common/lwan-lua.h b/common/lwan-lua.h index f3063f84c..23bc1f2eb 100644 --- a/common/lwan-lua.h +++ b/common/lwan-lua.h @@ -24,6 +24,7 @@ struct lwan_lua_settings_t { const char *default_type; const char *script_file; + const char *script; unsigned int cache_period; }; diff --git a/testrunner.conf b/testrunner.conf index 3d9e517cd..17ac7bc9c 100644 --- a/testrunner.conf +++ b/testrunner.conf @@ -51,6 +51,15 @@ listener *:8080 { password file = htpasswd } } + lua /inline { + default type = text/html + cache period = 30s + script = ''' + function handle_get_root(req) + req:say('Hello') + end + ''' + } lua /lua { default type = text/html script file = test.lua From 8d9f7045bad8c340c09fbcbb3ab2d4af089f76a5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 23 Jul 2016 10:44:32 -0300 Subject: [PATCH 0252/2505] strbuf_reset() should return true if strbuf was reset correctly --- common/strbuf.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/common/strbuf.c b/common/strbuf.c index 73f8bc7f4..2cc4588e1 100644 --- a/common/strbuf.c +++ b/common/strbuf.c @@ -326,8 +326,10 @@ strbuf_shrink_to_default(strbuf_t *s) ALWAYS_INLINE bool strbuf_reset(strbuf_t *s) { - strbuf_shrink_to_default(s); - s->len.buffer = 0; + if (LIKELY(strbuf_shrink_to_default(s))) { + s->len.buffer = 0; + return true; + } return false; } From 9e7d1513f8686e2f570b23c2b9f537aab9a2d655 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 23 Jul 2016 11:57:57 -0300 Subject: [PATCH 0253/2505] Fix possible null pointer dereference in config_open() --- common/lwan-config.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/common/lwan-config.c b/common/lwan-config.c index ed1edab54..ffae9c42c 100644 --- a/common/lwan-config.c +++ b/common/lwan-config.c @@ -374,7 +374,9 @@ bool config_read_line(config_t *conf, config_line_t *l) bool config_open(config_t *conf, const char *path) { - if (!conf || !path) + if (!conf) + return false; + if (!path) goto error_no_strbuf; conf->strbuf = strbuf_new(); From a1eb3aa41735a766a79898def31a6c72f6396024 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 23 Jul 2016 13:10:36 -0300 Subject: [PATCH 0254/2505] Ensure newlines are added to multiline config options --- common/lwan-config.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/common/lwan-config.c b/common/lwan-config.c index ffae9c42c..d87193d0b 100644 --- a/common/lwan-config.c +++ b/common/lwan-config.c @@ -235,6 +235,10 @@ static bool parse_multiline(config_t *c, config_line_t *l) config_error(c, "Could not append to multiline string"); return false; } + if (!strbuf_append_char(c->strbuf, '\n')) { + config_error(c, "Could not append to multiline string"); + return false; + } } config_error(c, "EOF while scanning for end of multiline string"); From f063eb61b680d876029b436516bddb8bdb5d8137 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 23 Jul 2016 13:12:36 -0300 Subject: [PATCH 0255/2505] Add test for Lua script inlined in configuration file --- tools/testsuite.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/testsuite.py b/tools/testsuite.py index dbca74991..68bd782f8 100755 --- a/tools/testsuite.py +++ b/tools/testsuite.py @@ -310,6 +310,11 @@ def test_request_too_large(self): class TestLua(LwanTest): + def test_inline(self): + r = requests.get('http://localhost:8080/inline') + self.assertResponseHtml(r) + self.assertEqual(r.text, 'Hello') + def test_hello(self): r = requests.get('http://localhost:8080/lua/hello') self.assertResponseHtml(r) From fe51da2b25b480d9f20a77939d9409c0f5fac7dc Mon Sep 17 00:00:00 2001 From: Ivy Foster Date: Sat, 23 Jul 2016 17:26:34 -0500 Subject: [PATCH 0256/2505] Remove missing.h from cmake install targets --- common/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 51e37e5a0..045e79365 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -123,5 +123,5 @@ INSTALL(TARGETS lwan-static lwan-shared DESTINATION "lib" ) INSTALL(FILES lwan.h lwan-coro.h lwan-trie.h lwan-status.h strbuf.h hash.h - lwan-template.h lwan-serve-files.h lwan-config.h missing.h + lwan-template.h lwan-serve-files.h lwan-config.h DESTINATION "include/lwan" ) From 4fc8b15f795b09196f299b51ad27d919144574d3 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 27 Jul 2016 22:36:13 -0300 Subject: [PATCH 0257/2505] Ensure POST body does not exceed DEFAULT_BUFFER_SIZE --- common/lwan-request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index 09e55e40e..f1008ea73 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -816,7 +816,7 @@ read_post_data(lwan_request_t *request __attribute__((unused)), return HTTP_BAD_REQUEST; parsed_length = parse_long(helper->content_length.value, DEFAULT_BUFFER_SIZE); - if (UNLIKELY(parsed_length > DEFAULT_BUFFER_SIZE)) + if (UNLIKELY(parsed_length >= DEFAULT_BUFFER_SIZE)) return HTTP_TOO_LARGE; if (UNLIKELY(parsed_length < 0)) return HTTP_BAD_REQUEST; From 603f27ba1bd04db3ff8b60692d14504d0b9bb819 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 31 Jul 2016 01:30:49 -0300 Subject: [PATCH 0258/2505] Remove duplicated prototypes --- common/lwan-template.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/common/lwan-template.c b/common/lwan-template.c index a8bad6fb1..e533f34d0 100644 --- a/common/lwan-template.c +++ b/common/lwan-template.c @@ -173,16 +173,12 @@ static void *lex_left_meta(struct lexer *lexer); static void *lex_right_meta(struct lexer *lexer); static void *lex_text(struct lexer *lexer); -static void *parser_meta(struct parser *parser, struct lexeme *lexeme); -static void *parser_text(struct parser *parser, struct lexeme *lexeme); -static void *parser_iter(struct parser *parser, struct lexeme *lexeme); -static void *parser_slash(struct parser *parser, struct lexeme *lexeme); static void *parser_end_iter(struct parser *parser, struct lexeme *lexeme); static void *parser_end_var_not_empty(struct parser *parser, struct lexeme *lexeme); -static void *parser_slash(struct parser *parser, struct lexeme *lexeme); static void *parser_iter(struct parser *parser, struct lexeme *lexeme); -static void *parser_negate_iter(struct parser *parser, struct lexeme *lexeme); static void *parser_meta(struct parser *parser, struct lexeme *lexeme); +static void *parser_negate_iter(struct parser *parser, struct lexeme *lexeme); +static void *parser_slash(struct parser *parser, struct lexeme *lexeme); static void *parser_text(struct parser *parser, struct lexeme *lexeme); static void error_vlexeme(struct lexeme *lexeme, const char *msg, va_list ap) From fae3de50b154f2f2012a0583c9f072e4aa3e89ba Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 31 Jul 2016 11:56:43 -0300 Subject: [PATCH 0259/2505] Implement coro_strndup() Use it to implement coro_strdup(). --- common/lwan-coro.c | 14 +++++++++++--- common/lwan-coro.h | 1 + 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/common/lwan-coro.c b/common/lwan-coro.c index 6a5aa8ff3..1a085f909 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -355,15 +355,23 @@ coro_malloc(coro_t *coro, size_t size) } char * -coro_strdup(coro_t *coro, const char *str) +coro_strndup(coro_t *coro, const char *str, size_t max_len) { - size_t len = strlen(str) + 1; + const size_t len = max_len + 1; char *dup = coro_malloc(coro, len); - if (LIKELY(dup)) + if (LIKELY(dup)) { memcpy(dup, str, len); + dup[len - 1] = '\0'; + } return dup; } +char * +coro_strdup(coro_t *coro, const char *str) +{ + return coro_strndup(coro, str, strlen(str)); +} + char * coro_printf(coro_t *coro, const char *fmt, ...) { diff --git a/common/lwan-coro.h b/common/lwan-coro.h index 7a14cfcbe..4da19f816 100644 --- a/common/lwan-coro.h +++ b/common/lwan-coro.h @@ -59,6 +59,7 @@ void coro_defer2(coro_t *coro, void (*func)(void *data1, void *data2), void *coro_malloc(coro_t *coro, size_t sz); void *coro_malloc_full(coro_t *coro, size_t size, void (*destroy_func)()); char *coro_strdup(coro_t *coro, const char *str); +char *coro_strndup(coro_t *coro, const char *str, size_t len); char *coro_printf(coro_t *coro, const char *fmt, ...); #define CORO_DEFER(fn) ((void (*)(void *))(fn)) From 1583ee32fa07236fb772bed82352f913b237e7a4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 31 Jul 2016 14:25:17 -0300 Subject: [PATCH 0260/2505] Implement utilities to get a lua_State with Lwan.Request metatable --- common/lwan-lua.c | 62 +++++++++++++++++++++++++++---------------- common/lwan-lua.h | 1 - common/lwan-private.h | 4 +++ 3 files changed, 43 insertions(+), 24 deletions(-) diff --git a/common/lwan-lua.c b/common/lwan-lua.c index 69e6096d3..0568db47a 100644 --- a/common/lwan-lua.c +++ b/common/lwan-lua.c @@ -205,40 +205,56 @@ static const struct luaL_reg lwan_request_meta_regs[] = { { NULL, NULL } }; -static struct cache_entry_t *state_create(const char *key __attribute__((unused)), - void *context) +const char *lwan_lua_state_last_error(lua_State *L) { - struct lwan_lua_priv_t *priv = context; - struct lwan_lua_state_t *state = malloc(sizeof(*state)); + return lua_tostring(L, -1); +} - if (UNLIKELY(!state)) - return NULL; +lua_State *lwan_lua_create_state(const char *script_file, const char *script) +{ + lua_State *L; - state->L = luaL_newstate(); - if (UNLIKELY(!state->L)) - goto free_state; + L = luaL_newstate(); + if (UNLIKELY(!L)) + return NULL; - luaL_openlibs(state->L); + luaL_openlibs(L); - luaL_newmetatable(state->L, request_metatable_name); - luaL_register(state->L, NULL, lwan_request_meta_regs); - lua_setfield(state->L, -1, "__index"); + luaL_newmetatable(L, request_metatable_name); + luaL_register(L, NULL, lwan_request_meta_regs); + lua_setfield(L, -1, "__index"); - if (priv->script_file) { - if (UNLIKELY(luaL_dofile(state->L, priv->script_file) != 0)) { - lwan_status_error("Error opening Lua script %s", lua_tostring(state->L, -1)); + if (script_file) { + if (UNLIKELY(luaL_dofile(L, script_file) != 0)) { + lwan_status_error("Error opening Lua script %s: %s", + script_file, lua_tostring(L, -1)); goto close_lua_state; } - } else if (UNLIKELY(luaL_dostring(state->L, priv->script) != 0)) { - lwan_status_error("Error evaluating Lua script %s", lua_tostring(state->L, -1)); + } else if (UNLIKELY(luaL_dostring(L, script) != 0)) { + lwan_status_error("Error evaluating Lua script %s", lua_tostring(L, -1)); goto close_lua_state; } - return (struct cache_entry_t *)state; + return L; close_lua_state: - lua_close(state->L); -free_state: + lua_close(L); + return NULL; +} + +static struct cache_entry_t *state_create(const char *key __attribute__((unused)), + void *context) +{ + struct lwan_lua_priv_t *priv = context; + struct lwan_lua_state_t *state = malloc(sizeof(*state)); + + if (UNLIKELY(!state)) + return NULL; + + state->L = lwan_lua_create_state(priv->script_file, priv->script); + if (LIKELY(state->L)) + return (struct cache_entry_t *)state; + free(state); return NULL; } @@ -331,7 +347,7 @@ static bool get_handler_function(lua_State *L, lwan_request_t *request) return lua_isfunction(L, -1); } -static void push_request(lua_State *L, lwan_request_t *request) +void lwan_lua_state_push_request(lua_State *L, lwan_request_t *request) { lwan_request_t **userdata = lua_newuserdata(L, sizeof(lwan_request_t *)); *userdata = request; @@ -378,7 +394,7 @@ lua_handle_cb(lwan_request_t *request, return HTTP_NOT_FOUND; int n_arguments = 1; - push_request(L, request); + lwan_lua_state_push_request(L, request); response->mime_type = priv->default_type; while (true) { switch (lua_resume(L, n_arguments)) { diff --git a/common/lwan-lua.h b/common/lwan-lua.h index 23bc1f2eb..a0f8cf48c 100644 --- a/common/lwan-lua.h +++ b/common/lwan-lua.h @@ -36,4 +36,3 @@ struct lwan_lua_settings_t { .flags = 0 const lwan_module_t *lwan_module_lua(void); - diff --git a/common/lwan-private.h b/common/lwan-private.h index b19d6d922..bb9fa70ca 100644 --- a/common/lwan-private.h +++ b/common/lwan-private.h @@ -50,3 +50,7 @@ void lwan_straitjacket_enforce(config_t *c, config_line_t *l); uint8_t lwan_char_isspace(char ch) __attribute__((pure)); uint8_t lwan_char_isxdigit(char ch) __attribute__((pure)); +typedef struct lua_State lua_State; +lua_State *lwan_lua_create_state(const char *script_file, const char *script); +void lwan_lua_state_push_request(lua_State *L, lwan_request_t *request); +const char *lwan_lua_state_last_error(lua_State *L); From e7369c5622a2655db0494978d1cc2db6f79b5dce Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 31 Jul 2016 14:26:44 -0300 Subject: [PATCH 0261/2505] Allow requests be rewritten using Lua scripts --- common/lwan-rewrite.c | 121 ++++++++++++++++++++++++++++++++++++------ testrunner.conf | 18 +++++++ tools/testsuite.py | 13 +++++ 3 files changed, 135 insertions(+), 17 deletions(-) diff --git a/common/lwan-rewrite.c b/common/lwan-rewrite.c index 60ae460a6..59bda04a2 100644 --- a/common/lwan-rewrite.c +++ b/common/lwan-rewrite.c @@ -29,6 +29,14 @@ #include "list.h" #include "patterns.h" +#ifdef HAVE_LUA +#include +#include +#include + +#include "lwan-lua.h" +#endif + struct private_data { struct list_head patterns; }; @@ -36,8 +44,12 @@ struct private_data { struct pattern { struct list_node list; char *pattern; - char *expand; + char *expand_pattern; + lwan_http_status_t (*handle)(lwan_request_t *request, const char *url); + const char *(*expand)(lwan_request_t *request, struct pattern *pattern, + const char *orig, char buffer[static PATH_MAX], struct str_find *sf, + int captures); }; struct str_builder { @@ -95,24 +107,26 @@ append_str(struct str_builder *builder, const char *src, size_t src_len) } static const char * -expand(const char *pattern, const char *orig, char buffer[static PATH_MAX], - struct str_find *sf, int captures) +expand(lwan_request_t *request __attribute__((unused)), struct pattern *pattern, + const char *orig, char buffer[static PATH_MAX], struct str_find *sf, + int captures) { + const char *expand_pattern = pattern->expand_pattern; struct str_builder builder = { .buffer = buffer, .size = PATH_MAX }; char *ptr; - ptr = strchr(pattern, '%'); + ptr = strchr(expand_pattern, '%'); if (!ptr) - return pattern; + return expand_pattern; do { size_t index_len = strspn(ptr + 1, "0123456789"); - if (ptr > pattern) { - if (UNLIKELY(!append_str(&builder, pattern, (size_t)(ptr - pattern)))) + if (ptr > expand_pattern) { + if (UNLIKELY(!append_str(&builder, expand_pattern, (size_t)(ptr - expand_pattern)))) return NULL; - pattern += ptr - pattern; + expand_pattern += ptr - expand_pattern; } if (LIKELY(index_len > 0)) { @@ -126,15 +140,15 @@ expand(const char *pattern, const char *orig, char buffer[static PATH_MAX], return NULL; ptr += index_len; - pattern += index_len; + expand_pattern += index_len; } else if (UNLIKELY(!append_str(&builder, "%", 1))) { return NULL; } - pattern++; + expand_pattern++; } while ((ptr = strchr(ptr + 1, '%'))); - if (*pattern && !append_str(&builder, pattern, strlen(pattern))) + if (*expand_pattern && !append_str(&builder, expand_pattern, strlen(expand_pattern))) return NULL; if (UNLIKELY(!builder.len)) @@ -143,10 +157,69 @@ expand(const char *pattern, const char *orig, char buffer[static PATH_MAX], return builder.buffer; } +#ifdef HAVE_LUA +static const char * +expand_lua(lwan_request_t *request, struct pattern *pattern, const char *orig, + char buffer[static PATH_MAX], struct str_find *sf, int captures) +{ + const char *output, *ret; + size_t output_len; + int i; + lua_State *L; + + L = lwan_lua_create_state(NULL, pattern->expand_pattern); + if (UNLIKELY(!L)) + return NULL; + coro_defer(request->conn->coro, CORO_DEFER(lua_close), L); + + lua_getglobal(L, "handle_rewrite"); + if (!lua_isfunction(L, -1)) { + lwan_status_error("Could not obtain reference to `handle_rewrite()` function: %s", + lwan_lua_state_last_error(L)); + return NULL; + } + + lwan_lua_state_push_request(L, request); + + lua_createtable(L, captures, 0); + for (i = 0; i < captures; i++) { + char *tmp = coro_strndup(request->conn->coro, orig + sf[i].sm_so, + (size_t)(sf[i].sm_eo - sf[i].sm_so)); + + lua_pushinteger(L, i); + if (tmp) + lua_pushstring(L, tmp); + else + lua_pushnil(L); + lua_settable(L, -3); + } + + if (lua_pcall(L, 2, 1, 0) != 0) { + lwan_status_error("Could not execute `handle_rewrite()` function: %s", + lwan_lua_state_last_error(L)); + + lua_pop(L, 2); /* 2: request + capture table */ + return NULL; + } + + output = lua_tolstring(L, -1, &output_len); + if (output_len >= PATH_MAX) { + lwan_status_error("Rewritten URL exceeds %d bytes (got %ld bytes)", + PATH_MAX, output_len); + + lua_pop(L, 1); /* 1: return value */ + return NULL; + } + + ret = memcpy(buffer, output, output_len); + lua_pop(L, 1); /* 1: return value */ + return ret; +} +#endif + static lwan_http_status_t module_handle_cb(lwan_request_t *request, - lwan_response_t *response __attribute__((unused)), - void *data) + lwan_response_t *response __attribute__((unused)), void *data) { const char *url = request->url.value; char final_url[PATH_MAX]; @@ -165,7 +238,7 @@ module_handle_cb(lwan_request_t *request, if (captures <= 0) continue; - expanded = expand(p->expand, url, final_url, sf, captures); + expanded = p->expand(request, p, url, final_url, sf, captures); if (LIKELY(expanded)) return p->handle(request, expanded); @@ -196,9 +269,10 @@ module_shutdown(void *data) list_for_each_safe(&pd->patterns, iter, next, list) { free(iter->pattern); - free(iter->expand); + free(iter->expand_pattern); free(iter); } + free(pd); } @@ -214,6 +288,7 @@ module_parse_conf_pattern(struct private_data *pd, config_t *config, config_line { struct pattern *pattern; char *redirect_to = NULL, *rewrite_as = NULL; + bool expand_with_lua = false; pattern = calloc(1, sizeof(*pattern)); if (!pattern) @@ -234,6 +309,8 @@ module_parse_conf_pattern(struct private_data *pd, config_t *config, config_line rewrite_as = strdup(line->line.value); if (!rewrite_as) goto out; + } else if (!strcmp(line->line.key, "expand_with_lua")) { + expand_with_lua = parse_bool(line->line.value, false); } else { config_error(config, "Unexpected key: %s", line->line.key); goto out; @@ -248,15 +325,25 @@ module_parse_conf_pattern(struct private_data *pd, config_t *config, config_line goto out; } if (redirect_to) { - pattern->expand = redirect_to; + pattern->expand_pattern = redirect_to; pattern->handle = module_redirect_to; } else if (rewrite_as) { - pattern->expand = rewrite_as; + pattern->expand_pattern = rewrite_as; pattern->handle = module_rewrite_as; } else { config_error(config, "either `redirect to` or `rewrite as` are required"); goto out; } + if (expand_with_lua) { +#ifdef HAVE_LUA + pattern->expand = expand_lua; +#else + config_error(config, "Lwan has been built without Lua. `expand_with_lua` is not available"); + goto out; +#endif + } else { + pattern->expand = expand; + } list_add_tail(&pd->patterns, &pattern->list); return true; } diff --git a/testrunner.conf b/testrunner.conf index 17ac7bc9c..eb0535194 100644 --- a/testrunner.conf +++ b/testrunner.conf @@ -72,6 +72,24 @@ listener *:8080 { pattern bar/(%d+)/test { rewrite as = /hello?name=rewritten%1 } + pattern lua/redir/(%d+)x(%d+) { + expand_with_lua = true + redirect to = ''' + function handle_rewrite(req, captures) + local r = captures[1] * captures[2] + return '/hello?name=redirected' .. r + end + ''' + } + pattern lua/rewrite/(%d+)x(%d+) { + expand_with_lua = true + rewrite as = ''' + function handle_rewrite(req, captures) + local r = captures[1] * captures[2] + return '/hello?name=rewritten' .. r + end + ''' + } } serve_files / { path = ./wwwroot diff --git a/tools/testsuite.py b/tools/testsuite.py index 68bd782f8..716f74eef 100755 --- a/tools/testsuite.py +++ b/tools/testsuite.py @@ -233,6 +233,19 @@ def test_pattern_rewrite_as(self): self.assertFalse('location' in r.headers) self.assertEqual(r.text, 'Hello, rewritten42!') + def test_lua_redirect_to(self): + r = requests.get('http://127.0.0.1:8080/pattern/lua/redir/6x7', allow_redirects=False) + + self.assertResponseHtml(r, 301) + self.assertTrue('location' in r.headers) + self.assertEqual(r.headers['location'], '/hello?name=redirected42') + + def test_lua_rewrite_as(self): + r = requests.get('http://127.0.0.1:8080/pattern/lua/rewrite/7x6', allow_redirects=False) + + self.assertResponsePlain(r, 200) + self.assertFalse('location' in r.headers) + self.assertEqual(r.text, 'Hello, rewritten42!') class SocketTest(LwanTest): def connect(self, host='127.0.0.1', port=8080): From 88257f6247abd5ea82a7c2d04da7e5d345a503b7 Mon Sep 17 00:00:00 2001 From: 0x003e <0x003e@gmail.com> Date: Sat, 30 Jul 2016 12:33:38 +0200 Subject: [PATCH 0262/2505] CMake: Add testsuite and benchmark target. --- CMakeLists.txt | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index f197787d2..d26415063 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -227,3 +227,23 @@ configure_file( ) install(FILES "${CMAKE_BINARY_DIR}/${PROJECT_NAME}.pc" DESTINATION lib/pkgconfig) + +if (LUA_FOUND) + function(add_python_test NAME FILE WORKING_DIRECTORY DEPEND) + add_custom_target(${NAME} + COMMAND python ${FILE} + DEPENDS ${DEPEND} + WORKING_DIRECTORY ${WORKING_DIRECTORY} + COMMENT "Running Python tests: ${FILE}.") + endfunction() + + add_python_test( testsuite + ${PROJECT_SOURCE_DIR}/tools/testsuite.py + ${PROJECT_SOURCE_DIR} + testrunner) + + add_python_test( benchmark + ${PROJECT_SOURCE_DIR}/tools/benchmark.py + ${PROJECT_SOURCE_DIR} + testrunner) +endif() \ No newline at end of file From d72c753763e41e358c39a23a1e45ba346606aa90 Mon Sep 17 00:00:00 2001 From: 0x003e <0x003e@gmail.com> Date: Sat, 30 Jul 2016 12:36:35 +0200 Subject: [PATCH 0263/2505] =?UTF-8?q?Test=20benchmark=20modification.=20At?= =?UTF-8?q?tach=20subprocess=20./build/testrunner/testrunner,=20to=20run?= =?UTF-8?q?=20it=20automatically=20before=20the=C2=A0=20weighttp=20benchma?= =?UTF-8?q?rk=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/benchmark.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tools/benchmark.py b/tools/benchmark.py index 9c0d7358d..fa90d64c5 100755 --- a/tools/benchmark.py +++ b/tools/benchmark.py @@ -4,6 +4,21 @@ import json import commands import time +import subprocess +import os + +LWAN_PATH = './build/testrunner/testrunner' +for arg in sys.argv[1:]: + if not arg.startswith('-') and os.path.exists(arg): + LWAN_PATH = arg + sys.argv.remove(arg) + +print 'Using', LWAN_PATH, 'for lwan' + +lwan=subprocess.Popen( + [LWAN_PATH], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT +) try: import matplotlib.pyplot as plt @@ -171,3 +186,4 @@ def log(self, keep_alive, n_connections, rps, kbps, _2xx, _3xx, _4xx, _5xx): sleepwithstatus('Waiting for keepalive connection timeout', keep_alive_timeout * 1.1) output.footer() + lwan.kill() \ No newline at end of file From da7e379d5504f0fc06d928b995844df2ff6a3601 Mon Sep 17 00:00:00 2001 From: 0x003e <0x003e@gmail.com> Date: Sat, 30 Jul 2016 12:39:27 +0200 Subject: [PATCH 0264/2505] Documentation upgrade. Add optional dependencies to the list, add testsuite instructions, add benchmark instructions. --- README.md | 19 ++++++++++++++++++- tools/benchmark.py | 2 +- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 511c463e1..496079b58 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,12 @@ The build system will look for these libraries and enable/link if available. - [TCMalloc](https://github.com/gperftools/gperftools) - [jemalloc](http://www.canonware.com/jemalloc/) - [Valgrind](http://valgrind.org) - - To run test suite, [Python](https://www.python.org/) (2.6+) with Requests module is required + - To run test suite: + - [Python](https://www.python.org/) (2.6+) with Requests + - [Lua 5.1](http://www.lua.org) + - To run benchmark : + - Special version of [Weighttp](https://github.com/lpereira/weighttp) + - [Matplotlib](https://github.com/matplotlib/matplotlib) ### Common operating system package names @@ -157,6 +162,18 @@ Valgrind, is built with Undefined Behavior Sanitizer, and includes debugging messages that are stripped in the release version. Debugging messages are printed for each and every request. +### Tests + + ~/lwan/build$ make teststuite + +This will compile `testrunner/testrunner` and execute regression test suite in `tool/testsuite.py`. + +### Benchmark + + ~/lwan/build$ make benchmark + +This will compile `testrunner/testrunner` and execute benchmark script `tools/benchmark.py`. + Running ------- diff --git a/tools/benchmark.py b/tools/benchmark.py index fa90d64c5..0182a2660 100755 --- a/tools/benchmark.py +++ b/tools/benchmark.py @@ -186,4 +186,4 @@ def log(self, keep_alive, n_connections, rps, kbps, _2xx, _3xx, _4xx, _5xx): sleepwithstatus('Waiting for keepalive connection timeout', keep_alive_timeout * 1.1) output.footer() - lwan.kill() \ No newline at end of file + lwan.kill() From 3247e6328a41b094ea8fed56ac3166ea4f189289 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 2 Aug 2016 08:52:57 -0300 Subject: [PATCH 0265/2505] Fix off-by-one error in rewrite when running Lua script --- common/lwan-rewrite.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lwan-rewrite.c b/common/lwan-rewrite.c index 59bda04a2..8314b8f25 100644 --- a/common/lwan-rewrite.c +++ b/common/lwan-rewrite.c @@ -211,7 +211,7 @@ expand_lua(lwan_request_t *request, struct pattern *pattern, const char *orig, return NULL; } - ret = memcpy(buffer, output, output_len); + ret = memcpy(buffer, output, output_len + 1); lua_pop(L, 1); /* 1: return value */ return ret; } From afb1482dabcd23a1a11d65e677de5091aab79c72 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 2 Aug 2016 08:55:59 -0300 Subject: [PATCH 0266/2505] No need to copy string to add it to the capture table --- common/lwan-rewrite.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/common/lwan-rewrite.c b/common/lwan-rewrite.c index 8314b8f25..c4f058c0e 100644 --- a/common/lwan-rewrite.c +++ b/common/lwan-rewrite.c @@ -183,14 +183,8 @@ expand_lua(lwan_request_t *request, struct pattern *pattern, const char *orig, lua_createtable(L, captures, 0); for (i = 0; i < captures; i++) { - char *tmp = coro_strndup(request->conn->coro, orig + sf[i].sm_so, - (size_t)(sf[i].sm_eo - sf[i].sm_so)); - lua_pushinteger(L, i); - if (tmp) - lua_pushstring(L, tmp); - else - lua_pushnil(L); + lua_pushlstring(L, orig + sf[i].sm_so, (size_t)(sf[i].sm_eo - sf[i].sm_so)); lua_settable(L, -3); } From f63f7982bba6b8cd893f305351bd1376d0ff7494 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 2 Aug 2016 20:48:07 -0300 Subject: [PATCH 0267/2505] Plug memory leak in the BSD epoll implementation The hash table to coalesce file descriptor events would not be freed if the call to kevent() returned an error. --- common/missing.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/common/missing.c b/common/missing.c index 59706a546..20d296781 100644 --- a/common/missing.c +++ b/common/missing.c @@ -260,12 +260,14 @@ epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) int i, r; coalesce = hash_int_new(NULL, NULL); - if (!coalesce) + if (UNLIKELY(!coalesce)) return -1; r = kevent(epfd, NULL, 0, evs, maxevents, to_timespec(&tmspec, timeout)); - if (UNLIKELY(r < 0)) + if (UNLIKELY(r < 0)) { + hash_free(coalesce); return -1; + } for (i = 0; i < r; i++) { struct kevent *kev = &evs[i]; From 204ac96928d645b68adcfbe031367cdce2200aee Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 2 Aug 2016 23:51:26 -0300 Subject: [PATCH 0268/2505] Convert benchmark.py and testsuite.py to Python 3 --- CMakeLists.txt | 11 ++-- tools/benchmark.py | 26 ++++---- tools/testsuite.py | 156 ++++++++++++++++++++++++--------------------- 3 files changed, 103 insertions(+), 90 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d26415063..724dc018b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -228,22 +228,23 @@ configure_file( install(FILES "${CMAKE_BINARY_DIR}/${PROJECT_NAME}.pc" DESTINATION lib/pkgconfig) -if (LUA_FOUND) +find_package(PythonInterp 3) +if (LUA_FOUND AND PYTHONINTERP_FOUND) function(add_python_test NAME FILE WORKING_DIRECTORY DEPEND) add_custom_target(${NAME} - COMMAND python ${FILE} + COMMAND ${PYTHON_EXECUTABLE} ${FILE} DEPENDS ${DEPEND} WORKING_DIRECTORY ${WORKING_DIRECTORY} COMMENT "Running Python tests: ${FILE}.") endfunction() - add_python_test( testsuite + add_python_test(testsuite ${PROJECT_SOURCE_DIR}/tools/testsuite.py ${PROJECT_SOURCE_DIR} testrunner) - add_python_test( benchmark + add_python_test(benchmark ${PROJECT_SOURCE_DIR}/tools/benchmark.py ${PROJECT_SOURCE_DIR} testrunner) -endif() \ No newline at end of file +endif() diff --git a/tools/benchmark.py b/tools/benchmark.py index 0182a2660..ce25642ef 100755 --- a/tools/benchmark.py +++ b/tools/benchmark.py @@ -1,11 +1,13 @@ #!/usr/bin/python -import sys -import json +from __future__ import print_function + import commands -import time -import subprocess +import json import os +import subprocess +import sys +import time LWAN_PATH = './build/testrunner/testrunner' for arg in sys.argv[1:]: @@ -13,7 +15,7 @@ LWAN_PATH = arg sys.argv.remove(arg) -print 'Using', LWAN_PATH, 'for lwan' +print('Using', LWAN_PATH, 'for lwan') lwan=subprocess.Popen( [LWAN_PATH], @@ -92,7 +94,7 @@ def cmdlineintarg(arg, default=0): try: value = int(sys.argv[index]) except ValueError: - print 'Argument is of invalid type for argument %s, assuming default (%d)' % (arg, default) + print('Argument is of invalid type for argument %s, assuming default (%d)' % (arg, default)) finally: del sys.argv[index] return value @@ -100,15 +102,15 @@ def cmdlineintarg(arg, default=0): class CSVOutput: def header(self): - print 'keep_alive,n_connections,rps,kbps,2xx,3xx,4xx,5xx' + print('keep_alive,n_connections,rps,kbps,2xx,3xx,4xx,5xx') def footer(self): clearstderrline() def log(self, keep_alive, n_connections, rps, kbps, _2xx, _3xx, _4xx, _5xx): clearstderrline() - print ','.join(str(token) for token in - (int(keep_alive), n_connections, rps, kbps, _2xx, _3xx, _4xx, _5xx)) + print(','.join(str(token) for token in + (int(keep_alive), n_connections, rps, kbps, _2xx, _3xx, _4xx, _5xx))) class MatplotlibOutput: @@ -150,8 +152,8 @@ def log(self, keep_alive, n_connections, rps, kbps, _2xx, _3xx, _4xx, _5xx): if __name__ == '__main__': if not weighttp_has_json_output(): - print 'This script requires a special version of weighttp which supports JSON' - print 'output. Get it at http://github.com/lpereira/weighttp' + print('This script requires a special version of weighttp which supports JSON') + print('output. Get it at http://github.com/lpereira/weighttp') sys.exit(1) plot = cmdlineboolarg('--plot') @@ -166,7 +168,7 @@ def log(self, keep_alive, n_connections, rps, kbps, _2xx, _3xx, _4xx, _5xx): if plt is None: if plot: - print 'Matplotlib not installed!' + print('Matplotlib not installed!') sys.exit(1) output = CSVOutput() elif plot: diff --git a/tools/testsuite.py b/tools/testsuite.py index 716f74eef..f35d731d4 100755 --- a/tools/testsuite.py +++ b/tools/testsuite.py @@ -3,14 +3,15 @@ # performs certain system calls. This should speed up the mmap tests # considerably and make it possible to perform more low-level tests. -import subprocess -import time -import unittest +import os +import re import requests +import signal import socket +import subprocess import sys -import os -import re +import time +import unittest LWAN_PATH = './build/testrunner/testrunner' for arg in sys.argv[1:]: @@ -18,7 +19,7 @@ LWAN_PATH = arg sys.argv.remove(arg) -print 'Using', LWAN_PATH, 'for lwan' +print('Using', LWAN_PATH, 'for lwan') class LwanTest(unittest.TestCase): def setUp(self): @@ -39,11 +40,8 @@ def setUp(self): raise Exception('Timeout waiting for lwan') def tearDown(self): - self.lwan.poll() - if self.lwan.returncode is not None: - self.assertEqual(self.lwan.returncode, 0) - else: - self.lwan.kill() + self.lwan.send_signal(signal.SIGINT) + self.lwan.communicate() def assertHttpResponseValid(self, request, status_code, content_type): self.assertEqual(request.status_code, status_code) @@ -248,72 +246,85 @@ def test_lua_rewrite_as(self): self.assertEqual(r.text, 'Hello, rewritten42!') class SocketTest(LwanTest): + class WrappedSock: + def __init__(self, sock): + self._wrapped_sock = sock + + def send(self, stuff): + return self._wrapped_sock.send(bytes(stuff, 'UTF-8')) + + def recv(self, n_bytes): + return str(self._wrapped_sock.recv(n_bytes), 'UTF-8') + + def __enter__(self): + return self + + def __exit__(self, *args): + return self._wrapped_sock.close() + + def __getattr__(self, attr): + if attr in self.__dict__: + return getattr(self, attr) + return getattr(self._wrapped_sock, attr) + def connect(self, host='127.0.0.1', port=8080): def _connect(host, port): try: - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.connect((host, port)) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect((host, port)) except socket.error: - return None - return sock + if sock: + sock.close() + del sock + + return None + finally: + return sock sock = _connect(host, port) self.assertNotEqual(sock, None) - return sock + return SocketTest.WrappedSock(sock) class TestMalformedRequests(SocketTest): def assertHttpCode(self, sock, code): contents = sock.recv(128) - self.assertRegexpMatches(contents, r'^HTTP/1\.[01] ' + str(code) + r' ') - - - def test_random_flood(self): - with open('/dev/urandom', 'rb') as urandom: - for step in range(10): - - buffer = b'' - while len(buffer) < 8192: - buffer += urandom.read(8192 - len(buffer)) - - sock = self.connect() - sock.send(buffer) - self.assertHttpCode(sock, 413) + self.assertRegex(contents, r'^HTTP/1\.[01] ' + str(code) + r' ') def test_cat_sleeping_on_keyboard(self): - sock = self.connect() - sock.send('asldkfjg238045tgqwdcjv1li 2u4ftw dfjkb12345t\r\n\r\n') + with self.connect() as sock: + sock.send('asldkfjg238045tgqwdcjv1li 2u4ftw dfjkb12345t\r\n\r\n') - self.assertHttpCode(sock, 405) + self.assertHttpCode(sock, 405) def test_no_http_version_fails(self): - sock = self.connect() - sock.send('GET /\r\n\r\n') + with self.connect() as sock: + sock.send('GET /\r\n\r\n') - self.assertHttpCode(sock, 400) + self.assertHttpCode(sock, 400) def test_proxy_get_fails(self): - sock = self.connect() - sock.send('GET http://example.com HTTP/1.0\r\n\r\n') + with self.connect() as sock: + sock.send('GET http://example.com HTTP/1.0\r\n\r\n') - self.assertHttpCode(sock, 400) + self.assertHttpCode(sock, 400) def test_get_not_http(self): - sock = self.connect() - sock.send('GET / FROG/1.0\r\n\r\n') + with self.connect() as sock: + sock.send('GET / FROG/1.0\r\n\r\n') - self.assertHttpCode(sock, 400) + self.assertHttpCode(sock, 400) def test_get_http_not_1_x(self): - sock = self.connect() - sock.send('GET / HTTP/2.0\r\n\r\n') + with self.connect() as sock: + sock.send('GET / HTTP/2.0\r\n\r\n') - self.assertHttpCode(sock, 400) + self.assertHttpCode(sock, 400) def test_request_too_large(self): @@ -350,7 +361,7 @@ def test_cookies(self): self.assertResponseHtml(r) self.assertEqual(r.text, 'Cookie FOO has value: BAR') - for cookie, value in cookies_to_receive.items(): + for cookie, value in list(cookies_to_receive.items()): self.assertTrue(cookie in r.cookies) self.assertEqual(r.cookies[cookie], value) @@ -367,7 +378,7 @@ def test_cookies(self): self.assertResponsePlain(r) self.assertTrue('\n\nCookies\n' in r.text) - for k, v in c.items(): + for k, v in list(c.items()): self.assertTrue('Key = "%s"; Value = "%s"\n' % (k, v) in r.text) def test_head_request_hello(self): @@ -434,15 +445,15 @@ def test_post_request(self): self.assertResponsePlain(r) self.assertTrue('POST data' in r.text) - for k, v in data.items(): + for k, v in list(data.items()): self.assertTrue('Key = "%s"; Value = "%s"\n' % (k, v) in r.text) class TestCache(LwanTest): def mmaps(self, f): - f = f + '\n' - return (l.endswith(f) for l in - file('/proc/%d/maps' % self.lwan.pid)) + with open('/proc/%d/maps' % self.lwan.pid) as map_file: + f = f + '\n' + return [l.endswith(f) for l in map_file] def count_mmaps(self, f): @@ -512,13 +523,12 @@ def test_proxy_version1(self): Connection: keep-alive\r Host: 192.168.0.11\r\n\r\n''' - sock = self.connect() - - for request in range(5): - sock.send(proxy + req if request == 0 else req) - response = sock.recv(4096) - self.assertTrue(response.startswith('HTTP/1.1 200 OK'), response) - self.assertTrue('X-Proxy: 192.168.242.221' in response, response) + with self.connect() as sock: + for request in range(5): + sock.send(proxy + req if request == 0 else req) + response = sock.recv(4096) + self.assertTrue(response.startswith('HTTP/1.1 200 OK'), response) + self.assertTrue('X-Proxy: 192.168.242.221' in response, response) def test_proxy_version2(self): proxy = ( @@ -530,12 +540,12 @@ def test_proxy_version2(self): Connection: keep-alive\r Host: 192.168.0.11\r\n\r\n''' - sock = self.connect() - for request in range(5): - sock.send(proxy + req if request == 0 else req) - response = sock.recv(4096) - self.assertTrue(response.startswith('HTTP/1.1 200 OK'), response) - self.assertTrue('X-Proxy: 1.2.3.4' in response, response) + with self.connect() as sock: + for request in range(5): + sock.send(proxy + req if request == 0 else req) + response = sock.recv(4096) + self.assertTrue(response.startswith('HTTP/1.1 200 OK'), response) + self.assertTrue('X-Proxy: 1.2.3.4' in response, response) class TestPipelinedRequests(SocketTest): def test_pipelined_requests(self): @@ -547,16 +557,16 @@ def test_pipelined_requests(self): Accept: text/plain,text/html;q=0.9,application/xhtml+xml;q=0.9,application/xml;q=0.8,*/*;q=0.7''' % name for name in names) reqs += '\r\n\r\n' - sock = self.connect() - sock.send(reqs) + with self.connect() as sock: + sock.send(reqs) - responses = '' - while len(response_separator.findall(responses)) != 16: - response = sock.recv(32) - if response: - responses += response - else: - break + responses = '' + while len(response_separator.findall(responses)) != 16: + response = sock.recv(32) + if response: + responses += response + else: + break for name in names: s = 'Hello, %s!' % name From 5c6fadfc783561757130218274edbd49ef3b2430 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 2 Aug 2016 23:56:21 -0300 Subject: [PATCH 0269/2505] Add a `generate-coverage' target Use the `Coverage` build type: $ cmake ${SRC_DIR} -DCMAKE_BUILD_TYPE=Coverage Then use the generate-coverage target: $ make generate-coverage This will build an instrumented version of Lwan suitable to be used with gcov and lcov, and will produce a report in HTML displaying the code coverage. --- CMakeLists.txt | 8 +- testrunner/CMakeLists.txt | 16 ++++ testrunner/CodeCoverage.cmake | 165 ++++++++++++++++++++++++++++++++++ 3 files changed, 186 insertions(+), 3 deletions(-) create mode 100644 testrunner/CodeCoverage.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 724dc018b..4eb904c65 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -199,10 +199,12 @@ include_directories(BEFORE common/missing) add_subdirectory(common) include_directories(common) -add_subdirectory(lwan) add_subdirectory(testrunner) -add_subdirectory(freegeoip) -add_subdirectory(techempower) +if (NOT ${CMAKE_BUILD_TYPE} MATCHES "Coverage") + add_subdirectory(lwan) + add_subdirectory(freegeoip) + add_subdirectory(techempower) +endif() set(PKG_CONFIG_REQUIRES "") set(PKG_CONFIG_LIBDIR "\${prefix}/lib") diff --git a/testrunner/CMakeLists.txt b/testrunner/CMakeLists.txt index 6bb35085c..a91816481 100644 --- a/testrunner/CMakeLists.txt +++ b/testrunner/CMakeLists.txt @@ -5,3 +5,19 @@ target_link_libraries(testrunner ${CMAKE_DL_LIBS} ${ADDITIONAL_LIBRARIES} ) + +if (${CMAKE_BUILD_TYPE} MATCHES "Coverage") + find_package(PythonInterp 3) + + if (PYTHONINTERP_FOUND) + include(CodeCoverage.cmake) + + setup_target_for_coverage(generate-coverage + "${PYTHON_EXECUTABLE} ${CMAKE_HOME_DIRECTORY}/tools/testsuite.py $" + coverage + ) + message(STATUS "Python found; generate-coverage target enabled") + else () + message(STATUS "Python not found; coverage report disabled") + endif() +endif () diff --git a/testrunner/CodeCoverage.cmake b/testrunner/CodeCoverage.cmake new file mode 100644 index 000000000..975a0cd57 --- /dev/null +++ b/testrunner/CodeCoverage.cmake @@ -0,0 +1,165 @@ +# Copyright (c) 2012 - 2015, Lars Bilke +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# +# +# 2012-01-31, Lars Bilke +# - Enable Code Coverage +# +# 2013-09-17, Joakim Söderberg +# - Added support for Clang. +# - Some additional usage instructions. +# +# USAGE: + +# 0. (Mac only) If you use Xcode 5.1 make sure to patch geninfo as described here: +# http://stackoverflow.com/a/22404544/80480 +# +# 1. Copy this file into your cmake modules path. +# +# 2. Add the following line to your CMakeLists.txt: +# INCLUDE(CodeCoverage) +# +# 3. Set compiler flags to turn off optimization and enable coverage: +# SET(CMAKE_CXX_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage") +# SET(CMAKE_C_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage") +# +# 3. Use the function SETUP_TARGET_FOR_COVERAGE to create a custom make target +# which runs your test executable and produces a lcov code coverage report: +# Example: +# SETUP_TARGET_FOR_COVERAGE( +# my_coverage_target # Name for custom target. +# test_driver # Name of the test driver executable that runs the tests. +# # NOTE! This should always have a ZERO as exit code +# # otherwise the coverage generation will not complete. +# coverage # Name of output directory. +# ) +# +# 4. Build a Debug build: +# cmake -DCMAKE_BUILD_TYPE=Debug .. +# make +# make my_coverage_target +# +# + +# Check prereqs +FIND_PROGRAM( GCOV_PATH gcov ) +FIND_PROGRAM( LCOV_PATH lcov ) +FIND_PROGRAM( GENHTML_PATH genhtml ) +FIND_PROGRAM( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/tests) + +IF(NOT GCOV_PATH) + MESSAGE(FATAL_ERROR "gcov not found! Aborting...") +ENDIF() # NOT GCOV_PATH + +IF(NOT CMAKE_COMPILER_IS_GNUCC) + # Clang version 3.0.0 and greater now supports gcov as well. + MESSAGE(WARNING "Compiler is not GNU gcc! Clang Version 3.0.0 and greater supports gcov as well, but older versions don't.") + + IF(NOT "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") + MESSAGE(FATAL_ERROR "Compiler is not GNU gcc! Aborting...") + ENDIF() +ENDIF() # NOT CMAKE_COMPILER_IS_GNUCC + +SET(CMAKE_CXX_FLAGS_COVERAGE + "-g -O0 --coverage -fprofile-arcs -ftest-coverage" + CACHE STRING "Flags used by the C++ compiler during coverage builds." + FORCE ) +SET(CMAKE_C_FLAGS_COVERAGE + "-g -O0 --coverage -fprofile-arcs -ftest-coverage" + CACHE STRING "Flags used by the C compiler during coverage builds." + FORCE ) +SET(CMAKE_EXE_LINKER_FLAGS_COVERAGE + "" + CACHE STRING "Flags used for linking binaries during coverage builds." + FORCE ) +SET(CMAKE_SHARED_LINKER_FLAGS_COVERAGE + "" + CACHE STRING "Flags used by the shared libraries linker during coverage builds." + FORCE ) +MARK_AS_ADVANCED( + CMAKE_CXX_FLAGS_COVERAGE + CMAKE_C_FLAGS_COVERAGE + CMAKE_EXE_LINKER_FLAGS_COVERAGE + CMAKE_SHARED_LINKER_FLAGS_COVERAGE ) + +IF ( NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "Coverage")) + MESSAGE( WARNING "Code coverage results with an optimized (non-Debug) build may be misleading" ) +ENDIF() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug" + + +# Param _targetname The name of new the custom make target +# Param _testrunner The name of the target which runs the tests. +# MUST return ZERO always, even on errors. +# If not, no coverage report will be created! +# Param _outputname lcov output is generated as _outputname.info +# HTML report is generated in _outputname/index.html +# Optional fourth parameter is passed as arguments to _testrunner +# Pass them in list form, e.g.: "-j;2" for -j 2 +FUNCTION(SETUP_TARGET_FOR_COVERAGE _targetname _testrunner _outputname) + + IF(NOT LCOV_PATH) + MESSAGE(FATAL_ERROR "lcov not found! Aborting...") + ENDIF() # NOT LCOV_PATH + + IF(NOT GENHTML_PATH) + MESSAGE(FATAL_ERROR "genhtml not found! Aborting...") + ENDIF() # NOT GENHTML_PATH + + SET(coverage_info "${CMAKE_BINARY_DIR}/${_outputname}.info") + SET(coverage_cleaned "${coverage_info}.cleaned") + + SEPARATE_ARGUMENTS(test_command UNIX_COMMAND "${_testrunner}") + + # Setup target + ADD_CUSTOM_TARGET(${_targetname} + + # Cleanup lcov + ${LCOV_PATH} --initial --directory . --capture --output-file ${coverage_info} + COMMAND ${LCOV_PATH} --directory . --zerocounters + + # Run tests + COMMAND ${test_command} ${ARGV3} + + # Capturing lcov counters and generating report + COMMAND ${LCOV_PATH} --directory . --capture --output-file ${coverage_info} + COMMAND ${LCOV_PATH} --remove ${coverage_info} '/usr/*' --output-file ${coverage_cleaned} + COMMAND ${GENHTML_PATH} -o ${_outputname} ${coverage_cleaned} + COMMAND ${CMAKE_COMMAND} -E remove ${coverage_cleaned} ${coverage} + + WORKING_DIRECTORY ${CMAKE_HOME_DIRECTORY} + COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report." + ) + + # Show info where to find the report + ADD_CUSTOM_COMMAND(TARGET ${_targetname} POST_BUILD + COMMAND ; + COMMENT "Open ./${_outputname}/index.html in your browser to view the coverage report." + ) + +ENDFUNCTION() # SETUP_TARGET_FOR_COVERAGE From b14998690818ba916f0aa68d212e11592c680fa0 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 3 Aug 2016 08:11:34 -0300 Subject: [PATCH 0270/2505] Fix ResourceWarning about unclosed connections in testsuite --- tools/testsuite.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/tools/testsuite.py b/tools/testsuite.py index f35d731d4..ccd06a660 100755 --- a/tools/testsuite.py +++ b/tools/testsuite.py @@ -479,12 +479,12 @@ def test_cache_munmaps_conn_close(self): def test_cache_munmaps_conn_keep_alive(self): - s = requests.Session() - r = s.get('http://127.0.0.1:8080/100.html') + with requests.Session() as s: + r = s.get('http://127.0.0.1:8080/100.html') - self.assertTrue(self.is_mmapped('/100.html')) - self.wait_munmap('/100.html') - self.assertFalse(self.is_mmapped('/100.html')) + self.assertTrue(self.is_mmapped('/100.html')) + self.wait_munmap('/100.html') + self.assertFalse(self.is_mmapped('/100.html')) def test_cache_does_not_mmap_large_files(self): @@ -493,11 +493,10 @@ def test_cache_does_not_mmap_large_files(self): def test_cache_mmaps_once_conn_keep_alive(self): - s = requests.Session() - - for request in range(5): - r = s.get('http://127.0.0.1:8080/100.html') - self.assertEqual(self.count_mmaps('/100.html'), 1) + with requests.Session() as s: + for request in range(5): + r = s.get('http://127.0.0.1:8080/100.html') + self.assertEqual(self.count_mmaps('/100.html'), 1) def test_cache_mmaps_once_conn_close(self): From 6ec6637451d4db48a6a595229256c305a0fd9441 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 3 Aug 2016 09:00:23 -0300 Subject: [PATCH 0271/2505] Add a /quit-lwan endpoint to testrunner This makes exiting the test program easier. --- testrunner.conf | 3 +++ testrunner/main.c | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/testrunner.conf b/testrunner.conf index eb0535194..3a2ac033b 100644 --- a/testrunner.conf +++ b/testrunner.conf @@ -23,6 +23,9 @@ listener *:8080 { prefix /hello { handler = hello_world } + prefix /quit-lwan { + handler = quit_lwan + } prefix /proxy { handler = test_proxy } diff --git a/testrunner/main.c b/testrunner/main.c index d62752455..12a7d99d0 100644 --- a/testrunner/main.c +++ b/testrunner/main.c @@ -24,6 +24,15 @@ #include "lwan.h" #include "lwan-serve-files.h" +lwan_http_status_t +quit_lwan(lwan_request_t *request __attribute__((unused)), + lwan_response_t *response __attribute__((unused)), + void *data __attribute__((unused))) +{ + exit(0); + return HTTP_OK; +} + lwan_http_status_t gif_beacon(lwan_request_t *request __attribute__((unused)), lwan_response_t *response, From f61e2819a173edecb36896b659a85940d25e607f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 3 Aug 2016 09:00:55 -0300 Subject: [PATCH 0272/2505] Run testsuite for coverage with -v --- testrunner/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testrunner/CMakeLists.txt b/testrunner/CMakeLists.txt index a91816481..f4847754c 100644 --- a/testrunner/CMakeLists.txt +++ b/testrunner/CMakeLists.txt @@ -13,7 +13,7 @@ if (${CMAKE_BUILD_TYPE} MATCHES "Coverage") include(CodeCoverage.cmake) setup_target_for_coverage(generate-coverage - "${PYTHON_EXECUTABLE} ${CMAKE_HOME_DIRECTORY}/tools/testsuite.py $" + "${PYTHON_EXECUTABLE} ${CMAKE_HOME_DIRECTORY}/tools/testsuite.py $ -v" coverage ) message(STATUS "Python found; generate-coverage target enabled") From 1e8b6fbb966a0eaf15ea611ee9cc6afc84991131 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 3 Aug 2016 09:01:34 -0300 Subject: [PATCH 0273/2505] Use /quit-lwan in testsuite.py This should plug a few ResourceWarning exceptions as well. --- tools/testsuite.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tools/testsuite.py b/tools/testsuite.py index ccd06a660..0a47725c4 100755 --- a/tools/testsuite.py +++ b/tools/testsuite.py @@ -40,8 +40,17 @@ def setUp(self): raise Exception('Timeout waiting for lwan') def tearDown(self): - self.lwan.send_signal(signal.SIGINT) - self.lwan.communicate() + try: + requests.get('http://127.0.0.1:8080/quit-lwan') + except requests.exceptions.ConnectionError: + # Requesting /quit-lwan will make testrunner exit(0), closing the + # connection without sending a response, raising this exception. + # That's expected here. + return + finally: + with self.lwan as l: + l.communicate(timeout=1.0) + l.kill() def assertHttpResponseValid(self, request, status_code, content_type): self.assertEqual(request.status_code, status_code) From c6924f2e660c7ff283a3857c7950bf5befb46ac3 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 3 Aug 2016 09:12:54 -0300 Subject: [PATCH 0274/2505] chdir("/") after calling chroot() to avoid escaping jail --- common/lwan-straitjacket.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/lwan-straitjacket.c b/common/lwan-straitjacket.c index 651b120cb..50560d35b 100644 --- a/common/lwan-straitjacket.c +++ b/common/lwan-straitjacket.c @@ -130,6 +130,8 @@ void lwan_straitjacket_enforce(config_t *c, config_line_t *l) lwan_status_critical_perror("Could not chroot() to %s", chroot_path); } + if (chdir("/") < 0) + lwan_status_critical_perror("Could not chdir() to /"); lwan_status_debug("Jailed to %s", chroot_path); } From f56d4e0af61d1265ba3b325ef1968f000290e79e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 3 Aug 2016 20:41:49 -0300 Subject: [PATCH 0275/2505] Ignore errors while generating Coverage reports --- testrunner/CodeCoverage.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testrunner/CodeCoverage.cmake b/testrunner/CodeCoverage.cmake index 975a0cd57..b95f4bd0c 100644 --- a/testrunner/CodeCoverage.cmake +++ b/testrunner/CodeCoverage.cmake @@ -144,7 +144,7 @@ FUNCTION(SETUP_TARGET_FOR_COVERAGE _targetname _testrunner _outputname) COMMAND ${LCOV_PATH} --directory . --zerocounters # Run tests - COMMAND ${test_command} ${ARGV3} + COMMAND ${test_command} ${ARGV3} || exit 0 # Capturing lcov counters and generating report COMMAND ${LCOV_PATH} --directory . --capture --output-file ${coverage_info} From 346f23d3f20ca072c97731500a96f68b574d5d45 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 3 Aug 2016 23:25:22 -0300 Subject: [PATCH 0276/2505] readdir_r() is deprecated, use readdir() --- common/lwan-serve-files.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/common/lwan-serve-files.c b/common/lwan-serve-files.c index a2b6753d0..3b896fedc 100644 --- a/common/lwan-serve-files.c +++ b/common/lwan-serve-files.c @@ -227,7 +227,7 @@ static int directory_list_generator(coro_t *coro) { DIR *dir; - struct dirent entry, *buffer; + struct dirent *entry; struct file_list *fl = coro_get_data(coro); int fd; @@ -239,16 +239,13 @@ directory_list_generator(coro_t *coro) if (fd < 0) goto out; - while (!readdir_r(dir, &entry, &buffer)) { + while ((entry = readdir(dir))) { struct stat st; - if (!buffer) - break; - - if (entry.d_name[0] == '.') + if (entry->d_name[0] == '.') continue; - if (fstatat(fd, entry.d_name, &st, 0) < 0) + if (fstatat(fd, entry->d_name, &st, 0) < 0) continue; if (S_ISDIR(st.st_mode)) { @@ -258,7 +255,7 @@ directory_list_generator(coro_t *coro) } else if (S_ISREG(st.st_mode)) { fl->file_list.icon = "file"; fl->file_list.icon_alt = "FILE"; - fl->file_list.type = lwan_determine_mime_type_for_file_name(entry.d_name); + fl->file_list.type = lwan_determine_mime_type_for_file_name(entry->d_name); } else { continue; } @@ -277,7 +274,7 @@ directory_list_generator(coro_t *coro) fl->file_list.unit = "GiB"; } - fl->file_list.name = entry.d_name; + fl->file_list.name = entry->d_name; if (coro_yield(coro, 1)) break; From 2828bb76d7c9430ef6b060db06ea0a4ecc629d6d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 6 Aug 2016 11:06:08 -0300 Subject: [PATCH 0277/2505] Fix build warning if Lua is disabled --- common/lwan-private.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/common/lwan-private.h b/common/lwan-private.h index bb9fa70ca..1eda589dc 100644 --- a/common/lwan-private.h +++ b/common/lwan-private.h @@ -50,7 +50,10 @@ void lwan_straitjacket_enforce(config_t *c, config_line_t *l); uint8_t lwan_char_isspace(char ch) __attribute__((pure)); uint8_t lwan_char_isxdigit(char ch) __attribute__((pure)); -typedef struct lua_State lua_State; +#ifdef HAVE_LUA +#include + lua_State *lwan_lua_create_state(const char *script_file, const char *script); void lwan_lua_state_push_request(lua_State *L, lwan_request_t *request); const char *lwan_lua_state_last_error(lua_State *L); +#endif From 7e22aeb117d96cc75fcecb98cc8400848c7457f6 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 6 Aug 2016 11:08:53 -0300 Subject: [PATCH 0278/2505] Fix compile warning on FreeBSD --- common/sd-daemon.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/sd-daemon.c b/common/sd-daemon.c index 651439b65..dc0a301db 100644 --- a/common/sd-daemon.c +++ b/common/sd-daemon.c @@ -35,7 +35,7 @@ #include "sd-daemon.h" -static rlim_t +static unsigned int get_max_fd(void) { struct rlimit r; @@ -46,7 +46,7 @@ get_max_fd(void) if (r.rlim_max == RLIM_INFINITY) return INT_MAX; - return r.rlim_max; + return (unsigned int)r.rlim_max; } int sd_listen_fds(int unset_environment) { From dc0e3e0805c196c407e768219d35a33fa2c8d6b9 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 14 Aug 2016 15:12:37 -0300 Subject: [PATCH 0279/2505] Remove req:yield() from Lua handlers Just use coroutine.yield() from the Lua side. This also hides the yielding primitive from the rewrite module (not really needed there). --- common/lwan-lua.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/common/lwan-lua.c b/common/lwan-lua.c index 0568db47a..6c690462e 100644 --- a/common/lwan-lua.c +++ b/common/lwan-lua.c @@ -77,11 +77,6 @@ static int req_send_event_cb(lua_State *L) return 0; } -static int req_yield_cb(lua_State *L) -{ - return lua_yield(L, 0); -} - static int req_set_response_cb(lua_State *L) { lwan_request_t *request = userdata_as_request(L, 1); @@ -196,7 +191,6 @@ static int req_set_headers_cb(lua_State *L) static const struct luaL_reg lwan_request_meta_regs[] = { { "query_param", req_query_param_cb }, { "post_param", req_post_param_cb }, - { "yield", req_yield_cb }, { "set_response", req_set_response_cb }, { "say", req_say_cb }, { "send_event", req_send_event_cb }, From ef4eebe97b76764c2ea470731197e40b3aa7715f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 14 Aug 2016 21:20:35 -0300 Subject: [PATCH 0280/2505] Add links to talks mentioning Lwan --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 496079b58..d315e2b53 100644 --- a/README.md +++ b/README.md @@ -225,6 +225,13 @@ Lwan has been also used as a benchmark: * [Kong](https://getkong.org/about/benchmark/) uses Lwan as the [backend API](https://gist.github.com/montanaflynn/01376991f0a3ad07059c) in its benchmark. * [TechEmpower Framework benchmarks](https://www.techempower.com/benchmarks/#section=data-r10&hw=peak&test=json) feature Lwan since round 10. +Some talks mentioning Lwan: + +* [Talk about Lwan](https://www.youtube.com/watch?v=cttY9FdCzUE) at Polyconf16, given by @lpereira. +* This [talk about Iron](https://michaelsproul.github.io/iron-talk/), a framework for Rust, mentions Lwan as an *insane C thing*. +* [https://github.com/cu-data-engineering-s15/syllabus/blob/master/student_lectures/LWAN.pdf](University seminar presentation) about Lwan. +* This [http://www.slideshare.net/EtieneDalcol/web-development-with-lua-bulgaria-web-summit](presentation about Sailor web framework) mentions Lwan. + Not really third-party, but alas: * The [author's blog](http://tia.mat.br). From ff549b46548fefb2a1dd2a4c6a22c345fcfb3eeb Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 16 Aug 2016 09:25:27 -0300 Subject: [PATCH 0281/2505] Check for terminating CRLF only when other headers don't match --- common/lwan-request.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index f1008ea73..671fefb5b 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -535,14 +535,6 @@ parse_headers(struct request_parser_helper *helper, char *buffer, char *buffer_e retry: if ((p + sizeof(int32_t)) >= buffer_end) break; - - STRING_SWITCH_SMALL(p) { - case HTTP_HDR_REQUEST_END: - *p = '\0'; - helper->next_request = p + sizeof("\r\n") - 1; - return p; - } - STRING_SWITCH_L(p) { CASE_HEADER(HTTP_HDR_ENCODING, "-Encoding") helper->accept_encoding.value = value; @@ -581,6 +573,13 @@ parse_headers(struct request_parser_helper *helper, char *buffer, char *buffer_e helper->range.value = value; helper->range.len = length; break; + default: + STRING_SWITCH_SMALL(p) { + case HTTP_HDR_REQUEST_END: + *p = '\0'; + helper->next_request = p + sizeof("\r\n") - 1; + return p; + } } did_not_match: p = memchr(p, '\n', (size_t)(buffer_end - p)); From 35daf3fe954d759f244eaa4772319d74bbdda9a5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 28 Aug 2016 09:35:58 -0300 Subject: [PATCH 0282/2505] Use nested switch statements for larger HTTP requests This ensures certain combinations will be matched correctly; for instance, "Accept" and "Content" were matched separately from "-Encoding", "-Type", and "-Length", allowing invalid matches to happen (e.g. "Accept-Length"). --- common/lwan-request.c | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index 671fefb5b..dafb8c4b4 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -532,25 +532,20 @@ parse_headers(struct request_parser_helper *helper, char *buffer, char *buffer_e char *value; size_t length; -retry: if ((p + sizeof(int32_t)) >= buffer_end) break; + STRING_SWITCH_L(p) { - CASE_HEADER(HTTP_HDR_ENCODING, "-Encoding") - helper->accept_encoding.value = value; - helper->accept_encoding.len = length; - break; - CASE_HEADER(HTTP_HDR_TYPE, "-Type") - helper->content_type.value = value; - helper->content_type.len = length; - break; - CASE_HEADER(HTTP_HDR_LENGTH, "-Length") - helper->content_length.value = value; - helper->content_length.len = length; - break; case HTTP_HDR_ACCEPT: p += sizeof("Accept") - 1; - goto retry; + + STRING_SWITCH_L(p) { + CASE_HEADER(HTTP_HDR_ENCODING, "-Encoding") + helper->accept_encoding.value = value; + helper->accept_encoding.len = length; + break; + } + break; CASE_HEADER(HTTP_HDR_AUTHORIZATION, "Authorization") helper->authorization.value = value; helper->authorization.len = length; @@ -560,7 +555,18 @@ parse_headers(struct request_parser_helper *helper, char *buffer, char *buffer_e break; case HTTP_HDR_CONTENT: p += sizeof("Content") - 1; - goto retry; + + STRING_SWITCH_L(p) { + CASE_HEADER(HTTP_HDR_TYPE, "-Type") + helper->content_type.value = value; + helper->content_type.len = length; + break; + CASE_HEADER(HTTP_HDR_LENGTH, "-Length") + helper->content_length.value = value; + helper->content_length.len = length; + break; + } + break; CASE_HEADER(HTTP_HDR_COOKIE, "Cookie") helper->cookie.value = value; helper->cookie.len = length; From 8bf537d14b3707f170cde52b4ac4ddb85ac4fe60 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 28 Aug 2016 09:44:45 -0300 Subject: [PATCH 0283/2505] Use a simple `if` in death_queue_move_to_last() Comparisons are cheaper than an integer multiplication. --- common/lwan-thread.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/common/lwan-thread.c b/common/lwan-thread.c index 7cdd1b140..f0cc15d64 100644 --- a/common/lwan-thread.c +++ b/common/lwan-thread.c @@ -92,8 +92,9 @@ static void death_queue_move_to_last(struct death_queue_t *dq, * If it's not a keep alive connection, or the coroutine shouldn't be * resumed -- then just mark it to be reaped right away. */ - conn->time_to_die = dq->time + dq->keep_alive_timeout * - (unsigned)!!(conn->flags & (CONN_KEEP_ALIVE | CONN_SHOULD_RESUME_CORO)); + conn->time_to_die = dq->time; + if (conn->flags & (CONN_KEEP_ALIVE | CONN_SHOULD_RESUME_CORO)) + conn->time_to_die += dq->keep_alive_timeout; death_queue_remove(dq, conn); death_queue_insert(dq, conn); From 8b893e6f43365226ac3b95a1c8b7f78410a970d4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 5 Sep 2016 01:50:43 -0300 Subject: [PATCH 0284/2505] Fix some links in README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d315e2b53..a725ef2f5 100644 --- a/README.md +++ b/README.md @@ -227,10 +227,10 @@ Lwan has been also used as a benchmark: Some talks mentioning Lwan: -* [Talk about Lwan](https://www.youtube.com/watch?v=cttY9FdCzUE) at Polyconf16, given by @lpereira. +* [Talk about Lwan](https://www.youtube.com/watch?v=cttY9FdCzUE) at Polyconf16, given by [@lpereira]. * This [talk about Iron](https://michaelsproul.github.io/iron-talk/), a framework for Rust, mentions Lwan as an *insane C thing*. -* [https://github.com/cu-data-engineering-s15/syllabus/blob/master/student_lectures/LWAN.pdf](University seminar presentation) about Lwan. -* This [http://www.slideshare.net/EtieneDalcol/web-development-with-lua-bulgaria-web-summit](presentation about Sailor web framework) mentions Lwan. +* [University seminar presentation](https://github.com/cu-data-engineering-s15/syllabus/blob/master/student_lectures/LWAN.pdf) about Lwan. +* This [presentation about Sailor web framework](http://www.slideshare.net/EtieneDalcol/web-development-with-lua-bulgaria-web-summit) mentions Lwan. Not really third-party, but alas: From 11ab4e7fd6dd6bac7fb7ecbce52c4fdc968e08a3 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 18 Sep 2016 12:44:21 -0300 Subject: [PATCH 0285/2505] Python requests module won't allow header values with stray spaces To avoid header injection, header names and values are validated to check if they don't have leading or preceding spaces. This is a good thing in the general case, but we're sort of losing a test case here. --- tools/testsuite.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/testsuite.py b/tools/testsuite.py index 0a47725c4..ca7dfd0ee 100755 --- a/tools/testsuite.py +++ b/tools/testsuite.py @@ -160,7 +160,6 @@ def test_get_root(self): def test_compressed_small_file(self): encodings = ( 'deflate', - ' deflate', 'foo,bar,deflate', 'foo, bar, deflate', 'deflote' # This should fail, but won't in our current implementation From f638b8f26baead0ba451a5890feec5b630eb246c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 18 Sep 2016 12:43:44 -0300 Subject: [PATCH 0286/2505] Allow POST request bodies larger than DEFAULT_BUFFER_SIZE --- common/lwan-request.c | 65 +++++++++++++++++++++++++++---------------- common/lwan.c | 26 +++++++++++------ common/lwan.h | 1 + testrunner.conf | 11 ++++++-- testrunner/main.c | 28 ++++++++++++++++++- tools/testsuite.py | 17 +++++++++-- 6 files changed, 110 insertions(+), 38 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index dafb8c4b4..3fe5ea6e9 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -807,40 +807,57 @@ read_request(lwan_request_t *request, struct request_parser_helper *helper) DEFAULT_BUFFER_SIZE, read_request_finalizer); } -static lwan_http_status_t -read_post_data(lwan_request_t *request __attribute__((unused)), - struct request_parser_helper *helper, - char *buffer __attribute__((unused))) +static lwan_read_finalizer_t post_data_finalizer(size_t total_read, + size_t buffer_size, struct request_parser_helper *helper __attribute__((unused))) { - long parsed_length; + return buffer_size == total_read ? FINALIZER_DONE : FINALIZER_TRY_AGAIN; +} - if (UNLIKELY(!helper->next_request)) - return HTTP_BAD_REQUEST; +static lwan_http_status_t +read_post_data(lwan_request_t *request, struct request_parser_helper *helper) +{ + /* Holy indirection, Batman! */ + const size_t max_post_data_size = request->conn->thread->lwan->config.max_post_data_size; + char *new_buffer; + long parsed_size; if (UNLIKELY(!helper->content_length.value)) return HTTP_BAD_REQUEST; - - parsed_length = parse_long(helper->content_length.value, DEFAULT_BUFFER_SIZE); - if (UNLIKELY(parsed_length >= DEFAULT_BUFFER_SIZE)) - return HTTP_TOO_LARGE; - if (UNLIKELY(parsed_length < 0)) + parsed_size = parse_long(helper->content_length.value, -1); + if (UNLIKELY(parsed_size < 0)) return HTTP_BAD_REQUEST; + if (UNLIKELY(parsed_size >= (long)max_post_data_size)) + return HTTP_TOO_LARGE; - size_t post_data_size = (size_t)parsed_length; - char *buffer_end = helper->buffer->value + helper->buffer->len; - size_t have = (size_t)(ptrdiff_t)(buffer_end - helper->next_request); + size_t post_data_size = (size_t)parsed_size; + size_t have; + if (!helper->next_request) { + have = 0; + } else { + char *buffer_end = helper->buffer->value + helper->buffer->len; + have = (size_t)(ptrdiff_t)(buffer_end - helper->next_request); - if (have == post_data_size) { - helper->post_data.value = helper->next_request; - helper->post_data.len = post_data_size; - helper->next_request += post_data_size; - return HTTP_OK; + if (have >= post_data_size) { + helper->post_data.value = helper->next_request; + helper->post_data.len = post_data_size; + helper->next_request += post_data_size; + return HTTP_OK; + } } - if (post_data_size > have) - return HTTP_TOO_LARGE; + new_buffer = coro_malloc(request->conn->coro, post_data_size + 1); + if (UNLIKELY(!new_buffer)) + return HTTP_INTERNAL_ERROR; + + helper->post_data.value = new_buffer; + helper->post_data.len = post_data_size; + if (have) + new_buffer = mempcpy(new_buffer, helper->next_request, have); + helper->next_request = NULL; - return HTTP_NOT_IMPLEMENTED; + lwan_value_t buffer = { .value = new_buffer, .len = post_data_size - have }; + return read_from_request_socket(request, &buffer, helper, buffer.len, + post_data_finalizer); } static char * @@ -900,7 +917,7 @@ parse_http_request(lwan_request_t *request, struct request_parser_helper *helper compute_keep_alive_flag(request, helper); if (request->flags & REQUEST_METHOD_POST) { - lwan_http_status_t status = read_post_data(request, helper, buffer); + lwan_http_status_t status = read_post_data(request, helper); if (UNLIKELY(status != HTTP_OK)) return status; } diff --git a/common/lwan.c b/common/lwan.c index 5e996e6f2..e794ffec8 100644 --- a/common/lwan.c +++ b/common/lwan.c @@ -51,7 +51,8 @@ static const lwan_config_t default_config = { .reuse_port = false, .proxy_protocol = false, .expires = 1 * ONE_WEEK, - .n_threads = 0 + .n_threads = 0, + .max_post_data_size = 10 * DEFAULT_BUFFER_SIZE }; static void lwan_module_init(lwan_t *l) @@ -388,22 +389,22 @@ static bool setup_from_config(lwan_t *lwan, const char *path) while (config_read_line(&conf, &line)) { switch (line.type) { case CONFIG_LINE_TYPE_LINE: - if (!strcmp(line.line.key, "keep_alive_timeout")) + if (!strcmp(line.line.key, "keep_alive_timeout")) { lwan->config.keep_alive_timeout = (unsigned short)parse_long(line.line.value, default_config.keep_alive_timeout); - else if (!strcmp(line.line.key, "quiet")) + } else if (!strcmp(line.line.key, "quiet")) { lwan->config.quiet = parse_bool(line.line.value, default_config.quiet); - else if (!strcmp(line.line.key, "reuse_port")) + } else if (!strcmp(line.line.key, "reuse_port")) { lwan->config.reuse_port = parse_bool(line.line.value, default_config.reuse_port); - else if (!strcmp(line.line.key, "proxy_protocol")) + } else if (!strcmp(line.line.key, "proxy_protocol")) { lwan->config.proxy_protocol = parse_bool(line.line.value, default_config.proxy_protocol); - else if (!strcmp(line.line.key, "expires")) + } else if (!strcmp(line.line.key, "expires")) { lwan->config.expires = parse_time_period(line.line.value, default_config.expires); - else if (!strcmp(line.line.key, "error_template")) { + } else if (!strcmp(line.line.key, "error_template")) { free(lwan->config.error_template); lwan->config.error_template = strdup(line.line.value); } else if (!strcmp(line.line.key, "threads")) { @@ -411,9 +412,16 @@ static bool setup_from_config(lwan_t *lwan, const char *path) if (n_threads < 0) config_error(&conf, "Invalid number of threads: %d", n_threads); lwan->config.n_threads = (unsigned short int)n_threads; - } - else + } else if (!strcmp(line.line.key, "max_post_data_size")) { + long max_post_data_size = parse_long(line.line.value, (long)default_config.max_post_data_size); + if (max_post_data_size < 0) + config_error(&conf, "Negative maximum post data size"); + else if (max_post_data_size > 1<<20) + config_error(&conf, "Maximum post data can't be over 1MiB"); + lwan->config.max_post_data_size = (size_t)max_post_data_size; + } else { config_error(&conf, "Unknown config key: %s", line.line.key); + } break; case CONFIG_LINE_TYPE_SECTION: if (!strcmp(line.section.name, "listener")) { diff --git a/common/lwan.h b/common/lwan.h index 4d34480d0..e14889a34 100644 --- a/common/lwan.h +++ b/common/lwan.h @@ -286,6 +286,7 @@ struct lwan_config_t_ { char *listener; char *error_template; char *config_file_path; + size_t max_post_data_size; unsigned short keep_alive_timeout; unsigned int expires; short unsigned int n_threads; diff --git a/testrunner.conf b/testrunner.conf index 3a2ac033b..5089ab177 100644 --- a/testrunner.conf +++ b/testrunner.conf @@ -19,6 +19,10 @@ threads = 0 # would be haproxy). proxy_protocol = true +# Maximum post data size of slightly less than 1MiB. The default is too +# small for testing purposes. +max_post_data_size = 1000000 + listener *:8080 { prefix /hello { handler = hello_world @@ -41,8 +45,11 @@ listener *:8080 { prefix /favicon.ico { handler = gif_beacon } - prefix /post { - handler = test_post + prefix /post/blend { + handler = test_post_will_it_blend + } + prefix /post/big { + handler = test_post_big } redirect /elsewhere { to = http://lwan.ws diff --git a/testrunner/main.c b/testrunner/main.c index 12a7d99d0..3aa27a480 100644 --- a/testrunner/main.c +++ b/testrunner/main.c @@ -117,7 +117,7 @@ test_proxy(lwan_request_t *request, } lwan_http_status_t -test_post(lwan_request_t *request, lwan_response_t *response, +test_post_will_it_blend(lwan_request_t *request, lwan_response_t *response, void *data __attribute__((unused))) { static const char type[] = "application/json"; @@ -148,6 +148,32 @@ test_post(lwan_request_t *request, lwan_response_t *response, return HTTP_OK; } +lwan_http_status_t +test_post_big(lwan_request_t *request, lwan_response_t *response, + void *data __attribute__((unused))) +{ + static const char type[] = "x-test/trololo"; + size_t i, sum = 0; + + if (!request->header.content_type) + return HTTP_BAD_REQUEST; + if (!request->header.content_type->value) + return HTTP_BAD_REQUEST; + if (request->header.content_type->len != sizeof(type) - 1) + return HTTP_BAD_REQUEST; + if (memcmp(request->header.content_type->value, type, sizeof(type) - 1) != 0) + return HTTP_BAD_REQUEST; + + for (i = 0; i < request->header.body->len; i++) + sum += (size_t)request->header.body->value[i]; + + response->mime_type = "application/json"; + strbuf_printf(response->buffer, "{\"received\": %zu, \"sum\": %zu}", + request->header.body->len, sum); + + return HTTP_OK; +} + lwan_http_status_t hello_world(lwan_request_t *request, lwan_response_t *response, diff --git a/tools/testsuite.py b/tools/testsuite.py index ca7dfd0ee..c6aae7c03 100755 --- a/tools/testsuite.py +++ b/tools/testsuite.py @@ -68,11 +68,24 @@ def assertResponsePlain(self, request, status_code=200): class TestPost(LwanTest): - def test_bat(self): - r = requests.post('http://127.0.0.1:8080/post', json={'will-it-blend': True}) + def test_will_it_blend(self): + r = requests.post('http://127.0.0.1:8080/post/blend', json={'will-it-blend': True}) self.assertHttpResponseValid(r, 200, 'application/json') self.assertEqual(r.json(), {'did-it-blend': 'oh-hell-yeah'}) + def test_big_request(self): + for size in (10, 100, 1000, 10000, 100000): + data = "tro" + "lo" * size + + r = requests.post('http://127.0.0.1:8080/post/big', data=data, + headers={'Content-Type': 'x-test/trololo'}) + + self.assertHttpResponseValid(r, 200, 'application/json') + self.assertEqual(r.json(), { + 'received': len(data), + 'sum': sum(ord(b) for b in data) + }) + class TestFileServing(LwanTest): def test_mime_type_is_correct(self): From a8471664929eed12e9fc95b2aeadc2879e8ba71e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 20 Sep 2016 07:49:55 -0300 Subject: [PATCH 0287/2505] Remove one branch in parse_http_request() when reading POST data --- common/lwan-request.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index 3fe5ea6e9..c8f274a70 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -916,11 +916,8 @@ parse_http_request(lwan_request_t *request, struct request_parser_helper *helper compute_keep_alive_flag(request, helper); - if (request->flags & REQUEST_METHOD_POST) { - lwan_http_status_t status = read_post_data(request, helper); - if (UNLIKELY(status != HTTP_OK)) - return status; - } + if (request->flags & REQUEST_METHOD_POST) + return read_post_data(request, helper); return HTTP_OK; } From 0bcc2ecf1814f5180b2fff0d7b776c6a6b2d59cf Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 21 Sep 2016 22:43:13 -0300 Subject: [PATCH 0288/2505] No need to check if it's a post request in read_request_finalizer() With read_post_data() being called when the method is properly identified, this check can be removed from the read-request finalizer. This should also fix a potential wrong behavior with pipelined requests. --- common/lwan-request.c | 9 --------- 1 file changed, 9 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index c8f274a70..f6afe9712 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -788,15 +788,6 @@ static lwan_read_finalizer_t read_request_finalizer(size_t total_read, if (LIKELY(!memcmp(helper->buffer->value + total_read - 4, "\r\n\r\n", 4))) return FINALIZER_DONE; - if (get_http_method(helper->buffer->value) == REQUEST_METHOD_POST) { - char *post_data_separator = memrchr(helper->buffer->value, '\n', - helper->buffer->len); - if (post_data_separator) { - if (LIKELY(!memcmp(post_data_separator - 3, "\r\n\r", 3))) - return FINALIZER_DONE; - } - } - return FINALIZER_TRY_AGAIN; } From 97509a6633f7245ac1a98507322275eca016899d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 21 Sep 2016 22:45:03 -0300 Subject: [PATCH 0289/2505] Merge back get_http_method() with identify_http_method() These functions were just identify_http_method(), but were split when the code to check for POST requests appeared in read request finalizer. With that gone, there's no need to maintain two functions anymore. --- common/lwan-request.c | 54 +++++++++++++++---------------------------- 1 file changed, 19 insertions(+), 35 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index f6afe9712..1723c2e14 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -89,29 +89,7 @@ union proxy_protocol_header { static char decode_hex_digit(char ch) __attribute__((pure)); static char *ignore_leading_whitespace(char *buffer) __attribute__((pure)); -static lwan_request_flags_t get_http_method(const char *buffer) __attribute__((pure)); -static ALWAYS_INLINE lwan_request_flags_t -get_http_method(const char *buffer) -{ - /* Note: keep in sync in identify_http_method() */ - enum { - HTTP_STR_GET = MULTICHAR_CONSTANT('G','E','T',' '), - HTTP_STR_HEAD = MULTICHAR_CONSTANT('H','E','A','D'), - HTTP_STR_POST = MULTICHAR_CONSTANT('P','O','S','T'), - }; - - STRING_SWITCH(buffer) { - case HTTP_STR_GET: - return REQUEST_METHOD_GET; - case HTTP_STR_HEAD: - return REQUEST_METHOD_HEAD; - case HTTP_STR_POST: - return REQUEST_METHOD_POST; - } - - return 0; -} static bool parse_ascii_port(char *port, unsigned short *out) @@ -283,15 +261,25 @@ parse_proxy_protocol_v2(lwan_request_t *request, char *buffer) static ALWAYS_INLINE char * identify_http_method(lwan_request_t *request, char *buffer) { - static const char sizes[] = { - [0] = 0, - [REQUEST_METHOD_GET] = sizeof("GET ") - 1, - [REQUEST_METHOD_HEAD] = sizeof("HEAD ") - 1, - [REQUEST_METHOD_POST] = sizeof("POST ") - 1, + enum { + HTTP_STR_GET = MULTICHAR_CONSTANT('G','E','T',' '), + HTTP_STR_HEAD = MULTICHAR_CONSTANT('H','E','A','D'), + HTTP_STR_POST = MULTICHAR_CONSTANT('P','O','S','T'), }; - lwan_request_flags_t flags = get_http_method(buffer); - request->flags |= flags; - return buffer + sizes[flags]; + + STRING_SWITCH(buffer) { + case HTTP_STR_GET: + request->flags |= REQUEST_METHOD_GET; + return buffer + sizeof("GET ") - 1; + case HTTP_STR_HEAD: + request->flags |= REQUEST_METHOD_HEAD; + return buffer + sizeof("HEAD ") - 1; + case HTTP_STR_POST: + request->flags |= REQUEST_METHOD_POST; + return buffer + sizeof("POST ") - 1; + } + + return NULL; } static ALWAYS_INLINE char @@ -885,12 +873,8 @@ parse_http_request(lwan_request_t *request, struct request_parser_helper *helper buffer = ignore_leading_whitespace(buffer); char *path = identify_http_method(request, buffer); - if (UNLIKELY(buffer == path)) { - if (UNLIKELY(!*buffer)) - return HTTP_BAD_REQUEST; - + if (UNLIKELY(!path)) return HTTP_NOT_ALLOWED; - } buffer = identify_http_path(request, path, helper); if (UNLIKELY(!buffer)) From 30ce61f4e46332f37edc209956930a9e89aebd55 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 23 Sep 2016 22:48:15 -0300 Subject: [PATCH 0290/2505] Simplify parse_bool() --- common/lwan-config.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/common/lwan-config.c b/common/lwan-config.c index d87193d0b..de9d1b639 100644 --- a/common/lwan-config.c +++ b/common/lwan-config.c @@ -93,8 +93,6 @@ int parse_int(const char *value, int default_value) bool parse_bool(const char *value, bool default_value) { - int int_value; - if (!value) return default_value; @@ -106,11 +104,7 @@ bool parse_bool(const char *value, bool default_value) || !strcmp(value, "no")) return false; - int_value = parse_int(value, -1); - if (int_value < 0) - return default_value; - - return int_value != 0; + return parse_int(value, default_value); } bool config_error(config_t *conf, const char *fmt, ...) From f2b8d5009380f2ac2b0dd5f6dc46c652bef170c1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 23 Sep 2016 22:49:53 -0300 Subject: [PATCH 0291/2505] Only read post data (and allocate temporary memory) if handler allows --- common/lwan-request.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index 1723c2e14..d69f06707 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -891,9 +891,6 @@ parse_http_request(lwan_request_t *request, struct request_parser_helper *helper compute_keep_alive_flag(request, helper); - if (request->flags & REQUEST_METHOD_POST) - return read_post_data(request, helper); - return HTTP_OK; } @@ -921,10 +918,22 @@ prepare_for_response(lwan_url_map_t *url_map, parse_cookies(request, helper); if (request->flags & REQUEST_METHOD_POST) { - if (url_map->flags & HANDLER_PARSE_POST_DATA) - parse_post_data(request, helper); - else + lwan_http_status_t status; + + if (!(url_map->flags & HANDLER_PARSE_POST_DATA)) { + /* FIXME: Discard POST data here? If a POST request is sent + * to a handler that is not supposed to handle a POST request, + * the next request in the pipeline will fail because the + * body of the previous request will be used as the next + * request itself. */ return HTTP_NOT_ALLOWED; + } + + status = read_post_data(request, helper); + if (UNLIKELY(status != HTTP_OK)) + return status; + + parse_post_data(request, helper); } if (url_map->flags & HANDLER_MUST_AUTHORIZE) { From e9e63ae8ec3d897d562e341548a76110ab37461b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 23 Sep 2016 22:50:46 -0300 Subject: [PATCH 0292/2505] Check for authorization before preparing to send response --- common/lwan-request.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index d69f06707..985365e03 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -902,6 +902,14 @@ prepare_for_response(lwan_url_map_t *url_map, request->url.value += url_map->prefix_len; request->url.len -= url_map->prefix_len; + if (url_map->flags & HANDLER_MUST_AUTHORIZE) { + if (!lwan_http_authorize(request, + &helper->authorization, + url_map->authorization.realm, + url_map->authorization.password_file)) + return HTTP_NOT_AUTHORIZED; + } + if (url_map->flags & HANDLER_PARSE_QUERY_STRING) parse_query_string(request, helper); @@ -936,13 +944,6 @@ prepare_for_response(lwan_url_map_t *url_map, parse_post_data(request, helper); } - if (url_map->flags & HANDLER_MUST_AUTHORIZE) { - if (!lwan_http_authorize(request, - &helper->authorization, - url_map->authorization.realm, - url_map->authorization.password_file)) - return HTTP_NOT_AUTHORIZED; - } if (url_map->flags & HANDLER_REMOVE_LEADING_SLASH) { while (*request->url.value == '/' && request->url.len > 0) { From 7d9b17487c3a49768d870b11c6de7c004cffe475 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 24 Sep 2016 08:17:50 -0300 Subject: [PATCH 0293/2505] Group actions with url_map inside prepare_for_response() --- common/lwan-request.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index 985365e03..9bfcb8417 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -925,6 +925,13 @@ prepare_for_response(lwan_url_map_t *url_map, if (url_map->flags & HANDLER_PARSE_COOKIES) parse_cookies(request, helper); + if (url_map->flags & HANDLER_REMOVE_LEADING_SLASH) { + while (*request->url.value == '/' && request->url.len > 0) { + ++request->url.value; + --request->url.len; + } + } + if (request->flags & REQUEST_METHOD_POST) { lwan_http_status_t status; @@ -944,14 +951,6 @@ prepare_for_response(lwan_url_map_t *url_map, parse_post_data(request, helper); } - - if (url_map->flags & HANDLER_REMOVE_LEADING_SLASH) { - while (*request->url.value == '/' && request->url.len > 0) { - ++request->url.value; - --request->url.len; - } - } - return HTTP_OK; } From 9e0a2042ac2a6855329d752f698c0bb9e85b780d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 25 Sep 2016 09:30:37 -0300 Subject: [PATCH 0294/2505] Correctly calculate file size if compute_range() successfully returns --- common/lwan-serve-files.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lwan-serve-files.c b/common/lwan-serve-files.c index 3b896fedc..0b9e0aa3a 100644 --- a/common/lwan-serve-files.c +++ b/common/lwan-serve-files.c @@ -884,7 +884,7 @@ sendfile_serve(lwan_request_t *request, void *data) compressed = compression_none; fd = sd->uncompressed.fd; - size = sd->uncompressed.size; + size = (size_t)(to - from); } if (UNLIKELY(fd < 0)) { switch (-fd) { From 55a11d08200ab59bcc375955e6b5ed71ed63e624 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 25 Sep 2016 00:12:55 -0300 Subject: [PATCH 0295/2505] Large POST request bodies should be fully received w/small MTUs --- common/lwan-request.c | 64 +++++++++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 17 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index 9bfcb8417..f0ec9806d 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -38,7 +38,8 @@ typedef enum { FINALIZER_DONE, FINALIZER_TRY_AGAIN, FINALIZER_YIELD_TRY_AGAIN, - FINALIZER_ERROR_TOO_LARGE + FINALIZER_ERROR_TOO_LARGE, + FINALIZER_ERROR_TIMEOUT } lwan_read_finalizer_t; struct request_parser_helper { @@ -57,6 +58,8 @@ struct request_parser_helper { lwan_value_t post_data; lwan_value_t content_type; + time_t error_when_time; + int error_when_n_packets; int urls_rewritten; char connection; }; @@ -687,11 +690,11 @@ compute_keep_alive_flag(lwan_request_t *request, struct request_parser_helper *h static lwan_http_status_t read_from_request_socket(lwan_request_t *request, lwan_value_t *buffer, struct request_parser_helper *helper, const size_t buffer_size, - lwan_read_finalizer_t (*finalizer)(size_t total_read, size_t buffer_size, struct request_parser_helper *helper)) + lwan_read_finalizer_t (*finalizer)(size_t total_read, size_t buffer_size, struct request_parser_helper *helper, int n_packets)) { ssize_t n; size_t total_read = 0; - int packets_remaining = 16; + int n_packets = 0; if (helper->next_request) { buffer->len -= (size_t)(helper->next_request - buffer->value); @@ -702,7 +705,7 @@ static lwan_http_status_t read_from_request_socket(lwan_request_t *request, goto try_to_finalize; } - for (; packets_remaining > 0; packets_remaining--) { + for (; ; n_packets++) { n = read(request->fd, buffer->value + total_read, (size_t)(buffer_size - total_read)); /* Client has shutdown orderly, nothing else to do; kill coro */ @@ -734,7 +737,7 @@ static lwan_http_status_t read_from_request_socket(lwan_request_t *request, buffer->len = (size_t)total_read; try_to_finalize: - switch (finalizer(total_read, buffer_size, helper)) { + switch (finalizer(total_read, buffer_size, helper, n_packets)) { case FINALIZER_DONE: request->conn->flags &= ~CONN_MUST_READ; buffer->value[buffer->len] = '\0'; @@ -745,23 +748,26 @@ static lwan_http_status_t read_from_request_socket(lwan_request_t *request, goto yield_and_read_again; case FINALIZER_ERROR_TOO_LARGE: return HTTP_TOO_LARGE; + case FINALIZER_ERROR_TIMEOUT: + return HTTP_TIMEOUT; } } - /* - * packets_remaining reached zero: return a timeout error to avoid clients - * being intentionally slow and hogging the server. - * - * FIXME: What should be the best approach? Error with 408, or give some more - * time by fiddling with the connection's time to die? - */ - - return HTTP_TIMEOUT; + /* Shouldn't reach here. */ + coro_yield(request->conn->coro, CONN_CORO_ABORT); + __builtin_unreachable(); + return HTTP_INTERNAL_ERROR; } static lwan_read_finalizer_t read_request_finalizer(size_t total_read, - size_t buffer_size, struct request_parser_helper *helper) + size_t buffer_size, struct request_parser_helper *helper, int n_packets) { + /* 16 packets should be enough to read a request (without the body, as + * is the case for POST requests). This yields a timeout error to avoid + * clients being intentionally slow and hogging the server. */ + if (UNLIKELY(n_packets > 16)) + return FINALIZER_ERROR_TIMEOUT; + if (UNLIKELY(total_read < 4)) return FINALIZER_YIELD_TRY_AGAIN; @@ -787,9 +793,28 @@ read_request(lwan_request_t *request, struct request_parser_helper *helper) } static lwan_read_finalizer_t post_data_finalizer(size_t total_read, - size_t buffer_size, struct request_parser_helper *helper __attribute__((unused))) + size_t buffer_size, struct request_parser_helper *helper, int n_packets) { - return buffer_size == total_read ? FINALIZER_DONE : FINALIZER_TRY_AGAIN; + if (buffer_size == total_read) + return FINALIZER_DONE; + + /* For POST requests, the body can be larger, and due to small MTUs on + * most ethernet connections, responding with a timeout solely based on + * number of packets doesn't work. Use keepalive timeout instead. */ + if (UNLIKELY(time(NULL) > helper->error_when_time)) + return FINALIZER_ERROR_TIMEOUT; + + /* In addition to time, also estimate the number of packets based on an + * usual MTU value and the request body size. */ + if (UNLIKELY(n_packets > helper->error_when_n_packets)) + return FINALIZER_ERROR_TIMEOUT; + + return FINALIZER_TRY_AGAIN; +} + +static ALWAYS_INLINE int max(int a, int b) +{ + return (a > b) ? a : b; } static lwan_http_status_t @@ -834,6 +859,11 @@ read_post_data(lwan_request_t *request, struct request_parser_helper *helper) new_buffer = mempcpy(new_buffer, helper->next_request, have); helper->next_request = NULL; + helper->error_when_time = time(NULL) + request->conn->thread->lwan->config.keep_alive_timeout; + /* 740 = 1480 (a common MTU) / 2, so that Lwan'll optimistically error out + * after ~2x number of expected packets to fully read the request body.*/ + helper->error_when_n_packets = max(1, (int)(post_data_size / 740)); + lwan_value_t buffer = { .value = new_buffer, .len = post_data_size - have }; return read_from_request_socket(request, &buffer, helper, buffer.len, post_data_finalizer); From 171f5fe464f675bfb0924de43508dc07e3927f2c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 25 Sep 2016 09:51:48 -0300 Subject: [PATCH 0296/2505] Use DEFAULT_BUFFER_SIZE to calculate maximum number of packets/request Use this instead of hardcoding 16. The result is 5 now, which is smaller, but should be plenty. The compiler is smart enough to fold the constants here, so there's no associated runtime cost. --- common/lwan-request.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index f0ec9806d..a637fc082 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -765,7 +765,7 @@ static lwan_read_finalizer_t read_request_finalizer(size_t total_read, /* 16 packets should be enough to read a request (without the body, as * is the case for POST requests). This yields a timeout error to avoid * clients being intentionally slow and hogging the server. */ - if (UNLIKELY(n_packets > 16)) + if (UNLIKELY(n_packets > helper->error_when_n_packets)) return FINALIZER_ERROR_TIMEOUT; if (UNLIKELY(total_read < 4)) @@ -817,6 +817,13 @@ static ALWAYS_INLINE int max(int a, int b) return (a > b) ? a : b; } +static ALWAYS_INLINE int calculate_n_packets(size_t total) +{ + /* 740 = 1480 (a common MTU) / 2, so that Lwan'll optimistically error out + * after ~2x number of expected packets to fully read the request body.*/ + return max(1, (int)(total / 740)); +} + static lwan_http_status_t read_post_data(lwan_request_t *request, struct request_parser_helper *helper) { @@ -860,9 +867,7 @@ read_post_data(lwan_request_t *request, struct request_parser_helper *helper) helper->next_request = NULL; helper->error_when_time = time(NULL) + request->conn->thread->lwan->config.keep_alive_timeout; - /* 740 = 1480 (a common MTU) / 2, so that Lwan'll optimistically error out - * after ~2x number of expected packets to fully read the request body.*/ - helper->error_when_n_packets = max(1, (int)(post_data_size / 740)); + helper->error_when_n_packets = max(1, calculate_n_packets(post_data_size)); lwan_value_t buffer = { .value = new_buffer, .len = post_data_size - have }; return read_from_request_socket(request, &buffer, helper, buffer.len, @@ -1000,6 +1005,7 @@ handle_rewrite(lwan_request_t *request, struct request_parser_helper *helper) return true; } + char * lwan_process_request(lwan_t *l, lwan_request_t *request, lwan_value_t *buffer, char *next_request) @@ -1009,7 +1015,8 @@ lwan_process_request(lwan_t *l, lwan_request_t *request, struct request_parser_helper helper = { .buffer = buffer, - .next_request = next_request + .next_request = next_request, + .error_when_n_packets = calculate_n_packets(DEFAULT_BUFFER_SIZE) }; status = read_request(request, &helper); From a0122786c9e4c6ddc5dfaf9a8b69a9529d266340 Mon Sep 17 00:00:00 2001 From: Henrique Dante de Almeida Date: Wed, 12 Oct 2016 11:43:47 -0300 Subject: [PATCH 0297/2505] Remove warning on 32 bit ARM build This patch works around a gcc deficiency that evaluates all possible architecture specific code paths, instead of only evaluating the correct one, resulting in warnings on the incorrect code paths. --- common/strbuf.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/strbuf.c b/common/strbuf.c index 2cc4588e1..289fd53f2 100644 --- a/common/strbuf.c +++ b/common/strbuf.c @@ -37,11 +37,11 @@ find_next_power_of_two(size_t number) static const int size_bits = (int)sizeof(number) * CHAR_BIT; if (sizeof(size_t) == sizeof(unsigned int)) { - return 1U << (size_bits - __builtin_clz((unsigned int)number)); + return (size_t)1 << (size_bits - __builtin_clz((unsigned int)number)); } else if (sizeof(size_t) == sizeof(unsigned long)) { - return 1UL << (size_bits - __builtin_clzl((unsigned long)number)); + return (size_t)1 << (size_bits - __builtin_clzl((unsigned long)number)); } else if (sizeof(size_t) == sizeof(unsigned long long)) { - return 1ULL << (size_bits - __builtin_clzll((unsigned long long)number)); + return (size_t)1 << (size_bits - __builtin_clzll((unsigned long long)number)); } else { (void)size_bits; } From 6940ecd25e717ae4466f7e8dc454e5d2f114bf27 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 26 Oct 2016 20:21:16 -0700 Subject: [PATCH 0298/2505] Avoid calling strndupa() within a loop Call within a helper function instead, which will help not blow up the stack (which is very unlikely in this case, but alas.) --- common/lwan-rewrite.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/common/lwan-rewrite.c b/common/lwan-rewrite.c index c4f058c0e..28e8476c1 100644 --- a/common/lwan-rewrite.c +++ b/common/lwan-rewrite.c @@ -106,6 +106,12 @@ append_str(struct str_builder *builder, const char *src, size_t src_len) return true; } +static int +parse_int_len(const char *s, size_t len, int default_value) +{ + return parse_int(strndupa(s, len), default_value); +} + static const char * expand(lwan_request_t *request __attribute__((unused)), struct pattern *pattern, const char *orig, char buffer[static PATH_MAX], struct str_find *sf, @@ -130,7 +136,7 @@ expand(lwan_request_t *request __attribute__((unused)), struct pattern *pattern, } if (LIKELY(index_len > 0)) { - int index = parse_int(strndupa(ptr + 1, index_len), -1); + int index = parse_int_len(ptr + 1, index_len, -1); if (UNLIKELY(index < 0 || index > captures)) return NULL; From 7a9831bec0d65f6b7cabdabffe0dc0a3e3e1810f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 27 Oct 2016 07:07:22 -0700 Subject: [PATCH 0299/2505] Form data on POST requests may have parameters Lwan was not parsing POST data if the Content-Type included parameters such as the charset (application/x-www-form-urlencoder; charset=UTF-8). Patch by Mario Fischer. Closes #177. --- common/lwan-request.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index a637fc082..071b400d2 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -403,9 +403,9 @@ parse_post_data(lwan_request_t *request, struct request_parser_helper *helper) request->header.body = &helper->post_data; request->header.content_type = &helper->content_type; - if (helper->content_type.len != sizeof(content_type) - 1) + if (helper->content_type.len < sizeof(content_type) - 1) return; - if (UNLIKELY(strcmp(helper->content_type.value, content_type))) + if (UNLIKELY(strncmp(helper->content_type.value, content_type, sizeof(content_type) - 1))) return; parse_key_values(request, &helper->post_data, From ab485f0318eb6977d69453cfec4c9c0a46d5785f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 27 Oct 2016 20:34:04 -0700 Subject: [PATCH 0300/2505] No need to call max() twice when calculating # of packets for POST --- common/lwan-request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index 071b400d2..90b575c9d 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -867,7 +867,7 @@ read_post_data(lwan_request_t *request, struct request_parser_helper *helper) helper->next_request = NULL; helper->error_when_time = time(NULL) + request->conn->thread->lwan->config.keep_alive_timeout; - helper->error_when_n_packets = max(1, calculate_n_packets(post_data_size)); + helper->error_when_n_packets = calculate_n_packets(post_data_size); lwan_value_t buffer = { .value = new_buffer, .len = post_data_size - have }; return read_from_request_socket(request, &buffer, helper, buffer.len, From 9b46a8a9b9bdcdcf31673acab74f390ab3b1f70d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 27 Oct 2016 20:48:22 -0700 Subject: [PATCH 0301/2505] Split test_big_request() into individual tests This helps pinpointing exactly which test is failing. --- tools/testsuite.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/tools/testsuite.py b/tools/testsuite.py index c6aae7c03..35f2683b6 100755 --- a/tools/testsuite.py +++ b/tools/testsuite.py @@ -73,18 +73,23 @@ def test_will_it_blend(self): self.assertHttpResponseValid(r, 200, 'application/json') self.assertEqual(r.json(), {'did-it-blend': 'oh-hell-yeah'}) - def test_big_request(self): - for size in (10, 100, 1000, 10000, 100000): - data = "tro" + "lo" * size - - r = requests.post('http://127.0.0.1:8080/post/big', data=data, - headers={'Content-Type': 'x-test/trololo'}) - - self.assertHttpResponseValid(r, 200, 'application/json') - self.assertEqual(r.json(), { - 'received': len(data), - 'sum': sum(ord(b) for b in data) - }) + def make_request_with_size(self, size): + data = "tro" + "lo" * size + + r = requests.post('http://127.0.0.1:8080/post/big', data=data, + headers={'Content-Type': 'x-test/trololo'}) + + self.assertHttpResponseValid(r, 200, 'application/json') + self.assertEqual(r.json(), { + 'received': len(data), + 'sum': sum(ord(b) for b in data) + }) + + def test_small_request(self): self.make_request_with_size(10) + def test_medium_request(self): self.make_request_with_size(100) + def test_large_request(self): self.make_request_with_size(1000) + def test_huge_request(self): self.make_request_with_size(10000) + def test_gigantic_request(self): self.make_request_with_size(100000) class TestFileServing(LwanTest): From cbe59e9a6b697d5948bd7a38b65383db2872d6de Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 30 Oct 2016 17:33:29 -0700 Subject: [PATCH 0302/2505] Provide a streq() function to clarify intent of string equality comparison Patch made using Coccinelle. --- common/lwan-config.c | 10 ++++------ common/lwan-http-authorize.c | 2 +- common/lwan-response.c | 8 ++++---- common/lwan-rewrite.c | 8 ++++---- common/lwan-socket.c | 4 ++-- common/lwan-straitjacket.c | 4 ++-- common/lwan.c | 34 +++++++++++++++++----------------- common/missing/string.h | 6 ++++++ tools/mimegen.c | 14 +++++++------- 9 files changed, 47 insertions(+), 43 deletions(-) diff --git a/common/lwan-config.c b/common/lwan-config.c index de9d1b639..518fd9eec 100644 --- a/common/lwan-config.c +++ b/common/lwan-config.c @@ -96,12 +96,10 @@ bool parse_bool(const char *value, bool default_value) if (!value) return default_value; - if (!strcmp(value, "true") || !strcmp(value, "on") - || !strcmp(value, "yes")) + if (streq(value, "true") || streq(value, "on") || streq(value, "yes")) return true; - if (!strcmp(value, "false") || !strcmp(value, "off") - || !strcmp(value, "no")) + if (streq(value, "false") || streq(value, "off") || streq(value, "no")) return false; return parse_int(value, default_value); @@ -220,7 +218,7 @@ static bool parse_multiline(config_t *c, config_line_t *l) while (config_fgets(c, buffer, sizeof(buffer))) { char *tmp = remove_trailing_spaces(remove_leading_spaces(buffer)); - if (!strcmp(tmp, "'''")) { + if (streq(tmp, "'''")) { l->line.value = strbuf_get_buffer(c->strbuf); return true; } @@ -249,7 +247,7 @@ static bool parse_line(config_t *c, char *line, config_line_t *l, char *equal) l->line.value = remove_leading_spaces(equal + 1); l->type = CONFIG_LINE_TYPE_LINE; - if (strcmp(l->line.value, "'''")) + if (!streq(l->line.value, "'''")) return true; return parse_multiline(c, l); diff --git a/common/lwan-http-authorize.c b/common/lwan-http-authorize.c index d0c81d299..91ab18ef8 100644 --- a/common/lwan-http-authorize.c +++ b/common/lwan-http-authorize.c @@ -173,7 +173,7 @@ authorize(coro_t *coro, looked_password = hash_find(rpf->entries, decoded); if (looked_password) - password_ok = !strcmp(password, looked_password); + password_ok = streq(password, looked_password); out: free(decoded); diff --git a/common/lwan-response.c b/common/lwan-response.c index 85b7b2f9c..66fac55e8 100644 --- a/common/lwan-response.c +++ b/common/lwan-response.c @@ -276,11 +276,11 @@ lwan_prepare_response_header(lwan_request_t *request, lwan_http_status_t status, lwan_key_value_t *header; for (header = request->response.headers; header->key; header++) { - if (UNLIKELY(!strcmp(header->key, "Server"))) + if (UNLIKELY(streq(header->key, "Server"))) continue; - if (UNLIKELY(!strcmp(header->key, "Date"))) + if (UNLIKELY(streq(header->key, "Date"))) date_overridden = true; - if (UNLIKELY(!strcmp(header->key, "Expires"))) + if (UNLIKELY(streq(header->key, "Expires"))) expires_overridden = true; RETURN_0_ON_OVERFLOW(4); @@ -295,7 +295,7 @@ lwan_prepare_response_header(lwan_request_t *request, lwan_http_status_t status, lwan_key_value_t *header; for (header = request->response.headers; header->key; header++) { - if (!strcmp(header->key, "WWW-Authenticate")) { + if (streq(header->key, "WWW-Authenticate")) { APPEND_CONSTANT("\r\nWWW-Authenticate: "); APPEND_STRING(header->value); break; diff --git a/common/lwan-rewrite.c b/common/lwan-rewrite.c index 28e8476c1..0dec4af13 100644 --- a/common/lwan-rewrite.c +++ b/common/lwan-rewrite.c @@ -301,15 +301,15 @@ module_parse_conf_pattern(struct private_data *pd, config_t *config, config_line while (config_read_line(config, line)) { switch (line->type) { case CONFIG_LINE_TYPE_LINE: - if (!strcmp(line->line.key, "redirect_to")) { + if (streq(line->line.key, "redirect_to")) { redirect_to = strdup(line->line.value); if (!redirect_to) goto out; - } else if (!strcmp(line->line.key, "rewrite_as")) { + } else if (streq(line->line.key, "rewrite_as")) { rewrite_as = strdup(line->line.value); if (!rewrite_as) goto out; - } else if (!strcmp(line->line.key, "expand_with_lua")) { + } else if (streq(line->line.key, "expand_with_lua")) { expand_with_lua = parse_bool(line->line.value, false); } else { config_error(config, "Unexpected key: %s", line->line.key); @@ -371,7 +371,7 @@ module_parse_conf(void *data, config_t *config) config_error(config, "Unknown option: %s", line.line.key); break; case CONFIG_LINE_TYPE_SECTION: - if (!strcmp(line.section.name, "pattern")) { + if (streq(line.section.name, "pattern")) { module_parse_conf_pattern(pd, config, &line); } else { config_error(config, "Unknown section: %s", line.section.name); diff --git a/common/lwan-socket.c b/common/lwan-socket.c index c9c9f1a91..e1ac282b7 100644 --- a/common/lwan-socket.c +++ b/common/lwan-socket.c @@ -95,7 +95,7 @@ parse_listener_ipv4(char *listener, char **node, char **port) *node = listener; *port = colon + 1; - if (!strcmp(*node, "*")) { + if (streq(*node, "*")) { /* *:8080 */ *node = "0.0.0.0"; } @@ -129,7 +129,7 @@ parse_listener_ipv6(char *listener, char **node, char **port) static sa_family_t parse_listener(char *listener, char **node, char **port) { - if (!strcmp(listener, "systemd")) { + if (streq(listener, "systemd")) { lwan_status_critical("Listener configured to use systemd socket activation, " "but started outside systemd."); return AF_UNSPEC; diff --git a/common/lwan-straitjacket.c b/common/lwan-straitjacket.c index 50560d35b..fe8be9f2f 100644 --- a/common/lwan-straitjacket.c +++ b/common/lwan-straitjacket.c @@ -107,9 +107,9 @@ void lwan_straitjacket_enforce(config_t *c, config_line_t *l) switch (l->type) { case CONFIG_LINE_TYPE_LINE: /* TODO: limit_syscalls */ - if (!strcmp(l->line.key, "user")) { + if (streq(l->line.key, "user")) { user_name = strdupa(l->line.value); - } else if (!strcmp(l->line.key, "chroot")) { + } else if (streq(l->line.key, "chroot")) { chroot_path = strdupa(l->line.value); } else { config_error(c, "Invalid key: %s", l->line.key); diff --git a/common/lwan.c b/common/lwan.c index e794ffec8..b24f9077a 100644 --- a/common/lwan.c +++ b/common/lwan.c @@ -157,7 +157,7 @@ static lwan_url_map_t *add_url_map(lwan_trie_t *t, const char *prefix, const lwa static void parse_listener_prefix_authorization(config_t *c, config_line_t *l, lwan_url_map_t *url_map) { - if (strcmp(l->section.param, "basic")) { + if (!streq(l->section.param, "basic")) { config_error(c, "Only basic authorization supported"); return; } @@ -167,10 +167,10 @@ static void parse_listener_prefix_authorization(config_t *c, while (config_read_line(c, l)) { switch (l->type) { case CONFIG_LINE_TYPE_LINE: - if (!strcmp(l->line.key, "realm")) { + if (streq(l->line.key, "realm")) { free(url_map->authorization.realm); url_map->authorization.realm = strdup(l->line.value); - } else if (!strcmp(l->line.key, "password_file")) { + } else if (streq(l->line.key, "password_file")) { free(url_map->authorization.password_file); url_map->authorization.password_file = strdup(l->line.value); } @@ -216,7 +216,7 @@ static void parse_listener_prefix(config_t *c, config_line_t *l, lwan_t *lwan, while (config_read_line(c, l)) { switch (l->type) { case CONFIG_LINE_TYPE_LINE: - if (!strcmp(l->line.key, "module")) { + if (streq(l->line.key, "module")) { if (module) { config_error(c, "Module already specified"); goto out; @@ -226,7 +226,7 @@ static void parse_listener_prefix(config_t *c, config_line_t *l, lwan_t *lwan, config_error(c, "Could not find module \"%s\"", l->line.value); goto out; } - } else if (!strcmp(l->line.key, "handler")) { + } else if (streq(l->line.key, "handler")) { handler = find_handler_symbol(l->line.value); if (!handler) { config_error(c, "Could not find handler \"%s\"", l->line.value); @@ -238,7 +238,7 @@ static void parse_listener_prefix(config_t *c, config_line_t *l, lwan_t *lwan, break; case CONFIG_LINE_TYPE_SECTION: - if (!strcmp(l->section.name, "authorization")) { + if (streq(l->section.name, "authorization")) { parse_listener_prefix_authorization(c, l, &url_map); } else { if (!config_skip_section(c, l)) { @@ -329,7 +329,7 @@ static void parse_listener(config_t *c, config_line_t *l, lwan_t *lwan) config_error(c, "Expecting prefix section"); return; case CONFIG_LINE_TYPE_SECTION: - if (!strcmp(l->section.name, "prefix")) { + if (streq(l->section.name, "prefix")) { parse_listener_prefix(c, l, lwan, NULL); } else { const lwan_module_t *module = lwan_module_find(lwan, l->section.name); @@ -389,30 +389,30 @@ static bool setup_from_config(lwan_t *lwan, const char *path) while (config_read_line(&conf, &line)) { switch (line.type) { case CONFIG_LINE_TYPE_LINE: - if (!strcmp(line.line.key, "keep_alive_timeout")) { + if (streq(line.line.key, "keep_alive_timeout")) { lwan->config.keep_alive_timeout = (unsigned short)parse_long(line.line.value, default_config.keep_alive_timeout); - } else if (!strcmp(line.line.key, "quiet")) { + } else if (streq(line.line.key, "quiet")) { lwan->config.quiet = parse_bool(line.line.value, default_config.quiet); - } else if (!strcmp(line.line.key, "reuse_port")) { + } else if (streq(line.line.key, "reuse_port")) { lwan->config.reuse_port = parse_bool(line.line.value, default_config.reuse_port); - } else if (!strcmp(line.line.key, "proxy_protocol")) { + } else if (streq(line.line.key, "proxy_protocol")) { lwan->config.proxy_protocol = parse_bool(line.line.value, default_config.proxy_protocol); - } else if (!strcmp(line.line.key, "expires")) { + } else if (streq(line.line.key, "expires")) { lwan->config.expires = parse_time_period(line.line.value, default_config.expires); - } else if (!strcmp(line.line.key, "error_template")) { + } else if (streq(line.line.key, "error_template")) { free(lwan->config.error_template); lwan->config.error_template = strdup(line.line.value); - } else if (!strcmp(line.line.key, "threads")) { + } else if (streq(line.line.key, "threads")) { long n_threads = parse_long(line.line.value, default_config.n_threads); if (n_threads < 0) config_error(&conf, "Invalid number of threads: %d", n_threads); lwan->config.n_threads = (unsigned short int)n_threads; - } else if (!strcmp(line.line.key, "max_post_data_size")) { + } else if (streq(line.line.key, "max_post_data_size")) { long max_post_data_size = parse_long(line.line.value, (long)default_config.max_post_data_size); if (max_post_data_size < 0) config_error(&conf, "Negative maximum post data size"); @@ -424,14 +424,14 @@ static bool setup_from_config(lwan_t *lwan, const char *path) } break; case CONFIG_LINE_TYPE_SECTION: - if (!strcmp(line.section.name, "listener")) { + if (streq(line.section.name, "listener")) { if (!has_listener) { parse_listener(&conf, &line, lwan); has_listener = true; } else { config_error(&conf, "Only one listener supported"); } - } else if (!strcmp(line.section.name, "straitjacket")) { + } else if (streq(line.section.name, "straitjacket")) { lwan_straitjacket_enforce(&conf, &line); } else { config_error(&conf, "Unknown section type: %s", line.section.name); diff --git a/common/missing/string.h b/common/missing/string.h index ab7fc6acf..661ec2b61 100644 --- a/common/missing/string.h +++ b/common/missing/string.h @@ -48,4 +48,10 @@ void *mempcpy(void *dest, const void *src, size_t len); void *memrchr(const void *s, int c, size_t n); #endif +static inline int +streq(const char *a, const char *b) +{ + return strcmp(a, b) == 0; +} + #endif /* MISSING_STRING_H */ diff --git a/tools/mimegen.c b/tools/mimegen.c index 3673ef2ec..53c3a57b5 100644 --- a/tools/mimegen.c +++ b/tools/mimegen.c @@ -117,19 +117,19 @@ static bool is_builtin_mime_type(const char *mime) { /* These are the mime types supported by Lwan without having to perform * a bsearch(). application/octet-stream is the fallback. */ - if (!strcmp(mime, "application/octet-stream")) + if (streq(mime, "application/octet-stream")) return true; - if (!strcmp(mime, "application/javascript")) + if (streq(mime, "application/javascript")) return true; - if (!strcmp(mime, "image/jpeg")) + if (streq(mime, "image/jpeg")) return true; - if (!strcmp(mime, "image/png")) + if (streq(mime, "image/png")) return true; - if (!strcmp(mime, "text/html")) + if (streq(mime, "text/html")) return true; - if (!strcmp(mime, "text/css")) + if (streq(mime, "text/css")) return true; - if (!strcmp(mime, "text/plain")) + if (streq(mime, "text/plain")) return true; return false; } From 4b847362a7190fb5b79e71740b30f5207ffd9766 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 10 Nov 2016 18:24:03 -0800 Subject: [PATCH 0303/2505] Fix build of mimegen after introducing streq() This required a clean build to reproduce. Thanks for @felipecruz for the report. --- tools/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index bd84683be..8db6f2e31 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -6,6 +6,8 @@ project(mimegen) find_package(ZLIB REQUIRED) set(ADDITIONAL_LIBRARIES ${ZLIB_LIBRARIES}) +include_directories(BEFORE ../common/missing) + add_executable(mimegen mimegen.c ../common/hash.c From faefcb779d4aa00d4f005c03edc3bebf9f18a97f Mon Sep 17 00:00:00 2001 From: k0zmo Date: Thu, 24 Nov 2016 19:34:31 +0100 Subject: [PATCH 0304/2505] Fix memory leak when reassigning trie's leaf --- common/lwan-trie.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/lwan-trie.c b/common/lwan-trie.c index d943c278a..7a94d1562 100644 --- a/common/lwan-trie.c +++ b/common/lwan-trie.c @@ -83,6 +83,8 @@ lwan_trie_add(lwan_trie_t *trie, const char *key, void *data) leaf = malloc(sizeof(*leaf)); if (!leaf) lwan_status_critical_perror("malloc"); + } else if (trie->free_node) { + trie->free_node(leaf->data); } leaf->data = data; From f76c4e423ac7c23e19b529530a0d6b58e9c9ca75 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 26 Nov 2016 14:18:17 -0800 Subject: [PATCH 0305/2505] Generate a lwan-build-config.h file with compile-time options This enables linking with both static and shared libraries without having to use the same build options. --- CMakeLists.txt | 31 ++++++++----------------- common/CMakeLists.txt | 26 +++++++-------------- common/lwan.h | 2 ++ common/missing/assert.h | 4 +++- common/missing/pthread.h | 2 ++ common/missing/time.h | 2 ++ common/strbuf.c | 2 +- freegeoip/CMakeLists.txt | 2 ++ lwan-build-config.h.cmake | 46 ++++++++++++++++++++++++++++++++++++++ lwan/CMakeLists.txt | 2 ++ techempower/CMakeLists.txt | 1 + testrunner/CMakeLists.txt | 2 ++ tools/CMakeLists.txt | 6 ++++- 13 files changed, 85 insertions(+), 43 deletions(-) create mode 100644 lwan-build-config.h.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 4eb904c65..cd7df87e3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,34 +24,16 @@ set(ADDITIONAL_LIBRARIES ${ZLIB_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) check_c_source_compiles("int main(void) { __builtin_cpu_init(); }" HAVE_BUILTIN_CPU_INIT) -if (HAVE_BUILTIN_CPU_INIT) - add_definitions("-DHAVE_BUILTIN_CPU_INIT") -endif () check_c_source_compiles("int main(void) { __builtin_clzll(0); }" HAVE_BUILTIN_CLZLL) -if (HAVE_BUILTIN_CLZLL) - add_definitions("-DHAVE_BUILTIN_CLZLL") -endif () check_c_source_compiles("int main(void) { unsigned long long p; (void)__builtin_mul_overflow(0, 0, &p); }" HAVE_BUILTIN_MUL_OVERFLOW) -if (HAVE_BUILTIN_MUL_OVERFLOW) - add_definitions("-DHAVE_BUILTIN_MUL_OVERFLOW") -endif () check_c_source_compiles("int main(void) { _Static_assert(1, \"\"); }" HAVE_STATIC_ASSERT) -if (HAVE_STATIC_ASSERT) - add_definitions("-DHAVE_STATIC_ASSERT") -endif () set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) check_function_exists(pthread_barrier_init HAS_PTHREADBARRIER) -if (HAS_PTHREADBARRIER) - add_definitions(-DHAS_PTHREADBARRIER) -endif () set(CMAKE_EXTRA_INCLUDE_FILES time.h) check_function_exists(clock_gettime HAS_CLOCK_GETTIME) -if (HAS_CLOCK_GETTIME) - add_definitions("-DHAS_CLOCK_GETTIME") -elseif (${CMAKE_SYSTEM_NAME} MATCHES "Linux") - add_definitions("-DHAS_CLOCK_GETTIME") +if (NOT HAS_CLOCK_GETTIME AND ${CMAKE_SYSTEM_NAME} MATCHES "Linux") list(APPEND ADDITIONAL_LIBRARIES rt) endif () @@ -126,7 +108,6 @@ if (${CMAKE_BUILD_TYPE} MATCHES "Rel") check_c_compiler_flag(-mcrc32 HAVE_BUILTIN_IA32_CRC32) if (HAVE_BUILTIN_IA32_CRC32) - add_definitions("-DHAVE_BUILTIN_IA32_CRC32=1") set(C_FLAGS_REL "${C_FLAGS_REL} -mcrc32") endif () else () @@ -178,7 +159,7 @@ endif () find_path(VALGRIND_INCLUDE_DIR valgrind.h /usr/include /usr/include/valgrind /usr/local/include /usr/local/include/valgrind) if (VALGRIND_INCLUDE_DIR) message(STATUS "Building with Valgrind support") - add_definitions("-DUSE_VALGRIND=1") + set(USE_VALGRIND 1) else () message(STATUS "Valgrind headers not found -- disabling valgrind support") endif() @@ -223,11 +204,17 @@ execute_process( OUTPUT_STRIP_TRAILING_WHITESPACE ) +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/lwan-build-config.h.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/lwan-build-config.h" +) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/lwan-build-config.h + DESTINATION "include/lwan") + configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/lwan.pc.cmake" "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc" ) - install(FILES "${CMAKE_BINARY_DIR}/${PROJECT_NAME}.pc" DESTINATION lib/pkgconfig) find_package(PythonInterp 3) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 045e79365..acc5cc25a 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -34,31 +34,21 @@ set(SOURCES include(CheckFunctionExists) set(CMAKE_EXTRA_INCLUDE_FILES string.h) check_function_exists(rawmemchr HAS_RAWMEMCHR) -if (HAS_RAWMEMCHR) - add_definitions(-DHAS_RAWMEMCHR) -endif () +set(HAS_RAWMEMCHR ${HAS_RAWMEMCHR} PARENT_SCOPE) check_function_exists(mempcpy HAS_MEMPCPY) -if (HAS_MEMPCPY) - add_definitions(-DHAS_MEMPCPY) -endif () +set(HAS_MEMPCPY ${HAS_MEMPCPY} PARENT_SCOPE) check_function_exists(memrchr HAS_MEMRCHR) -if (HAS_MEMRCHR) - add_definitions(-DHAS_MEMRCHR) -endif () +set(HAS_MEMRCHR ${HAS_MEMRCHR} PARENT_SCOPE) set(CMAKE_EXTRA_INCLUDE_FILES unistd.h) check_function_exists(pipe2 HAS_PIPE2) -if (HAS_PIPE2) - add_definitions(-DHAS_PIPE2) -endif () +set(HAS_PIPE2 ${HAS_PIPE2} PARENT_SCOPE) set(CMAKE_EXTRA_INCLUDE_FILES sys/types.h sys/socket.h) check_function_exists(accept4 HAS_ACCEPT4) -if (HAS_ACCEPT4) - add_definitions(-DHAS_ACCEPT4) -endif () +set(HAS_ACCEPT4 ${HAS_ACCEPT4} PARENT_SCOPE) include(FindPkgConfig) foreach (pc_file luajit lua lua51 lua5.1 lua-5.1) @@ -71,7 +61,6 @@ foreach (pc_file luajit lua lua51 lua5.1 lua-5.1) list(APPEND ADDITIONAL_LIBRARIES "-l${LUA_LIBRARIES} ${LUA_LDFLAGS}") list(APPEND SOURCES lwan-lua.c) include_directories(${LUA_INCLUDE_DIRS}) - add_definitions(-DHAVE_LUA) break() endif() endforeach () @@ -79,6 +68,7 @@ if (NOT LUA_FOUND) message(STATUS "Disabling Lua support") else () message(STATUS "Building with Lua support using ${LUA_LIBRARIES}") + set(HAVE_LUA 1 PARENT_SCOPE) endif () add_library(lwan-static STATIC ${SOURCES}) @@ -119,9 +109,9 @@ include_directories(${CMAKE_BINARY_DIR}) set(ADDITIONAL_LIBRARIES ${ADDITIONAL_LIBRARIES} PARENT_SCOPE) -INSTALL(TARGETS lwan-static lwan-shared +install(TARGETS lwan-static lwan-shared DESTINATION "lib" ) -INSTALL(FILES lwan.h lwan-coro.h lwan-trie.h lwan-status.h strbuf.h hash.h +install(FILES lwan.h lwan-coro.h lwan-trie.h lwan-status.h strbuf.h hash.h lwan-template.h lwan-serve-files.h lwan-config.h DESTINATION "include/lwan" ) diff --git a/common/lwan.h b/common/lwan.h index e14889a34..9817280f3 100644 --- a/common/lwan.h +++ b/common/lwan.h @@ -29,6 +29,8 @@ extern "C" { #include #include +#include "lwan-build-config.h" + #include "hash.h" #include "lwan-config.h" #include "lwan-coro.h" diff --git a/common/missing/assert.h b/common/missing/assert.h index b0879cc7a..d55290c6f 100644 --- a/common/missing/assert.h +++ b/common/missing/assert.h @@ -22,8 +22,10 @@ #ifndef MISSING_ASSERT_H #define MISSING_ASSERT_H +#include "lwan-build-config.h" + #undef static_assert -#if HAVE_STATIC_ASSERT +#if defined(HAVE_STATIC_ASSERT) # define static_assert(expr, msg) _Static_assert(expr, msg) #else # define static_assert(expr, msg) diff --git a/common/missing/pthread.h b/common/missing/pthread.h index 46e3c819e..ff11b1f6b 100644 --- a/common/missing/pthread.h +++ b/common/missing/pthread.h @@ -22,6 +22,8 @@ #ifndef MISSING_PTHREAD_H #define MISSING_PTHREAD_H +#include "lwan-build-config.h" + #ifndef HAS_PTHREADBARRIER typedef int pthread_barrierattr_t; typedef struct pthread_barrier { diff --git a/common/missing/time.h b/common/missing/time.h index adbbb411c..d05b60f8e 100644 --- a/common/missing/time.h +++ b/common/missing/time.h @@ -22,6 +22,8 @@ #ifndef MISSING_TIME_H #define MISSING_TIME_H +#include "lwan-build-config.h" + #ifndef HAS_CLOCK_GETTIME typedef int clockid_t; int clock_gettime(clockid_t clk_id, struct timespec *ts); diff --git a/common/strbuf.c b/common/strbuf.c index 289fd53f2..bba903096 100644 --- a/common/strbuf.c +++ b/common/strbuf.c @@ -33,7 +33,7 @@ static const size_t DEFAULT_BUF_SIZE = 64; static size_t find_next_power_of_two(size_t number) { -#if HAVE_BUILTIN_CLZLL +#if defined(HAVE_BUILTIN_CLZLL) static const int size_bits = (int)sizeof(number) * CHAR_BIT; if (sizeof(size_t) == sizeof(unsigned int)) { diff --git a/freegeoip/CMakeLists.txt b/freegeoip/CMakeLists.txt index dc915df54..b7636c103 100644 --- a/freegeoip/CMakeLists.txt +++ b/freegeoip/CMakeLists.txt @@ -2,6 +2,8 @@ include(FindPkgConfig) pkg_check_modules(SQLITE sqlite3>=3.6.20) if (SQLITE_FOUND) + include_directories(BEFORE ${CMAKE_BINARY_DIR}) + add_executable(freegeoip freegeoip.c ) diff --git a/lwan-build-config.h.cmake b/lwan-build-config.h.cmake new file mode 100644 index 000000000..05d008c41 --- /dev/null +++ b/lwan-build-config.h.cmake @@ -0,0 +1,46 @@ +/* + * lwan - simple web server + * Copyright (c) 2016 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#pragma once + +/* API available in Glibc/Linux, but possibly not elsewhere */ +#cmakedefine HAS_ACCEPT4 +#cmakedefine HAS_CLOCK_GETTIME +#cmakedefine HAS_MEMPCPY +#cmakedefine HAS_MEMRCHR +#cmakedefine HAS_PIPE2 +#cmakedefine HAS_PTHREADBARRIER +#cmakedefine HAS_RAWMEMCHR + +/* Compiler builtins for specific CPU instruction support */ +#cmakedefine HAVE_BUILTIN_CLZLL +#cmakedefine HAVE_BUILTIN_CPU_INIT +#cmakedefine HAVE_BUILTIN_IA32_CRC32 +#cmakedefine HAVE_BUILTIN_MUL_OVERFLOW + +/* C11 _Static_assert() */ +#cmakedefine HAVE_STATIC_ASSERT + +/* Libraries */ +#cmakedefine HAVE_LUA +#cmakedefine HAVE_ZOPFLI + +/* Valgrind support for coroutines */ +#cmakedefine USE_VALGRIND + diff --git a/lwan/CMakeLists.txt b/lwan/CMakeLists.txt index 947174ef8..9e39d3275 100644 --- a/lwan/CMakeLists.txt +++ b/lwan/CMakeLists.txt @@ -1,3 +1,5 @@ +include_directories(BEFORE ${CMAKE_BINARY_DIR}) + add_executable(lwan main.c) target_link_libraries(lwan diff --git a/techempower/CMakeLists.txt b/techempower/CMakeLists.txt index 54092295d..118249d0c 100644 --- a/techempower/CMakeLists.txt +++ b/techempower/CMakeLists.txt @@ -35,6 +35,7 @@ if (MYSQL_INCLUDE_DIR AND SQLITE_FOUND) ${MYSQL_LIBRARY} ) include_directories(${SQLITE_INCLUDE_DIRS}) + include_directories(BEFORE ${CMAKE_BINARY_DIR}) endif () endif () diff --git a/testrunner/CMakeLists.txt b/testrunner/CMakeLists.txt index f4847754c..cc3dad9ba 100644 --- a/testrunner/CMakeLists.txt +++ b/testrunner/CMakeLists.txt @@ -1,3 +1,5 @@ +include_directories(BEFORE ${CMAKE_BINARY_DIR}) + add_executable(testrunner main.c) target_link_libraries(testrunner diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 8db6f2e31..60f8772d7 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -7,6 +7,7 @@ find_package(ZLIB REQUIRED) set(ADDITIONAL_LIBRARIES ${ZLIB_LIBRARIES}) include_directories(BEFORE ../common/missing) +include_directories(${CMAKE_BINARY_DIR}) add_executable(mimegen mimegen.c @@ -19,8 +20,11 @@ find_library(ZOPFLI_LIBRARY NAMES zopfli PATHS /usr/lib /usr/local/lib) if (ZOPFLI_LIBRARY) message(STATUS "Using Zopfli (${ZOPFLI_LIBRARY}) for mimegen") target_link_libraries(mimegen ${ZOPFLI_LIBRARY}) - add_definitions(-DHAVE_ZOPFLI) + set (HAVE_ZOPFLI 1) else () message(STATUS "Using zlib (${ZLIB_LIBRARIES}) for mimegen") target_link_libraries(mimegen ${ZLIB_LIBRARIES}) endif () + +configure_file(${CMAKE_SOURCE_DIR}/../lwan-build-config.h.cmake + ${CMAKE_BINARY_DIR}/lwan-build-config.h) From 06828d65472ab73fd959dab30b99ae0fbf4c8e86 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 26 Nov 2016 14:33:19 -0800 Subject: [PATCH 0306/2505] Fix link to @lpereira profile in README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index a725ef2f5..813e1178e 100644 --- a/README.md +++ b/README.md @@ -227,7 +227,7 @@ Lwan has been also used as a benchmark: Some talks mentioning Lwan: -* [Talk about Lwan](https://www.youtube.com/watch?v=cttY9FdCzUE) at Polyconf16, given by [@lpereira]. +* [Talk about Lwan](https://www.youtube.com/watch?v=cttY9FdCzUE) at Polyconf16, given by [@lpereira](https://github.com/lpereira). * This [talk about Iron](https://michaelsproul.github.io/iron-talk/), a framework for Rust, mentions Lwan as an *insane C thing*. * [University seminar presentation](https://github.com/cu-data-engineering-s15/syllabus/blob/master/student_lectures/LWAN.pdf) about Lwan. * This [presentation about Sailor web framework](http://www.slideshare.net/EtieneDalcol/web-development-with-lua-bulgaria-web-summit) mentions Lwan. @@ -237,7 +237,6 @@ Not really third-party, but alas: * The [author's blog](http://tia.mat.br). * The [project's webpage](http://lwan.ws). - Build status ------------ From b556bbace23b7368845e96da1512b1b471806c4b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 29 Nov 2016 23:24:13 -0800 Subject: [PATCH 0307/2505] Add mempmove(), similar to mempcpy(), but backed by memmove() This avoids the warning from Clang Static Analyzer: realpathat.c:133:20: warning: Memory copy function overflows destination buffer --- common/missing/string.h | 7 +++++++ common/realpathat.c | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/common/missing/string.h b/common/missing/string.h index 661ec2b61..d460c07e9 100644 --- a/common/missing/string.h +++ b/common/missing/string.h @@ -54,4 +54,11 @@ streq(const char *a, const char *b) return strcmp(a, b) == 0; } +static inline void * +mempmove(void *dest, const void *src, size_t len) +{ + unsigned char *d = memmove(dest, src, len); + return d + len; +} + #endif /* MISSING_STRING_H */ diff --git a/common/realpathat.c b/common/realpathat.c index 1d26d38ec..24614cf2c 100644 --- a/common/realpathat.c +++ b/common/realpathat.c @@ -130,7 +130,7 @@ realpathat2(int dirfd, char *dirfdpath, const char *name, char *resolved, dest = rpath + dest_offset; } - dest = mempcpy(dest, start, (size_t)(end - start)); + dest = mempmove(dest, start, (size_t)(end - start)); *dest = '\0'; if (LIKELY(!strncmp(rpath, dirfdpath, (size_t)dirfdlen))) { From ade94f3c968e82cd6e47828262f87738b0ec59cb Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 29 Nov 2016 23:31:33 -0800 Subject: [PATCH 0308/2505] If config_isolate_section() fails, close the isolated config file --- common/lwan-config.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/common/lwan-config.c b/common/lwan-config.c index 518fd9eec..83fb4a7a7 100644 --- a/common/lwan-config.c +++ b/common/lwan-config.c @@ -293,6 +293,7 @@ bool config_isolate_section(config_t *current_conf, int origin_line = current_conf->line; *isolated = *current_conf; + isolated->file = NULL; if (current_conf->error_message) return false; @@ -320,10 +321,14 @@ bool config_isolate_section(config_t *current_conf, resetpos: if (fseek(current_conf->file, startpos, SEEK_SET) < 0) { config_error(current_conf, "Could not reset file position"); - return false; + r = false; } - if (!r) + if (!r || current_conf->error_message) { config_error(current_conf, "Unknown error while isolating section"); + r = false; + } + if (!r) + config_close(isolated); return r; } From 55905cf6d0518abcf5a4e28e3c9e203ac572db5d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 1 Dec 2016 17:51:26 +0000 Subject: [PATCH 0309/2505] Add HTTP error codes 420 and 520 Based on this RFC: https://twitter.com/bcrypt/status/804067971916148736 --- common/lwan-tables.c | 8 +++++++- common/lwan.h | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/common/lwan-tables.c b/common/lwan-tables.c index 05169b2a7..29b532961 100644 --- a/common/lwan-tables.c +++ b/common/lwan-tables.c @@ -138,9 +138,11 @@ lwan_http_status_as_string_with_code(lwan_http_status_t status) RESP(413, "Request too large"), RESP(416, "Requested range unsatisfiable"), RESP(418, "I'm a teapot"), + RESP(420, "Client too high"), RESP(500, "Internal server error"), RESP(501, "Not implemented"), - RESP(503, "Service unavailable") + RESP(503, "Service unavailable"), + RESP(520, "Server too high"), }; #undef RESP @@ -184,12 +186,16 @@ lwan_http_status_as_descriptive_string(lwan_http_status_t status) return "The server can't supply the requested portion of the requested resource."; case HTTP_I_AM_A_TEAPOT: return "Client requested to brew coffee but device is a teapot."; + case HTTP_CLIENT_TOO_HIGH: + return "Client is too high to make a request."; case HTTP_INTERNAL_ERROR: return "The server encountered an internal error that couldn't be recovered from."; case HTTP_NOT_IMPLEMENTED: return "Server lacks the ability to fulfil the request."; case HTTP_UNAVAILABLE: return "The server is either overloaded or down for maintenance."; + case HTTP_SERVER_TOO_HIGH: + return "The server is too high to answer the request."; } return "Invalid"; } diff --git a/common/lwan.h b/common/lwan.h index 9817280f3..ac8874d07 100644 --- a/common/lwan.h +++ b/common/lwan.h @@ -129,9 +129,11 @@ typedef enum { HTTP_TOO_LARGE = 413, HTTP_RANGE_UNSATISFIABLE = 416, HTTP_I_AM_A_TEAPOT = 418, + HTTP_CLIENT_TOO_HIGH = 420, HTTP_INTERNAL_ERROR = 500, HTTP_NOT_IMPLEMENTED = 501, HTTP_UNAVAILABLE = 503, + HTTP_SERVER_TOO_HIGH = 520, } lwan_http_status_t; typedef enum { From a4cf46610f95015449ade730ff89cc96ffd01517 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 12 Dec 2016 18:55:57 -0800 Subject: [PATCH 0310/2505] Refactor sendfile_init() and move the compressed file logic outside This simplifies a lot the sendfile_init() function by using an auxiliary function that handles only the logic behind opening precompressed files. The mess with gotos has been replaced with return statements in the auxiliary function; in the process, a few redundant branches have been removed as well. --- common/lwan-serve-files.c | 88 ++++++++++++++++++++++----------------- 1 file changed, 50 insertions(+), 38 deletions(-) diff --git a/common/lwan-serve-files.c b/common/lwan-serve-files.c index 0b9e0aa3a..805ac6a29 100644 --- a/common/lwan-serve-files.c +++ b/common/lwan-serve-files.c @@ -353,66 +353,78 @@ mmap_init(struct file_cache_entry *ce, struct serve_files_priv *priv, return success; } -static bool -sendfile_init(struct file_cache_entry *ce, struct serve_files_priv *priv, - const char *full_path, struct stat *st) +static int +try_open_compressed(const char *relpath, const struct serve_files_priv *priv, + const struct stat *uncompressed, size_t *compressed_sz) { char gzpath[PATH_MAX]; - struct sendfile_cache_data *sd = (struct sendfile_cache_data *)(ce + 1); - struct stat compressed_st; - int ret; - - ce->mime_type = lwan_determine_mime_type_for_file_name( - full_path + priv->root.path_len); - - if (UNLIKELY(!priv->serve_precompressed_files)) - goto only_uncompressed; + struct stat st; + int ret, fd; /* Try to serve a compressed file using sendfile() if $FILENAME.gz exists */ - ret = snprintf(gzpath, PATH_MAX, "%s.gz", full_path + priv->root.path_len + 1); + ret = snprintf(gzpath, PATH_MAX, "%s.gz", relpath + 1); if (UNLIKELY(ret < 0 || ret >= PATH_MAX)) - goto only_uncompressed; + goto out; + + fd = openat(priv->root.fd, gzpath, priv->open_mode); + if (UNLIKELY(fd < 0)) + goto out; - sd->compressed.fd = openat(priv->root.fd, gzpath, priv->open_mode); - if (UNLIKELY(sd->compressed.fd < 0)) - goto only_uncompressed; + ret = fstat(fd, &st); + if (UNLIKELY(ret < 0)) + goto close_and_out; - ret = fstat(sd->compressed.fd, &compressed_st); - if (LIKELY(!ret && compressed_st.st_mtime >= st->st_mtime && - is_compression_worthy((size_t)compressed_st.st_size, (size_t)st->st_size))) { - sd->compressed.size = (size_t)compressed_st.st_size; - } else { - close(sd->compressed.fd); + if (UNLIKELY(st.st_mtime < uncompressed->st_mtime)) + goto close_and_out; -only_uncompressed: - sd->compressed.fd = -1; - sd->compressed.size = 0; + if (LIKELY(is_compression_worthy((size_t)st.st_size, (size_t)uncompressed->st_size))) { + *compressed_sz = (size_t)st.st_size; + return fd; } - /* Regardless of the existence of $FILENAME.gz, keep the uncompressed file open */ - sd->uncompressed.size = (size_t)st->st_size; - sd->uncompressed.fd = openat(priv->root.fd, full_path + priv->root.path_len + 1, priv->open_mode); - if (UNLIKELY(sd->uncompressed.fd < 0)) { - int openat_errno = errno; +close_and_out: + close(fd); +out: + *compressed_sz = 0; + return -ENOENT; +} - if (sd->compressed.fd >= 0) - close(sd->compressed.fd); +static bool +sendfile_init(struct file_cache_entry *ce, struct serve_files_priv *priv, + const char *full_path, struct stat *st) +{ + struct sendfile_cache_data *sd = (struct sendfile_cache_data *)(ce + 1); + const char *relpath = full_path + priv->root.path_len; - switch (openat_errno) { + sd->uncompressed.fd = openat(priv->root.fd, relpath + 1, priv->open_mode); + if (UNLIKELY(sd->uncompressed.fd < 0)) { + switch (-errno) { case ENFILE: case EMFILE: case EACCES: - /* These errors should produce responses other than 404, so store errno as the - * file descriptor. */ + /* These errors should produce responses other than 404, so + * store errno as the file descriptor. */ + sd->uncompressed.fd = sd->compressed.fd = -errno; + sd->compressed.size = sd->uncompressed.size = 0; - sd->uncompressed.fd = sd->compressed.fd = -openat_errno; - sd->compressed.size = 0; return true; } return false; } + /* If precompressed files can be served, try opening it */ + if (LIKELY(priv->serve_precompressed_files)) { + size_t compressed_sz; + int fd = try_open_compressed(relpath, priv, st, &compressed_sz); + + sd->compressed.fd = fd; + sd->compressed.size = compressed_sz; + } + + ce->mime_type = lwan_determine_mime_type_for_file_name(relpath); + sd->uncompressed.size = (size_t)st->st_size; + return true; } From 43b964bc66bb346c7672d62394f7ec301140b650 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 13 Dec 2016 08:52:53 -0800 Subject: [PATCH 0311/2505] sendfile_init() should always determine the MIME type Or else, if it's not initialized, the default response will be generated on errors such as EACCESS, EMFILE, and ENFILE. --- common/lwan-serve-files.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/lwan-serve-files.c b/common/lwan-serve-files.c index 805ac6a29..5deee14b7 100644 --- a/common/lwan-serve-files.c +++ b/common/lwan-serve-files.c @@ -396,6 +396,8 @@ sendfile_init(struct file_cache_entry *ce, struct serve_files_priv *priv, struct sendfile_cache_data *sd = (struct sendfile_cache_data *)(ce + 1); const char *relpath = full_path + priv->root.path_len; + ce->mime_type = lwan_determine_mime_type_for_file_name(relpath); + sd->uncompressed.fd = openat(priv->root.fd, relpath + 1, priv->open_mode); if (UNLIKELY(sd->uncompressed.fd < 0)) { switch (-errno) { @@ -422,7 +424,6 @@ sendfile_init(struct file_cache_entry *ce, struct serve_files_priv *priv, sd->compressed.size = compressed_sz; } - ce->mime_type = lwan_determine_mime_type_for_file_name(relpath); sd->uncompressed.size = (size_t)st->st_size; return true; From baad751d548bba9cf2de6e114f815d2f6fa1bbeb Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 17 Dec 2016 11:00:07 -0800 Subject: [PATCH 0312/2505] Update build shield image URLs --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 813e1178e..bcbf8e902 100644 --- a/README.md +++ b/README.md @@ -242,6 +242,6 @@ Build status | Platform | Release | Debug | Static Analysis | Tests | |----------|---------|-------|-----------------|------------| -| Linux | ![release](https://buildbot.lwan.ws/buildstatusimage?builder=release&number=-1 "Release") | ![debug](https://buildbot.lwan.ws/buildstatusimage?builder=debug&number=-1 "Debug") | ![static-analysis](https://buildbot.lwan.ws/buildstatusimage?builder=clang-analyze&number=-1 "Static Analysis") ![coverity](https://scan.coverity.com/projects/375/badge.svg) [Report history](https://buildbot.lwan.ws/sa/) | ![tests](https://buildbot.lwan.ws/buildstatusimage?builder=unit-tests&number=-1 "Test") | -| FreeBSD | ![freebsd-release](https://buildbot.lwan.ws/buildstatusimage?builder=release-freebsd&number=-1 "Release FreeBSD") | ![freebsd-debug](https://buildbot.lwan.ws/buildstatusimage?builder=debug-freebsd&number=-1 "Debug FreeBSD") | | | -| OS X | ![osx-release](https://buildbot.lwan.ws/buildstatusimage?builder=release-yosemite&number=-1 "Release OS X") | ![osx-debug](https://buildbot.lwan.ws/buildstatusimage?builder=debug-yosemite&number=-1 "Debug OS X") | | | +| Linux | ![release](https://shield.lwan.ws/img/gycKbr/release "Release") | ![debug](https://shield.lwan.ws/img/gycKbr/debug "Debug") | ![static-analysis](https://shield.lwan.ws/img/gycKbr/clang-analyze "Static Analysis") ![coverity](https://scan.coverity.com/projects/375/badge.svg) [Report history](https://buildbot.lwan.ws/sa/) | ![tests](https://shield.lwan.ws/img/gycKbr/unit-tests "Test") | +| FreeBSD | ![freebsd-release](https://shield.lwan.ws/img/gycKbr/release-freebsd "Release FreeBSD") | ![freebsd-debug](https://shield.lwan.ws/img/gycKbr/debug-freebsd "Debug FreeBSD") | | | +| OS X | ![osx-release](https://shield.lwan.ws/img/gycKbr/release-yosemite "Release OS X") | ![osx-debug](https://shield.lwan.ws/img/gycKbr/debug-yosemite "Debug OS X") | | | From d4c973f9a6d3630960292521ef52087ca82d0ce6 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 19 Dec 2016 20:55:30 -0800 Subject: [PATCH 0313/2505] Check for `errno` as a positive value when opening compressed file --- common/lwan-serve-files.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lwan-serve-files.c b/common/lwan-serve-files.c index 5deee14b7..e317fe146 100644 --- a/common/lwan-serve-files.c +++ b/common/lwan-serve-files.c @@ -400,7 +400,7 @@ sendfile_init(struct file_cache_entry *ce, struct serve_files_priv *priv, sd->uncompressed.fd = openat(priv->root.fd, relpath + 1, priv->open_mode); if (UNLIKELY(sd->uncompressed.fd < 0)) { - switch (-errno) { + switch (errno) { case ENFILE: case EMFILE: case EACCES: From da5f090c059d0e624ab01730eba6f13c897ed081 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 22 Dec 2016 17:44:39 -0800 Subject: [PATCH 0314/2505] Mark lwan_status_*() functions with noinline and cold attributes These functions are unlikely to be executed in the fast path, so mark them with attributes that will move them out of the way. The gcc manual has some explanation on how this works: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-g_t_0040code_007bcold_007d-function-attribute-3315 --- common/lwan-status.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/lwan-status.h b/common/lwan-status.h index 67c0b2bb4..c6681eeaf 100644 --- a/common/lwan-status.h +++ b/common/lwan-status.h @@ -23,6 +23,7 @@ #define DECLARE_STATUS_PROTO(type_, ...) \ void lwan_status_##type_(const char *fmt, ...) \ __attribute__((format(printf, 1, 2))) \ + __attribute__((noinline,cold)) \ __VA_ARGS__; #define lwan_status_debug(fmt, ...) @@ -31,6 +32,7 @@ void lwan_status_##type_##_debug(const char *file, const int line, \ const char *func, const char *fmt, ...) \ __attribute__((format(printf, 4, 5))) \ + __attribute__((noinline,cold)) \ __VA_ARGS__; #define lwan_status_info(fmt, ...) \ From cfbef8f34e10531ec0ad92717a8a305a8d871fa4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 24 Dec 2016 10:38:55 -0800 Subject: [PATCH 0315/2505] Use fpclassify() to compare doubles in templates to zero --- common/CMakeLists.txt | 2 +- common/lwan-template.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index acc5cc25a..0354b4ac6 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -107,7 +107,7 @@ add_custom_target(generate_mime_types_table DEPENDS ${CMAKE_BINARY_DIR}/mime-typ add_dependencies(lwan-static generate_mime_types_table) include_directories(${CMAKE_BINARY_DIR}) -set(ADDITIONAL_LIBRARIES ${ADDITIONAL_LIBRARIES} PARENT_SCOPE) +set(ADDITIONAL_LIBRARIES ${ADDITIONAL_LIBRARIES} m PARENT_SCOPE) install(TARGETS lwan-static lwan-shared DESTINATION "lib" diff --git a/common/lwan-template.c b/common/lwan-template.c index e533f34d0..79ec04625 100644 --- a/common/lwan-template.c +++ b/common/lwan-template.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -861,7 +862,7 @@ lwan_append_double_to_strbuf(strbuf_t *buf, void *ptr) bool lwan_tpl_double_is_empty(void *ptr) { - return (*(double *)ptr) == 0.0f; + return fpclassify(*(double *)ptr) == FP_ZERO; } void From 7aca8613f9ebf04ab630b00d4af27f6c8249ed4c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 28 Dec 2016 13:16:04 +0100 Subject: [PATCH 0316/2505] Use SCNu64 instead of PRIu64 when calling sscanf() --- common/lwan-request.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index 90b575c9d..d19d4d27a 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -621,13 +621,13 @@ parse_range(lwan_request_t *request, struct request_parser_helper *helper) range += sizeof("bytes=") - 1; off_t from, to; - if (sscanf(range, "%"PRIu64"-%"PRIu64, &from, &to) == 2) { + if (sscanf(range, "%"SCNu64"-%"PRIu64, &from, &to) == 2) { request->header.range.from = from; request->header.range.to = to; - } else if (sscanf(range, "-%"PRIu64, &to) == 1) { + } else if (sscanf(range, "-%"SCNu64, &to) == 1) { request->header.range.from = 0; request->header.range.to = to; - } else if (sscanf(range, "%"PRIu64"-", &from) == 1) { + } else if (sscanf(range, "%"SCNu64"-", &from) == 1) { request->header.range.from = from; request->header.range.to = -1; } else { From 7bfcfc2f9203aff7748b356a2f75d6437350b6d5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 28 Dec 2016 18:46:13 +0100 Subject: [PATCH 0317/2505] Ensure ${FILE}.gz is world readable before serving --- common/lwan-serve-files.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/common/lwan-serve-files.c b/common/lwan-serve-files.c index e317fe146..1695d2ad9 100644 --- a/common/lwan-serve-files.c +++ b/common/lwan-serve-files.c @@ -353,6 +353,14 @@ mmap_init(struct file_cache_entry *ce, struct serve_files_priv *priv, return success; } +static bool +is_world_readable(mode_t mode) +{ + const mode_t world_readable = S_IRUSR | S_IRGRP | S_IROTH; + + return (mode & world_readable) == world_readable; +} + static int try_open_compressed(const char *relpath, const struct serve_files_priv *priv, const struct stat *uncompressed, size_t *compressed_sz) @@ -377,6 +385,9 @@ try_open_compressed(const char *relpath, const struct serve_files_priv *priv, if (UNLIKELY(st.st_mtime < uncompressed->st_mtime)) goto close_and_out; + if (UNLIKELY(!is_world_readable(st.st_mode))) + goto close_and_out; + if (LIKELY(is_compression_worthy((size_t)st.st_size, (size_t)uncompressed->st_size))) { *compressed_sz = (size_t)st.st_size; return fd; @@ -465,14 +476,6 @@ redir_init(struct file_cache_entry *ce, struct serve_files_priv *priv, return true; } -static bool -is_world_readable(mode_t mode) -{ - const mode_t world_readable = S_IRUSR | S_IRGRP | S_IROTH; - - return (mode & world_readable) == world_readable; -} - static const struct cache_funcs * get_funcs(struct serve_files_priv *priv, const char *key, char *full_path, struct stat *st) From c2e368295b34713e9c573ede7de492b81ab5502b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 29 Dec 2016 15:24:56 +0100 Subject: [PATCH 0318/2505] Be more explicit and use a bool variable instead of an enum --- common/lwan-template.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lwan-template.c b/common/lwan-template.c index 79ec04625..6143aca98 100644 --- a/common/lwan-template.c +++ b/common/lwan-template.c @@ -1286,7 +1286,7 @@ action_apply_tpl: { coro = coro_new(&switcher, cd->descriptor->generator, variables); bool resumed = coro_resume_value(coro, 0); - enum flags negate = chunk->flags & FLAGS_NEGATE; + bool negate = (chunk->flags & FLAGS_NEGATE) == FLAGS_NEGATE; if (negate) resumed = !resumed; if (!resumed) { From 7171a631af83e104e79d0c6f540f561aaa42248e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 29 Dec 2016 15:26:43 +0100 Subject: [PATCH 0319/2505] Check if name is NULL before trying to dereference it --- common/realpathat.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/common/realpathat.c b/common/realpathat.c index 24614cf2c..0cd374321 100644 --- a/common/realpathat.c +++ b/common/realpathat.c @@ -44,12 +44,6 @@ realpathat2(int dirfd, char *dirfdpath, const char *name, char *resolved, ptrdiff_t dirfdlen; char *pathat; - /* If any of the additional parameters are null, or if the name to - resolve the real path is an absolute path, use the standard - realpath() routine. */ - if (UNLIKELY(dirfd < 0 || dirfdpath == NULL || name[0] == '/')) - return realpath(name, resolved); - if (UNLIKELY(name == NULL)) { /* As per Single Unix Specification V2 we must return an error if either parameter is a null pointer. We extend this to allow @@ -59,6 +53,12 @@ realpathat2(int dirfd, char *dirfdpath, const char *name, char *resolved, return NULL; } + /* If any of the additional parameters are null, or if the name to + resolve the real path is an absolute path, use the standard + realpath() routine. */ + if (UNLIKELY(dirfd < 0 || dirfdpath == NULL || name[0] == '/')) + return realpath(name, resolved); + if (name[0] == '\0') { if (UNLIKELY(fstat(dirfd, st) < 0)) return NULL; From 701abc30b9c79d58c2b22a5c3f7b1746881239f2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 2 Jan 2017 17:27:35 -0800 Subject: [PATCH 0320/2505] Use an array instead of a linked list for deferred callbacks This should reduce the amount of dynamic memory allocation in the hot path. --- common/lwan-coro.c | 57 +++++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/common/lwan-coro.c b/common/lwan-coro.c index 1a085f909..86e4c4b8a 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -47,7 +47,6 @@ typedef struct coro_defer_t_ coro_defer_t; typedef void (*defer_func)(); struct coro_defer_t_ { - coro_defer_t *next; defer_func func; void *data1; void *data2; @@ -62,8 +61,8 @@ struct coro_t_ { unsigned int vg_stack_id; #endif - coro_defer_t *defer; void *data; + coro_defer_t defer[16]; bool ended; }; @@ -162,16 +161,26 @@ coro_entry_point(coro_t *coro, coro_function_t func) } static void -coro_run_deferred(coro_t *coro) +run_or_recurse(coro_defer_t *defer, coro_defer_t *last) { - while (coro->defer) { - coro_defer_t *tmp = coro->defer; + if (defer == last || !defer->func) + return; - coro->defer = coro->defer->next; + run_or_recurse(defer + 1, last); + defer->func(defer->data1, defer->data2); + defer->func = NULL; +} - tmp->func(tmp->data1, tmp->data2); - free(tmp); - } +static coro_defer_t *last_defer(const coro_t *coro) +{ + return (coro_defer_t *)(&coro->defer + 1); +} + +static void +coro_run_deferred(coro_t *coro) +{ + /* Some uses require deferred statements are arranged in a stack. */ + run_or_recurse(coro->defer, last_defer(coro)); } void @@ -221,7 +230,7 @@ coro_new(coro_switcher_t *switcher, coro_function_t function, void *data) return NULL; coro->switcher = switcher; - coro->defer = NULL; + memset(coro->defer, 0, sizeof(coro->defer)); coro_reset(coro, function, data); #if !defined(NDEBUG) && defined(USE_VALGRIND) @@ -300,18 +309,20 @@ coro_free(coro_t *coro) static void coro_defer_any(coro_t *coro, defer_func func, void *data1, void *data2) { - coro_defer_t *defer = malloc(sizeof(*defer)); - if (UNLIKELY(!defer)) - return; + coro_defer_t *defer; + coro_defer_t *last = last_defer(coro); assert(func); - /* Some uses require deferred statements are arranged in a stack. */ - defer->next = coro->defer; + for (defer = coro->defer; defer <= last && defer->func; defer++); + if (UNLIKELY(defer == last)) { + lwan_status_error("Coro %p exhausted space for deferred callback", coro); + return; + } + defer->func = func; defer->data1 = data1; defer->data2 = data2; - coro->defer = defer; } ALWAYS_INLINE void @@ -330,18 +341,12 @@ coro_defer2(coro_t *coro, void (*func)(void *data1, void *data2), void * coro_malloc_full(coro_t *coro, size_t size, void (*destroy_func)()) { - coro_defer_t *defer = malloc(sizeof(*defer) + size); - if (UNLIKELY(!defer)) + void *mem = malloc(size); + if (UNLIKELY(!mem)) return NULL; - defer->next = coro->defer; - defer->func = destroy_func; - defer->data1 = defer + 1; - defer->data2 = NULL; - - coro->defer = defer; - - return defer + 1; + coro_defer(coro, CORO_DEFER(destroy_func), mem); + return mem; } static void nothing() From e728142d16304050a19e076e7a3d03868fa0bc08 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 2 Jan 2017 17:28:27 -0800 Subject: [PATCH 0321/2505] Allocate coroutine string buffer on stack This further reduces the amount of dynamic memory allocation in the fast path. The important bit in this change is that the process_request_coro() function should never return: coro_yield() should be called to terminate it. This way, the string buffer remains alive when the deferred callbacks are executed to clean it up. --- common/lwan-thread.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/common/lwan-thread.c b/common/lwan-thread.c index f0cc15d64..a855fd939 100644 --- a/common/lwan-thread.c +++ b/common/lwan-thread.c @@ -139,8 +139,11 @@ min(const int a, const int b) static int process_request_coro(coro_t *coro) { + /* NOTE: This function should not return; coro_yield should be used + * instead. This ensures the storage for `strbuf` is alive when the + * coroutine ends and strbuf_free() is called. */ const lwan_request_flags_t flags_filter = REQUEST_PROXIED; - strbuf_t *strbuf = coro_malloc_full(coro, sizeof(*strbuf), strbuf_free); + strbuf_t strbuf; lwan_connection_t *conn = coro_get_data(coro); lwan_t *lwan = conn->thread->lwan; int fd = lwan_connection_get_fd(lwan, conn); @@ -154,17 +157,18 @@ process_request_coro(coro_t *coro) lwan->config.proxy_protocol ? REQUEST_ALLOW_PROXY_REQS : 0; lwan_proxy_t proxy; - if (UNLIKELY(!strbuf)) - return CONN_CORO_ABORT; - - strbuf_init(strbuf); + if (UNLIKELY(!strbuf_init(&strbuf))) { + coro_yield(coro, CONN_CORO_ABORT); + __builtin_unreachable(); + } + coro_defer(coro, CORO_DEFER(strbuf_free), &strbuf); while (true) { lwan_request_t request = { .conn = conn, .fd = fd, .response = { - .buffer = strbuf + .buffer = &strbuf }, .flags = flags, .proxy = &proxy @@ -175,13 +179,16 @@ process_request_coro(coro_t *coro) next_request = lwan_process_request(lwan, &request, &buffer, next_request); coro_yield(coro, CONN_CORO_MAY_RESUME); - if (UNLIKELY(!strbuf_reset_length(strbuf))) - return CONN_CORO_ABORT; + if (UNLIKELY(!strbuf_reset_length(&strbuf))) { + coro_yield(coro, CONN_CORO_ABORT); + __builtin_unreachable(); + } flags = request.flags & flags_filter; } - return CONN_CORO_FINISHED; + coro_yield(coro, CONN_CORO_FINISHED); + __builtin_unreachable(); } static ALWAYS_INLINE void From b1f3add4611e8bdfdf8142ae3c9b3d2eed9f8187 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 2 Jan 2017 17:53:12 -0800 Subject: [PATCH 0322/2505] Fix return type of last_defer() --- common/lwan-coro.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/lwan-coro.c b/common/lwan-coro.c index 86e4c4b8a..f10c4b529 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -171,9 +171,9 @@ run_or_recurse(coro_defer_t *defer, coro_defer_t *last) defer->func = NULL; } -static coro_defer_t *last_defer(const coro_t *coro) +static coro_defer_t *last_defer(coro_t *coro) { - return (coro_defer_t *)(&coro->defer + 1); + return *(&coro->defer + 1); } static void From 9a3952f95b880c0d170c4ea1c90839c1e0394166 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 2 Jan 2017 17:57:08 -0800 Subject: [PATCH 0323/2505] Ensure coroutine stack is aligned by embedding it in struct coro_t --- common/lwan-coro.c | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/common/lwan-coro.c b/common/lwan-coro.c index f10c4b529..dfe05ae3c 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -34,12 +34,12 @@ #endif #ifdef __GLIBC__ -#define CORO_STACK_MIN ((3 * (PTHREAD_STACK_MIN)) / 2) +#define CORO_STACK ((3 * (PTHREAD_STACK_MIN)) / 2) #else -#define CORO_STACK_MIN (5 * (PTHREAD_STACK_MIN)) +#define CORO_STACK (5 * (PTHREAD_STACK_MIN)) #endif -static_assert(DEFAULT_BUFFER_SIZE < (CORO_STACK_MIN + PTHREAD_STACK_MIN), +static_assert(DEFAULT_BUFFER_SIZE < (CORO_STACK + PTHREAD_STACK_MIN), "Request buffer fits inside coroutine stack"); typedef struct coro_defer_t_ coro_defer_t; @@ -57,14 +57,15 @@ struct coro_t_ { coro_context_t context; int yield_value; -#if !defined(NDEBUG) && defined(USE_VALGRIND) - unsigned int vg_stack_id; -#endif - void *data; coro_defer_t defer[16]; +#if !defined(NDEBUG) && defined(USE_VALGRIND) + unsigned int vg_stack_id; +#endif bool ended; + + unsigned char stack[CORO_STACK]; }; static void coro_entry_point(coro_t *data, coro_function_t func); @@ -186,8 +187,6 @@ coro_run_deferred(coro_t *coro) void coro_reset(coro_t *coro, coro_function_t func, void *data) { - unsigned char *stack = (unsigned char *)(coro + 1); - coro->ended = false; coro->data = data; @@ -197,10 +196,10 @@ coro_reset(coro_t *coro, coro_function_t func, void *data) coro->context[6 /* RDI */] = (uintptr_t) coro; coro->context[7 /* RSI */] = (uintptr_t) func; coro->context[8 /* RIP */] = (uintptr_t) coro_entry_point; - coro->context[9 /* RSP */] = (uintptr_t) stack + CORO_STACK_MIN; + coro->context[9 /* RSP */] = (uintptr_t) coro->stack + CORO_STACK; #elif defined(__i386__) /* Align stack and make room for two arguments */ - stack = (unsigned char *)((uintptr_t)(stack + CORO_STACK_MIN - + stack = (unsigned char *)((uintptr_t)(coro->stack + CORO_STACK - sizeof(uintptr_t) * 2) & 0xfffffff0); uintptr_t *argp = (uintptr_t *)stack; @@ -214,7 +213,7 @@ coro_reset(coro_t *coro, coro_function_t func, void *data) getcontext(&coro->context); coro->context.uc_stack.ss_sp = stack; - coro->context.uc_stack.ss_size = CORO_STACK_MIN; + coro->context.uc_stack.ss_size = CORO_STACK; coro->context.uc_stack.ss_flags = 0; coro->context.uc_link = NULL; @@ -225,7 +224,7 @@ coro_reset(coro_t *coro, coro_function_t func, void *data) ALWAYS_INLINE coro_t * coro_new(coro_switcher_t *switcher, coro_function_t function, void *data) { - coro_t *coro = malloc(sizeof(*coro) + CORO_STACK_MIN); + coro_t *coro = malloc(sizeof(*coro)); if (!coro) return NULL; @@ -234,8 +233,7 @@ coro_new(coro_switcher_t *switcher, coro_function_t function, void *data) coro_reset(coro, function, data); #if !defined(NDEBUG) && defined(USE_VALGRIND) - char *stack = (char *)(coro + 1); - coro->vg_stack_id = VALGRIND_STACK_REGISTER(stack, stack + CORO_STACK_MIN); + coro->vg_stack_id = VALGRIND_STACK_REGISTER(stack, coro->stack + CORO_STACK); #endif return coro; From 0b0caeaf4c81baf015910516ea1e1bf7ec1e23cd Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 3 Jan 2017 06:21:07 -0800 Subject: [PATCH 0324/2505] Better pack struct serve_files_priv This now uses one cache line only as opposed to two. --- common/lwan-serve-files.c | 46 +++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/common/lwan-serve-files.c b/common/lwan-serve-files.c index 1695d2ad9..b941c1fcc 100644 --- a/common/lwan-serve-files.c +++ b/common/lwan-serve-files.c @@ -47,11 +47,9 @@ struct file_cache_entry; struct serve_files_priv { struct cache_t *cache; - struct { - char *path; - size_t path_len; - int fd; - } root; + char *root_path; + size_t root_path_len; + int root_fd; int open_mode; const char *index_html; @@ -323,7 +321,7 @@ mmap_init(struct file_cache_entry *ce, struct serve_files_priv *priv, int file_fd; bool success; - file_fd = openat(priv->root.fd, full_path + priv->root.path_len + 1, + file_fd = openat(priv->root_fd, full_path + priv->root_path_len + 1, priv->open_mode); if (UNLIKELY(file_fd < 0)) return false; @@ -343,7 +341,7 @@ mmap_init(struct file_cache_entry *ce, struct serve_files_priv *priv, compress_cached_entry(md); ce->mime_type = lwan_determine_mime_type_for_file_name( - full_path + priv->root.path_len); + full_path + priv->root_path_len); success = true; @@ -374,7 +372,7 @@ try_open_compressed(const char *relpath, const struct serve_files_priv *priv, if (UNLIKELY(ret < 0 || ret >= PATH_MAX)) goto out; - fd = openat(priv->root.fd, gzpath, priv->open_mode); + fd = openat(priv->root_fd, gzpath, priv->open_mode); if (UNLIKELY(fd < 0)) goto out; @@ -405,11 +403,11 @@ sendfile_init(struct file_cache_entry *ce, struct serve_files_priv *priv, const char *full_path, struct stat *st) { struct sendfile_cache_data *sd = (struct sendfile_cache_data *)(ce + 1); - const char *relpath = full_path + priv->root.path_len; + const char *relpath = full_path + priv->root_path_len; ce->mime_type = lwan_determine_mime_type_for_file_name(relpath); - sd->uncompressed.fd = openat(priv->root.fd, relpath + 1, priv->open_mode); + sd->uncompressed.fd = openat(priv->root_fd, relpath + 1, priv->open_mode); if (UNLIKELY(sd->uncompressed.fd < 0)) { switch (errno) { case ENFILE: @@ -443,7 +441,7 @@ sendfile_init(struct file_cache_entry *ce, struct serve_files_priv *priv, static const char * get_rel_path(const char *full_path, struct serve_files_priv *priv) { - const char *root_path = full_path + priv->root.path_len; + const char *root_path = full_path + priv->root_path_len; return *root_path ? root_path : priv->prefix; } @@ -469,7 +467,7 @@ redir_init(struct file_cache_entry *ce, struct serve_files_priv *priv, { struct redir_cache_data *rd = (struct redir_cache_data *)(ce + 1); - if (asprintf(&rd->redir_to, "%s/", full_path + priv->root.path_len) < 0) + if (asprintf(&rd->redir_to, "%s/", full_path + priv->root_path_len) < 0) return false; ce->mime_type = "text/plain"; @@ -503,7 +501,7 @@ get_funcs(struct serve_files_priv *priv, const char *key, char *full_path, } /* See if it exists. */ - if (fstatat(priv->root.fd, index_html_path, st, 0) < 0) { + if (fstatat(priv->root_fd, index_html_path, st, 0) < 0) { if (UNLIKELY(errno != ENOENT)) return NULL; @@ -523,13 +521,13 @@ get_funcs(struct serve_files_priv *priv, const char *key, char *full_path, /* If it does, we want its full path. */ /* FIXME: Use strlcpy() here instead of calling strlen()? */ - if (UNLIKELY(priv->root.path_len + 1 /* slash */ + + if (UNLIKELY(priv->root_path_len + 1 /* slash */ + strlen(index_html_path) + 1 >= PATH_MAX)) return NULL; - full_path[priv->root.path_len] = '/'; - strncpy(full_path + priv->root.path_len + 1, index_html_path, - PATH_MAX - priv->root.path_len - 1); + full_path[priv->root_path_len] = '/'; + strncpy(full_path + priv->root_path_len + 1, index_html_path, + PATH_MAX - priv->root_path_len - 1); } /* Only serve regular files. */ @@ -576,14 +574,14 @@ create_cache_entry(const char *key, void *context) const struct cache_funcs *funcs; char full_path[PATH_MAX]; - if (UNLIKELY(!realpathat2(priv->root.fd, priv->root.path, + if (UNLIKELY(!realpathat2(priv->root_fd, priv->root_path, key, full_path, &st))) return NULL; if (UNLIKELY(!is_world_readable(st.st_mode))) return NULL; - if (UNLIKELY(strncmp(full_path, priv->root.path, priv->root.path_len))) + if (UNLIKELY(strncmp(full_path, priv->root_path, priv->root_path_len))) return NULL; funcs = get_funcs(priv, key, full_path, &st); @@ -737,9 +735,9 @@ serve_files_init(const char *prefix, void *args) goto out_tpl_prefix_copy; } - priv->root.path = canonical_root; - priv->root.path_len = strlen(canonical_root); - priv->root.fd = root_fd; + priv->root_path = canonical_root; + priv->root_path_len = strlen(canonical_root); + priv->root_fd = root_fd; priv->open_mode = open_mode; priv->index_html = settings->index_html ? settings->index_html : "index.html"; priv->serve_precompressed_files = settings->serve_precompressed_files; @@ -786,8 +784,8 @@ serve_files_shutdown(void *data) lwan_tpl_free(priv->directory_list_tpl); cache_destroy(priv->cache); - close(priv->root.fd); - free(priv->root.path); + close(priv->root_fd); + free(priv->root_path); free(priv->prefix); free(priv); } From 328a42a1359ad8447bce3d187a08ae01d536d782 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 3 Jan 2017 06:32:27 -0800 Subject: [PATCH 0325/2505] Fix debug build --- common/lwan-coro.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lwan-coro.c b/common/lwan-coro.c index dfe05ae3c..1afdb3a66 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -233,7 +233,7 @@ coro_new(coro_switcher_t *switcher, coro_function_t function, void *data) coro_reset(coro, function, data); #if !defined(NDEBUG) && defined(USE_VALGRIND) - coro->vg_stack_id = VALGRIND_STACK_REGISTER(stack, coro->stack + CORO_STACK); + coro->vg_stack_id = VALGRIND_STACK_REGISTER(coro->stack, coro->stack + CORO_STACK); #endif return coro; From 749ed152cc7a5fe376e868efaa9e946faea117b9 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 3 Jan 2017 06:32:37 -0800 Subject: [PATCH 0326/2505] Make clang happy with last_defer() --- common/lwan-coro.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lwan-coro.c b/common/lwan-coro.c index 1afdb3a66..c12fb7f35 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -174,7 +174,7 @@ run_or_recurse(coro_defer_t *defer, coro_defer_t *last) static coro_defer_t *last_defer(coro_t *coro) { - return *(&coro->defer + 1); + return coro->defer + sizeof(coro->defer); } static void From d718be1069e327e37deb454f0760979abb2b4916 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 3 Jan 2017 07:09:08 -0800 Subject: [PATCH 0327/2505] Align coroutine stack on x86-64 Unaligned pointers shouldn't cause issues on x86-64 -- except when SIMD is used. Since the stack is assumed to be aligned by gcc, it ends up using SIMD instructions at will -- which might cause problems if it's not actually aligned. Since the coroutine stuff messes with the stack pointer, it was a miracle that this thing worked for so long. Found with the help of address sanitizer. --- common/lwan-coro.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/lwan-coro.c b/common/lwan-coro.c index c12fb7f35..7b56f9956 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -196,7 +196,8 @@ coro_reset(coro_t *coro, coro_function_t func, void *data) coro->context[6 /* RDI */] = (uintptr_t) coro; coro->context[7 /* RSI */] = (uintptr_t) func; coro->context[8 /* RIP */] = (uintptr_t) coro_entry_point; - coro->context[9 /* RSP */] = (uintptr_t) coro->stack + CORO_STACK; + uintptr_t stack = (uintptr_t) coro->stack + CORO_STACK; + coro->context[9 /* RSP */] = stack - (stack & 7); #elif defined(__i386__) /* Align stack and make room for two arguments */ stack = (unsigned char *)((uintptr_t)(coro->stack + CORO_STACK - From 0ab83daceb5d224ce1d4e0044010b0822a9ba290 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 3 Jan 2017 07:09:26 -0800 Subject: [PATCH 0328/2505] Fix build on i386 I need a 32-bit x86 build worker. --- common/lwan-coro.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lwan-coro.c b/common/lwan-coro.c index 7b56f9956..c7abcdc3f 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -200,7 +200,7 @@ coro_reset(coro_t *coro, coro_function_t func, void *data) coro->context[9 /* RSP */] = stack - (stack & 7); #elif defined(__i386__) /* Align stack and make room for two arguments */ - stack = (unsigned char *)((uintptr_t)(coro->stack + CORO_STACK - + uintptr_t stack = ((uintptr_t)(coro->stack + CORO_STACK - sizeof(uintptr_t) * 2) & 0xfffffff0); uintptr_t *argp = (uintptr_t *)stack; From defe3ba0b2cdca1ced63dbac21dcfb08ac8ba9d5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 3 Jan 2017 07:38:09 -0800 Subject: [PATCH 0329/2505] Fix build with ucontext fallback Maybe an ARM build slave would help here? --- common/lwan-coro.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lwan-coro.c b/common/lwan-coro.c index c7abcdc3f..54b7747f1 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -213,7 +213,7 @@ coro_reset(coro_t *coro, coro_function_t func, void *data) #else getcontext(&coro->context); - coro->context.uc_stack.ss_sp = stack; + coro->context.uc_stack.ss_sp = coro->stack; coro->context.uc_stack.ss_size = CORO_STACK; coro->context.uc_stack.ss_flags = 0; coro->context.uc_link = NULL; From 0361b797487326d664c0b4d04d2292dedfab58b2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 3 Jan 2017 07:38:31 -0800 Subject: [PATCH 0330/2505] Ensure stack is aligned on 16-byte boundaries on x86-64 --- common/lwan-coro.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lwan-coro.c b/common/lwan-coro.c index 54b7747f1..8b53185dd 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -197,7 +197,7 @@ coro_reset(coro_t *coro, coro_function_t func, void *data) coro->context[7 /* RSI */] = (uintptr_t) func; coro->context[8 /* RIP */] = (uintptr_t) coro_entry_point; uintptr_t stack = (uintptr_t) coro->stack + CORO_STACK; - coro->context[9 /* RSP */] = stack - (stack & 7); + coro->context[9 /* RSP */] = stack - (stack & 15); #elif defined(__i386__) /* Align stack and make room for two arguments */ uintptr_t stack = ((uintptr_t)(coro->stack + CORO_STACK - From 0cb50833ff4b9439a772ed3eecfd98307bed871c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 3 Jan 2017 20:25:35 -0800 Subject: [PATCH 0331/2505] Plug memory leak after moving coro_defer to an array Previously, coro_malloc() would allocate a chunk of memory sufficiently large to hold a struct coro_defer and the requested amount of memory; the callback to free the struct was an empty function, since the linked list node would be freed with free() anyway. With the move to an array rather than a linked list, the deferred callback should be a call to free(). --- common/lwan-coro.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/common/lwan-coro.c b/common/lwan-coro.c index 8b53185dd..85cee7265 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -348,14 +348,10 @@ coro_malloc_full(coro_t *coro, size_t size, void (*destroy_func)()) return mem; } -static void nothing() -{ -} - inline void * coro_malloc(coro_t *coro, size_t size) { - return coro_malloc_full(coro, size, nothing); + return coro_malloc_full(coro, size, free); } char * From d0d52f2922f5ab2bad21aecbbdfee3718c2d5b76 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 3 Jan 2017 20:35:11 -0800 Subject: [PATCH 0332/2505] Simplify parser for first line of HTTP request This makes the parser slightly more strict as well, since now the protocol name is completely checked ("HTTP" rather than just "H"), and the version is fully compared against "1.0" and "1.1", rather than checking if there's a 0 and a 1 in the correct places. --- common/lwan-request.c | 43 ++++++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index d19d4d27a..3c29fa180 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -442,36 +442,45 @@ identify_http_path(lwan_request_t *request, char *buffer, struct request_parser_helper *helper) { static const size_t minimal_request_line_len = sizeof("/ HTTP/1.0") - 1; + char *space, *end_of_line; + enum { + HTTP_PROTO_NAME = MULTICHAR_CONSTANT('H', 'T', 'T', 'P'), + HTTP_PROTO_VER_1_0 = MULTICHAR_CONSTANT('/', '1', '.', '0'), + HTTP_PROTO_VER_1_1 = MULTICHAR_CONSTANT('/', '1', '.', '1') + }; + + if (UNLIKELY(*buffer != '/')) + return NULL; - char *end_of_line = memchr(buffer, '\r', - (helper->buffer->len - (size_t)(buffer - helper->buffer->value))); + end_of_line = memchr(buffer, '\r', + (helper->buffer->len - (size_t)(buffer - helper->buffer->value))); if (UNLIKELY(!end_of_line)) return NULL; if (UNLIKELY((size_t)(end_of_line - buffer) < minimal_request_line_len)) return NULL; *end_of_line = '\0'; - char *space = end_of_line - sizeof("HTTP/X.X"); - if (UNLIKELY(*(space + 1) != 'H')) /* assume HTTP/X.Y */ - return NULL; - *space = '\0'; - - if (UNLIKELY(*(space + 6) != '1')) - return NULL; - - if (*(space + 8) == '0') - request->flags |= REQUEST_IS_HTTP_1_0; - - if (UNLIKELY(*buffer != '/')) - return NULL; + space = end_of_line - sizeof("HTTP/X.X"); request->url.value = buffer; request->url.len = (size_t)(space - buffer); - parse_fragment_and_query(request, helper, space); - request->original_url = request->url; + *space++ = '\0'; + + if (UNLIKELY(string_as_int32(space) != HTTP_PROTO_NAME)) + return NULL; + STRING_SWITCH(space + sizeof("HTTP") - 1) { + case HTTP_PROTO_VER_1_0: + request->flags |= REQUEST_IS_HTTP_1_0; + /* fallthrough */ + case HTTP_PROTO_VER_1_1: + break; + default: + return NULL; + } + return end_of_line + 1; } From adc81d8e81ea9d7ddfc13556fefe4f7d2c000f95 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 4 Jan 2017 20:26:34 -0800 Subject: [PATCH 0333/2505] Use 64-bit string switch to reduce comparisons/branches in header parsing Checks for protocol and version with only two 64-bit integer comparison, instead of three 32-bit comparisons. --- common/lwan-request.c | 13 +++++-------- common/lwan.h | 34 ++++++++++++++++++++++++++++------ 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index 3c29fa180..c28c8f900 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -444,9 +444,8 @@ identify_http_path(lwan_request_t *request, char *buffer, static const size_t minimal_request_line_len = sizeof("/ HTTP/1.0") - 1; char *space, *end_of_line; enum { - HTTP_PROTO_NAME = MULTICHAR_CONSTANT('H', 'T', 'T', 'P'), - HTTP_PROTO_VER_1_0 = MULTICHAR_CONSTANT('/', '1', '.', '0'), - HTTP_PROTO_VER_1_1 = MULTICHAR_CONSTANT('/', '1', '.', '1') + HTTP_VERSION_1_0 = MULTICHAR_CONSTANT_LARGE('H','T','T','P','/','1','.','0'), + HTTP_VERSION_1_1 = MULTICHAR_CONSTANT_LARGE('H','T','T','P','/','1','.','1'), }; if (UNLIKELY(*buffer != '/')) @@ -469,13 +468,11 @@ identify_http_path(lwan_request_t *request, char *buffer, *space++ = '\0'; - if (UNLIKELY(string_as_int32(space) != HTTP_PROTO_NAME)) - return NULL; - STRING_SWITCH(space + sizeof("HTTP") - 1) { - case HTTP_PROTO_VER_1_0: + STRING_SWITCH_LARGE(space) { + case HTTP_VERSION_1_0: request->flags |= REQUEST_IS_HTTP_1_0; /* fallthrough */ - case HTTP_PROTO_VER_1_1: + case HTTP_VERSION_1_1: break; default: return NULL; diff --git a/common/lwan.h b/common/lwan.h index ac8874d07..f8cb6715c 100644 --- a/common/lwan.h +++ b/common/lwan.h @@ -56,17 +56,36 @@ extern "C" { #endif #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ -# define MULTICHAR_CONSTANT(a,b,c,d) ((int32_t)((a) | (b) << 8 | (c) << 16 | (d) << 24)) -# define MULTICHAR_CONSTANT_SMALL(a,b) ((int16_t)((a) | (b) << 8)) +# define MULTICHAR_CONSTANT(a,b,c,d) \ + ((int32_t)((a) | (b) << 8 | (c) << 16 | (d) << 24)) +# define MULTICHAR_CONSTANT_SMALL(a,b) \ + ((int16_t)((a) | (b) << 8)) +# define MULTICHAR_CONSTANT_LARGE(a,b,c,d,e,f,g,h) \ + ((int64_t)MULTICHAR_CONSTANT(a,b,c,d) | (int64_t)MULTICHAR_CONSTANT(e,f,g,h)<<32) #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ -# define MULTICHAR_CONSTANT(d,c,b,a) ((int32_t)((a) | (b) << 8 | (c) << 16 | (d) << 24)) -# define MULTICHAR_CONSTANT_SMALL(b,a) ((int16_t)((a) | (b) << 8)) +# define MULTICHAR_CONSTANT(d,c,b,a) \ + ((int32_t)((a) | (b) << 8 | (c) << 16 | (d) << 24)) +# define MULTICHAR_CONSTANT_SMALL(b,a) \ + ((int16_t)((a) | (b) << 8)) +# define MULTICHAR_CONSTANT_LARGE(a,b,c,d,e,f,g,h) \ + ((int64_t)MULTICHAR_CONSTANT(a,b,c,d)<<32 | (int64_t)MULTICHAR_CONSTANT(e,f,g,h)) #elif __BYTE_ORDER__ == __ORDER_PDP_ENDIAN__ # error A PDP? Seriously? #endif -#define MULTICHAR_CONSTANT_L(a,b,c,d) (MULTICHAR_CONSTANT(a,b,c,d) | 0x20202020) -#define MULTICHAR_CONSTANT_SMALL_L(a,b) (MULTICHAR_CONSTANT_SMALL(a,b) | 0x2020) +#define MULTICHAR_CONSTANT_L(a,b,c,d) \ + (MULTICHAR_CONSTANT(a,b,c,d) | 0x20202020) +#define MULTICHAR_CONSTANT_SMALL_L(a,b) \ + (MULTICHAR_CONSTANT_SMALL(a,b) | 0x2020) +#define MULTICHAR_CONSTANT_LARGE_L(a,b,c,d,e,f,g,h) \ + (MULTICHAR_CONSTANT_LARGE(a,b,c,d,e,f,g,h) | 0x2020202020202020) + +static ALWAYS_INLINE int64_t string_as_int64(const char *s) +{ + int64_t i; + memcpy(&i, s, sizeof(int64_t)); + return i; +} static ALWAYS_INLINE int32_t string_as_int32(const char *s) { @@ -88,6 +107,9 @@ static ALWAYS_INLINE int16_t string_as_int16(const char *s) #define STRING_SWITCH_SMALL(s) switch (string_as_int16(s)) #define STRING_SWITCH_SMALL_L(s) switch (string_as_int16(s) | 0x2020) +#define STRING_SWITCH_LARGE(s) switch (string_as_int64(s)) +#define STRING_SWITCH_LARGE_L(s) switch (string_as_int64(s) | 0x2020202020202020) + #define LIKELY(x) LIKELY_IS(!!(x), 1) #define UNLIKELY(x) LIKELY_IS((x), 0) From 2b3a309245ad220e8e9ebc357b49f99d819da604 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 9 Jan 2017 19:54:17 -0800 Subject: [PATCH 0334/2505] PRIu64 shouldn't be used by scanf(); use SCNu64 instead --- common/lwan-request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index c28c8f900..bad2bdc02 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -627,7 +627,7 @@ parse_range(lwan_request_t *request, struct request_parser_helper *helper) range += sizeof("bytes=") - 1; off_t from, to; - if (sscanf(range, "%"SCNu64"-%"PRIu64, &from, &to) == 2) { + if (sscanf(range, "%"SCNu64"-%"SCNu64, &from, &to) == 2) { request->header.range.from = from; request->header.range.to = to; } else if (sscanf(range, "-%"SCNu64, &to) == 1) { From a1841a576ccd975dfc2cffc4ebaf76aaf39f16e2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 5 Jan 2017 08:22:22 -0800 Subject: [PATCH 0335/2505] Plug (harmless) memory leak to keep clang static analyzer happy --- lwan/main.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lwan/main.c b/lwan/main.c index ccfead2b5..4f096ea81 100644 --- a/lwan/main.c +++ b/lwan/main.c @@ -105,6 +105,7 @@ main(int argc, char *argv[]) lwan_t l; lwan_config_t c; char root[PATH_MAX]; + int ret = EXIT_SUCCESS; if (!getcwd(root, PATH_MAX)) return 1; @@ -130,11 +131,16 @@ main(int argc, char *argv[]) lwan_init(&l); break; case ARGS_FAILED: - return EXIT_FAILURE; + ret = EXIT_FAILURE; + goto out; } lwan_main_loop(&l); lwan_shutdown(&l); - return EXIT_SUCCESS; +out: + free(c.listener); + free(c.config_file_path); + + return ret; } From 225e3ebaac1135d0acbdbe80f7967122070cc1bf Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 12 Jan 2017 22:44:17 -0800 Subject: [PATCH 0336/2505] Revert all changes in lwan-coro since 7171a6 For some reason I couldn't identify, the redirect module when used with Lua scripts are crashing. The crash makes absolutely no sense (null pointer dereference within sprintf(), while Lua tries to convert an integer to string -- and this happens both with LuaJit and Lua.org). I've tried a bunch of different scenarios to fix this, but nothing worked. Reverting fixes the problem, but makes me very uneasy since the changes are supposed to be correct. --- common/lwan-coro.c | 92 +++++++++++++++++++++++----------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/common/lwan-coro.c b/common/lwan-coro.c index 85cee7265..1a085f909 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -34,12 +34,12 @@ #endif #ifdef __GLIBC__ -#define CORO_STACK ((3 * (PTHREAD_STACK_MIN)) / 2) +#define CORO_STACK_MIN ((3 * (PTHREAD_STACK_MIN)) / 2) #else -#define CORO_STACK (5 * (PTHREAD_STACK_MIN)) +#define CORO_STACK_MIN (5 * (PTHREAD_STACK_MIN)) #endif -static_assert(DEFAULT_BUFFER_SIZE < (CORO_STACK + PTHREAD_STACK_MIN), +static_assert(DEFAULT_BUFFER_SIZE < (CORO_STACK_MIN + PTHREAD_STACK_MIN), "Request buffer fits inside coroutine stack"); typedef struct coro_defer_t_ coro_defer_t; @@ -47,6 +47,7 @@ typedef struct coro_defer_t_ coro_defer_t; typedef void (*defer_func)(); struct coro_defer_t_ { + coro_defer_t *next; defer_func func; void *data1; void *data2; @@ -57,15 +58,14 @@ struct coro_t_ { coro_context_t context; int yield_value; - void *data; - coro_defer_t defer[16]; - #if !defined(NDEBUG) && defined(USE_VALGRIND) unsigned int vg_stack_id; #endif - bool ended; - unsigned char stack[CORO_STACK]; + coro_defer_t *defer; + void *data; + + bool ended; }; static void coro_entry_point(coro_t *data, coro_function_t func); @@ -162,31 +162,23 @@ coro_entry_point(coro_t *coro, coro_function_t func) } static void -run_or_recurse(coro_defer_t *defer, coro_defer_t *last) +coro_run_deferred(coro_t *coro) { - if (defer == last || !defer->func) - return; + while (coro->defer) { + coro_defer_t *tmp = coro->defer; - run_or_recurse(defer + 1, last); - defer->func(defer->data1, defer->data2); - defer->func = NULL; -} + coro->defer = coro->defer->next; -static coro_defer_t *last_defer(coro_t *coro) -{ - return coro->defer + sizeof(coro->defer); -} - -static void -coro_run_deferred(coro_t *coro) -{ - /* Some uses require deferred statements are arranged in a stack. */ - run_or_recurse(coro->defer, last_defer(coro)); + tmp->func(tmp->data1, tmp->data2); + free(tmp); + } } void coro_reset(coro_t *coro, coro_function_t func, void *data) { + unsigned char *stack = (unsigned char *)(coro + 1); + coro->ended = false; coro->data = data; @@ -196,11 +188,10 @@ coro_reset(coro_t *coro, coro_function_t func, void *data) coro->context[6 /* RDI */] = (uintptr_t) coro; coro->context[7 /* RSI */] = (uintptr_t) func; coro->context[8 /* RIP */] = (uintptr_t) coro_entry_point; - uintptr_t stack = (uintptr_t) coro->stack + CORO_STACK; - coro->context[9 /* RSP */] = stack - (stack & 15); + coro->context[9 /* RSP */] = (uintptr_t) stack + CORO_STACK_MIN; #elif defined(__i386__) /* Align stack and make room for two arguments */ - uintptr_t stack = ((uintptr_t)(coro->stack + CORO_STACK - + stack = (unsigned char *)((uintptr_t)(stack + CORO_STACK_MIN - sizeof(uintptr_t) * 2) & 0xfffffff0); uintptr_t *argp = (uintptr_t *)stack; @@ -213,8 +204,8 @@ coro_reset(coro_t *coro, coro_function_t func, void *data) #else getcontext(&coro->context); - coro->context.uc_stack.ss_sp = coro->stack; - coro->context.uc_stack.ss_size = CORO_STACK; + coro->context.uc_stack.ss_sp = stack; + coro->context.uc_stack.ss_size = CORO_STACK_MIN; coro->context.uc_stack.ss_flags = 0; coro->context.uc_link = NULL; @@ -225,16 +216,17 @@ coro_reset(coro_t *coro, coro_function_t func, void *data) ALWAYS_INLINE coro_t * coro_new(coro_switcher_t *switcher, coro_function_t function, void *data) { - coro_t *coro = malloc(sizeof(*coro)); + coro_t *coro = malloc(sizeof(*coro) + CORO_STACK_MIN); if (!coro) return NULL; coro->switcher = switcher; - memset(coro->defer, 0, sizeof(coro->defer)); + coro->defer = NULL; coro_reset(coro, function, data); #if !defined(NDEBUG) && defined(USE_VALGRIND) - coro->vg_stack_id = VALGRIND_STACK_REGISTER(coro->stack, coro->stack + CORO_STACK); + char *stack = (char *)(coro + 1); + coro->vg_stack_id = VALGRIND_STACK_REGISTER(stack, stack + CORO_STACK_MIN); #endif return coro; @@ -308,20 +300,18 @@ coro_free(coro_t *coro) static void coro_defer_any(coro_t *coro, defer_func func, void *data1, void *data2) { - coro_defer_t *defer; - coro_defer_t *last = last_defer(coro); + coro_defer_t *defer = malloc(sizeof(*defer)); + if (UNLIKELY(!defer)) + return; assert(func); - for (defer = coro->defer; defer <= last && defer->func; defer++); - if (UNLIKELY(defer == last)) { - lwan_status_error("Coro %p exhausted space for deferred callback", coro); - return; - } - + /* Some uses require deferred statements are arranged in a stack. */ + defer->next = coro->defer; defer->func = func; defer->data1 = data1; defer->data2 = data2; + coro->defer = defer; } ALWAYS_INLINE void @@ -340,18 +330,28 @@ coro_defer2(coro_t *coro, void (*func)(void *data1, void *data2), void * coro_malloc_full(coro_t *coro, size_t size, void (*destroy_func)()) { - void *mem = malloc(size); - if (UNLIKELY(!mem)) + coro_defer_t *defer = malloc(sizeof(*defer) + size); + if (UNLIKELY(!defer)) return NULL; - coro_defer(coro, CORO_DEFER(destroy_func), mem); - return mem; + defer->next = coro->defer; + defer->func = destroy_func; + defer->data1 = defer + 1; + defer->data2 = NULL; + + coro->defer = defer; + + return defer + 1; +} + +static void nothing() +{ } inline void * coro_malloc(coro_t *coro, size_t size) { - return coro_malloc_full(coro, size, free); + return coro_malloc_full(coro, size, nothing); } char * From 3b8a4847b86e98ba9fb895c624538b23bdb590ab Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 14 Jan 2017 08:11:48 -0800 Subject: [PATCH 0337/2505] Use a dynamically-allocated deferred callback array when the coroutine is created. This array has space for 16 deferred calbacks, which is more than pentiful for all the usecases in Lwan; more could be added later using the coro_malloc_full() primitive, if necessary. Ideally, this would be an array embedded within coro_t itself. However, for some unknown reason, the Rewrite module crashes while expanding with a Lua script. I'll eventualy find the reason and reduce the coroutine creation to only a single malloc() call, but for now this should use slightly less memory and amortize the dynamic allocation cost as the number of deferred callbacks grows. --- common/lwan-coro.c | 70 +++++++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/common/lwan-coro.c b/common/lwan-coro.c index 1a085f909..2397eee04 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -42,12 +42,13 @@ static_assert(DEFAULT_BUFFER_SIZE < (CORO_STACK_MIN + PTHREAD_STACK_MIN), "Request buffer fits inside coroutine stack"); +#define DEFER_MAX 16 + typedef struct coro_defer_t_ coro_defer_t; typedef void (*defer_func)(); struct coro_defer_t_ { - coro_defer_t *next; defer_func func; void *data1; void *data2; @@ -162,16 +163,21 @@ coro_entry_point(coro_t *coro, coro_function_t func) } static void -coro_run_deferred(coro_t *coro) +run_deferred(coro_defer_t *defer, coro_defer_t *last) { - while (coro->defer) { - coro_defer_t *tmp = coro->defer; + if (!defer->func || defer == last) + return; - coro->defer = coro->defer->next; + run_deferred(defer + 1, last); - tmp->func(tmp->data1, tmp->data2); - free(tmp); - } + defer->func(defer->data1, defer->data2); + defer->func = NULL; +} + +static void +coro_run_deferred(coro_t *coro) +{ + run_deferred(coro->defer, coro->defer + DEFER_MAX); } void @@ -220,8 +226,13 @@ coro_new(coro_switcher_t *switcher, coro_function_t function, void *data) if (!coro) return NULL; + coro->defer = calloc(DEFER_MAX, sizeof(coro_defer_t)); + if (UNLIKELY(!coro->defer)) { + free(coro); + return NULL; + } + coro->switcher = switcher; - coro->defer = NULL; coro_reset(coro, function, data); #if !defined(NDEBUG) && defined(USE_VALGRIND) @@ -294,24 +305,29 @@ coro_free(coro_t *coro) VALGRIND_STACK_DEREGISTER(coro->vg_stack_id); #endif coro_run_deferred(coro); + free(coro->defer); free(coro); } static void coro_defer_any(coro_t *coro, defer_func func, void *data1, void *data2) { - coro_defer_t *defer = malloc(sizeof(*defer)); - if (UNLIKELY(!defer)) - return; + const coro_defer_t *last = coro->defer + DEFER_MAX; assert(func); /* Some uses require deferred statements are arranged in a stack. */ - defer->next = coro->defer; - defer->func = func; - defer->data1 = data1; - defer->data2 = data2; - coro->defer = defer; + + for (coro_defer_t *defer = coro->defer; defer < last; defer++) { + if (!defer->func) { + defer->func = func; + defer->data1 = data1; + defer->data2 = data2; + return; + } + } + + lwan_status_error("Coroutine %p ran out of deferred callback slots", coro); } ALWAYS_INLINE void @@ -330,28 +346,18 @@ coro_defer2(coro_t *coro, void (*func)(void *data1, void *data2), void * coro_malloc_full(coro_t *coro, size_t size, void (*destroy_func)()) { - coro_defer_t *defer = malloc(sizeof(*defer) + size); - if (UNLIKELY(!defer)) - return NULL; - - defer->next = coro->defer; - defer->func = destroy_func; - defer->data1 = defer + 1; - defer->data2 = NULL; + void *ptr = malloc(size); - coro->defer = defer; + if (LIKELY(ptr)) + coro_defer(coro, destroy_func, ptr); - return defer + 1; -} - -static void nothing() -{ + return ptr; } inline void * coro_malloc(coro_t *coro, size_t size) { - return coro_malloc_full(coro, size, nothing); + return coro_malloc_full(coro, size, free); } char * From 452cbbdcbc4a261f1edf2e83603b9253310eae54 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 16 Jan 2017 18:08:12 -0800 Subject: [PATCH 0338/2505] Always build with -fPIC Should make it build on ARM as well. --- common/CMakeLists.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 0354b4ac6..49487b353 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -74,9 +74,7 @@ endif () add_library(lwan-static STATIC ${SOURCES}) set_target_properties(lwan-static PROPERTIES OUTPUT_NAME lwan CLEAN_DIRECT_OUTPUT 1) -if (CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64") - set_target_properties(lwan-static PROPERTIES COMPILE_FLAGS "-fPIC") -endif() +set_target_properties(lwan-static PROPERTIES COMPILE_FLAGS "-fPIC") # Can't call add_library() without source files. Create an empty .c file, # then link with the static library just recently built. From 543b9b62ea8e8897d17a9fe27d7024b0c3901959 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 16 Jan 2017 22:05:53 -0800 Subject: [PATCH 0339/2505] Use setresgid()/setresuid() to set saved GID/UID as well Patch is quite large as there's a wrapper for OS X as well. It's not possible to set the saved GID/UID on OS X, but it's possible to query it -- so do that. The implementation for idpriv_drop() found in GNULib has this comment, reproduced here: On systems which have setresuid(), we use it instead of setreuid(), because <1> says about setreuid(): "The rule by which the saved uid id is modified is complicated." Similarly, <2> says about setreuid(): "What exactly happens to the saved UID when this is used seems to vary a lot." <1> http://www.usenix.org/events/sec02/full_papers/chen/chen.pdf <2> http://unixpapa.com/incnote/setuid.html --- common/lwan-straitjacket.c | 19 ++++++++++--- common/missing.c | 55 ++++++++++++++++++++++++++++++++++++++ common/missing/unistd.h | 11 ++++++++ 3 files changed, 82 insertions(+), 3 deletions(-) diff --git a/common/lwan-straitjacket.c b/common/lwan-straitjacket.c index fe8be9f2f..90fd16fa0 100644 --- a/common/lwan-straitjacket.c +++ b/common/lwan-straitjacket.c @@ -73,10 +73,13 @@ static bool get_user_uid_gid(const char *user, uid_t *uid, gid_t *gid) static bool switch_to_user(uid_t uid, gid_t gid, const char *username) { + uid_t ruid, euid, suid; + gid_t rgid, egid, sgid; + lwan_status_info("Dropping privileges to UID %d, GID %d (%s)", uid, gid, username); - if (setgid(gid) < 0) + if (setresgid(gid, gid, gid) < 0) return false; #if defined(__APPLE__) if (initgroups(username, (int)gid) < 0) @@ -85,10 +88,20 @@ static bool switch_to_user(uid_t uid, gid_t gid, const char *username) if (initgroups(username, gid) < 0) return false; #endif - if (setuid(uid) < 0) + if (setresuid(uid, uid, uid) < 0) + return false; + + if (getresuid(&ruid, &euid, &suid) < 0) + return false; + if (ruid != euid || euid != suid || suid != uid) + return false; + + if (getresgid(&rgid, &egid, &sgid) < 0) + return false; + if (rgid != egid || egid != sgid || sgid != gid) return false; - return getegid() == gid && geteuid() == uid; + return true; } void lwan_straitjacket_enforce(config_t *c, config_line_t *l) diff --git a/common/missing.c b/common/missing.c index 20d296781..eb6512a92 100644 --- a/common/missing.c +++ b/common/missing.c @@ -380,3 +380,58 @@ gettid(void) return (long)pthread_self(); } #endif + +#if defined(__APPLE__) +/* NOTE: Although saved UID/GID cannot be set using sysctl(), for the use + * case in Lwan, it's possible to obtain the value and check if they're the + * ones expected -- and abort if it's not. Should be good enough for a + * wrapper like this. */ + +#include + +int +getresuid(uid_t *ruid, uid_t *euid, uid_t *suid) +{ + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid() }; + struct kinfo_proc kp; + size_t len = sizeof(kp); + + *ruid = getuid(); + *euid = geteuid(); + + if (sysctl(mib, N_ELEMENTS(mib), &kp, &len, NULL, 0) < 0) + return -1; + *suid = kp.kp_eproc.e_pcred.p_svuid; + + return 0; +} + +int +setresuid(uid_t ruid, uid_t euid, uid_t suid __attribute__((unused))) +{ + return setreuid(ruid, euid); +} + +int +setresgid(gid_t rgid, gid_t egid, gid_t sgid __attribute__((unused))) +{ + return setregid(rgid, egid); +} + +int +getresgid(gid_t *rgid, gid_t *egid, gid_t *sgid) +{ + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid() }; + struct kinfo_proc kp; + size_t len = sizeof(kp); + + *rgid = getgid(); + *egid = getegid(); + + if (sysctl(mib, N_ELEMENTS(mib), &kp, &len, NULL, 0) < 0) + return -1; + *sgid = kp.kp_eproc.e_pcred.p_svgid; + + return 0; +} +#endif diff --git a/common/missing/unistd.h b/common/missing/unistd.h index 9622cd3a8..385b28593 100644 --- a/common/missing/unistd.h +++ b/common/missing/unistd.h @@ -26,4 +26,15 @@ int pipe2(int pipefd[2], int flags); #endif +#if defined(__APPLE__) +int setresuid(uid_t ruid, uid_t euid, uid_t suid) + __attribute__((warn_unused_result)); +int setresgid(gid_t rgid, gid_t egid, gid_t sgid) + __attribute__((warn_unused_result)); +int getresuid(uid_t *ruid, uid_t *euid, uid_t *suid) + __attribute__((warn_unused_result)); +int getresgid(gid_t *rgid, gid_t *egid, gid_t *sgid) + __attribute__((warn_unused_result)); +#endif + #endif /* MISSING_UNISTD_H */ From 704902d69e3a7d990dd5d20174d411dcda50c83c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 17 Jan 2017 07:58:55 -0800 Subject: [PATCH 0340/2505] Add badges for ARM buildbots --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index bcbf8e902..91cc3d92d 100644 --- a/README.md +++ b/README.md @@ -240,8 +240,9 @@ Not really third-party, but alas: Build status ------------ -| Platform | Release | Debug | Static Analysis | Tests | -|----------|---------|-------|-----------------|------------| -| Linux | ![release](https://shield.lwan.ws/img/gycKbr/release "Release") | ![debug](https://shield.lwan.ws/img/gycKbr/debug "Debug") | ![static-analysis](https://shield.lwan.ws/img/gycKbr/clang-analyze "Static Analysis") ![coverity](https://scan.coverity.com/projects/375/badge.svg) [Report history](https://buildbot.lwan.ws/sa/) | ![tests](https://shield.lwan.ws/img/gycKbr/unit-tests "Test") | -| FreeBSD | ![freebsd-release](https://shield.lwan.ws/img/gycKbr/release-freebsd "Release FreeBSD") | ![freebsd-debug](https://shield.lwan.ws/img/gycKbr/debug-freebsd "Debug FreeBSD") | | | -| OS X | ![osx-release](https://shield.lwan.ws/img/gycKbr/release-yosemite "Release OS X") | ![osx-debug](https://shield.lwan.ws/img/gycKbr/debug-yosemite "Debug OS X") | | | +| OS | Arch | Release | Debug | Static Analysis | Tests | +|---------|--------|---------|-------|-----------------|------------| +| Linux | x86_64 | ![release](https://shield.lwan.ws/img/gycKbr/release "Release") | ![debug](https://shield.lwan.ws/img/gycKbr/debug "Debug") | ![static-analysis](https://shield.lwan.ws/img/gycKbr/clang-analyze "Static Analysis") ![coverity](https://scan.coverity.com/projects/375/badge.svg) [Report history](https://buildbot.lwan.ws/sa/) | ![tests](https://shield.lwan.ws/img/gycKbr/unit-tests "Test") | +| Linux | armv7 | ![release-arm](https://shield.lwan.ws/img/gycKbr/release-arm "Release") | ![debug-arm](https://shield.lwan.ws/img/gycKbr/debug-arm "Debug") | | | +| FreeBSD | x86_64 | ![freebsd-release](https://shield.lwan.ws/img/gycKbr/release-freebsd "Release FreeBSD") | ![freebsd-debug](https://shield.lwan.ws/img/gycKbr/debug-freebsd "Debug FreeBSD") | | | +| OS X | x86_64 | ![osx-release](https://shield.lwan.ws/img/gycKbr/release-yosemite "Release OS X") | ![osx-debug](https://shield.lwan.ws/img/gycKbr/debug-yosemite "Debug OS X") | | | From a4f9287f3481093b99129c8eebee0843fd596a79 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 17 Jan 2017 18:51:37 -0800 Subject: [PATCH 0341/2505] Simplify slightly the OS X implementation of getres[ug]id() --- common/missing.c | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/common/missing.c b/common/missing.c index eb6512a92..2db252e6e 100644 --- a/common/missing.c +++ b/common/missing.c @@ -389,21 +389,29 @@ gettid(void) #include +static int +get_current_proc_info(struct kinfo_proc *kp) +{ + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid() }; + size_t len = sizeof(kp); + + return sysctl(mib, N_ELEMENTS(mib), kp, &len, NULL, 0); +} + int getresuid(uid_t *ruid, uid_t *euid, uid_t *suid) { - int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid() }; struct kinfo_proc kp; - size_t len = sizeof(kp); - *ruid = getuid(); - *euid = geteuid(); + if (!get_current_proc_info(&kp)) { + *ruid = getuid(); + *euid = geteuid(); + *suid = kp.kp_eproc.e_pcred.p_svuid; - if (sysctl(mib, N_ELEMENTS(mib), &kp, &len, NULL, 0) < 0) - return -1; - *suid = kp.kp_eproc.e_pcred.p_svuid; + return 0; + } - return 0; + return -1; } int @@ -421,17 +429,16 @@ setresgid(gid_t rgid, gid_t egid, gid_t sgid __attribute__((unused))) int getresgid(gid_t *rgid, gid_t *egid, gid_t *sgid) { - int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid() }; struct kinfo_proc kp; - size_t len = sizeof(kp); - *rgid = getgid(); - *egid = getegid(); + if (!get_current_proc_info(&kp)) { + *rgid = getgid(); + *egid = getegid(); + *sgid = kp.kp_eproc.e_pcred.p_svgid; - if (sysctl(mib, N_ELEMENTS(mib), &kp, &len, NULL, 0) < 0) - return -1; - *sgid = kp.kp_eproc.e_pcred.p_svgid; + return 0; + } - return 0; + return -1; } #endif From dfbbdac0c4b4ec4bc91cf53059f569e5e68667e4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 17 Jan 2017 18:44:03 -0800 Subject: [PATCH 0342/2505] Before calling chroot(), ensure no directories are open This works only on Linux, and even then, it is racy and borderline useless from a security perspective. It is more useful, however, in the cases where people add the "straitjack" section in the configuration file after declaring an instance of the file serving module, and the directory is outside the chroot. --- common/lwan-straitjacket.c | 71 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/common/lwan-straitjacket.c b/common/lwan-straitjacket.c index 90fd16fa0..085b01b70 100644 --- a/common/lwan-straitjacket.c +++ b/common/lwan-straitjacket.c @@ -18,12 +18,14 @@ */ #define _GNU_SOURCE +#include #include #include #include #include #include #include +#include #include #include "lwan-private.h" @@ -104,6 +106,74 @@ static bool switch_to_user(uid_t uid, gid_t gid, const char *username) return true; } +#ifdef __linux__ +static void abort_on_open_directories(void) +{ + /* This is racy, of course, but right now I see no other way of ensuring + * that there are no file descriptors to directories open. */ + DIR *dir = opendir("/proc/self/fd"); + struct dirent *ent; + char own_fd[3 * sizeof(int)]; + int ret; + + if (!dir) { + lwan_status_critical_perror( + "Could not determine if there are open directory fds"); + } + + ret = snprintf(own_fd, sizeof(own_fd), "%d", dirfd(dir)); + if (ret < 0 || ret >= (int)sizeof(own_fd)) { + lwan_status_critical("Could not get descriptor of /proc/self/fd"); + } + + while ((ent = readdir(dir))) { + char path[PATH_MAX]; + struct stat st; + ssize_t len; + + if (!strcmp(ent->d_name, own_fd)) + continue; + if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) + continue; + + len = readlinkat(dirfd(dir), ent->d_name, path, sizeof(path)); + if (len < 0) { + lwan_status_critical_perror( + "Could not get information about fd %s", ent->d_name); + } + path[len] = '\0'; + + if (path[0] != '/') { + /* readlink() there will point to the realpath() of a file, so + * if it's on a filesystem, it starts with '/'. Sockets, for + * instance, begin with "socket:" instead... so no need for + * stat(). */ + continue; + } + + if (stat(path, &st) < 0) { + lwan_status_critical_perror( + "Could not get information about open file: %s", path); + } + + if (S_ISDIR(st.st_mode)) { + closedir(dir); + + lwan_status_critical( + "The directory '%s' is open (fd %s), can't chroot", + path, ent->d_name); + return; + } + } + + closedir(dir); +} +#else +static bool abort_on_open_directories(void) +{ +} +#endif + void lwan_straitjacket_enforce(config_t *c, config_line_t *l) { char *user_name = NULL; @@ -139,6 +209,7 @@ void lwan_straitjacket_enforce(config_t *c, config_line_t *l) } if (chroot_path) { + abort_on_open_directories(); if (chroot(chroot_path) < 0) { lwan_status_critical_perror("Could not chroot() to %s", chroot_path); From a258044b96fbebbf4a3e5edefa2dda5878b1f443 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 18 Jan 2017 07:57:26 -0800 Subject: [PATCH 0343/2505] PkgConfig configuration file should have "-lm" as libraries CMake is smart enough to understand "m" as the math lib when it is generating the Makefiles, but I was not smart enough to generate the .pc file with the correct contents. Closes #182. --- common/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 49487b353..8143e788f 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -105,7 +105,7 @@ add_custom_target(generate_mime_types_table DEPENDS ${CMAKE_BINARY_DIR}/mime-typ add_dependencies(lwan-static generate_mime_types_table) include_directories(${CMAKE_BINARY_DIR}) -set(ADDITIONAL_LIBRARIES ${ADDITIONAL_LIBRARIES} m PARENT_SCOPE) +set(ADDITIONAL_LIBRARIES ${ADDITIONAL_LIBRARIES} -lm PARENT_SCOPE) install(TARGETS lwan-static lwan-shared DESTINATION "lib" From da4eb82f329e267f727589db42bf321991ba8c83 Mon Sep 17 00:00:00 2001 From: Mario Fischer Date: Mon, 31 Oct 2016 18:47:22 +0100 Subject: [PATCH 0344/2505] Add request method OPTIONS --- common/lwan-request.c | 4 ++++ common/lwan-response.c | 6 +++++- common/lwan.h | 19 ++++++++++--------- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index bad2bdc02..c0326bb31 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -268,6 +268,7 @@ identify_http_method(lwan_request_t *request, char *buffer) HTTP_STR_GET = MULTICHAR_CONSTANT('G','E','T',' '), HTTP_STR_HEAD = MULTICHAR_CONSTANT('H','E','A','D'), HTTP_STR_POST = MULTICHAR_CONSTANT('P','O','S','T'), + HTTP_STR_OPTIONS = MULTICHAR_CONSTANT('O','P','T','I'), }; STRING_SWITCH(buffer) { @@ -280,6 +281,9 @@ identify_http_method(lwan_request_t *request, char *buffer) case HTTP_STR_POST: request->flags |= REQUEST_METHOD_POST; return buffer + sizeof("POST ") - 1; + case HTTP_STR_OPTIONS: + request->flags |= REQUEST_METHOD_OPTIONS; + return buffer + sizeof("OPTIONS ") - 1; } return NULL; diff --git a/common/lwan-response.c b/common/lwan-response.c index 66fac55e8..5f4bf7964 100644 --- a/common/lwan-response.c +++ b/common/lwan-response.c @@ -105,6 +105,8 @@ get_request_method(lwan_request_t *request) return "HEAD"; if (request->flags & REQUEST_METHOD_POST) return "POST"; + if (request->flags & REQUEST_METHOD_OPTIONS) + return "OPTIONS"; return "UNKNOWN"; } @@ -173,7 +175,7 @@ lwan_response(lwan_request_t *request, lwan_http_status_t status) return; } - if (request->flags & REQUEST_METHOD_HEAD) { + if (request->flags & (REQUEST_METHOD_HEAD | REQUEST_METHOD_OPTIONS)) { lwan_write(request, headers, header_len); return; } @@ -260,6 +262,8 @@ lwan_prepare_response_header(lwan_request_t *request, lwan_http_status_t status, APPEND_CONSTANT("\r\nContent-Length: "); if (request->response.stream.callback) APPEND_UINT(request->response.content_length); + else if (request->flags & REQUEST_METHOD_OPTIONS) + APPEND_UINT(0); else APPEND_UINT(strbuf_get_length(request->response.buffer)); } diff --git a/common/lwan.h b/common/lwan.h index f8cb6715c..544b5f2fe 100644 --- a/common/lwan.h +++ b/common/lwan.h @@ -178,15 +178,16 @@ typedef enum { REQUEST_METHOD_GET = 1<<0, REQUEST_METHOD_HEAD = 1<<1, REQUEST_METHOD_POST = 1<<2, - REQUEST_ACCEPT_DEFLATE = 1<<3, - REQUEST_ACCEPT_GZIP = 1<<4, - REQUEST_IS_HTTP_1_0 = 1<<5, - RESPONSE_SENT_HEADERS = 1<<6, - RESPONSE_CHUNKED_ENCODING = 1<<7, - RESPONSE_NO_CONTENT_LENGTH = 1<<8, - RESPONSE_URL_REWRITTEN = 1<<9, - REQUEST_ALLOW_PROXY_REQS = 1<<10, - REQUEST_PROXIED = 1<<11 + REQUEST_METHOD_OPTIONS = 1<<3, + REQUEST_ACCEPT_DEFLATE = 1<<4, + REQUEST_ACCEPT_GZIP = 1<<5, + REQUEST_IS_HTTP_1_0 = 1<<6, + RESPONSE_SENT_HEADERS = 1<<7, + RESPONSE_CHUNKED_ENCODING = 1<<8, + RESPONSE_NO_CONTENT_LENGTH = 1<<9, + RESPONSE_URL_REWRITTEN = 1<<10, + REQUEST_ALLOW_PROXY_REQS = 1<<11, + REQUEST_PROXIED = 1<<12 } lwan_request_flags_t; typedef enum { From edae838689b14e18da95b61683689ba30d72346f Mon Sep 17 00:00:00 2001 From: Mario Fischer Date: Mon, 31 Oct 2016 18:49:31 +0100 Subject: [PATCH 0345/2505] Add option "allow_cors" to configuration If set to "yes" cross-domain requests are allowed by printing additional header information. --- common/lwan-response.c | 7 +++++++ common/lwan-thread.c | 10 +++++++--- common/lwan.c | 4 ++++ common/lwan.h | 4 +++- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/common/lwan-response.c b/common/lwan-response.c index 5f4bf7964..acbadea45 100644 --- a/common/lwan-response.c +++ b/common/lwan-response.c @@ -317,6 +317,13 @@ lwan_prepare_response_header(lwan_request_t *request, lwan_http_status_t status, APPEND_STRING_LEN(request->conn->thread->date.expires, 29); } + if (request->flags & REQUEST_ALLOW_CORS) { + APPEND_CONSTANT("\r\nAccess-Control-Allow-Origin: *" + "\r\nAccess-Control-Allow-Methods: GET, POST, OPTIONS" + "\r\nAccess-Control-Allow-Credentials: true" + "\r\nAccess-Control-Allow-Headers: Origin, Accept, Content-Type"); + } + APPEND_CONSTANT("\r\nServer: lwan\r\n\r\n\0"); return (size_t)(p_headers - headers - 1); diff --git a/common/lwan-thread.c b/common/lwan-thread.c index a855fd939..df322bdfd 100644 --- a/common/lwan-thread.c +++ b/common/lwan-thread.c @@ -142,7 +142,7 @@ process_request_coro(coro_t *coro) /* NOTE: This function should not return; coro_yield should be used * instead. This ensures the storage for `strbuf` is alive when the * coroutine ends and strbuf_free() is called. */ - const lwan_request_flags_t flags_filter = REQUEST_PROXIED; + const lwan_request_flags_t flags_filter = (REQUEST_PROXIED | REQUEST_ALLOW_CORS); strbuf_t strbuf; lwan_connection_t *conn = coro_get_data(coro); lwan_t *lwan = conn->thread->lwan; @@ -153,8 +153,7 @@ process_request_coro(coro_t *coro) .len = 0 }; char *next_request = NULL; - lwan_request_flags_t flags = - lwan->config.proxy_protocol ? REQUEST_ALLOW_PROXY_REQS : 0; + lwan_request_flags_t flags = 0; lwan_proxy_t proxy; if (UNLIKELY(!strbuf_init(&strbuf))) { @@ -163,6 +162,11 @@ process_request_coro(coro_t *coro) } coro_defer(coro, CORO_DEFER(strbuf_free), &strbuf); + if (lwan->config.proxy_protocol) + flags |= REQUEST_ALLOW_PROXY_REQS; + if (lwan->config.allow_cors) + flags |= REQUEST_ALLOW_CORS; + while (true) { lwan_request_t request = { .conn = conn, diff --git a/common/lwan.c b/common/lwan.c index b24f9077a..39604eca4 100644 --- a/common/lwan.c +++ b/common/lwan.c @@ -50,6 +50,7 @@ static const lwan_config_t default_config = { .quiet = false, .reuse_port = false, .proxy_protocol = false, + .allow_cors = false, .expires = 1 * ONE_WEEK, .n_threads = 0, .max_post_data_size = 10 * DEFAULT_BUFFER_SIZE @@ -401,6 +402,9 @@ static bool setup_from_config(lwan_t *lwan, const char *path) } else if (streq(line.line.key, "proxy_protocol")) { lwan->config.proxy_protocol = parse_bool(line.line.value, default_config.proxy_protocol); + } else if (streq(line.line.key, "allow_cors")) { + lwan->config.allow_cors = parse_bool(line.line.value, + default_config.allow_cors); } else if (streq(line.line.key, "expires")) { lwan->config.expires = parse_time_period(line.line.value, default_config.expires); diff --git a/common/lwan.h b/common/lwan.h index 544b5f2fe..d1cdfe72a 100644 --- a/common/lwan.h +++ b/common/lwan.h @@ -187,7 +187,8 @@ typedef enum { RESPONSE_NO_CONTENT_LENGTH = 1<<9, RESPONSE_URL_REWRITTEN = 1<<10, REQUEST_ALLOW_PROXY_REQS = 1<<11, - REQUEST_PROXIED = 1<<12 + REQUEST_PROXIED = 1<<12, + REQUEST_ALLOW_CORS = 1<<13, } lwan_request_flags_t; typedef enum { @@ -320,6 +321,7 @@ struct lwan_config_t_ { bool quiet; bool reuse_port; bool proxy_protocol; + bool allow_cors; }; struct lwan_t_ { From 15b009d6c1ae58080cc7123215ffc89a5b39a953 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 19 Jan 2017 07:59:53 -0800 Subject: [PATCH 0346/2505] Size of `struct kinfo_proc` should be passed to sysctl() I've made a rookie mistake when refactoring this function. Initialize len with sizeof(*kp) instead. --- common/missing.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/missing.c b/common/missing.c index 2db252e6e..711727973 100644 --- a/common/missing.c +++ b/common/missing.c @@ -393,7 +393,7 @@ static int get_current_proc_info(struct kinfo_proc *kp) { int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid() }; - size_t len = sizeof(kp); + size_t len = sizeof(*kp); return sysctl(mib, N_ELEMENTS(mib), kp, &len, NULL, 0); } From 28d6e2afe2488bb225578c465f6d3afb243ae828 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 23 Jan 2017 19:18:32 -0800 Subject: [PATCH 0347/2505] Use unnamed fields in configuration line struct Makes for some slightly cleaner code (line.key instead of line.line.key). --- common/lwan-config.c | 12 +++--- common/lwan-config.h | 4 +- common/lwan-http-authorize.c | 6 +-- common/lwan-rewrite.c | 24 +++++------ common/lwan-straitjacket.c | 10 ++--- common/lwan.c | 82 ++++++++++++++++++------------------ 6 files changed, 69 insertions(+), 69 deletions(-) diff --git a/common/lwan-config.c b/common/lwan-config.c index 83fb4a7a7..4bd828702 100644 --- a/common/lwan-config.c +++ b/common/lwan-config.c @@ -171,8 +171,8 @@ static bool parse_section(char *line, config_line_t *l, char *bracket) name = remove_trailing_spaces(remove_leading_spaces(line)); param = remove_trailing_spaces(remove_leading_spaces(space + 1)); - l->section.name = name; - l->section.param = param; + l->name = name; + l->param = param; l->type = CONFIG_LINE_TYPE_SECTION; return true; @@ -219,7 +219,7 @@ static bool parse_multiline(config_t *c, config_line_t *l) while (config_fgets(c, buffer, sizeof(buffer))) { char *tmp = remove_trailing_spaces(remove_leading_spaces(buffer)); if (streq(tmp, "'''")) { - l->line.value = strbuf_get_buffer(c->strbuf); + l->value = strbuf_get_buffer(c->strbuf); return true; } @@ -243,11 +243,11 @@ static bool parse_line(config_t *c, char *line, config_line_t *l, char *equal) line = remove_leading_spaces(line); line = remove_trailing_spaces(line); - l->line.key = replace_space_with_underscore(line); - l->line.value = remove_leading_spaces(equal + 1); + l->key = replace_space_with_underscore(line); + l->value = remove_leading_spaces(equal + 1); l->type = CONFIG_LINE_TYPE_LINE; - if (!streq(l->line.value, "'''")) + if (!streq(l->value, "'''")) return true; return parse_multiline(c, l); diff --git a/common/lwan-config.h b/common/lwan-config.h index fe9dc9b62..950f2a4ab 100644 --- a/common/lwan-config.h +++ b/common/lwan-config.h @@ -54,10 +54,10 @@ struct config_line_t_ { union { struct { char *name, *param; - } section; + }; struct { char *key, *value; - } line; + }; }; config_line_type_t type; char buffer[1024]; diff --git a/common/lwan-http-authorize.c b/common/lwan-http-authorize.c index 91ab18ef8..6d8ac3bb0 100644 --- a/common/lwan-http-authorize.c +++ b/common/lwan-http-authorize.c @@ -66,11 +66,11 @@ static struct cache_entry_t *create_realm_file( /* FIXME: Storing plain-text passwords in memory isn't a good idea. */ switch (l.type) { case CONFIG_LINE_TYPE_LINE: { - char *username = strdup(l.line.key); + char *username = strdup(l.key); if (!username) goto error; - char *password = strdup(l.line.value); + char *password = strdup(l.value); if (!password) { free(username); goto error; @@ -86,7 +86,7 @@ static struct cache_entry_t *create_realm_file( if (err == -EEXIST) { lwan_status_warning( "Username entry already exists, ignoring: \"%s\"", - l.line.key); + l.key); continue; } diff --git a/common/lwan-rewrite.c b/common/lwan-rewrite.c index 0dec4af13..fc1200fac 100644 --- a/common/lwan-rewrite.c +++ b/common/lwan-rewrite.c @@ -294,30 +294,30 @@ module_parse_conf_pattern(struct private_data *pd, config_t *config, config_line if (!pattern) goto out_no_free; - pattern->pattern = strdup(line->section.param); + pattern->pattern = strdup(line->param); if (!pattern->pattern) goto out; while (config_read_line(config, line)) { switch (line->type) { case CONFIG_LINE_TYPE_LINE: - if (streq(line->line.key, "redirect_to")) { - redirect_to = strdup(line->line.value); + if (streq(line->key, "redirect_to")) { + redirect_to = strdup(line->value); if (!redirect_to) goto out; - } else if (streq(line->line.key, "rewrite_as")) { - rewrite_as = strdup(line->line.value); + } else if (streq(line->key, "rewrite_as")) { + rewrite_as = strdup(line->value); if (!rewrite_as) goto out; - } else if (streq(line->line.key, "expand_with_lua")) { - expand_with_lua = parse_bool(line->line.value, false); + } else if (streq(line->key, "expand_with_lua")) { + expand_with_lua = parse_bool(line->value, false); } else { - config_error(config, "Unexpected key: %s", line->line.key); + config_error(config, "Unexpected key: %s", line->key); goto out; } break; case CONFIG_LINE_TYPE_SECTION: - config_error(config, "Unexpected section: %s", line->section.name); + config_error(config, "Unexpected section: %s", line->name); break; case CONFIG_LINE_TYPE_SECTION_END: if (redirect_to && rewrite_as) { @@ -368,13 +368,13 @@ module_parse_conf(void *data, config_t *config) while (config_read_line(config, &line)) { switch (line.type) { case CONFIG_LINE_TYPE_LINE: - config_error(config, "Unknown option: %s", line.line.key); + config_error(config, "Unknown option: %s", line.key); break; case CONFIG_LINE_TYPE_SECTION: - if (streq(line.section.name, "pattern")) { + if (streq(line.name, "pattern")) { module_parse_conf_pattern(pd, config, &line); } else { - config_error(config, "Unknown section: %s", line.section.name); + config_error(config, "Unknown section: %s", line.name); } break; case CONFIG_LINE_TYPE_SECTION_END: diff --git a/common/lwan-straitjacket.c b/common/lwan-straitjacket.c index 085b01b70..b1fc8529f 100644 --- a/common/lwan-straitjacket.c +++ b/common/lwan-straitjacket.c @@ -190,12 +190,12 @@ void lwan_straitjacket_enforce(config_t *c, config_line_t *l) switch (l->type) { case CONFIG_LINE_TYPE_LINE: /* TODO: limit_syscalls */ - if (streq(l->line.key, "user")) { - user_name = strdupa(l->line.value); - } else if (streq(l->line.key, "chroot")) { - chroot_path = strdupa(l->line.value); + if (streq(l->key, "user")) { + user_name = strdupa(l->value); + } else if (streq(l->key, "chroot")) { + chroot_path = strdupa(l->value); } else { - config_error(c, "Invalid key: %s", l->line.key); + config_error(c, "Invalid key: %s", l->key); return; } break; diff --git a/common/lwan.c b/common/lwan.c index 39604eca4..d175bf227 100644 --- a/common/lwan.c +++ b/common/lwan.c @@ -158,7 +158,7 @@ static lwan_url_map_t *add_url_map(lwan_trie_t *t, const char *prefix, const lwa static void parse_listener_prefix_authorization(config_t *c, config_line_t *l, lwan_url_map_t *url_map) { - if (!streq(l->section.param, "basic")) { + if (!streq(l->param, "basic")) { config_error(c, "Only basic authorization supported"); return; } @@ -168,17 +168,17 @@ static void parse_listener_prefix_authorization(config_t *c, while (config_read_line(c, l)) { switch (l->type) { case CONFIG_LINE_TYPE_LINE: - if (streq(l->line.key, "realm")) { + if (streq(l->key, "realm")) { free(url_map->authorization.realm); - url_map->authorization.realm = strdup(l->line.value); - } else if (streq(l->line.key, "password_file")) { + url_map->authorization.realm = strdup(l->value); + } else if (streq(l->key, "password_file")) { free(url_map->authorization.password_file); - url_map->authorization.password_file = strdup(l->line.value); + url_map->authorization.password_file = strdup(l->value); } break; case CONFIG_LINE_TYPE_SECTION: - config_error(c, "Unexpected section: %s", l->section.name); + config_error(c, "Unexpected section: %s", l->name); goto error; case CONFIG_LINE_TYPE_SECTION_END: @@ -206,7 +206,7 @@ static void parse_listener_prefix(config_t *c, config_line_t *l, lwan_t *lwan, lwan_url_map_t url_map = { }; struct hash *hash = hash_str_new(free, free); void *handler = NULL; - char *prefix = strdupa(l->line.value); + char *prefix = strdupa(l->value); config_t isolated = { }; if (!config_isolate_section(c, l, &isolated)) { @@ -217,29 +217,29 @@ static void parse_listener_prefix(config_t *c, config_line_t *l, lwan_t *lwan, while (config_read_line(c, l)) { switch (l->type) { case CONFIG_LINE_TYPE_LINE: - if (streq(l->line.key, "module")) { + if (streq(l->key, "module")) { if (module) { config_error(c, "Module already specified"); goto out; } - module = lwan_module_find(lwan, l->line.value); + module = lwan_module_find(lwan, l->value); if (!module) { - config_error(c, "Could not find module \"%s\"", l->line.value); + config_error(c, "Could not find module \"%s\"", l->value); goto out; } - } else if (streq(l->line.key, "handler")) { - handler = find_handler_symbol(l->line.value); + } else if (streq(l->key, "handler")) { + handler = find_handler_symbol(l->value); if (!handler) { - config_error(c, "Could not find handler \"%s\"", l->line.value); + config_error(c, "Could not find handler \"%s\"", l->value); goto out; } } else { - hash_add(hash, strdup(l->line.key), strdup(l->line.value)); + hash_add(hash, strdup(l->key), strdup(l->value)); } break; case CONFIG_LINE_TYPE_SECTION: - if (streq(l->section.name, "authorization")) { + if (streq(l->name, "authorization")) { parse_listener_prefix_authorization(c, l, &url_map); } else { if (!config_skip_section(c, l)) { @@ -322,7 +322,7 @@ void lwan_set_url_map(lwan_t *l, const lwan_url_map_t *map) static void parse_listener(config_t *c, config_line_t *l, lwan_t *lwan) { - lwan->config.listener = strdup(l->section.param); + lwan->config.listener = strdup(l->param); while (config_read_line(c, l)) { switch (l->type) { @@ -330,13 +330,13 @@ static void parse_listener(config_t *c, config_line_t *l, lwan_t *lwan) config_error(c, "Expecting prefix section"); return; case CONFIG_LINE_TYPE_SECTION: - if (streq(l->section.name, "prefix")) { + if (streq(l->name, "prefix")) { parse_listener_prefix(c, l, lwan, NULL); } else { - const lwan_module_t *module = lwan_module_find(lwan, l->section.name); + const lwan_module_t *module = lwan_module_find(lwan, l->name); if (!module) { config_error(c, "Invalid section name or module not found: %s", - l->section.name); + l->name); } else { parse_listener_prefix(c, l, lwan, module); } @@ -390,55 +390,55 @@ static bool setup_from_config(lwan_t *lwan, const char *path) while (config_read_line(&conf, &line)) { switch (line.type) { case CONFIG_LINE_TYPE_LINE: - if (streq(line.line.key, "keep_alive_timeout")) { - lwan->config.keep_alive_timeout = (unsigned short)parse_long(line.line.value, + if (streq(line.key, "keep_alive_timeout")) { + lwan->config.keep_alive_timeout = (unsigned short)parse_long(line.value, default_config.keep_alive_timeout); - } else if (streq(line.line.key, "quiet")) { - lwan->config.quiet = parse_bool(line.line.value, + } else if (streq(line.key, "quiet")) { + lwan->config.quiet = parse_bool(line.value, default_config.quiet); - } else if (streq(line.line.key, "reuse_port")) { - lwan->config.reuse_port = parse_bool(line.line.value, + } else if (streq(line.key, "reuse_port")) { + lwan->config.reuse_port = parse_bool(line.value, default_config.reuse_port); - } else if (streq(line.line.key, "proxy_protocol")) { - lwan->config.proxy_protocol = parse_bool(line.line.value, + } else if (streq(line.key, "proxy_protocol")) { + lwan->config.proxy_protocol = parse_bool(line.value, default_config.proxy_protocol); - } else if (streq(line.line.key, "allow_cors")) { - lwan->config.allow_cors = parse_bool(line.line.value, + } else if (streq(line.key, "allow_cors")) { + lwan->config.allow_cors = parse_bool(line.value, default_config.allow_cors); - } else if (streq(line.line.key, "expires")) { - lwan->config.expires = parse_time_period(line.line.value, + } else if (streq(line.key, "expires")) { + lwan->config.expires = parse_time_period(line.value, default_config.expires); - } else if (streq(line.line.key, "error_template")) { + } else if (streq(line.key, "error_template")) { free(lwan->config.error_template); - lwan->config.error_template = strdup(line.line.value); - } else if (streq(line.line.key, "threads")) { - long n_threads = parse_long(line.line.value, default_config.n_threads); + lwan->config.error_template = strdup(line.value); + } else if (streq(line.key, "threads")) { + long n_threads = parse_long(line.value, default_config.n_threads); if (n_threads < 0) config_error(&conf, "Invalid number of threads: %d", n_threads); lwan->config.n_threads = (unsigned short int)n_threads; - } else if (streq(line.line.key, "max_post_data_size")) { - long max_post_data_size = parse_long(line.line.value, (long)default_config.max_post_data_size); + } else if (streq(line.key, "max_post_data_size")) { + long max_post_data_size = parse_long(line.value, (long)default_config.max_post_data_size); if (max_post_data_size < 0) config_error(&conf, "Negative maximum post data size"); else if (max_post_data_size > 1<<20) config_error(&conf, "Maximum post data can't be over 1MiB"); lwan->config.max_post_data_size = (size_t)max_post_data_size; } else { - config_error(&conf, "Unknown config key: %s", line.line.key); + config_error(&conf, "Unknown config key: %s", line.key); } break; case CONFIG_LINE_TYPE_SECTION: - if (streq(line.section.name, "listener")) { + if (streq(line.name, "listener")) { if (!has_listener) { parse_listener(&conf, &line, lwan); has_listener = true; } else { config_error(&conf, "Only one listener supported"); } - } else if (streq(line.section.name, "straitjacket")) { + } else if (streq(line.name, "straitjacket")) { lwan_straitjacket_enforce(&conf, &line); } else { - config_error(&conf, "Unknown section type: %s", line.section.name); + config_error(&conf, "Unknown section type: %s", line.name); } break; case CONFIG_LINE_TYPE_SECTION_END: From 6b6254eb43371fcdce11f2140a23c44bf6f3f2d8 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 23 Jan 2017 23:05:38 -0800 Subject: [PATCH 0348/2505] Dynamically-grow deferred callback array --- CMakeLists.txt | 1 + common/CMakeLists.txt | 1 + common/lwan-array.c | 89 +++++++++++++++++++++++++++++++++++++++ common/lwan-array.h | 36 ++++++++++++++++ common/lwan-coro.c | 48 +++++++++------------ lwan-build-config.h.cmake | 1 + 6 files changed, 147 insertions(+), 29 deletions(-) create mode 100644 common/lwan-array.c create mode 100644 common/lwan-array.h diff --git a/CMakeLists.txt b/CMakeLists.txt index cd7df87e3..553f8f3b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,7 @@ set(ADDITIONAL_LIBRARIES ${ZLIB_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) check_c_source_compiles("int main(void) { __builtin_cpu_init(); }" HAVE_BUILTIN_CPU_INIT) check_c_source_compiles("int main(void) { __builtin_clzll(0); }" HAVE_BUILTIN_CLZLL) check_c_source_compiles("int main(void) { unsigned long long p; (void)__builtin_mul_overflow(0, 0, &p); }" HAVE_BUILTIN_MUL_OVERFLOW) +check_c_source_compiles("int main(void) { unsigned long long p; (void)__builtin_add_overflow(0, 0, &p); }" HAVE_BUILTIN_ADD_OVERFLOW) check_c_source_compiles("int main(void) { _Static_assert(1, \"\"); }" HAVE_STATIC_ASSERT) set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 8143e788f..90ca74fc4 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -3,6 +3,7 @@ set(SOURCES hash.c int-to-str.c list.c + lwan-array.c lwan.c lwan-cache.c lwan-config.c diff --git a/common/lwan-array.c b/common/lwan-array.c new file mode 100644 index 000000000..45daeb04a --- /dev/null +++ b/common/lwan-array.c @@ -0,0 +1,89 @@ +/* + * lwan - simple web server + * Copyright (c) 2017 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include + +#include "lwan.h" +#include "lwan-array.h" +#include "reallocarray.h" + +#define INCREMENT 16 + +int +lwan_array_init(struct lwan_array *a, size_t element_size) +{ + if (UNLIKELY(!a)) + return -EINVAL; + + a->base = NULL; + a->element_size = element_size; + a->elements = 0; + + return 0; +} + +int +lwan_array_reset(struct lwan_array *a) +{ + if (UNLIKELY(!a)) + return -EINVAL; + + free(a->base); + a->base = NULL; + a->elements = 0; + + return 0; +} + +#if !defined(HAVE_BUILTIN_ADD_OVERFLOW) +static inline bool add_overflow(size_t a, size_t b, size_t *out) +{ + if (UNLIKELY(a > 0 && b > SIZE_MAX - a)) + return true; + + *out = a + INCREMENT; + return false; +} +#else +#define add_overflow __builtin_add_overflow +#endif + +void * +lwan_array_append(struct lwan_array *a) +{ + if (!(a->elements % INCREMENT)) { + void *new_base; + size_t new_cap; + + if (UNLIKELY(add_overflow(a->elements, INCREMENT, &new_cap))) { + errno = EOVERFLOW; + return NULL; + } + + new_base = reallocarray(a->base, new_cap, a->element_size); + if (UNLIKELY(!new_base)) + return NULL; + + a->base = new_base; + } + + return ((unsigned char *)a->base) + (a->elements++) * a->element_size; +} diff --git a/common/lwan-array.h b/common/lwan-array.h new file mode 100644 index 000000000..241de6edd --- /dev/null +++ b/common/lwan-array.h @@ -0,0 +1,36 @@ +/* + * lwan - simple web server + * Copyright (c) 2017 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include + +struct lwan_array { + void *base; + size_t element_size; + size_t elements; +}; + +#define ARRAY_INITIALIZER(element_size_) \ + { .base = NULL, .element_size = (element_size_), .elements = 0 } + +int lwan_array_init(struct lwan_array *a, size_t element_size); +int lwan_array_reset(struct lwan_array *a); +void *lwan_array_append(struct lwan_array *a); + diff --git a/common/lwan-coro.c b/common/lwan-coro.c index 2397eee04..304c6f39c 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -27,6 +27,7 @@ #include "lwan-private.h" +#include "lwan-array.h" #include "lwan-coro.h" #ifdef USE_VALGRIND @@ -42,8 +43,6 @@ static_assert(DEFAULT_BUFFER_SIZE < (CORO_STACK_MIN + PTHREAD_STACK_MIN), "Request buffer fits inside coroutine stack"); -#define DEFER_MAX 16 - typedef struct coro_defer_t_ coro_defer_t; typedef void (*defer_func)(); @@ -63,7 +62,7 @@ struct coro_t_ { unsigned int vg_stack_id; #endif - coro_defer_t *defer; + struct lwan_array defer; void *data; bool ended; @@ -163,21 +162,15 @@ coro_entry_point(coro_t *coro, coro_function_t func) } static void -run_deferred(coro_defer_t *defer, coro_defer_t *last) +coro_run_deferred(coro_t *coro) { - if (!defer->func || defer == last) - return; + coro_defer_t *defers = coro->defer.base; - run_deferred(defer + 1, last); + for (size_t i = coro->defer.elements; i != 0; i--) { + coro_defer_t *defer = &defers[i - 1]; - defer->func(defer->data1, defer->data2); - defer->func = NULL; -} - -static void -coro_run_deferred(coro_t *coro) -{ - run_deferred(coro->defer, coro->defer + DEFER_MAX); + defer->func(defer->data1, defer->data2); + } } void @@ -189,6 +182,7 @@ coro_reset(coro_t *coro, coro_function_t func, void *data) coro->data = data; coro_run_deferred(coro); + lwan_array_reset(&coro->defer); #if defined(__x86_64__) coro->context[6 /* RDI */] = (uintptr_t) coro; @@ -226,8 +220,7 @@ coro_new(coro_switcher_t *switcher, coro_function_t function, void *data) if (!coro) return NULL; - coro->defer = calloc(DEFER_MAX, sizeof(coro_defer_t)); - if (UNLIKELY(!coro->defer)) { + if (lwan_array_init(&coro->defer, sizeof(coro_defer_t)) < 0) { free(coro); return NULL; } @@ -305,29 +298,26 @@ coro_free(coro_t *coro) VALGRIND_STACK_DEREGISTER(coro->vg_stack_id); #endif coro_run_deferred(coro); - free(coro->defer); + lwan_array_reset(&coro->defer); free(coro); } static void coro_defer_any(coro_t *coro, defer_func func, void *data1, void *data2) { - const coro_defer_t *last = coro->defer + DEFER_MAX; + coro_defer_t *defer; assert(func); - /* Some uses require deferred statements are arranged in a stack. */ - - for (coro_defer_t *defer = coro->defer; defer < last; defer++) { - if (!defer->func) { - defer->func = func; - defer->data1 = data1; - defer->data2 = data2; - return; - } + defer = lwan_array_append(&coro->defer); + if (!defer) { + lwan_status_error("Could not add new deferred function for coro %p", coro); + return; } - lwan_status_error("Coroutine %p ran out of deferred callback slots", coro); + defer->func = func; + defer->data1 = data1; + defer->data2 = data2; } ALWAYS_INLINE void diff --git a/lwan-build-config.h.cmake b/lwan-build-config.h.cmake index 05d008c41..e905d7338 100644 --- a/lwan-build-config.h.cmake +++ b/lwan-build-config.h.cmake @@ -33,6 +33,7 @@ #cmakedefine HAVE_BUILTIN_CPU_INIT #cmakedefine HAVE_BUILTIN_IA32_CRC32 #cmakedefine HAVE_BUILTIN_MUL_OVERFLOW +#cmakedefine HAVE_BUILTIN_ADD_OVERFLOW /* C11 _Static_assert() */ #cmakedefine HAVE_STATIC_ASSERT From 9c55d7d7c20f1abef788dd0883e6bcb0f360fa18 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 25 Jan 2017 18:03:52 -0800 Subject: [PATCH 0349/2505] Remove `element_size` member from struct lwan_array --- common/lwan-array.c | 9 ++++----- common/lwan-array.h | 24 ++++++++++++++++++------ common/lwan-coro.c | 29 +++++++++++++++-------------- 3 files changed, 37 insertions(+), 25 deletions(-) diff --git a/common/lwan-array.c b/common/lwan-array.c index 45daeb04a..52528ef55 100644 --- a/common/lwan-array.c +++ b/common/lwan-array.c @@ -28,13 +28,12 @@ #define INCREMENT 16 int -lwan_array_init(struct lwan_array *a, size_t element_size) +lwan_array_init(struct lwan_array *a) { if (UNLIKELY(!a)) return -EINVAL; a->base = NULL; - a->element_size = element_size; a->elements = 0; return 0; @@ -67,7 +66,7 @@ static inline bool add_overflow(size_t a, size_t b, size_t *out) #endif void * -lwan_array_append(struct lwan_array *a) +lwan_array_append(struct lwan_array *a, size_t element_size) { if (!(a->elements % INCREMENT)) { void *new_base; @@ -78,12 +77,12 @@ lwan_array_append(struct lwan_array *a) return NULL; } - new_base = reallocarray(a->base, new_cap, a->element_size); + new_base = reallocarray(a->base, new_cap, element_size); if (UNLIKELY(!new_base)) return NULL; a->base = new_base; } - return ((unsigned char *)a->base) + (a->elements++) * a->element_size; + return ((unsigned char *)a->base) + a->elements++ * element_size; } diff --git a/common/lwan-array.h b/common/lwan-array.h index 241de6edd..d5a3cd286 100644 --- a/common/lwan-array.h +++ b/common/lwan-array.h @@ -23,14 +23,26 @@ struct lwan_array { void *base; - size_t element_size; size_t elements; }; -#define ARRAY_INITIALIZER(element_size_) \ - { .base = NULL, .element_size = (element_size_), .elements = 0 } - -int lwan_array_init(struct lwan_array *a, size_t element_size); +int lwan_array_init(struct lwan_array *a); int lwan_array_reset(struct lwan_array *a); -void *lwan_array_append(struct lwan_array *a); +void *lwan_array_append(struct lwan_array *a, size_t element_size); +#define DEFINE_ARRAY_TYPE(array_type_, element_type_) \ + struct array_type_ { \ + struct lwan_array base; \ + }; \ + static inline int array_type_ ## _init(struct array_type_ *array) \ + { \ + return lwan_array_init((struct lwan_array *)array); \ + } \ + static inline int array_type_ ## _reset(struct array_type_ *array) \ + { \ + return lwan_array_reset((struct lwan_array *)array); \ + } \ + static inline element_type_ * array_type_ ## _append(struct array_type_ *array) \ + { \ + return lwan_array_append((struct lwan_array *)array, sizeof(element_type_)); \ + } diff --git a/common/lwan-coro.c b/common/lwan-coro.c index 304c6f39c..1047c7468 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -43,16 +43,16 @@ static_assert(DEFAULT_BUFFER_SIZE < (CORO_STACK_MIN + PTHREAD_STACK_MIN), "Request buffer fits inside coroutine stack"); -typedef struct coro_defer_t_ coro_defer_t; - typedef void (*defer_func)(); -struct coro_defer_t_ { +struct coro_defer_t { defer_func func; void *data1; void *data2; }; +DEFINE_ARRAY_TYPE(coro_defer_array, struct coro_defer_t) + struct coro_t_ { coro_switcher_t *switcher; coro_context_t context; @@ -62,7 +62,7 @@ struct coro_t_ { unsigned int vg_stack_id; #endif - struct lwan_array defer; + struct coro_defer_array defer; void *data; bool ended; @@ -164,10 +164,11 @@ coro_entry_point(coro_t *coro, coro_function_t func) static void coro_run_deferred(coro_t *coro) { - coro_defer_t *defers = coro->defer.base; + struct lwan_array *array = (struct lwan_array *)&coro->defer; + struct coro_defer_t *defers = array->base; - for (size_t i = coro->defer.elements; i != 0; i--) { - coro_defer_t *defer = &defers[i - 1]; + for (size_t i = array->elements; i != 0; i--) { + struct coro_defer_t *defer = &defers[i - 1]; defer->func(defer->data1, defer->data2); } @@ -182,7 +183,7 @@ coro_reset(coro_t *coro, coro_function_t func, void *data) coro->data = data; coro_run_deferred(coro); - lwan_array_reset(&coro->defer); + coro_defer_array_reset(&coro->defer); #if defined(__x86_64__) coro->context[6 /* RDI */] = (uintptr_t) coro; @@ -217,10 +218,10 @@ ALWAYS_INLINE coro_t * coro_new(coro_switcher_t *switcher, coro_function_t function, void *data) { coro_t *coro = malloc(sizeof(*coro) + CORO_STACK_MIN); - if (!coro) + if (UNLIKELY(!coro)) return NULL; - if (lwan_array_init(&coro->defer, sizeof(coro_defer_t)) < 0) { + if (UNLIKELY(coro_defer_array_init(&coro->defer) < 0)) { free(coro); return NULL; } @@ -298,19 +299,19 @@ coro_free(coro_t *coro) VALGRIND_STACK_DEREGISTER(coro->vg_stack_id); #endif coro_run_deferred(coro); - lwan_array_reset(&coro->defer); + coro_defer_array_reset(&coro->defer); free(coro); } static void coro_defer_any(coro_t *coro, defer_func func, void *data1, void *data2) { - coro_defer_t *defer; + struct coro_defer_t *defer; assert(func); - defer = lwan_array_append(&coro->defer); - if (!defer) { + defer = coro_defer_array_append(&coro->defer); + if (UNLIKELY(!defer)) { lwan_status_error("Could not add new deferred function for coro %p", coro); return; } From dd14500538fd18e359936eac0709f2f4301363c4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 25 Jan 2017 18:33:10 -0800 Subject: [PATCH 0350/2505] Install lwan-array.h header --- common/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 90ca74fc4..7deb86540 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -112,5 +112,5 @@ install(TARGETS lwan-static lwan-shared DESTINATION "lib" ) install(FILES lwan.h lwan-coro.h lwan-trie.h lwan-status.h strbuf.h hash.h - lwan-template.h lwan-serve-files.h lwan-config.h + lwan-template.h lwan-serve-files.h lwan-config.h lwan-array.h DESTINATION "include/lwan" ) From bdfdd6a0c6e63e861d0fa09181fa48fa650635e5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 25 Jan 2017 18:33:21 -0800 Subject: [PATCH 0351/2505] Add sorting routine to lwan_array --- common/lwan-array.c | 6 ++++++ common/lwan-array.h | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/common/lwan-array.c b/common/lwan-array.c index 52528ef55..427ddba43 100644 --- a/common/lwan-array.c +++ b/common/lwan-array.c @@ -86,3 +86,9 @@ lwan_array_append(struct lwan_array *a, size_t element_size) return ((unsigned char *)a->base) + a->elements++ * element_size; } + +void +lwan_array_sort(struct lwan_array *a, size_t element_size, int (*cmp)(const void *a, const void *b)) +{ + qsort(a->base, a->elements, element_size, cmp); +} diff --git a/common/lwan-array.h b/common/lwan-array.h index d5a3cd286..e2f73dc3a 100644 --- a/common/lwan-array.h +++ b/common/lwan-array.h @@ -29,6 +29,7 @@ struct lwan_array { int lwan_array_init(struct lwan_array *a); int lwan_array_reset(struct lwan_array *a); void *lwan_array_append(struct lwan_array *a, size_t element_size); +void lwan_array_sort(struct lwan_array *a, size_t element_size, int (*cmp)(const void *a, const void *b)); #define DEFINE_ARRAY_TYPE(array_type_, element_type_) \ struct array_type_ { \ @@ -45,4 +46,8 @@ void *lwan_array_append(struct lwan_array *a, size_t element_size); static inline element_type_ * array_type_ ## _append(struct array_type_ *array) \ { \ return lwan_array_append((struct lwan_array *)array, sizeof(element_type_)); \ + } \ + static inline void array_type_ ## _sort(struct array_type_ *array, int (*cmp)(const void *a, const void *b)) \ + { \ + lwan_array_sort((struct lwan_array *)array, sizeof(element_type_), cmp); \ } From d9635e4e1d8b3945481a80b839a43ccb99e04412 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 25 Jan 2017 18:34:03 -0800 Subject: [PATCH 0352/2505] Use lwan_array for query_params, post_data, & cookies Makes for slightly nicer code. --- common/lwan-request.c | 70 +++++++++++++++++++++++-------------------- common/lwan.h | 9 +++--- testrunner/main.c | 21 ++++++------- 3 files changed, 51 insertions(+), 49 deletions(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index c0326bb31..9d0e78862 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -328,22 +328,19 @@ key_value_compare(const void *a, const void *b) static void parse_key_values(lwan_request_t *request, - lwan_value_t *helper_value, lwan_key_value_t **base, size_t *len, + lwan_value_t *helper_value, struct lwan_key_value_array *array, size_t (*decode_value)(char *value), const char separator) { - const size_t n_elements = 32; + lwan_key_value_t *kv; char *ptr = helper_value->value; - lwan_key_value_t *kvs; - size_t values = 0; if (!helper_value->len) return; - kvs = coro_malloc(request->conn->coro, n_elements * sizeof(*kvs)); - if (UNLIKELY(!kvs)) { - coro_yield(request->conn->coro, CONN_CORO_ABORT); - __builtin_unreachable(); - } + lwan_key_value_array_init(array); + /* Calling lwan_key_value_array_reset() twice is fine, so even if 'goto + * error' is executed in this function, nothing bad should happen. */ + coro_defer(request->conn->coro, CORO_DEFER(lwan_key_value_array_reset), array); do { char *key, *value; @@ -351,7 +348,7 @@ parse_key_values(lwan_request_t *request, while (*ptr == ' ' || *ptr == separator) ptr++; if (UNLIKELY(*ptr == '\0')) - return; + goto error; key = ptr; ptr = strsep_char(key, separator); @@ -360,22 +357,30 @@ parse_key_values(lwan_request_t *request, if (UNLIKELY(!value)) value = ""; else if (UNLIKELY(!decode_value(value))) - return; + goto error; if (UNLIKELY(!decode_value(key))) - return; + goto error; - kvs[values].key = key; - kvs[values].value = value; + kv = lwan_key_value_array_append(array); + if (UNLIKELY(!kv)) + goto error; - values++; - } while (ptr && values < (n_elements - 1)); + kv->key = key; + kv->value = value; + } while (ptr); - kvs[values].key = kvs[values].value = NULL; + kv = lwan_key_value_array_append(array); + if (UNLIKELY(!kv)) + goto error; + kv->key = kv->value = NULL; - qsort(kvs, values, sizeof(lwan_key_value_t), key_value_compare); - *base = kvs; - *len = values; + lwan_key_value_array_sort(array, key_value_compare); + + return; + +error: + lwan_key_value_array_reset(array); } static size_t @@ -387,15 +392,14 @@ identity_decode(char *input __attribute__((unused))) static void parse_cookies(lwan_request_t *request, struct request_parser_helper *helper) { - parse_key_values(request, &helper->cookie, &request->cookies.base, - &request->cookies.len, identity_decode, ';'); + parse_key_values(request, &helper->cookie, &request->cookies, + identity_decode, ';'); } static void parse_query_string(lwan_request_t *request, struct request_parser_helper *helper) { - parse_key_values(request, &helper->query_string, - &request->query_params.base, &request->query_params.len, + parse_key_values(request, &helper->query_string, &request->query_params, url_decode, '&'); } @@ -412,8 +416,7 @@ parse_post_data(lwan_request_t *request, struct request_parser_helper *helper) if (UNLIKELY(strncmp(helper->content_type.value, content_type, sizeof(content_type) - 1))) return; - parse_key_values(request, &helper->post_data, - &request->post_data.base, &request->post_data.len, + parse_key_values(request, &helper->post_data, &request->post_data, url_decode, '&'); } @@ -1079,13 +1082,15 @@ lwan_process_request(lwan_t *l, lwan_request_t *request, } static inline void * -value_lookup(const void *base, size_t len, const char *key) +value_lookup(const struct lwan_key_value_array *array, const char *key) { - if (LIKELY(base)) { + const struct lwan_array *la = (const struct lwan_array *)array; + + if (LIKELY(la->base)) { lwan_key_value_t k = { .key = (char *)key }; lwan_key_value_t *entry; - entry = bsearch(&k, base, len, sizeof(k), key_value_compare); + entry = bsearch(&k, la->base, la->elements, sizeof(k), key_value_compare); if (LIKELY(entry)) return entry->value; } @@ -1096,20 +1101,19 @@ value_lookup(const void *base, size_t len, const char *key) const char * lwan_request_get_query_param(lwan_request_t *request, const char *key) { - return value_lookup(request->query_params.base, request->query_params.len, - key); + return value_lookup(&request->query_params, key); } const char * lwan_request_get_post_param(lwan_request_t *request, const char *key) { - return value_lookup(request->post_data.base, request->post_data.len, key); + return value_lookup(&request->post_data, key); } const char * lwan_request_get_cookie(lwan_request_t *request, const char *key) { - return value_lookup(request->cookies.base, request->cookies.len, key); + return value_lookup(&request->cookies, key); } ALWAYS_INLINE int diff --git a/common/lwan.h b/common/lwan.h index d1cdfe72a..8f2db3041 100644 --- a/common/lwan.h +++ b/common/lwan.h @@ -32,6 +32,7 @@ extern "C" { #include "lwan-build-config.h" #include "hash.h" +#include "lwan-array.h" #include "lwan-config.h" #include "lwan-coro.h" #include "lwan-status.h" @@ -246,6 +247,8 @@ struct lwan_proxy_t_ { } from, to; }; +DEFINE_ARRAY_TYPE(lwan_key_value_array, lwan_key_value_t) + struct lwan_request_t_ { lwan_request_flags_t flags; int fd; @@ -254,10 +257,8 @@ struct lwan_request_t_ { lwan_connection_t *conn; lwan_proxy_t *proxy; - struct { - lwan_key_value_t *base; - size_t len; - } query_params, post_data, cookies; + struct lwan_key_value_array query_params, post_data, cookies; + struct { time_t if_modified_since; struct { diff --git a/testrunner/main.c b/testrunner/main.c index 3aa27a480..b77b2a55e 100644 --- a/testrunner/main.c +++ b/testrunner/main.c @@ -196,21 +196,18 @@ hello_world(lwan_request_t *request, if (!dump_vars) goto end; - if (request->cookies.base) { - strbuf_append_str(response->buffer, "\n\nCookies\n", 0); - strbuf_append_str(response->buffer, "-------\n\n", 0); - - lwan_key_value_t *qs = request->cookies.base; - for (; qs->key; qs++) - strbuf_append_printf(response->buffer, - "Key = \"%s\"; Value = \"%s\"\n", qs->key, qs->value); - } + strbuf_append_str(response->buffer, "\n\nCookies\n", 0); + strbuf_append_str(response->buffer, "-------\n\n", 0); + + lwan_key_value_t *qs = request->cookies.base.base; + for (; qs->key; qs++) + strbuf_append_printf(response->buffer, + "Key = \"%s\"; Value = \"%s\"\n", qs->key, qs->value); strbuf_append_str(response->buffer, "\n\nQuery String Variables\n", 0); strbuf_append_str(response->buffer, "----------------------\n\n", 0); - lwan_key_value_t *qs = request->query_params.base; - for (; qs->key; qs++) + for (qs = request->query_params.base.base; qs->key; qs++) strbuf_append_printf(response->buffer, "Key = \"%s\"; Value = \"%s\"\n", qs->key, qs->value); @@ -220,7 +217,7 @@ hello_world(lwan_request_t *request, strbuf_append_str(response->buffer, "\n\nPOST data\n", 0); strbuf_append_str(response->buffer, "---------\n\n", 0); - for (qs = request->post_data.base; qs->key; qs++) + for (qs = request->post_data.base.base; qs->key; qs++) strbuf_append_printf(response->buffer, "Key = \"%s\"; Value = \"%s\"\n", qs->key, qs->value); From eba2c2b1a20cc258b72cc888207c2a8c8ae1cfa0 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 25 Jan 2017 18:49:49 -0800 Subject: [PATCH 0353/2505] Use lwan_array in techempower benchmark --- techempower/CMakeLists.txt | 1 - techempower/array.c | 116 ------------------------------------- techempower/array.h | 22 ------- techempower/techempower.c | 34 ++++++----- 4 files changed, 19 insertions(+), 154 deletions(-) delete mode 100644 techempower/array.c delete mode 100644 techempower/array.h diff --git a/techempower/CMakeLists.txt b/techempower/CMakeLists.txt index 118249d0c..0c76c8cc4 100644 --- a/techempower/CMakeLists.txt +++ b/techempower/CMakeLists.txt @@ -22,7 +22,6 @@ if (MYSQL_INCLUDE_DIR AND SQLITE_FOUND) add_executable(techempower techempower.c json.c - array.c database.c ) diff --git a/techempower/array.c b/techempower/array.c deleted file mode 100644 index 7b963e319..000000000 --- a/techempower/array.c +++ /dev/null @@ -1,116 +0,0 @@ -/* - * libkmod - interface to kernel module operations - * - * Copyright (C) 2011-2013 ProFUSION embedded systems - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#include "array.h" -#include "reallocarray.h" - -#include -#include -#include -#include -#include - -/* basic pointer array growing in steps */ - - -static int array_realloc(struct array *array, size_t new_total) -{ - void *tmp = reallocarray(array->array, sizeof(void *), new_total); - if (tmp == NULL) - return -ENOMEM; - array->array = tmp; - array->total = new_total; - return 0; -} - -void array_init(struct array *array, size_t step) -{ - assert(step > 0); - array->array = NULL; - array->count = 0; - array->total = 0; - array->step = step; -} - -int array_append(struct array *array, const void *element) -{ - size_t idx; - - if (array->count + 1 >= array->total) { - int r = array_realloc(array, array->total + array->step); - if (r < 0) - return r; - } - idx = array->count; - array->array[idx] = (void *)element; - array->count++; - return (int)idx; -} - -int array_append_unique(struct array *array, const void *element) -{ - void **itr = array->array; - void **itr_end = itr + array->count; - for (; itr < itr_end; itr++) - if (*itr == element) - return -EEXIST; - return array_append(array, element); -} - -void array_pop(struct array *array) { - array->count--; - if (array->count + array->step < array->total) { - int r = array_realloc(array, array->total - array->step); - if (r < 0) - return; - } -} - -void array_free_array(struct array *array) { - free(array->array); - array->count = 0; - array->total = 0; -} - - -void array_sort(struct array *array, int (*cmp)(const void *a, const void *b)) -{ - qsort(array->array, array->count, sizeof(void *), cmp); -} - -int array_remove_at(struct array *array, unsigned int pos) -{ - if (array->count <= pos) - return -ENOENT; - - array->count--; - if (pos < array->count) - memmove(array->array + pos, array->array + pos + 1, - sizeof(void *) * (array->count - pos)); - - if (array->count + array->step < array->total) { - int r = array_realloc(array, array->total - array->step); - /* ignore error */ - if (r < 0) - return 0; - } - - return 0; -} diff --git a/techempower/array.h b/techempower/array.h deleted file mode 100644 index b88482fe4..000000000 --- a/techempower/array.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include - -/* - * Declaration of struct array is in header because we may want to embed the - * structure into another, so we need to know its size - */ -struct array { - void **array; - size_t count; - size_t total; - size_t step; -}; - -void array_init(struct array *array, size_t step); -int array_append(struct array *array, const void *element); -int array_append_unique(struct array *array, const void *element); -void array_pop(struct array *array); -void array_free_array(struct array *array); -void array_sort(struct array *array, int (*cmp)(const void *a, const void *b)); -int array_remove_at(struct array *array, unsigned int pos); diff --git a/techempower/techempower.c b/techempower/techempower.c index 61a8ebfb1..1c1e95e29 100644 --- a/techempower/techempower.c +++ b/techempower/techempower.c @@ -24,7 +24,6 @@ #include "lwan-config.h" #include "lwan-template.h" -#include "array.h" #include "database.h" #include "json.h" @@ -40,6 +39,8 @@ struct Fortune { } item; }; +DEFINE_ARRAY_TYPE(fortune_array, struct Fortune) + static const char fortunes_template_str[] = "" \ "" \ "Fortunes" \ @@ -209,8 +210,8 @@ plaintext(lwan_request_t *request __attribute__((unused)), static int fortune_compare(const void *a, const void *b) { - const struct Fortune *fortune_a = *(const struct Fortune **)a; - const struct Fortune *fortune_b = *(const struct Fortune **)b; + const struct Fortune *fortune_a = (const struct Fortune *)a; + const struct Fortune *fortune_b = (const struct Fortune *)b; size_t a_len = strlen(fortune_a->item.message); size_t b_len = strlen(fortune_b->item.message); @@ -225,21 +226,24 @@ static int fortune_compare(const void *a, const void *b) return cmp > 0; } -static bool append_fortune(coro_t *coro, struct array *fortunes, +static bool append_fortune(coro_t *coro, struct fortune_array *fortunes, int id, const char *message) { struct Fortune *fortune; + char *message_copy; + + message_copy = coro_strdup(coro, message); + if (UNLIKELY(!message_copy)) + return false; - fortune = coro_malloc(coro, sizeof(*fortune)); + fortune = fortune_array_append(fortunes); if (UNLIKELY(!fortune)) return false; fortune->item.id = id; - fortune->item.message = coro_strdup(coro, message); - if (UNLIKELY(!fortune->item.message)) - return false; + fortune->item.message = message_copy; - return array_append(fortunes, fortune) >= 0; + return true; } static int fortune_list_generator(coro_t *coro) @@ -247,7 +251,7 @@ static int fortune_list_generator(coro_t *coro) static const char fortune_query[] = "SELECT * FROM Fortune"; char fortune_buffer[256]; struct Fortune *fortune; - struct array fortunes; + struct fortune_array fortunes; struct db_stmt *stmt; size_t i; @@ -255,7 +259,7 @@ static int fortune_list_generator(coro_t *coro) if (UNLIKELY(!stmt)) return 0; - array_init(&fortunes, 16); + fortune_array_init(&fortunes); struct db_row results[] = { { .kind = 'i' }, @@ -271,18 +275,18 @@ static int fortune_list_generator(coro_t *coro) "Additional fortune added at request time.")) goto out; - array_sort(&fortunes, fortune_compare); + fortune_array_sort(&fortunes, fortune_compare); fortune = coro_get_data(coro); - for (i = 0; i < fortunes.count; i++) { - struct Fortune *f = fortunes.array[i]; + for (i = 0; i < fortunes.base.elements; i++) { + struct Fortune *f = &((struct Fortune *)fortunes.base.base)[i]; fortune->item.id = f->item.id; fortune->item.message = f->item.message; coro_yield(coro, 1); } out: - array_free_array(&fortunes); + fortune_array_reset(&fortunes); db_stmt_finalize(stmt); return 0; } From 1be1e64ee8ae0e2e3f28433f2cec81a51fa9b8a8 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 25 Jan 2017 23:27:26 -0800 Subject: [PATCH 0354/2505] Add option to allocate array from coroutine Array will reset when coroutine ends. --- common/lwan-array.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/common/lwan-array.h b/common/lwan-array.h index e2f73dc3a..6b41fdf91 100644 --- a/common/lwan-array.h +++ b/common/lwan-array.h @@ -21,6 +21,8 @@ #include +#include "lwan-coro.h" + struct lwan_array { void *base; size_t elements; @@ -50,4 +52,13 @@ void lwan_array_sort(struct lwan_array *a, size_t element_size, int (*cmp)(const static inline void array_type_ ## _sort(struct array_type_ *array, int (*cmp)(const void *a, const void *b)) \ { \ lwan_array_sort((struct lwan_array *)array, sizeof(element_type_), cmp); \ + } \ + static inline struct array_type_ *coro_ ## array_type_ ## _new(coro_t *coro) \ + { \ + struct array_type_ *array = coro_malloc(coro, sizeof(struct array_type_)); \ + if (array) { \ + array_type_ ## _init(array); \ + coro_defer(coro, CORO_DEFER(array_type_ ## _reset), array); \ + } \ + return array; \ } From 89cbccd11364d0e36b05599e527b4c504d75f818 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 25 Jan 2017 23:27:59 -0800 Subject: [PATCH 0355/2505] Use lwan_array when setting headers in Lua --- common/lwan-lua.c | 82 ++++++++++++++++++++++++++--------------------- 1 file changed, 46 insertions(+), 36 deletions(-) diff --git a/common/lwan-lua.c b/common/lwan-lua.c index 6c690462e..b9817d2ec 100644 --- a/common/lwan-lua.c +++ b/common/lwan-lua.c @@ -28,6 +28,7 @@ #include "lwan-private.h" +#include "lwan-array.h" #include "lwan-cache.h" #include "lwan-config.h" #include "lwan-lua.h" @@ -118,58 +119,62 @@ static int req_cookie_cb(lua_State *L) return request_param_getter(L, lwan_request_get_cookie); } +static bool append_key_value(lua_State *L, coro_t *coro, + struct lwan_key_value_array *arr, char *key, int value_index) +{ + lwan_key_value_t *kv; + + kv = lwan_key_value_array_append(arr); + if (!kv) + return false; + + kv->key = key; + kv->value = coro_strdup(coro, lua_tostring(L, value_index)); + + return kv->value != NULL; +} + static int req_set_headers_cb(lua_State *L) { - const size_t max_headers = 16; const int table_index = 2; const int key_index = 1 + table_index; const int value_index = 2 + table_index; const int nested_value_index = value_index * 2 - table_index; + struct lwan_key_value_array *headers; lwan_request_t *request = userdata_as_request(L, 1); - lwan_key_value_t *headers = coro_malloc(request->conn->coro, max_headers * sizeof(*headers)); - size_t n_headers = 0; + lwan_key_value_t *kv; - if (!headers) { - lua_pushnil(L); - return 1; - } + if (request->flags & RESPONSE_SENT_HEADERS) + goto out; - if (request->flags & RESPONSE_SENT_HEADERS) { - lua_pushnil(L); - return 1; - } + if (!lua_istable(L, table_index)) + goto out; - if (!lua_istable(L, table_index)) { - lua_pushnil(L); - return 1; - } + headers = coro_lwan_key_value_array_new(request->conn->coro); + if (!headers) + goto out; lua_pushnil(L); - while (n_headers < (max_headers - 1) && lua_next(L, table_index) != 0) { + while (lua_next(L, table_index) != 0) { + char *key; + if (!lua_isstring(L, key_index)) { lua_pop(L, 1); continue; } - if (lua_isstring(L, value_index)) { - headers[n_headers].key = coro_strdup(request->conn->coro, - lua_tostring(L, key_index)); - headers[n_headers].value = coro_strdup(request->conn->coro, - lua_tostring(L, value_index)); + key = coro_strdup(request->conn->coro, lua_tostring(L, key_index)); - n_headers++; + if (lua_isstring(L, value_index)) { + if (!append_key_value(L, request->conn->coro, headers, key, value_index)) + goto out; } else if (lua_istable(L, value_index)) { - char *header_name = coro_strdup(request->conn->coro, - lua_tostring(L, key_index)); - lua_pushnil(L); - while (n_headers < (max_headers - 1) && lua_next(L, value_index) != 0) { - if (lua_isstring(L, nested_value_index)) { - headers[n_headers].key = header_name; - headers[n_headers].value = coro_strdup(request->conn->coro, - lua_tostring(L, nested_value_index)); - n_headers++; + while (lua_next(L, value_index) != 0) { + if (lua_isstring(L, nested_value_index)) { + if (!append_key_value(L, request->conn->coro, headers, key, nested_value_index)) + goto out; } lua_pop(L, 1); @@ -178,13 +183,18 @@ static int req_set_headers_cb(lua_State *L) lua_pop(L, 1); } - if (n_headers == (max_headers - 1)) - lua_pop(L, 1); - headers[n_headers].key = headers[n_headers].value = NULL; - request->response.headers = headers; + kv = lwan_key_value_array_append(headers); + if (!kv) + goto out; + kv->key = kv->value = NULL; - lua_pushinteger(L, (lua_Integer)n_headers); + request->response.headers = headers->base.base; + lua_pushinteger(L, (lua_Integer)((struct lwan_array *)headers->base.elements)); + return 1; + +out: + lua_pushnil(L); return 1; } From 86dae7946ccbcac4d9f775d75c7fb85fd1bb2de2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 25 Jan 2017 23:30:44 -0800 Subject: [PATCH 0356/2505] `elements` field in lwan_array is not inclusive --- common/lwan-array.c | 3 ++- common/lwan-request.c | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/common/lwan-array.c b/common/lwan-array.c index 427ddba43..21e6c3b70 100644 --- a/common/lwan-array.c +++ b/common/lwan-array.c @@ -90,5 +90,6 @@ lwan_array_append(struct lwan_array *a, size_t element_size) void lwan_array_sort(struct lwan_array *a, size_t element_size, int (*cmp)(const void *a, const void *b)) { - qsort(a->base, a->elements, element_size, cmp); + if (LIKELY(a->elements)) + qsort(a->base, a->elements - 1, element_size, cmp); } diff --git a/common/lwan-request.c b/common/lwan-request.c index 9d0e78862..f03986eb8 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -1086,11 +1086,11 @@ value_lookup(const struct lwan_key_value_array *array, const char *key) { const struct lwan_array *la = (const struct lwan_array *)array; - if (LIKELY(la->base)) { + if (LIKELY(la->elements)) { lwan_key_value_t k = { .key = (char *)key }; lwan_key_value_t *entry; - entry = bsearch(&k, la->base, la->elements, sizeof(k), key_value_compare); + entry = bsearch(&k, la->base, la->elements - 1, sizeof(k), key_value_compare); if (LIKELY(entry)) return entry->value; } From be16cae77ce047899071750aaad57e328b5fdf6d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 26 Jan 2017 07:46:22 -0800 Subject: [PATCH 0357/2505] Fix reading of (very small) POST requests Lwan was looking for '\r\n\r\n' at the end of the POST data instead of immediately after the request headers. Curiously, this worked before (the tests were passing, afterall!). --- common/lwan-request.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/common/lwan-request.c b/common/lwan-request.c index f03986eb8..ab890f5a7 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -792,7 +792,10 @@ static lwan_read_finalizer_t read_request_finalizer(size_t total_read, return FINALIZER_DONE; } - if (LIKELY(!memcmp(helper->buffer->value + total_read - 4, "\r\n\r\n", 4))) + /* FIXME: Would saving the location of CRLFCRLF be useful? Maybe + * parse_headers() could benefit from this information? How would it + * compare to helper->next_request? */ + if (LIKELY(memmem(helper->buffer->value, helper->buffer->len, "\r\n\r\n", 4))) return FINALIZER_DONE; return FINALIZER_TRY_AGAIN; From f0e7ae5a2ef6483efbf3188571b0245454220036 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 26 Jan 2017 07:47:44 -0800 Subject: [PATCH 0358/2505] Fix crash in testrunner after move to use lwan_array --- testrunner/main.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/testrunner/main.c b/testrunner/main.c index b77b2a55e..2c4b4ac79 100644 --- a/testrunner/main.c +++ b/testrunner/main.c @@ -200,14 +200,14 @@ hello_world(lwan_request_t *request, strbuf_append_str(response->buffer, "-------\n\n", 0); lwan_key_value_t *qs = request->cookies.base.base; - for (; qs->key; qs++) + for (; qs && qs->key; qs++) strbuf_append_printf(response->buffer, "Key = \"%s\"; Value = \"%s\"\n", qs->key, qs->value); strbuf_append_str(response->buffer, "\n\nQuery String Variables\n", 0); strbuf_append_str(response->buffer, "----------------------\n\n", 0); - for (qs = request->query_params.base.base; qs->key; qs++) + for (qs = request->query_params.base.base; qs && qs->key; qs++) strbuf_append_printf(response->buffer, "Key = \"%s\"; Value = \"%s\"\n", qs->key, qs->value); @@ -217,7 +217,7 @@ hello_world(lwan_request_t *request, strbuf_append_str(response->buffer, "\n\nPOST data\n", 0); strbuf_append_str(response->buffer, "---------\n\n", 0); - for (qs = request->post_data.base.base; qs->key; qs++) + for (qs = request->post_data.base.base; qs && qs->key; qs++) strbuf_append_printf(response->buffer, "Key = \"%s\"; Value = \"%s\"\n", qs->key, qs->value); From 60b381964ed8b61bbd07e618a59cb55af9a621f6 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 26 Jan 2017 07:59:55 -0800 Subject: [PATCH 0359/2505] Simplify req_set_headers_cb() slightly --- common/lwan-lua.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/common/lwan-lua.c b/common/lwan-lua.c index b9817d2ec..3797acee4 100644 --- a/common/lwan-lua.c +++ b/common/lwan-lua.c @@ -142,6 +142,7 @@ static int req_set_headers_cb(lua_State *L) const int nested_value_index = value_index * 2 - table_index; struct lwan_key_value_array *headers; lwan_request_t *request = userdata_as_request(L, 1); + coro_t *coro = request->conn->coro; lwan_key_value_t *kv; if (request->flags & RESPONSE_SENT_HEADERS) @@ -164,20 +165,20 @@ static int req_set_headers_cb(lua_State *L) } key = coro_strdup(request->conn->coro, lua_tostring(L, key_index)); + if (!key) + goto out; if (lua_isstring(L, value_index)) { - if (!append_key_value(L, request->conn->coro, headers, key, value_index)) + if (!append_key_value(L, coro, headers, key, value_index)) goto out; } else if (lua_istable(L, value_index)) { lua_pushnil(L); - while (lua_next(L, value_index) != 0) { - if (lua_isstring(L, nested_value_index)) { - if (!append_key_value(L, request->conn->coro, headers, key, nested_value_index)) - goto out; - } - - lua_pop(L, 1); + for (; lua_next(L, value_index) != 0; lua_pop(L, 1)) { + if (lua_isstring(L, nested_value_index)) + continue; + if (!append_key_value(L, coro, headers, key, nested_value_index)) + goto out; } } From 0c7095adb354ac1be2cd22a4e3d531bb56bb2d85 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 26 Jan 2017 21:08:37 -0800 Subject: [PATCH 0360/2505] Refactor functions to add items to the hash table Use a common auxiliary function for bosh hash_add() and hash_add_unique(). --- common/hash.c | 83 ++++++++++++++++++++++++--------------------------- 1 file changed, 39 insertions(+), 44 deletions(-) diff --git a/common/hash.c b/common/hash.c index e215a48f5..d132d7852 100644 --- a/common/hash.c +++ b/common/hash.c @@ -166,10 +166,11 @@ static struct hash *hash_internal_new( n_buckets * sizeof(struct hash_bucket)); if (hash == NULL) return NULL; + hash->hash_value = hash_value; hash->key_compare = key_compare; - hash->free_value = free_value ? free_value : no_op; - hash->free_key = free_key ? free_key : no_op; + hash->free_value = free_value; + hash->free_key = free_key; return hash; } @@ -227,13 +228,7 @@ void hash_free(struct hash *hash) free(hash); } -/* - * add or replace key in hash map. - * - * none of key or value are copied, just references are remembered as is, - * make sure they are live while pair exists in hash! - */ -int hash_add(struct hash *hash, const void *key, const void *value) +static struct hash_entry *hash_add_entry(struct hash *hash, const void *key) { unsigned hashval = hash->hash_value(key); unsigned pos = hashval & (n_buckets - 1); @@ -244,7 +239,7 @@ int hash_add(struct hash *hash, const void *key, const void *value) unsigned new_total = bucket->total + steps; struct hash_entry *tmp = reallocarray(bucket->entries, new_total, sizeof(*tmp)); if (tmp == NULL) - return -errno; + return NULL; bucket->entries = tmp; bucket->total = new_total; } @@ -254,55 +249,55 @@ int hash_add(struct hash *hash, const void *key, const void *value) for (; entry < entry_end; entry++) { if (hashval != entry->hashval) continue; - if (hash->key_compare(key, entry->key) != 0) - continue; - hash->free_value((void *)entry->value); - hash->free_key((void *)entry->key); - - entry->key = key; - entry->value = value; - return 0; + if (!hash->key_compare(key, entry->key)) + return entry; } - entry->key = key; - entry->value = value; - entry->hashval = hashval; bucket->used++; hash->count++; + + entry->hashval = hashval; + entry->key = entry->value = NULL; + + return entry; +} + +/* + * add or replace key in hash map. + * + * none of key or value are copied, just references are remembered as is, + * make sure they are live while pair exists in hash! + */ +int hash_add(struct hash *hash, const void *key, const void *value) +{ + struct hash_entry *entry = hash_add_entry(hash, key); + + if (!entry) + return -errno; + + hash->free_value((void *)entry->value); + hash->free_key((void *)entry->key); + + entry->key = key; + entry->value = value; + return 0; } /* similar to hash_add(), but fails if key already exists */ int hash_add_unique(struct hash *hash, const void *key, const void *value) { - unsigned hashval = hash->hash_value(key); - unsigned pos = hashval & (n_buckets - 1); - struct hash_bucket *bucket = hash->buckets + pos; - struct hash_entry *entry, *entry_end; + struct hash_entry *entry = hash_add_entry(hash, key); - if (bucket->used + 1 >= bucket->total) { - unsigned new_total = bucket->total + steps; - struct hash_entry *tmp = reallocarray(bucket->entries, new_total, sizeof(*tmp)); - if (tmp == NULL) - return -errno; - bucket->entries = tmp; - bucket->total = new_total; - } + if (!entry) + return -errno; - entry = bucket->entries; - entry_end = entry + bucket->used; - for (; entry < entry_end; entry++) { - if (hashval != entry->hashval) - continue; - if (hash->key_compare(key, entry->key) == 0) - return -EEXIST; - } + if (entry->key || entry->value) + return -EEXIST; entry->key = key; entry->value = value; - entry->hashval = hashval; - bucket->used++; - hash->count++; + return 0; } From 9f5f5ae0ac8f1623e14e96fa0d933685341bd149 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 29 Jan 2017 20:02:27 -0800 Subject: [PATCH 0361/2505] Fix compile warning on on-Linux platforms This the abort_on_open_directories() function should be void, not bool. --- common/lwan-straitjacket.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lwan-straitjacket.c b/common/lwan-straitjacket.c index b1fc8529f..6a2376724 100644 --- a/common/lwan-straitjacket.c +++ b/common/lwan-straitjacket.c @@ -169,7 +169,7 @@ static void abort_on_open_directories(void) closedir(dir); } #else -static bool abort_on_open_directories(void) +static void abort_on_open_directories(void) { } #endif From bcf04b5edede71d05d5f8f34cf5b79a4a2c5415d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 26 Jan 2017 08:28:48 -0800 Subject: [PATCH 0362/2505] Remove "_t" suffix from Lwan types This suffix is reserved for POSIX types, so remove them from all Lwan structs, enums, and typedefs. This is a big change, and it breaks the API... but something to do before releasing the first version, which will happen anytime soon. --- common/lwan-array.h | 2 +- common/lwan-cache.c | 34 ++++---- common/lwan-cache.h | 24 +++--- common/lwan-config.c | 24 +++--- common/lwan-config.h | 29 +++---- common/lwan-coro.c | 52 ++++++------ common/lwan-coro.h | 45 ++++++----- common/lwan-http-authorize.c | 28 +++---- common/lwan-http-authorize.h | 4 +- common/lwan-io-wrappers.c | 12 +-- common/lwan-io-wrappers.h | 10 +-- common/lwan-lua.c | 80 +++++++++---------- common/lwan-lua.h | 6 +- common/lwan-private.h | 26 +++--- common/lwan-redirect.c | 16 ++-- common/lwan-redirect.h | 6 +- common/lwan-request.c | 120 ++++++++++++++-------------- common/lwan-response.c | 34 ++++---- common/lwan-rewrite.c | 34 ++++---- common/lwan-rewrite.h | 2 +- common/lwan-serve-files.c | 88 ++++++++++----------- common/lwan-serve-files.h | 8 +- common/lwan-socket.c | 4 +- common/lwan-status.c | 20 ++--- common/lwan-straitjacket.c | 2 +- common/lwan-tables.c | 6 +- common/lwan-template.c | 96 +++++++++++----------- common/lwan-template.h | 37 ++++----- common/lwan-thread.c | 86 ++++++++++---------- common/lwan-trie.c | 40 +++++----- common/lwan-trie.h | 32 ++++---- common/lwan.c | 68 ++++++++-------- common/lwan.h | 149 ++++++++++++++++------------------- common/strbuf.c | 44 +++++------ common/strbuf.h | 44 +++++------ freegeoip/freegeoip.c | 90 ++++++++++----------- lwan/main.c | 8 +- techempower/techempower.c | 52 ++++++------ testrunner/main.c | 52 ++++++------ 39 files changed, 745 insertions(+), 769 deletions(-) diff --git a/common/lwan-array.h b/common/lwan-array.h index 6b41fdf91..b50410862 100644 --- a/common/lwan-array.h +++ b/common/lwan-array.h @@ -53,7 +53,7 @@ void lwan_array_sort(struct lwan_array *a, size_t element_size, int (*cmp)(const { \ lwan_array_sort((struct lwan_array *)array, sizeof(element_type_), cmp); \ } \ - static inline struct array_type_ *coro_ ## array_type_ ## _new(coro_t *coro) \ + static inline struct array_type_ *coro_ ## array_type_ ## _new(struct coro *coro) \ { \ struct array_type_ *array = coro_malloc(coro, sizeof(struct array_type_)); \ if (array) { \ diff --git a/common/lwan-cache.c b/common/lwan-cache.c index efa1a1e20..ab1cf4722 100644 --- a/common/lwan-cache.c +++ b/common/lwan-cache.c @@ -41,7 +41,7 @@ enum { SHUTTING_DOWN = 1 << 0 }; -struct cache_t { +struct cache { struct { struct hash *table; pthread_rwlock_t lock; @@ -53,8 +53,8 @@ struct cache_t { } queue; struct { - CreateEntryCallback create_entry; - DestroyEntryCallback destroy_entry; + cache_create_entry_cb create_entry; + cache_destroy_entry_cb destroy_entry; void *context; } cb; @@ -87,19 +87,19 @@ static clockid_t detect_fastest_monotonic_clock(void) return CLOCK_MONOTONIC; } -static ALWAYS_INLINE void clock_monotonic_gettime(struct cache_t *cache, +static ALWAYS_INLINE void clock_monotonic_gettime(struct cache *cache, struct timespec *ts) { if (UNLIKELY(clock_gettime(cache->settings.clock_id, ts) < 0)) lwan_status_perror("clock_gettime"); } -struct cache_t *cache_create(CreateEntryCallback create_entry_cb, - DestroyEntryCallback destroy_entry_cb, +struct cache *cache_create(cache_create_entry_cb create_entry_cb, + cache_destroy_entry_cb destroy_entry_cb, void *cb_context, time_t time_to_live) { - struct cache_t *cache; + struct cache *cache; assert(create_entry_cb); assert(destroy_entry_cb); @@ -141,7 +141,7 @@ struct cache_t *cache_create(CreateEntryCallback create_entry_cb, return NULL; } -void cache_destroy(struct cache_t *cache) +void cache_destroy(struct cache *cache) { assert(cache); @@ -160,15 +160,15 @@ void cache_destroy(struct cache_t *cache) free(cache); } -static ALWAYS_INLINE void convert_to_temporary(struct cache_entry_t *entry) +static ALWAYS_INLINE void convert_to_temporary(struct cache_entry *entry) { entry->flags = TEMPORARY; } -struct cache_entry_t *cache_get_and_ref_entry(struct cache_t *cache, +struct cache_entry *cache_get_and_ref_entry(struct cache *cache, const char *key, int *error) { - struct cache_entry_t *entry; + struct cache_entry *entry; char *key_copy; assert(cache); @@ -254,7 +254,7 @@ struct cache_entry_t *cache_get_and_ref_entry(struct cache_t *cache, return entry; } -void cache_entry_unref(struct cache_t *cache, struct cache_entry_t *entry) +void cache_entry_unref(struct cache *cache, struct cache_entry *entry) { assert(entry); @@ -279,8 +279,8 @@ void cache_entry_unref(struct cache_t *cache, struct cache_entry_t *entry) static bool cache_pruner_job(void *data) { - struct cache_t *cache = data; - struct cache_entry_t *node, *next; + struct cache *cache = data; + struct cache_entry *node, *next; struct timespec now; bool shutting_down = cache->flags & SHUTTING_DOWN; unsigned evicted = 0; @@ -361,13 +361,13 @@ static bool cache_pruner_job(void *data) return evicted; } -struct cache_entry_t* -cache_coro_get_and_ref_entry(struct cache_t *cache, coro_t *coro, +struct cache_entry* +cache_coro_get_and_ref_entry(struct cache *cache, struct coro *coro, const char *key) { for (int tries = GET_AND_REF_TRIES; tries; tries--) { int error; - struct cache_entry_t *ce = cache_get_and_ref_entry(cache, key, &error); + struct cache_entry *ce = cache_get_and_ref_entry(cache, key, &error); if (LIKELY(ce)) { /* diff --git a/common/lwan-cache.h b/common/lwan-cache.h index 8e4d23647..a46895602 100644 --- a/common/lwan-cache.h +++ b/common/lwan-cache.h @@ -24,7 +24,7 @@ #include "list.h" #include "lwan-coro.h" -struct cache_entry_t { +struct cache_entry { struct list_node entries; char *key; int refs; @@ -32,21 +32,21 @@ struct cache_entry_t { time_t time_to_die; }; -typedef struct cache_entry_t *(*CreateEntryCallback)( +typedef struct cache_entry *(*cache_create_entry_cb)( const char *key, void *context); -typedef void (*DestroyEntryCallback)( - struct cache_entry_t *entry, void *context); +typedef void (*cache_destroy_entry_cb)( + struct cache_entry *entry, void *context); -struct cache_t; +struct cache; -struct cache_t *cache_create(CreateEntryCallback create_entry_cb, - DestroyEntryCallback destroy_entry_cb, +struct cache *cache_create(cache_create_entry_cb create_entry_cb, + cache_destroy_entry_cb destroy_entry_cb, void *cb_context, time_t time_to_live); -void cache_destroy(struct cache_t *cache); +void cache_destroy(struct cache *cache); -struct cache_entry_t *cache_get_and_ref_entry(struct cache_t *cache, +struct cache_entry *cache_get_and_ref_entry(struct cache *cache, const char *key, int *error); -void cache_entry_unref(struct cache_t *cache, struct cache_entry_t *entry); -struct cache_entry_t *cache_coro_get_and_ref_entry(struct cache_t *cache, - coro_t *coro, const char *key); +void cache_entry_unref(struct cache *cache, struct cache_entry *entry); +struct cache_entry *cache_coro_get_and_ref_entry(struct cache *cache, + struct coro *coro, const char *key); diff --git a/common/lwan-config.c b/common/lwan-config.c index 4bd828702..ed2f19385 100644 --- a/common/lwan-config.c +++ b/common/lwan-config.c @@ -105,7 +105,7 @@ bool parse_bool(const char *value, bool default_value) return parse_int(value, default_value); } -bool config_error(config_t *conf, const char *fmt, ...) +bool config_error(struct config *conf, const char *fmt, ...) { va_list values; int len; @@ -159,7 +159,7 @@ static char *find_line_end(char *line) return (char *)rawmemchr(line, '\0') - 1; } -static bool parse_section(char *line, config_line_t *l, char *bracket) +static bool parse_section(char *line, struct config_line *l, char *bracket) { char *name, *param; char *space = strchr(line, ' '); @@ -187,7 +187,7 @@ static char *replace_space_with_underscore(char *line) return line; } -static bool config_fgets(config_t *conf, char *buffer, size_t buffer_len) +static bool config_fgets(struct config *conf, char *buffer, size_t buffer_len) { assert(buffer_len <= INT_MAX); if (!fgets(buffer, (int)buffer_len, conf->file)) @@ -207,7 +207,7 @@ static bool config_fgets(config_t *conf, char *buffer, size_t buffer_len) return true; } -static bool parse_multiline(config_t *c, config_line_t *l) +static bool parse_multiline(struct config *c, struct config_line *l) { char buffer[1024]; @@ -237,7 +237,7 @@ static bool parse_multiline(config_t *c, config_line_t *l) return false; } -static bool parse_line(config_t *c, char *line, config_line_t *l, char *equal) +static bool parse_line(struct config *c, char *line, struct config_line *l, char *equal) { *equal = '\0'; line = remove_leading_spaces(line); @@ -253,7 +253,7 @@ static bool parse_line(config_t *c, char *line, config_line_t *l, char *equal) return parse_multiline(c, l); } -static bool find_section_end(config_t *config, config_line_t *line, int recursion_level) +static bool find_section_end(struct config *config, struct config_line *line, int recursion_level) { if (recursion_level > 10) { config_error(config, "Recursion level too deep"); @@ -276,7 +276,7 @@ static bool find_section_end(config_t *config, config_line_t *line, int recursio return false; } -bool config_skip_section(config_t *conf, config_line_t *line) +bool config_skip_section(struct config *conf, struct config_line *line) { if (conf->error_message) return false; @@ -285,8 +285,8 @@ bool config_skip_section(config_t *conf, config_line_t *line) return find_section_end(conf, line, 0); } -bool config_isolate_section(config_t *current_conf, - config_line_t *current_line, config_t *isolated) +bool config_isolate_section(struct config *current_conf, + struct config_line *current_line, struct config *isolated) { long startpos, endpos; bool r = false; @@ -332,7 +332,7 @@ bool config_isolate_section(config_t *current_conf, return r; } -bool config_read_line(config_t *conf, config_line_t *l) +bool config_read_line(struct config *conf, struct config_line *l) { char *line, *line_end; @@ -373,7 +373,7 @@ bool config_read_line(config_t *conf, config_line_t *l) return true; } -bool config_open(config_t *conf, const char *path) +bool config_open(struct config *conf, const char *path) { if (!conf) return false; @@ -407,7 +407,7 @@ bool config_open(config_t *conf, const char *path) return false; } -void config_close(config_t *conf) +void config_close(struct config *conf) { if (!conf) return; diff --git a/common/lwan-config.h b/common/lwan-config.h index 950f2a4ab..ec9c512a5 100644 --- a/common/lwan-config.h +++ b/common/lwan-config.h @@ -31,26 +31,23 @@ #include "strbuf.h" -typedef struct config_t_ config_t; -typedef struct config_line_t_ config_line_t; - -typedef enum { +enum config_line_type { CONFIG_LINE_TYPE_LINE, CONFIG_LINE_TYPE_SECTION, CONFIG_LINE_TYPE_SECTION_END -} config_line_type_t; +}; -struct config_t_ { +struct config { FILE *file; char *path, *error_message; - strbuf_t *strbuf; + struct strbuf *strbuf; struct { long end; } isolated; int line; }; -struct config_line_t_ { +struct config_line { union { struct { char *name, *param; @@ -59,18 +56,18 @@ struct config_line_t_ { char *key, *value; }; }; - config_line_type_t type; + enum config_line_type type; char buffer[1024]; }; -bool config_open(config_t *conf, const char *path); -void config_close(config_t *conf); -bool config_error(config_t *conf, const char *fmt, ...); -bool config_read_line(config_t *conf, config_line_t *l); +bool config_open(struct config *conf, const char *path); +void config_close(struct config *conf); +bool config_error(struct config *conf, const char *fmt, ...); +bool config_read_line(struct config *conf, struct config_line *l); -bool config_isolate_section(config_t *current_conf, - config_line_t *current_line, config_t *isolated); -bool config_skip_section(config_t *conf, config_line_t *line); +bool config_isolate_section(struct config *current_conf, + struct config_line *current_line, struct config *isolated); +bool config_skip_section(struct config *conf, struct config_line *line); bool parse_bool(const char *value, bool default_value); long parse_long(const char *value, long default_value); diff --git a/common/lwan-coro.c b/common/lwan-coro.c index 1047c7468..c9945417c 100644 --- a/common/lwan-coro.c +++ b/common/lwan-coro.c @@ -53,9 +53,9 @@ struct coro_defer_t { DEFINE_ARRAY_TYPE(coro_defer_array, struct coro_defer_t) -struct coro_t_ { - coro_switcher_t *switcher; - coro_context_t context; +struct coro { + struct coro_switcher *switcher; + coro_context context; int yield_value; #if !defined(NDEBUG) && defined(USE_VALGRIND) @@ -68,7 +68,7 @@ struct coro_t_ { bool ended; }; -static void coro_entry_point(coro_t *data, coro_function_t func); +static void coro_entry_point(struct coro *data, coro_function_t func); /* * This swapcontext() implementation was obtained from glibc and modified @@ -82,7 +82,7 @@ static void coro_entry_point(coro_t *data, coro_function_t func); */ #if defined(__x86_64__) void __attribute__((noinline)) -coro_swapcontext(coro_context_t *current, coro_context_t *other); +coro_swapcontext(coro_context *current, coro_context *other); asm( ".text\n\t" ".p2align 4\n\t" @@ -118,7 +118,7 @@ coro_swapcontext(coro_context_t *current, coro_context_t *other); "jmp *%rcx\n\t"); #elif defined(__i386__) void __attribute__((noinline)) -coro_swapcontext(coro_context_t *current, coro_context_t *other); +coro_swapcontext(coro_context *current, coro_context *other); asm( ".text\n\t" ".p2align 16\n\t" @@ -154,7 +154,7 @@ coro_swapcontext(coro_context_t *current, coro_context_t *other); #endif static void -coro_entry_point(coro_t *coro, coro_function_t func) +coro_entry_point(struct coro *coro, coro_function_t func) { int return_value = func(coro); coro->ended = true; @@ -162,7 +162,7 @@ coro_entry_point(coro_t *coro, coro_function_t func) } static void -coro_run_deferred(coro_t *coro) +coro_run_deferred(struct coro *coro) { struct lwan_array *array = (struct lwan_array *)&coro->defer; struct coro_defer_t *defers = array->base; @@ -175,7 +175,7 @@ coro_run_deferred(coro_t *coro) } void -coro_reset(coro_t *coro, coro_function_t func, void *data) +coro_reset(struct coro *coro, coro_function_t func, void *data) { unsigned char *stack = (unsigned char *)(coro + 1); @@ -214,10 +214,10 @@ coro_reset(coro_t *coro, coro_function_t func, void *data) #endif } -ALWAYS_INLINE coro_t * -coro_new(coro_switcher_t *switcher, coro_function_t function, void *data) +ALWAYS_INLINE struct coro * +coro_new(struct coro_switcher *switcher, coro_function_t function, void *data) { - coro_t *coro = malloc(sizeof(*coro) + CORO_STACK_MIN); + struct coro *coro = malloc(sizeof(*coro) + CORO_STACK_MIN); if (UNLIKELY(!coro)) return NULL; @@ -238,7 +238,7 @@ coro_new(coro_switcher_t *switcher, coro_function_t function, void *data) } ALWAYS_INLINE void * -coro_get_data(coro_t *coro) +coro_get_data(struct coro *coro) { assert(coro); @@ -246,7 +246,7 @@ coro_get_data(coro_t *coro) } ALWAYS_INLINE int -coro_resume(coro_t *coro) +coro_resume(struct coro *coro) { assert(coro); assert(coro->ended == false); @@ -257,7 +257,7 @@ coro_resume(coro_t *coro) memcpy(&coro->context, &coro->switcher->callee, sizeof(coro->context)); #else - coro_context_t prev_caller; + coro_context prev_caller; memcpy(&prev_caller, &coro->switcher->caller, sizeof(prev_caller)); coro_swapcontext(&coro->switcher->caller, &coro->context); @@ -273,7 +273,7 @@ coro_resume(coro_t *coro) } ALWAYS_INLINE int -coro_resume_value(coro_t *coro, int value) +coro_resume_value(struct coro *coro, int value) { assert(coro); assert(coro->ended == false); @@ -283,7 +283,7 @@ coro_resume_value(coro_t *coro, int value) } ALWAYS_INLINE int -coro_yield(coro_t *coro, int value) +coro_yield(struct coro *coro, int value) { assert(coro); coro->yield_value = value; @@ -292,7 +292,7 @@ coro_yield(coro_t *coro, int value) } void -coro_free(coro_t *coro) +coro_free(struct coro *coro) { assert(coro); #if !defined(NDEBUG) && defined(USE_VALGRIND) @@ -304,7 +304,7 @@ coro_free(coro_t *coro) } static void -coro_defer_any(coro_t *coro, defer_func func, void *data1, void *data2) +coro_defer_any(struct coro *coro, defer_func func, void *data1, void *data2) { struct coro_defer_t *defer; @@ -322,20 +322,20 @@ coro_defer_any(coro_t *coro, defer_func func, void *data1, void *data2) } ALWAYS_INLINE void -coro_defer(coro_t *coro, void (*func)(void *data), void *data) +coro_defer(struct coro *coro, void (*func)(void *data), void *data) { coro_defer_any(coro, func, data, NULL); } ALWAYS_INLINE void -coro_defer2(coro_t *coro, void (*func)(void *data1, void *data2), +coro_defer2(struct coro *coro, void (*func)(void *data1, void *data2), void *data1, void *data2) { coro_defer_any(coro, func, data1, data2); } void * -coro_malloc_full(coro_t *coro, size_t size, void (*destroy_func)()) +coro_malloc_full(struct coro *coro, size_t size, void (*destroy_func)()) { void *ptr = malloc(size); @@ -346,13 +346,13 @@ coro_malloc_full(coro_t *coro, size_t size, void (*destroy_func)()) } inline void * -coro_malloc(coro_t *coro, size_t size) +coro_malloc(struct coro *coro, size_t size) { return coro_malloc_full(coro, size, free); } char * -coro_strndup(coro_t *coro, const char *str, size_t max_len) +coro_strndup(struct coro *coro, const char *str, size_t max_len) { const size_t len = max_len + 1; char *dup = coro_malloc(coro, len); @@ -364,13 +364,13 @@ coro_strndup(coro_t *coro, const char *str, size_t max_len) } char * -coro_strdup(coro_t *coro, const char *str) +coro_strdup(struct coro *coro, const char *str) { return coro_strndup(coro, str, strlen(str)); } char * -coro_printf(coro_t *coro, const char *fmt, ...) +coro_printf(struct coro *coro, const char *fmt, ...) { va_list values; int len; diff --git a/common/lwan-coro.h b/common/lwan-coro.h index 4da19f816..d0aee6299 100644 --- a/common/lwan-coro.h +++ b/common/lwan-coro.h @@ -22,45 +22,44 @@ #include #if defined(__x86_64__) #include -typedef uintptr_t coro_context_t[10]; +typedef uintptr_t coro_context[10]; #elif defined(__i386__) #include -typedef uintptr_t coro_context_t[7]; +typedef uintptr_t coro_context[7]; #else #include -typedef ucontext_t coro_context_t; +typedef ucontext_t coro_context; #endif -typedef struct coro_t_ coro_t; -typedef struct coro_switcher_t_ coro_switcher_t; +struct coro; -typedef int (*coro_function_t) (coro_t *coro); +typedef int (*coro_function_t) (struct coro *coro); -struct coro_switcher_t_ { - coro_context_t caller; - coro_context_t callee; +struct coro_switcher { + coro_context caller; + coro_context callee; }; -coro_t *coro_new(coro_switcher_t *switcher, coro_function_t function, void *data); -void coro_free(coro_t *coro); +struct coro *coro_new(struct coro_switcher *switcher, coro_function_t function, void *data); +void coro_free(struct coro *coro); -void coro_reset(coro_t *coro, coro_function_t func, void *data); +void coro_reset(struct coro *coro, coro_function_t func, void *data); -int coro_resume(coro_t *coro); -int coro_resume_value(coro_t *coro, int value); -int coro_yield(coro_t *coro, int value); +int coro_resume(struct coro *coro); +int coro_resume_value(struct coro *coro, int value); +int coro_yield(struct coro *coro, int value); -void *coro_get_data(coro_t *coro); +void *coro_get_data(struct coro *coro); -void coro_defer(coro_t *coro, void (*func)(void *data), void *data); -void coro_defer2(coro_t *coro, void (*func)(void *data1, void *data2), +void coro_defer(struct coro *coro, void (*func)(void *data), void *data); +void coro_defer2(struct coro *coro, void (*func)(void *data1, void *data2), void *data1, void *data2); -void *coro_malloc(coro_t *coro, size_t sz); -void *coro_malloc_full(coro_t *coro, size_t size, void (*destroy_func)()); -char *coro_strdup(coro_t *coro, const char *str); -char *coro_strndup(coro_t *coro, const char *str, size_t len); -char *coro_printf(coro_t *coro, const char *fmt, ...); +void *coro_malloc(struct coro *coro, size_t sz); +void *coro_malloc_full(struct coro *coro, size_t size, void (*destroy_func)()); +char *coro_strdup(struct coro *coro, const char *str); +char *coro_strndup(struct coro *coro, const char *str, size_t len); +char *coro_printf(struct coro *coro, const char *fmt, ...); #define CORO_DEFER(fn) ((void (*)(void *))(fn)) #define CORO_DEFER2(fn) ((void (*)(void *, void *))(fn)) diff --git a/common/lwan-http-authorize.c b/common/lwan-http-authorize.c index 6d8ac3bb0..149a3c896 100644 --- a/common/lwan-http-authorize.c +++ b/common/lwan-http-authorize.c @@ -28,11 +28,11 @@ #include "lwan-http-authorize.h" struct realm_password_file_t { - struct cache_entry_t base; + struct cache_entry base; struct hash *entries; }; -static struct cache_t *realm_password_cache = NULL; +static struct cache *realm_password_cache = NULL; static void fourty_two_and_free(void *str) { @@ -44,13 +44,13 @@ static void fourty_two_and_free(void *str) } } -static struct cache_entry_t *create_realm_file( +static struct cache_entry *create_realm_file( const char *key, void *context __attribute__((unused))) { struct realm_password_file_t *rpf = malloc(sizeof(*rpf)); - config_t f; - config_line_t l; + struct config f; + struct config_line l; if (UNLIKELY(!rpf)) return NULL; @@ -105,7 +105,7 @@ static struct cache_entry_t *create_realm_file( } config_close(&f); - return (struct cache_entry_t *)rpf; + return (struct cache_entry *)rpf; error: config_close(&f); @@ -115,7 +115,7 @@ static struct cache_entry_t *create_realm_file( return NULL; } -static void destroy_realm_file(struct cache_entry_t *entry, +static void destroy_realm_file(struct cache_entry *entry, void *context __attribute__((unused))) { struct realm_password_file_t *rpf = (struct realm_password_file_t *)entry; @@ -139,9 +139,9 @@ lwan_http_authorize_shutdown(void) } static bool -authorize(coro_t *coro, - lwan_value_t *authorization, - const char *password_file) +authorize(struct coro *coro, + struct lwan_value *authorization, + const char *password_file) { struct realm_password_file_t *rpf; unsigned char *decoded; @@ -161,7 +161,7 @@ authorize(coro_t *coro, if (UNLIKELY(!decoded)) return false; - if (UNLIKELY(decoded_len >= sizeof(((config_line_t *)0)->buffer))) + if (UNLIKELY(decoded_len >= sizeof(((struct config_line *)0)->buffer))) goto out; colon = memchr(decoded, ':', decoded_len); @@ -181,14 +181,14 @@ authorize(coro_t *coro, } bool -lwan_http_authorize(lwan_request_t *request, - lwan_value_t *authorization, +lwan_http_authorize(struct lwan_request *request, + struct lwan_value *authorization, const char *realm, const char *password_file) { static const char authenticate_tmpl[] = "Basic realm=\"%s\""; static const size_t basic_len = sizeof("Basic ") - 1; - lwan_key_value_t *headers; + struct lwan_key_value *headers; if (!authorization->value) goto unauthorized; diff --git a/common/lwan-http-authorize.h b/common/lwan-http-authorize.h index c2655970b..b56bc5986 100644 --- a/common/lwan-http-authorize.h +++ b/common/lwan-http-authorize.h @@ -25,7 +25,7 @@ bool lwan_http_authorize_init(void); void lwan_http_authorize_shutdown(void); bool -lwan_http_authorize(lwan_request_t *request, - lwan_value_t *authorization, +lwan_http_authorize(struct lwan_request *request, + struct lwan_value *authorization, const char *realm, const char *password_file); diff --git a/common/lwan-io-wrappers.c b/common/lwan-io-wrappers.c index b2a601c55..b297cfe31 100644 --- a/common/lwan-io-wrappers.c +++ b/common/lwan-io-wrappers.c @@ -31,7 +31,7 @@ static const int MAX_FAILED_TRIES = 5; int -lwan_openat(lwan_request_t *request, +lwan_openat(struct lwan_request *request, int dirfd, const char *pathname, int flags) { for (int tries = MAX_FAILED_TRIES; tries; tries--) { @@ -58,7 +58,7 @@ lwan_openat(lwan_request_t *request, } ssize_t -lwan_writev(lwan_request_t *request, struct iovec *iov, int iov_count) +lwan_writev(struct lwan_request *request, struct iovec *iov, int iov_count) { ssize_t total_written = 0; int curr_iov = 0; @@ -101,7 +101,7 @@ lwan_writev(lwan_request_t *request, struct iovec *iov, int iov_count) } ssize_t -lwan_write(lwan_request_t *request, const void *buf, size_t count) +lwan_write(struct lwan_request *request, const void *buf, size_t count) { ssize_t total_written = 0; @@ -135,7 +135,7 @@ lwan_write(lwan_request_t *request, const void *buf, size_t count) } ssize_t -lwan_send(lwan_request_t *request, const void *buf, size_t count, int flags) +lwan_send(struct lwan_request *request, const void *buf, size_t count, int flags) { ssize_t total_sent = 0; @@ -170,7 +170,7 @@ lwan_send(lwan_request_t *request, const void *buf, size_t count, int flags) #if defined(__linux__) void -lwan_sendfile(lwan_request_t *request, int in_fd, off_t offset, size_t count, +lwan_sendfile(struct lwan_request *request, int in_fd, off_t offset, size_t count, const char *header, size_t header_len) { size_t to_be_written = count; @@ -199,7 +199,7 @@ lwan_sendfile(lwan_request_t *request, int in_fd, off_t offset, size_t count, } #elif defined(__FreeBSD__) || defined(__APPLE__) void -lwan_sendfile(lwan_request_t *request, int in_fd, off_t offset, size_t count, +lwan_sendfile(struct lwan_request *request, int in_fd, off_t offset, size_t count, const char *header, size_t header_len) { struct sf_hdtr headers = { diff --git a/common/lwan-io-wrappers.h b/common/lwan-io-wrappers.h index 37d7859ba..9ff2fd03b 100644 --- a/common/lwan-io-wrappers.h +++ b/common/lwan-io-wrappers.h @@ -24,15 +24,15 @@ #include "lwan.h" -int lwan_openat(lwan_request_t *request, int dirfd, const char *pathname, +int lwan_openat(struct lwan_request *request, int dirfd, const char *pathname, int flags); -ssize_t lwan_writev(lwan_request_t *request, struct iovec *iov, +ssize_t lwan_writev(struct lwan_request *request, struct iovec *iov, int iovcnt); -ssize_t lwan_write(lwan_request_t *request, const void *buffer, +ssize_t lwan_write(struct lwan_request *request, const void *buffer, size_t count); -ssize_t lwan_send(lwan_request_t *request, const void *buf, size_t count, +ssize_t lwan_send(struct lwan_request *request, const void *buf, size_t count, int flags); -void lwan_sendfile(lwan_request_t *request, int in_fd, +void lwan_sendfile(struct lwan_request *request, int in_fd, off_t offset, size_t count, const char *header, size_t header_len); diff --git a/common/lwan-lua.c b/common/lwan-lua.c index 3797acee4..c9be519e7 100644 --- a/common/lwan-lua.c +++ b/common/lwan-lua.c @@ -35,7 +35,7 @@ static const char *request_metatable_name = "Lwan.Request"; -struct lwan_lua_priv_t { +struct lwan_lua_priv { char *default_type; char *script_file; char *script; @@ -43,19 +43,19 @@ struct lwan_lua_priv_t { unsigned cache_period; }; -struct lwan_lua_state_t { - struct cache_entry_t base; +struct lwan_lua_state { + struct cache_entry base; lua_State *L; }; -static ALWAYS_INLINE lwan_request_t *userdata_as_request(lua_State *L, int n) +static ALWAYS_INLINE struct lwan_request *userdata_as_request(lua_State *L, int n) { - return *((lwan_request_t **)luaL_checkudata(L, n, request_metatable_name)); + return *((struct lwan_request **)luaL_checkudata(L, n, request_metatable_name)); } static int req_say_cb(lua_State *L) { - lwan_request_t *request = userdata_as_request(L, 1); + struct lwan_request *request = userdata_as_request(L, 1); size_t response_str_len; const char *response_str = lua_tolstring(L, -1, &response_str_len); @@ -67,7 +67,7 @@ static int req_say_cb(lua_State *L) static int req_send_event_cb(lua_State *L) { - lwan_request_t *request = userdata_as_request(L, 1); + struct lwan_request *request = userdata_as_request(L, 1); size_t event_str_len; const char *event_str = lua_tolstring(L, -1, &event_str_len); const char *event_name = lua_tostring(L, -2); @@ -80,7 +80,7 @@ static int req_send_event_cb(lua_State *L) static int req_set_response_cb(lua_State *L) { - lwan_request_t *request = userdata_as_request(L, 1); + struct lwan_request *request = userdata_as_request(L, 1); size_t response_str_len; const char *response_str = lua_tolstring(L, -1, &response_str_len); @@ -90,9 +90,9 @@ static int req_set_response_cb(lua_State *L) } static int request_param_getter(lua_State *L, - const char *(*getter)(lwan_request_t *req, const char *key)) + const char *(*getter)(struct lwan_request *req, const char *key)) { - lwan_request_t *request = userdata_as_request(L, 1); + struct lwan_request *request = userdata_as_request(L, 1); const char *key_str = lua_tostring(L, -1); const char *value = getter(request, key_str); @@ -119,10 +119,10 @@ static int req_cookie_cb(lua_State *L) return request_param_getter(L, lwan_request_get_cookie); } -static bool append_key_value(lua_State *L, coro_t *coro, +static bool append_key_value(lua_State *L, struct coro *coro, struct lwan_key_value_array *arr, char *key, int value_index) { - lwan_key_value_t *kv; + struct lwan_key_value *kv; kv = lwan_key_value_array_append(arr); if (!kv) @@ -141,9 +141,9 @@ static int req_set_headers_cb(lua_State *L) const int value_index = 2 + table_index; const int nested_value_index = value_index * 2 - table_index; struct lwan_key_value_array *headers; - lwan_request_t *request = userdata_as_request(L, 1); - coro_t *coro = request->conn->coro; - lwan_key_value_t *kv; + struct lwan_request *request = userdata_as_request(L, 1); + struct coro *coro = request->conn->coro; + struct lwan_key_value *kv; if (request->flags & RESPONSE_SENT_HEADERS) goto out; @@ -247,35 +247,35 @@ lua_State *lwan_lua_create_state(const char *script_file, const char *script) return NULL; } -static struct cache_entry_t *state_create(const char *key __attribute__((unused)), +static struct cache_entry *state_create(const char *key __attribute__((unused)), void *context) { - struct lwan_lua_priv_t *priv = context; - struct lwan_lua_state_t *state = malloc(sizeof(*state)); + struct lwan_lua_priv *priv = context; + struct lwan_lua_state *state = malloc(sizeof(*state)); if (UNLIKELY(!state)) return NULL; state->L = lwan_lua_create_state(priv->script_file, priv->script); if (LIKELY(state->L)) - return (struct cache_entry_t *)state; + return (struct cache_entry *)state; free(state); return NULL; } -static void state_destroy(struct cache_entry_t *entry, +static void state_destroy(struct cache_entry *entry, void *context __attribute__((unused))) { - struct lwan_lua_state_t *state = (struct lwan_lua_state_t *)entry; + struct lwan_lua_state *state = (struct lwan_lua_state *)entry; lua_close(state->L); free(state); } -static struct cache_t *get_or_create_cache(struct lwan_lua_priv_t *priv) +static struct cache *get_or_create_cache(struct lwan_lua_priv *priv) { - struct cache_t *cache = pthread_getspecific(priv->cache_key); + struct cache *cache = pthread_getspecific(priv->cache_key); if (UNLIKELY(!cache)) { lwan_status_debug("Creating cache for this thread"); cache = cache_create(state_create, state_destroy, priv, priv->cache_period); @@ -295,7 +295,7 @@ static void unref_thread(void *data1, void *data2) luaL_unref(L, LUA_REGISTRYINDEX, thread_ref); } -static ALWAYS_INLINE const char *get_handle_prefix(lwan_request_t *request, size_t *len) +static ALWAYS_INLINE const char *get_handle_prefix(struct lwan_request *request, size_t *len) { if (request->flags & REQUEST_METHOD_GET) { *len = sizeof("handle_get_"); @@ -313,7 +313,7 @@ static ALWAYS_INLINE const char *get_handle_prefix(lwan_request_t *request, size return NULL; } -static bool get_handler_function(lua_State *L, lwan_request_t *request) +static bool get_handler_function(lua_State *L, struct lwan_request *request) { char handler_name[128]; size_t handle_prefix_len; @@ -352,15 +352,15 @@ static bool get_handler_function(lua_State *L, lwan_request_t *request) return lua_isfunction(L, -1); } -void lwan_lua_state_push_request(lua_State *L, lwan_request_t *request) +void lwan_lua_state_push_request(lua_State *L, struct lwan_request *request) { - lwan_request_t **userdata = lua_newuserdata(L, sizeof(lwan_request_t *)); + struct lwan_request **userdata = lua_newuserdata(L, sizeof(struct lwan_request *)); *userdata = request; luaL_getmetatable(L, request_metatable_name); lua_setmetatable(L, -2); } -static lua_State *push_newthread(lua_State *L, coro_t *coro) +static lua_State *push_newthread(lua_State *L, struct coro *coro) { lua_State *L1 = lua_newthread(L); if (UNLIKELY(!L1)) @@ -372,21 +372,21 @@ static lua_State *push_newthread(lua_State *L, coro_t *coro) return L1; } -static lwan_http_status_t -lua_handle_cb(lwan_request_t *request, - lwan_response_t *response, +static enum lwan_http_status +lua_handle_cb(struct lwan_request *request, + struct lwan_response *response, void *data) { - struct lwan_lua_priv_t *priv = data; + struct lwan_lua_priv *priv = data; if (UNLIKELY(!priv)) return HTTP_INTERNAL_ERROR; - struct cache_t *cache = get_or_create_cache(priv); + struct cache *cache = get_or_create_cache(priv); if (UNLIKELY(!cache)) return HTTP_INTERNAL_ERROR; - struct lwan_lua_state_t *state = (struct lwan_lua_state_t *)cache_coro_get_and_ref_entry( + struct lwan_lua_state *state = (struct lwan_lua_state *)cache_coro_get_and_ref_entry( cache, request->conn->coro, ""); if (UNLIKELY(!state)) return HTTP_NOT_FOUND; @@ -418,8 +418,8 @@ lua_handle_cb(lwan_request_t *request, static void *lua_init(const char *prefix __attribute__((unused)), void *data) { - struct lwan_lua_settings_t *settings = data; - struct lwan_lua_priv_t *priv; + struct lwan_lua_settings *settings = data; + struct lwan_lua_priv *priv; priv = calloc(1, sizeof(*priv)); if (!priv) { @@ -470,7 +470,7 @@ static void *lua_init(const char *prefix __attribute__((unused)), void *data) static void lua_shutdown(void *data) { - struct lwan_lua_priv_t *priv = data; + struct lwan_lua_priv *priv = data; if (priv) { pthread_key_delete(priv->cache_key); free(priv->default_type); @@ -482,7 +482,7 @@ static void lua_shutdown(void *data) static void *lua_init_from_hash(const char *prefix, const struct hash *hash) { - struct lwan_lua_settings_t settings = { + struct lwan_lua_settings settings = { .default_type = hash_find(hash, "default_type"), .script_file = hash_find(hash, "script_file"), .cache_period = parse_time_period(hash_find(hash, "cache_period"), 15), @@ -491,9 +491,9 @@ static void *lua_init_from_hash(const char *prefix, const struct hash *hash) return lua_init(prefix, &settings); } -const lwan_module_t *lwan_module_lua(void) +const struct lwan_module *lwan_module_lua(void) { - static const lwan_module_t lua_module = { + static const struct lwan_module lua_module = { .init = lua_init, .init_from_hash = lua_init_from_hash, .shutdown = lua_shutdown, diff --git a/common/lwan-lua.h b/common/lwan-lua.h index a0f8cf48c..e8251fd69 100644 --- a/common/lwan-lua.h +++ b/common/lwan-lua.h @@ -21,7 +21,7 @@ #include "lwan.h" -struct lwan_lua_settings_t { +struct lwan_lua_settings { const char *default_type; const char *script_file; const char *script; @@ -30,9 +30,9 @@ struct lwan_lua_settings_t { #define LUA(default_type_) \ .module = lwan_module_lua(), \ - .args = ((struct lwan_lua_t[]) {{ \ + .args = ((struct lwan_lua[]) {{ \ .default_type = default_type_ \ }}), \ .flags = 0 -const lwan_module_t *lwan_module_lua(void); +const struct lwan_module *lwan_module_lua(void); diff --git a/common/lwan-private.h b/common/lwan-private.h index 1eda589dc..6608cd407 100644 --- a/common/lwan-private.h +++ b/common/lwan-private.h @@ -21,18 +21,18 @@ #include "lwan.h" -void lwan_response_init(lwan_t *l); -void lwan_response_shutdown(lwan_t *l); +void lwan_response_init(struct lwan *l); +void lwan_response_shutdown(struct lwan *l); -void lwan_socket_init(lwan_t *l); -void lwan_socket_shutdown(lwan_t *l); +void lwan_socket_init(struct lwan *l); +void lwan_socket_shutdown(struct lwan *l); -void lwan_thread_init(lwan_t *l); -void lwan_thread_shutdown(lwan_t *l); -void lwan_thread_add_client(lwan_thread_t *t, int fd); +void lwan_thread_init(struct lwan *l); +void lwan_thread_shutdown(struct lwan *l); +void lwan_thread_add_client(struct lwan_thread *t, int fd); -void lwan_status_init(lwan_t *l); -void lwan_status_shutdown(lwan_t *l); +void lwan_status_init(struct lwan *l); +void lwan_status_shutdown(struct lwan *l); void lwan_job_thread_init(void); void lwan_job_thread_shutdown(void); @@ -42,10 +42,10 @@ void lwan_job_del(bool (*cb)(void *data), void *data); void lwan_tables_init(void); void lwan_tables_shutdown(void); -char *lwan_process_request(lwan_t *l, lwan_request_t *request, - lwan_value_t *buffer, char *next_request); +char *lwan_process_request(struct lwan *l, struct lwan_request *request, + struct lwan_value *buffer, char *next_request); -void lwan_straitjacket_enforce(config_t *c, config_line_t *l); +void lwan_straitjacket_enforce(struct config *c, struct config_line *l); uint8_t lwan_char_isspace(char ch) __attribute__((pure)); uint8_t lwan_char_isxdigit(char ch) __attribute__((pure)); @@ -54,6 +54,6 @@ uint8_t lwan_char_isxdigit(char ch) __attribute__((pure)); #include lua_State *lwan_lua_create_state(const char *script_file, const char *script); -void lwan_lua_state_push_request(lua_State *L, lwan_request_t *request); +void lwan_lua_state_push_request(lua_State *L, struct lwan_request *request); const char *lwan_lua_state_last_error(lua_State *L); #endif diff --git a/common/lwan-redirect.c b/common/lwan-redirect.c index 4aa89715f..4c851c0c7 100644 --- a/common/lwan-redirect.c +++ b/common/lwan-redirect.c @@ -23,15 +23,15 @@ #include "lwan.h" #include "lwan-redirect.h" -static lwan_http_status_t -redirect_handle_cb(lwan_request_t *request, - lwan_response_t *response, +static enum lwan_http_status +redirect_handle_cb(struct lwan_request *request, + struct lwan_response *response, void *data) { if (UNLIKELY(!data)) return HTTP_INTERNAL_ERROR; - lwan_key_value_t *headers = coro_malloc(request->conn->coro, sizeof(*headers) * 2); + struct lwan_key_value *headers = coro_malloc(request->conn->coro, sizeof(*headers) * 2); if (UNLIKELY(!headers)) return HTTP_INTERNAL_ERROR; @@ -47,21 +47,21 @@ redirect_handle_cb(lwan_request_t *request, static void *redirect_init(const char *prefix __attribute__((unused)), void *data) { - struct lwan_redirect_settings_t *settings = data; + struct lwan_redirect_settings *settings = data; return (settings->to) ? strdup(settings->to) : NULL; } static void *redirect_init_from_hash(const char *prefix, const struct hash *hash) { - struct lwan_redirect_settings_t settings = { + struct lwan_redirect_settings settings = { .to = hash_find(hash, "to") }; return redirect_init(prefix, &settings); } -const lwan_module_t *lwan_module_redirect(void) +const struct lwan_module *lwan_module_redirect(void) { - static const lwan_module_t redirect_module = { + static const struct lwan_module redirect_module = { .init = redirect_init, .init_from_hash = redirect_init_from_hash, .shutdown = free, diff --git a/common/lwan-redirect.h b/common/lwan-redirect.h index d74967504..c2de1c184 100644 --- a/common/lwan-redirect.h +++ b/common/lwan-redirect.h @@ -21,16 +21,16 @@ #include "lwan.h" -struct lwan_redirect_settings_t { +struct lwan_redirect_settings { char *to; }; #define REDIRECT(to_) \ .module = lwan_module_redirect(), \ - .args = ((struct lwan_redirect_settings_t[]) {{ \ + .args = ((struct lwan_redirect_settings[]) {{ \ .to = to_ \ }}), \ .flags = 0 -const lwan_module_t *lwan_module_redirect(void); +const struct lwan_module *lwan_module_redirect(void); diff --git a/common/lwan-request.c b/common/lwan-request.c index ab890f5a7..2a313792e 100644 --- a/common/lwan-request.c +++ b/common/lwan-request.c @@ -34,29 +34,29 @@ #include "lwan-config.h" #include "lwan-http-authorize.h" -typedef enum { +enum lwan_read_finalizer { FINALIZER_DONE, FINALIZER_TRY_AGAIN, FINALIZER_YIELD_TRY_AGAIN, FINALIZER_ERROR_TOO_LARGE, FINALIZER_ERROR_TIMEOUT -} lwan_read_finalizer_t; +}; struct request_parser_helper { - lwan_value_t *buffer; + struct lwan_value *buffer; char *next_request; /* For pipelined requests */ - lwan_value_t accept_encoding; - lwan_value_t if_modified_since; - lwan_value_t range; - lwan_value_t cookie; + struct lwan_value accept_encoding; + struct lwan_value if_modified_since; + struct lwan_value range; + struct lwan_value cookie; - lwan_value_t query_string; - lwan_value_t fragment; - lwan_value_t content_length; - lwan_value_t authorization; + struct lwan_value query_string; + struct lwan_value fragment; + struct lwan_value content_length; + struct lwan_value authorization; - lwan_value_t post_data; - lwan_value_t content_type; + struct lwan_value post_data; + struct lwan_value content_type; time_t error_when_time; int error_when_n_packets; @@ -133,12 +133,12 @@ strsep_char(char *strp, char delim) } static char * -parse_proxy_protocol_v1(lwan_request_t *request, char *buffer) +parse_proxy_protocol_v1(struct lwan_request *request, char *buffer) { union proxy_protocol_header *hdr = (union proxy_protocol_header *) buffer; char *end, *protocol, *src_addr, *dst_addr, *src_port, *dst_port; unsigned int size; - lwan_proxy_t *const proxy = request->proxy; + struct lwan_proxy *const proxy = request->proxy; end = memchr(hdr->v1.line, '\r', sizeof(hdr->v1.line)); if (UNLIKELY(!end || end[1] != '\n')) @@ -204,12 +204,12 @@ parse_proxy_protocol_v1(lwan_request_t *request, char *buffer) } static char * -parse_proxy_protocol_v2(lwan_request_t *request, char *buffer) +parse_proxy_protocol_v2(struct lwan_request *request, char *buffer) { union proxy_protocol_header *hdr = (union proxy_protocol_header *)buffer; const unsigned int proto_signature_length = 16; unsigned int size; - lwan_proxy_t *const proxy = request->proxy; + struct lwan_proxy *const proxy = request->proxy; enum { LOCAL = 0x20, @@ -262,7 +262,7 @@ parse_proxy_protocol_v2(lwan_request_t *request, char *buffer) } static ALWAYS_INLINE char * -identify_http_method(lwan_request_t *request, char *buffer) +identify_http_method(struct lwan_request *request, char *buffer) { enum { HTTP_STR_GET = MULTICHAR_CONSTANT('G','E','T',' '), @@ -323,15 +323,15 @@ url_decode(char *str) static int key_value_compare(const void *a, const void *b) { - return strcmp(((lwan_key_value_t *)a)->key, ((lwan_key_value_t *)b)->key); + return strcmp(((struct lwan_key_value *)a)->key, ((struct lwan_key_value *)b)->key); } static void -parse_key_values(lwan_request_t *request, - lwan_value_t *helper_value, struct lwan_key_value_array *array, +parse_key_values(struct lwan_request *request, + struct lwan_value *helper_value, struct lwan_key_value_array *array, size_t (*decode_value)(char *value), const char separator) { - lwan_key_value_t *kv; + struct lwan_key_value *kv; char *ptr = helper_value->value; if (!helper_value->len) @@ -390,21 +390,21 @@ identity_decode(char *input __attribute__((unused))) } static void -parse_cookies(lwan_request_t *request, struct request_parser_helper *helper) +parse_cookies(struct lwan_request *request, struct request_parser_helper *helper) { parse_key_values(request, &helper->cookie, &request->cookies, identity_decode, ';'); } static void -parse_query_string(lwan_request_t *request, struct request_parser_helper *helper) +parse_query_string(struct lwan_request *request, struct request_parser_helper *helper) { parse_key_values(request, &helper->query_string, &request->query_params, url_decode, '&'); } static void -parse_post_data(lwan_request_t *request, struct request_parser_helper *helper) +parse_post_data(struct lwan_request *request, struct request_parser_helper *helper) { static const char content_type[] = "application/x-www-form-urlencoded"; @@ -421,7 +421,7 @@ parse_post_data(lwan_request_t *request, struct request_parser_helper *helper) } static void -parse_fragment_and_query(lwan_request_t *request, +parse_fragment_and_query(struct lwan_request *request, struct request_parser_helper *helper, const char *space) { /* Most of the time, fragments are small -- so search backwards */ @@ -445,7 +445,7 @@ parse_fragment_and_query(lwan_request_t *request, } static char * -identify_http_path(lwan_request_t *request, char *buffer, +identify_http_path(struct lwan_request *request, char *buffer, struct request_parser_helper *helper) { static const size_t minimal_request_line_len = sizeof("/ HTTP/1.0") - 1; @@ -604,7 +604,7 @@ parse_headers(struct request_parser_helper *helper, char *buffer, char *buffer_e #undef MATCH_HEADER static void -parse_if_modified_since(lwan_request_t *request, struct request_parser_helper *helper) +parse_if_modified_since(struct lwan_request *request, struct request_parser_helper *helper) { if (UNLIKELY(!helper->if_modified_since.len)) return; @@ -622,7 +622,7 @@ parse_if_modified_since(lwan_request_t *request, struct request_parser_helper *h } static void -parse_range(lwan_request_t *request, struct request_parser_helper *helper) +parse_range(struct lwan_request *request, struct request_parser_helper *helper) { if (UNLIKELY(helper->range.len <= (sizeof("bytes=") - 1))) return; @@ -650,7 +650,7 @@ parse_range(lwan_request_t *request, struct request_parser_helper *helper) } static void -parse_accept_encoding(lwan_request_t *request, struct request_parser_helper *helper) +parse_accept_encoding(struct lwan_request *request, struct request_parser_helper *helper) { if (!helper->accept_encoding.len) return; @@ -688,7 +688,7 @@ ignore_leading_whitespace(char *buffer) } static ALWAYS_INLINE void -compute_keep_alive_flag(lwan_request_t *request, struct request_parser_helper *helper) +compute_keep_alive_flag(struct lwan_request *request, struct request_parser_helper *helper) { bool is_keep_alive; if (request->flags & REQUEST_IS_HTTP_1_0) @@ -701,9 +701,9 @@ compute_keep_alive_flag(lwan_request_t *request, struct request_parser_helper *h request->conn->flags &= ~CONN_KEEP_ALIVE; } -static lwan_http_status_t read_from_request_socket(lwan_request_t *request, - lwan_value_t *buffer, struct request_parser_helper *helper, const size_t buffer_size, - lwan_read_finalizer_t (*finalizer)(size_t total_read, size_t buffer_size, struct request_parser_helper *helper, int n_packets)) +static enum lwan_http_status read_from_request_socket(struct lwan_request *request, + struct lwan_value *buffer, struct request_parser_helper *helper, const size_t buffer_size, + enum lwan_read_finalizer (*finalizer)(size_t total_read, size_t buffer_size, struct request_parser_helper *helper, int n_packets)) { ssize_t n; size_t total_read = 0; @@ -772,7 +772,7 @@ static lwan_http_status_t read_from_request_socket(lwan_request_t *request, return HTTP_INTERNAL_ERROR; } -static lwan_read_finalizer_t read_request_finalizer(size_t total_read, +static enum lwan_read_finalizer read_request_finalizer(size_t total_read, size_t buffer_size, struct request_parser_helper *helper, int n_packets) { /* 16 packets should be enough to read a request (without the body, as @@ -801,14 +801,14 @@ static lwan_read_finalizer_t read_request_finalizer(size_t total_read, return FINALIZER_TRY_AGAIN; } -static ALWAYS_INLINE lwan_http_status_t -read_request(lwan_request_t *request, struct request_parser_helper *helper) +static ALWAYS_INLINE enum lwan_http_status +read_request(struct lwan_request *request, struct request_parser_helper *helper) { return read_from_request_socket(request, helper->buffer, helper, DEFAULT_BUFFER_SIZE, read_request_finalizer); } -static lwan_read_finalizer_t post_data_finalizer(size_t total_read, +static enum lwan_read_finalizer post_data_finalizer(size_t total_read, size_t buffer_size, struct request_parser_helper *helper, int n_packets) { if (buffer_size == total_read) @@ -840,8 +840,8 @@ static ALWAYS_INLINE int calculate_n_packets(size_t total) return max(1, (int)(total / 740)); } -static lwan_http_status_t -read_post_data(lwan_request_t *request, struct request_parser_helper *helper) +static enum lwan_http_status +read_post_data(struct lwan_request *request, struct request_parser_helper *helper) { /* Holy indirection, Batman! */ const size_t max_post_data_size = request->conn->thread->lwan->config.max_post_data_size; @@ -885,13 +885,13 @@ read_post_data(lwan_request_t *request, struct request_parser_helper *helper) helper->error_when_time = time(NULL) + request->conn->thread->lwan->config.keep_alive_timeout; helper->error_when_n_packets = calculate_n_packets(post_data_size); - lwan_value_t buffer = { .value = new_buffer, .len = post_data_size - have }; + struct lwan_value buffer = { .value = new_buffer, .len = post_data_size - have }; return read_from_request_socket(request, &buffer, helper, buffer.len, post_data_finalizer); } static char * -parse_proxy_protocol(lwan_request_t *request, char *buffer) +parse_proxy_protocol(struct lwan_request *request, char *buffer) { enum { HTTP_PROXY_VER1 = MULTICHAR_CONSTANT('P','R','O','X'), @@ -908,8 +908,8 @@ parse_proxy_protocol(lwan_request_t *request, char *buffer) return buffer; } -static lwan_http_status_t -parse_http_request(lwan_request_t *request, struct request_parser_helper *helper) +static enum lwan_http_status +parse_http_request(struct lwan_request *request, struct request_parser_helper *helper) { char *buffer = helper->buffer->value; @@ -945,9 +945,9 @@ parse_http_request(lwan_request_t *request, struct request_parser_helper *helper return HTTP_OK; } -static lwan_http_status_t -prepare_for_response(lwan_url_map_t *url_map, - lwan_request_t *request, +static enum lwan_http_status +prepare_for_response(struct lwan_url_map *url_map, + struct lwan_request *request, struct request_parser_helper *helper) { request->url.value += url_map->prefix_len; @@ -984,7 +984,7 @@ prepare_for_response(lwan_url_map_t *url_map, } if (request->flags & REQUEST_METHOD_POST) { - lwan_http_status_t status; + enum lwan_http_status status; if (!(url_map->flags & HANDLER_PARSE_POST_DATA)) { /* FIXME: Discard POST data here? If a POST request is sent @@ -1006,7 +1006,7 @@ prepare_for_response(lwan_url_map_t *url_map, } static bool -handle_rewrite(lwan_request_t *request, struct request_parser_helper *helper) +handle_rewrite(struct lwan_request *request, struct request_parser_helper *helper) { request->flags &= ~RESPONSE_URL_REWRITTEN; @@ -1023,11 +1023,11 @@ handle_rewrite(lwan_request_t *request, struct request_parser_helper *helper) } char * -lwan_process_request(lwan_t *l, lwan_request_t *request, - lwan_value_t *buffer, char *next_request) +lwan_process_request(struct lwan *l, struct lwan_request *request, + struct lwan_value *buffer, char *next_request) { - lwan_http_status_t status; - lwan_url_map_t *url_map; + enum lwan_http_status status; + struct lwan_url_map *url_map; struct request_parser_helper helper = { .buffer = buffer, @@ -1090,8 +1090,8 @@ value_lookup(const struct lwan_key_value_array *array, const char *key) const struct lwan_array *la = (const struct lwan_array *)array; if (LIKELY(la->elements)) { - lwan_key_value_t k = { .key = (char *)key }; - lwan_key_value_t *entry; + struct lwan_key_value k = { .key = (char *)key }; + struct lwan_key_value *entry; entry = bsearch(&k, la->base, la->elements - 1, sizeof(k), key_value_compare); if (LIKELY(entry)) @@ -1102,31 +1102,31 @@ value_lookup(const struct lwan_key_value_array *array, const char *key) } const char * -lwan_request_get_query_param(lwan_request_t *request, const char *key) +lwan_request_get_query_param(struct lwan_request *request, const char *key) { return value_lookup(&request->query_params, key); } const char * -lwan_request_get_post_param(lwan_request_t *request, const char *key) +lwan_request_get_post_param(struct lwan_request *request, const char *key) { return value_lookup(&request->post_data, key); } const char * -lwan_request_get_cookie(lwan_request_t *request, const char *key) +lwan_request_get_cookie(struct lwan_request *request, const char *key) { return value_lookup(&request->cookies, key); } ALWAYS_INLINE int -lwan_connection_get_fd(const lwan_t *lwan, const lwan_connection_t *conn) +lwan_connection_get_fd(const struct lwan *lwan, const struct lwan_connection *conn) { return (int)(ptrdiff_t)(conn - lwan->conns); } const char * -lwan_request_get_remote_address(lwan_request_t *request, +lwan_request_get_remote_address(struct lwan_request *request, char buffer[static INET6_ADDRSTRLEN]) { struct sockaddr_storage non_proxied_addr = { .ss_family = AF_UNSPEC }; diff --git a/common/lwan-response.c b/common/lwan-response.c index acbadea45..6b5e36a79 100644 --- a/common/lwan-response.c +++ b/common/lwan-response.c @@ -31,7 +31,7 @@ #include "lwan-io-wrappers.h" #include "lwan-template.h" -static lwan_tpl_t *error_template = NULL; +static struct lwan_tpl *error_template = NULL; static const char *error_template_str = "" \ + "Lwan Clock Sample\n" + "" \ + "" \ + "" \ + "
" \ + "
" \ + "
" \ + "" \ + ""; + response->mime_type = "text/html"; + lwan_strbuf_set_static(response->buffer, index, sizeof(index) - 1); + + return HTTP_OK; +} + int main(void) { const struct lwan_url_map default_map[] = { - {.prefix = "/", .handler = LWAN_HANDLER_REF(clock)}, + {.prefix = "/clock.gif", .handler = LWAN_HANDLER_REF(clock)}, + {.prefix = "/", .handler = LWAN_HANDLER_REF(index)}, {.prefix = NULL}, }; struct lwan l; From d8a9f86b31a9226a27cd9e8175b494ea4f425855 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 28 Jul 2018 19:36:35 -0700 Subject: [PATCH 0683/2505] /sleep test endpoint shouldn't crash if `ms` query param isn't set --- src/bin/testrunner/main.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/bin/testrunner/main.c b/src/bin/testrunner/main.c index 8dccd943f..39ab43d8c 100644 --- a/src/bin/testrunner/main.c +++ b/src/bin/testrunner/main.c @@ -204,9 +204,14 @@ LWAN_HANDLER(hello_world) LWAN_HANDLER(sleep) { + const char *ms_param = lwan_request_get_query_param(request, "ms"); + uint64_t ms; + + if (!ms_param) + return HTTP_INTERNAL_ERROR; + + ms = (uint64_t)parse_long(ms_param, 0); response->mime_type = "text/plain"; - uint64_t ms = - (uint64_t)parse_long(lwan_request_get_query_param(request, "ms"), 0); if (ms) { struct timespec t1, t2; From fc7b64f88c4a5fbeacd2f500b8eb8fda69bbf167 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 29 Jul 2018 10:41:43 -0700 Subject: [PATCH 0684/2505] Draw blinking second dots in clock sample --- src/samples/clock/main.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/samples/clock/main.c b/src/samples/clock/main.c index c88251753..8b32752f9 100644 --- a/src/samples/clock/main.c +++ b/src/samples/clock/main.c @@ -31,7 +31,7 @@ static const uint8_t font[10][5] = { [9] = {7, 5, 7, 1, 7}, }; -static const uint16_t width = 24; +static const uint16_t width = 24 + 6; static const uint16_t height = 5; static void destroy_gif(void *data) @@ -43,7 +43,9 @@ static void destroy_gif(void *data) LWAN_HANDLER(clock) { + static const uint8_t base_offsets[] = {0, 0, 2, 2, 4, 4}; ge_GIF *gif = ge_new_gif(response->buffer, width, height, NULL, 2, 0); + uint8_t dot_visible = 0; if (!gif) return HTTP_INTERNAL_ERROR; @@ -71,17 +73,25 @@ LWAN_HANDLER(clock) for (digit = 0; digit < 6; digit++) { int dig = digits[digit] - '0'; + uint8_t off = base_offsets[digit]; for (line = 0, base = digit * 4; line < 5; line++, base += width) { - gif->frame[base + 0] = !!(font[dig][line] & 1<<2); - gif->frame[base + 1] = !!(font[dig][line] & 1<<1); - gif->frame[base + 2] = !!(font[dig][line] & 1<<0); + gif->frame[base + 0 + off] = !!(font[dig][line] & 1<<2); + gif->frame[base + 1 + off] = !!(font[dig][line] & 1<<1); + gif->frame[base + 2 + off] = !!(font[dig][line] & 1<<0); + } } + gif->frame[8 + width] = dot_visible; + gif->frame[18 + width] = dot_visible; + gif->frame[8 + width * 3] = dot_visible; + gif->frame[18 + width * 3] = dot_visible; + dot_visible = dot_visible ? 0 : 3; + ge_add_frame(gif, 100); lwan_response_send_chunk(request); - lwan_request_sleep(request, 1000); + lwan_request_sleep(request, 500); } return HTTP_OK; From 50148d5905794ee3f10552279e4e9a416f849fa7 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 29 Jul 2018 11:12:35 -0700 Subject: [PATCH 0685/2505] Remove timeout iterator code timeout_next() has been removed prior to integrating the timer wheel code in Lwan because it wasn't reentrant, but the function prototypes, macros, and structs to implement the timeout iterators were still in timeout.h. --- src/lib/timeout.h | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/src/lib/timeout.h b/src/lib/timeout.h index 81c824b78..a6521cab1 100644 --- a/src/lib/timeout.h +++ b/src/lib/timeout.h @@ -206,31 +206,6 @@ TIMEOUT_PUBLIC bool timeouts_check(struct timeouts *, FILE *); #define TIMEOUTS_ALL (TIMEOUTS_PENDING|TIMEOUTS_EXPIRED) #define TIMEOUTS_CLEAR 0x40 -#define TIMEOUTS_IT_INITIALIZER(flags) { (flags), 0, 0, 0, 0 } - -#define TIMEOUTS_IT_INIT(cur, _flags) do { \ - (cur)->flags = (_flags); \ - (cur)->pc = 0; \ -} while (0) - -struct timeouts_it { - int flags; - unsigned pc, i, j; - struct timeout *to; -}; /* struct timeouts_it */ - -TIMEOUT_PUBLIC struct timeout *timeouts_next(struct timeouts *, struct timeouts_it *); -/* return next timeout in pending wheel or expired queue. caller can delete - * the returned timeout, but should not otherwise manipulate the timing - * wheel. in particular, caller SHOULD NOT delete any other timeout as that - * could invalidate cursor state and trigger a use-after-free. - */ - -#define TIMEOUTS_FOREACH(var, T, flags) \ - struct timeouts_it _it = TIMEOUTS_IT_INITIALIZER((flags)); \ - while (((var) = timeouts_next((T), &_it))) - - /* * B O N U S W H E E L I N T E R F A C E S * From 1297e48c49e1ccdcde5bd49b8400f601f4ab0b04 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 29 Jul 2018 12:07:36 -0700 Subject: [PATCH 0686/2505] Do not add delay between frames --- src/samples/clock/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/samples/clock/main.c b/src/samples/clock/main.c index 8b32752f9..cf1aef90c 100644 --- a/src/samples/clock/main.c +++ b/src/samples/clock/main.c @@ -89,7 +89,7 @@ LWAN_HANDLER(clock) gif->frame[18 + width * 3] = dot_visible; dot_visible = dot_visible ? 0 : 3; - ge_add_frame(gif, 100); + ge_add_frame(gif, 0); lwan_response_send_chunk(request); lwan_request_sleep(request, 500); } From d4d75baf01162abf891e40e105133f8b555d8bc6 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 29 Jul 2018 15:56:19 -0700 Subject: [PATCH 0687/2505] Use list.h instead of QUEUE(3) Instead of wasting a lot of time trying to understand why timeouts_close() was making an invalid pointer access, just port the whole timeout library to use list.h instead of QUEUE(3). Functionality is the same. --- src/lib/timeout.c | 72 +++++++++++++++++------------------------------ src/lib/timeout.h | 7 ++--- 2 files changed, 29 insertions(+), 50 deletions(-) diff --git a/src/lib/timeout.c b/src/lib/timeout.c index 5f0999e9b..0b3d74594 100644 --- a/src/lib/timeout.c +++ b/src/lib/timeout.c @@ -35,7 +35,7 @@ #include /* errno */ -#include /* TAILQ(3) */ +#include "list.h" #include "timeout.h" @@ -73,24 +73,6 @@ #define MAX(a, b) (((a) > (b))? (a) : (b)) #endif -#if !defined TAILQ_CONCAT -#define TAILQ_CONCAT(head1, head2, field) do { \ - if (!TAILQ_EMPTY(head2)) { \ - *(head1)->tqh_last = (head2)->tqh_first; \ - (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \ - (head1)->tqh_last = (head2)->tqh_last; \ - TAILQ_INIT((head2)); \ - } \ -} while (0) -#endif - -#if !defined TAILQ_FOREACH_SAFE -#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \ - for ((var) = TAILQ_FIRST(head); \ - (var) && ((tvar) = TAILQ_NEXT(var, field), 1); \ - (var) = (tvar)) -#endif - /* * B I T M A N I P U L A T I O N R O U T I N E S @@ -203,10 +185,9 @@ static inline wheel_t rotr(const wheel_t v, unsigned int c) { * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -TAILQ_HEAD(timeout_list, timeout); - struct timeouts { - struct timeout_list wheel[WHEEL_NUM][WHEEL_LEN], expired; + struct list_head wheel[WHEEL_NUM][WHEEL_LEN]; + struct list_head expired; wheel_t pending[WHEEL_NUM]; @@ -220,11 +201,11 @@ static struct timeouts *timeouts_init(struct timeouts *T, timeout_t hz) { for (i = 0; i < countof(T->wheel); i++) { for (j = 0; j < countof(T->wheel[i]); j++) { - TAILQ_INIT(&T->wheel[i][j]); + list_head_init(&T->wheel[i][j]); } } - TAILQ_INIT(&T->expired); + list_head_init(&T->expired); for (i = 0; i < countof(T->pending); i++) { T->pending[i] = 0; @@ -250,21 +231,21 @@ TIMEOUT_PUBLIC struct timeouts *timeouts_open(timeout_t hz, int *error) { static void timeouts_reset(struct timeouts *T) { - struct timeout_list reset; + struct list_head reset; struct timeout *to; unsigned i, j; - TAILQ_INIT(&reset); + list_head_init(&reset); for (i = 0; i < countof(T->wheel); i++) { for (j = 0; j < countof(T->wheel[i]); j++) { - TAILQ_CONCAT(&reset, &T->wheel[i][j], tqe); + list_append_list(&reset, &T->wheel[i][j]); } } - TAILQ_CONCAT(&reset, &T->expired, tqe); + list_append_list(&reset, &T->expired); - TAILQ_FOREACH(to, &reset, tqe) { + list_for_each(&reset, to, tqe) { to->pending = NULL; TO_SET_TIMEOUTS(to, NULL); } @@ -289,9 +270,9 @@ TIMEOUT_PUBLIC timeout_t timeouts_hz(struct timeouts *T) { TIMEOUT_PUBLIC void timeouts_del(struct timeouts *T, struct timeout *to) { if (to->pending) { - TAILQ_REMOVE(to->pending, to, tqe); + list_del_from(to->pending, &to->tqe); - if (to->pending != &T->expired && TAILQ_EMPTY(to->pending)) { + if (to->pending != &T->expired && list_empty(to->pending)) { ptrdiff_t index = to->pending - &T->wheel[0][0]; ptrdiff_t wheel = index / WHEEL_LEN; ptrdiff_t slot = index % WHEEL_LEN; @@ -343,13 +324,13 @@ static void timeouts_sched(struct timeouts *T, struct timeout *to, timeout_t exp slot = timeout_slot(wheel, to->expires); to->pending = &T->wheel[wheel][slot]; - TAILQ_INSERT_TAIL(to->pending, to, tqe); T->pending[wheel] |= WHEEL_C(1) << slot; } else { to->pending = &T->expired; - TAILQ_INSERT_TAIL(to->pending, to, tqe); } + + list_add_tail(to->pending, &to->tqe); } /* timeouts_sched() */ @@ -387,10 +368,10 @@ TIMEOUT_PUBLIC void timeouts_add(struct timeouts *T, struct timeout *to, timeout TIMEOUT_PUBLIC void timeouts_update(struct timeouts *T, abstime_t curtime) { timeout_t elapsed = curtime - T->curtime; - struct timeout_list todo; + struct list_head todo; int wheel; - TAILQ_INIT(&todo); + list_head_init(&todo); /* * There's no avoiding looping over every wheel. It's best to keep @@ -436,7 +417,7 @@ TIMEOUT_PUBLIC void timeouts_update(struct timeouts *T, abstime_t curtime) { while (pending & T->pending[wheel]) { /* ctz input cannot be zero: loop condition. */ int slot = ctz(pending & T->pending[wheel]); - TAILQ_CONCAT(&todo, &T->wheel[wheel][slot], tqe); + list_append_list(&todo, &T->wheel[wheel][slot]); T->pending[wheel] &= ~(UINT64_C(1) << slot); } @@ -449,10 +430,9 @@ TIMEOUT_PUBLIC void timeouts_update(struct timeouts *T, abstime_t curtime) { T->curtime = curtime; - while (!TAILQ_EMPTY(&todo)) { - struct timeout *to = TAILQ_FIRST(&todo); - - TAILQ_REMOVE(&todo, to, tqe); + struct timeout *to, *next; + list_for_each_safe(&todo, to, next, tqe) { + list_del_from(&todo, &to->tqe); to->pending = NULL; timeouts_sched(T, to, to->expires); @@ -480,7 +460,7 @@ TIMEOUT_PUBLIC bool timeouts_pending(struct timeouts *T) { TIMEOUT_PUBLIC bool timeouts_expired(struct timeouts *T) { - return !TAILQ_EMPTY(&T->expired); + return !list_empty(&T->expired); } /* timeouts_expired() */ @@ -535,7 +515,7 @@ static timeout_t timeouts_int(struct timeouts *T) { * events. */ TIMEOUT_PUBLIC timeout_t timeouts_timeout(struct timeouts *T) { - if (!TAILQ_EMPTY(&T->expired)) + if (!list_empty(&T->expired)) return 0; return timeouts_int(T); @@ -543,10 +523,10 @@ TIMEOUT_PUBLIC timeout_t timeouts_timeout(struct timeouts *T) { TIMEOUT_PUBLIC struct timeout *timeouts_get(struct timeouts *T) { - if (!TAILQ_EMPTY(&T->expired)) { - struct timeout *to = TAILQ_FIRST(&T->expired); + if (!list_empty(&T->expired)) { + struct timeout *to = list_top(&T->expired, struct timeout, tqe); - TAILQ_REMOVE(&T->expired, to, tqe); + list_del_from(&T->expired, &to->tqe); to->pending = NULL; TO_SET_TIMEOUTS(to, NULL); @@ -557,7 +537,7 @@ TIMEOUT_PUBLIC struct timeout *timeouts_get(struct timeouts *T) { return to; } else { - return 0; + return NULL; } } /* timeouts_get() */ diff --git a/src/lib/timeout.h b/src/lib/timeout.h index a6521cab1..efbdc5227 100644 --- a/src/lib/timeout.h +++ b/src/lib/timeout.h @@ -31,11 +31,10 @@ #include /* PRIu64 PRIx64 PRIX64 uint64_t */ -#include /* TAILQ(3) */ - #define TIMEOUT_DISABLE_INTERVALS #define TIMEOUT_DISABLE_CALLBACKS #define TIMEOUT_DISABLE_RELATIVE_ACCESS +#include "list.h" /* * V E R S I O N I N T E R F A C E S @@ -121,10 +120,10 @@ struct timeout { timeout_t expires; /* absolute expiration time */ - struct timeout_list *pending; + struct list_head *pending; /* timeout list if pending on wheel or expiry queue */ - TAILQ_ENTRY(timeout) tqe; + struct list_node tqe; /* entry member for struct timeout_list lists */ #ifndef TIMEOUT_DISABLE_CALLBACKS From 99698386b7f4de87f3625a3669e8d142c33a61a6 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 29 Jul 2018 15:56:35 -0700 Subject: [PATCH 0688/2505] Remove unused features from timeouts library --- src/lib/timeout.c | 164 ---------------------------------------------- src/lib/timeout.h | 55 +--------------- 2 files changed, 1 insertion(+), 218 deletions(-) diff --git a/src/lib/timeout.c b/src/lib/timeout.c index 0b3d74594..6435ed07c 100644 --- a/src/lib/timeout.c +++ b/src/lib/timeout.c @@ -43,12 +43,6 @@ #include "timeout-debug.h" #endif -#ifdef TIMEOUT_DISABLE_RELATIVE_ACCESS -#define TO_SET_TIMEOUTS(to, T) ((void)0) -#else -#define TO_SET_TIMEOUTS(to, T) ((to)->timeouts = (T)) -#endif - /* * A N C I L L A R Y R O U T I N E S * @@ -247,7 +241,6 @@ static void timeouts_reset(struct timeouts *T) { list_for_each(&reset, to, tqe) { to->pending = NULL; - TO_SET_TIMEOUTS(to, NULL); } } /* timeouts_reset() */ @@ -281,7 +274,6 @@ TIMEOUT_PUBLIC void timeouts_del(struct timeouts *T, struct timeout *to) { } to->pending = NULL; - TO_SET_TIMEOUTS(to, NULL); } } /* timeouts_del() */ @@ -310,8 +302,6 @@ static void timeouts_sched(struct timeouts *T, struct timeout *to, timeout_t exp to->expires = expires; - TO_SET_TIMEOUTS(to, T); - if (expires > T->curtime) { rem = timeout_rem(T, to); @@ -334,31 +324,7 @@ static void timeouts_sched(struct timeouts *T, struct timeout *to, timeout_t exp } /* timeouts_sched() */ -#ifndef TIMEOUT_DISABLE_INTERVALS -static void timeouts_readd(struct timeouts *T, struct timeout *to) { - to->expires += to->interval; - - if (to->expires <= T->curtime) { - /* If we've missed the next firing of this timeout, reschedule - * it to occur at the next multiple of its interval after - * the last time that it fired. - */ - timeout_t n = T->curtime - to->expires; - timeout_t r = n % to->interval; - to->expires = T->curtime + (to->interval - r); - } - - timeouts_sched(T, to, to->expires); -} /* timeouts_readd() */ -#endif - - TIMEOUT_PUBLIC void timeouts_add(struct timeouts *T, struct timeout *to, timeout_t timeout) { -#ifndef TIMEOUT_DISABLE_INTERVALS - if (to->flags & TIMEOUT_INT) - to->interval = MAX(1, timeout); -#endif - if (to->flags & TIMEOUT_ABS) timeouts_sched(T, to, timeout); else @@ -528,139 +494,9 @@ TIMEOUT_PUBLIC struct timeout *timeouts_get(struct timeouts *T) { list_del_from(&T->expired, &to->tqe); to->pending = NULL; - TO_SET_TIMEOUTS(to, NULL); - -#ifndef TIMEOUT_DISABLE_INTERVALS - if ((to->flags & TIMEOUT_INT) && to->interval > 0) - timeouts_readd(T, to); -#endif return to; } else { return NULL; } } /* timeouts_get() */ - - -/* - * Use dumb looping to locate the earliest timeout pending on the wheel so - * our invariant assertions can check the result of our optimized code. - */ -static struct timeout *timeouts_min(struct timeouts *T) { - struct timeout *to, *min = NULL; - unsigned i, j; - - for (i = 0; i < countof(T->wheel); i++) { - for (j = 0; j < countof(T->wheel[i]); j++) { - TAILQ_FOREACH(to, &T->wheel[i][j], tqe) { - if (!min || to->expires < min->expires) - min = to; - } - } - } - - return min; -} /* timeouts_min() */ - - -/* - * Check some basic algorithm invariants. If these invariants fail then - * something is definitely broken. - */ -#define report(...) do { \ - if ((fp)) \ - fprintf(fp, __VA_ARGS__); \ -} while (0) - -#define check(expr, ...) do { \ - if (!(expr)) { \ - report(__VA_ARGS__); \ - return 0; \ - } \ -} while (0) - -TIMEOUT_PUBLIC bool timeouts_check(struct timeouts *T, FILE *fp) { - timeout_t timeout; - struct timeout *to; - - if ((to = timeouts_min(T))) { - check(to->expires > T->curtime, "missed timeout (expires:%" TIMEOUT_PRIu " <= curtime:%" TIMEOUT_PRIu ")\n", to->expires, T->curtime); - - timeout = timeouts_int(T); - check(timeout <= to->expires - T->curtime, "wrong soft timeout (soft:%" TIMEOUT_PRIu " > hard:%" TIMEOUT_PRIu ") (expires:%" TIMEOUT_PRIu "; curtime:%" TIMEOUT_PRIu ")\n", timeout, (to->expires - T->curtime), to->expires, T->curtime); - - timeout = timeouts_timeout(T); - check(timeout <= to->expires - T->curtime, "wrong soft timeout (soft:%" TIMEOUT_PRIu " > hard:%" TIMEOUT_PRIu ") (expires:%" TIMEOUT_PRIu "; curtime:%" TIMEOUT_PRIu ")\n", timeout, (to->expires - T->curtime), to->expires, T->curtime); - } else { - timeout = timeouts_timeout(T); - - if (!TAILQ_EMPTY(&T->expired)) - check(timeout == 0, "wrong soft timeout (soft:%" TIMEOUT_PRIu " != hard:%" TIMEOUT_PRIu ")\n", timeout, TIMEOUT_C(0)); - else - check(timeout == ~TIMEOUT_C(0), "wrong soft timeout (soft:%" TIMEOUT_PRIu " != hard:%" TIMEOUT_PRIu ")\n", timeout, ~TIMEOUT_C(0)); - } - - return 1; -} /* timeouts_check() */ - - -/* - * T I M E O U T R O U T I N E S - * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -TIMEOUT_PUBLIC struct timeout *timeout_init(struct timeout *to, int flags) { - memset(to, 0, sizeof *to); - - to->flags = flags; - - return to; -} /* timeout_init() */ - - -#ifndef TIMEOUT_DISABLE_RELATIVE_ACCESS -TIMEOUT_PUBLIC bool timeout_pending(struct timeout *to) { - return to->pending && to->pending != &to->timeouts->expired; -} /* timeout_pending() */ - - -TIMEOUT_PUBLIC bool timeout_expired(struct timeout *to) { - return to->pending && to->pending == &to->timeouts->expired; -} /* timeout_expired() */ - - -TIMEOUT_PUBLIC void timeout_del(struct timeout *to) { - timeouts_del(to->timeouts, to); -} /* timeout_del() */ -#endif - - -/* - * V E R S I O N I N T E R F A C E S - * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -TIMEOUT_PUBLIC int timeout_version(void) { - return TIMEOUT_VERSION; -} /* timeout_version() */ - - -TIMEOUT_PUBLIC const char *timeout_vendor(void) { - return TIMEOUT_VENDOR; -} /* timeout_version() */ - - -TIMEOUT_PUBLIC int timeout_v_rel(void) { - return TIMEOUT_V_REL; -} /* timeout_version() */ - - -TIMEOUT_PUBLIC int timeout_v_abi(void) { - return TIMEOUT_V_ABI; -} /* timeout_version() */ - - -TIMEOUT_PUBLIC int timeout_v_api(void) { - return TIMEOUT_V_API; -} /* timeout_version() */ - diff --git a/src/lib/timeout.h b/src/lib/timeout.h index efbdc5227..da982480e 100644 --- a/src/lib/timeout.h +++ b/src/lib/timeout.h @@ -31,9 +31,6 @@ #include /* PRIu64 PRIx64 PRIX64 uint64_t */ -#define TIMEOUT_DISABLE_INTERVALS -#define TIMEOUT_DISABLE_CALLBACKS -#define TIMEOUT_DISABLE_RELATIVE_ACCESS #include "list.h" /* @@ -81,39 +78,15 @@ typedef uint64_t timeout_t; #define timeout_error_t int /* for documentation purposes */ - -/* - * C A L L B A C K I N T E R F A C E - * - * Callback function parameters unspecified to make embedding into existing - * applications easier. - * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -#ifndef TIMEOUT_CB_OVERRIDE -struct timeout_cb { - void (*fn)(); - void *arg; -}; /* struct timeout_cb */ -#endif - /* * T I M E O U T I N T E R F A C E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -#ifndef TIMEOUT_DISABLE_INTERVALS -#define TIMEOUT_INT 0x01 /* interval (repeating) timeout */ -#endif -#define TIMEOUT_ABS 0x02 /* treat timeout values as absolute */ +#define TIMEOUT_ABS 0x01 /* treat timeout values as absolute */ #define TIMEOUT_INITIALIZER(flags) { (flags) } -#define timeout_setcb(to, fn, arg) do { \ - (to)->callback.fn = (fn); \ - (to)->callback.arg = (arg); \ -} while (0) - struct timeout { int flags; @@ -125,38 +98,12 @@ struct timeout { struct list_node tqe; /* entry member for struct timeout_list lists */ - -#ifndef TIMEOUT_DISABLE_CALLBACKS - struct timeout_cb callback; - /* optional callback information */ -#endif - -#ifndef TIMEOUT_DISABLE_INTERVALS - timeout_t interval; - /* timeout interval if periodic */ -#endif - -#ifndef TIMEOUT_DISABLE_RELATIVE_ACCESS - struct timeouts *timeouts; - /* timeouts collection if member of */ -#endif }; /* struct timeout */ TIMEOUT_PUBLIC struct timeout *timeout_init(struct timeout *, int); /* initialize timeout structure (same as TIMEOUT_INITIALIZER) */ -#ifndef TIMEOUT_DISABLE_RELATIVE_ACCESS -TIMEOUT_PUBLIC bool timeout_pending(struct timeout *); -/* true if on timing wheel, false otherwise */ - -TIMEOUT_PUBLIC bool timeout_expired(struct timeout *); -/* true if on expired queue, false otherwise */ - -TIMEOUT_PUBLIC void timeout_del(struct timeout *); -/* remove timeout from any timing wheel (okay if not member of any) */ -#endif - /* * T I M I N G W H E E L I N T E R F A C E S * From 9bb7727e58b5c2f29e85dc9ae066a2b75362c223 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 29 Jul 2018 16:45:55 -0700 Subject: [PATCH 0689/2505] Remove more unused stuff from timeouts library --- src/lib/lwan-thread.c | 2 +- src/lib/timeout.c | 38 ++++++------------- src/lib/timeout.h | 86 ++++++------------------------------------- 3 files changed, 24 insertions(+), 102 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 61e760f56..087f2efa5 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -477,7 +477,7 @@ static void create_thread(struct lwan *l, struct lwan_thread *thread) memset(thread, 0, sizeof(*thread)); thread->lwan = l; - thread->wheel = timeouts_open(0, &ignore); + thread->wheel = timeouts_open(&ignore); if (!thread->wheel) lwan_status_critical("Could not create timer wheel"); diff --git a/src/lib/timeout.c b/src/lib/timeout.c index 6435ed07c..ab7945ebc 100644 --- a/src/lib/timeout.c +++ b/src/lib/timeout.c @@ -39,10 +39,6 @@ #include "timeout.h" -#if TIMEOUT_DEBUG - 0 -#include "timeout-debug.h" -#endif - /* * A N C I L L A R Y R O U T I N E S * @@ -55,10 +51,6 @@ #define countof(a) (sizeof (a) / sizeof *(a)) #endif -#if !defined endof -#define endof(a) (&(a)[countof(a)]) -#endif - #if !defined MIN #define MIN(a, b) (((a) < (b))? (a) : (b)) #endif @@ -190,7 +182,7 @@ struct timeouts { }; /* struct timeouts */ -static struct timeouts *timeouts_init(struct timeouts *T, timeout_t hz) { +static struct timeouts *timeouts_init(struct timeouts *T) { unsigned i, j; for (i = 0; i < countof(T->wheel); i++) { @@ -206,17 +198,16 @@ static struct timeouts *timeouts_init(struct timeouts *T, timeout_t hz) { } T->curtime = 0; - T->hertz = (hz)? hz : TIMEOUT_mHZ; return T; } /* timeouts_init() */ -TIMEOUT_PUBLIC struct timeouts *timeouts_open(timeout_t hz, int *error) { +struct timeouts *timeouts_open(timeout_error_t *error) { struct timeouts *T; if ((T = malloc(sizeof *T))) - return timeouts_init(T, hz); + return timeouts_init(T); *error = errno; @@ -245,7 +236,7 @@ static void timeouts_reset(struct timeouts *T) { } /* timeouts_reset() */ -TIMEOUT_PUBLIC void timeouts_close(struct timeouts *T) { +void timeouts_close(struct timeouts *T) { /* * NOTE: Delete installed timeouts so timeout_pending() and * timeout_expired() worked as expected. @@ -256,12 +247,7 @@ TIMEOUT_PUBLIC void timeouts_close(struct timeouts *T) { } /* timeouts_close() */ -TIMEOUT_PUBLIC timeout_t timeouts_hz(struct timeouts *T) { - return T->hertz; -} /* timeouts_hz() */ - - -TIMEOUT_PUBLIC void timeouts_del(struct timeouts *T, struct timeout *to) { +void timeouts_del(struct timeouts *T, struct timeout *to) { if (to->pending) { list_del_from(to->pending, &to->tqe); @@ -324,7 +310,7 @@ static void timeouts_sched(struct timeouts *T, struct timeout *to, timeout_t exp } /* timeouts_sched() */ -TIMEOUT_PUBLIC void timeouts_add(struct timeouts *T, struct timeout *to, timeout_t timeout) { +void timeouts_add(struct timeouts *T, struct timeout *to, timeout_t timeout) { if (to->flags & TIMEOUT_ABS) timeouts_sched(T, to, timeout); else @@ -332,7 +318,7 @@ TIMEOUT_PUBLIC void timeouts_add(struct timeouts *T, struct timeout *to, timeout } /* timeouts_add() */ -TIMEOUT_PUBLIC void timeouts_update(struct timeouts *T, abstime_t curtime) { +void timeouts_update(struct timeouts *T, abstime_t curtime) { timeout_t elapsed = curtime - T->curtime; struct list_head todo; int wheel; @@ -408,12 +394,12 @@ TIMEOUT_PUBLIC void timeouts_update(struct timeouts *T, abstime_t curtime) { } /* timeouts_update() */ -TIMEOUT_PUBLIC void timeouts_step(struct timeouts *T, reltime_t elapsed) { +void timeouts_step(struct timeouts *T, reltime_t elapsed) { timeouts_update(T, T->curtime + elapsed); } /* timeouts_step() */ -TIMEOUT_PUBLIC bool timeouts_pending(struct timeouts *T) { +bool timeouts_pending(struct timeouts *T) { wheel_t pending = 0; int wheel; @@ -425,7 +411,7 @@ TIMEOUT_PUBLIC bool timeouts_pending(struct timeouts *T) { } /* timeouts_pending() */ -TIMEOUT_PUBLIC bool timeouts_expired(struct timeouts *T) { +bool timeouts_expired(struct timeouts *T) { return !list_empty(&T->expired); } /* timeouts_expired() */ @@ -480,7 +466,7 @@ static timeout_t timeouts_int(struct timeouts *T) { * Calculate the interval our caller can wait before needing to process * events. */ -TIMEOUT_PUBLIC timeout_t timeouts_timeout(struct timeouts *T) { +timeout_t timeouts_timeout(struct timeouts *T) { if (!list_empty(&T->expired)) return 0; @@ -488,7 +474,7 @@ TIMEOUT_PUBLIC timeout_t timeouts_timeout(struct timeouts *T) { } /* timeouts_timeout() */ -TIMEOUT_PUBLIC struct timeout *timeouts_get(struct timeouts *T) { +struct timeout *timeouts_get(struct timeouts *T) { if (!list_empty(&T->expired)) { struct timeout *to = list_top(&T->expired, struct timeout, tqe); diff --git a/src/lib/timeout.h b/src/lib/timeout.h index da982480e..e11967af0 100644 --- a/src/lib/timeout.h +++ b/src/lib/timeout.h @@ -33,33 +33,6 @@ #include "list.h" -/* - * V E R S I O N I N T E R F A C E S - * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -#if !defined TIMEOUT_PUBLIC -#define TIMEOUT_PUBLIC -#endif - -#define TIMEOUT_VERSION TIMEOUT_V_REL -#define TIMEOUT_VENDOR "william@25thandClement.com" - -#define TIMEOUT_V_REL 0x20160226 -#define TIMEOUT_V_ABI 0x20160224 -#define TIMEOUT_V_API 0x20160226 - -TIMEOUT_PUBLIC int timeout_version(void); - -TIMEOUT_PUBLIC const char *timeout_vendor(void); - -TIMEOUT_PUBLIC int timeout_v_rel(void); - -TIMEOUT_PUBLIC int timeout_v_abi(void); - -TIMEOUT_PUBLIC int timeout_v_api(void); - - /* * I N T E G E R T Y P E I N T E R F A C E S * @@ -70,10 +43,6 @@ TIMEOUT_PUBLIC int timeout_v_api(void); #define TIMEOUT_PRIx PRIx64 #define TIMEOUT_PRIX PRIX64 -#define TIMEOUT_mHZ TIMEOUT_C(1000) -#define TIMEOUT_uHZ TIMEOUT_C(1000000) -#define TIMEOUT_nHZ TIMEOUT_C(1000000000) - typedef uint64_t timeout_t; #define timeout_error_t int /* for documentation purposes */ @@ -101,7 +70,7 @@ struct timeout { }; /* struct timeout */ -TIMEOUT_PUBLIC struct timeout *timeout_init(struct timeout *, int); +struct timeout *timeout_init(struct timeout *); /* initialize timeout structure (same as TIMEOUT_INITIALIZER) */ /* @@ -111,67 +80,34 @@ TIMEOUT_PUBLIC struct timeout *timeout_init(struct timeout *, int); struct timeouts; -TIMEOUT_PUBLIC struct timeouts *timeouts_open(timeout_t, timeout_error_t *); +struct timeouts *timeouts_open(timeout_error_t *); /* open a new timing wheel, setting optional HZ (for float conversions) */ -TIMEOUT_PUBLIC void timeouts_close(struct timeouts *); +void timeouts_close(struct timeouts *); /* destroy timing wheel */ -TIMEOUT_PUBLIC timeout_t timeouts_hz(struct timeouts *); -/* return HZ setting (for float conversions) */ - -TIMEOUT_PUBLIC void timeouts_update(struct timeouts *, timeout_t); +void timeouts_update(struct timeouts *, timeout_t); /* update timing wheel with current absolute time */ -TIMEOUT_PUBLIC void timeouts_step(struct timeouts *, timeout_t); +void timeouts_step(struct timeouts *, timeout_t); /* step timing wheel by relative time */ -TIMEOUT_PUBLIC timeout_t timeouts_timeout(struct timeouts *); +timeout_t timeouts_timeout(struct timeouts *); /* return interval to next required update */ -TIMEOUT_PUBLIC void timeouts_add(struct timeouts *, struct timeout *, timeout_t); +void timeouts_add(struct timeouts *, struct timeout *, timeout_t); /* add timeout to timing wheel */ -TIMEOUT_PUBLIC void timeouts_del(struct timeouts *, struct timeout *); +void timeouts_del(struct timeouts *, struct timeout *); /* remove timeout from any timing wheel or expired queue (okay if on neither) */ -TIMEOUT_PUBLIC struct timeout *timeouts_get(struct timeouts *); +struct timeout *timeouts_get(struct timeouts *); /* return any expired timeout (caller should loop until NULL-return) */ -TIMEOUT_PUBLIC bool timeouts_pending(struct timeouts *); +bool timeouts_pending(struct timeouts *); /* return true if any timeouts pending on timing wheel */ -TIMEOUT_PUBLIC bool timeouts_expired(struct timeouts *); +bool timeouts_expired(struct timeouts *); /* return true if any timeouts on expired queue */ -TIMEOUT_PUBLIC bool timeouts_check(struct timeouts *, FILE *); -/* return true if invariants hold. describes failures to optional file handle. */ - -#define TIMEOUTS_PENDING 0x10 -#define TIMEOUTS_EXPIRED 0x20 -#define TIMEOUTS_ALL (TIMEOUTS_PENDING|TIMEOUTS_EXPIRED) -#define TIMEOUTS_CLEAR 0x40 - -/* - * B O N U S W H E E L I N T E R F A C E S - * - * I usually use floating point timeouts in all my code, but it's cleaner to - * separate it to keep the core algorithmic code simple. - * - * Using macros instead of static inline routines where routines - * might be used to keep -lm linking optional. - * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -#include /* ceil(3) */ - -#define timeouts_f2i(T, f) \ - ((timeout_t)ceil((f) * timeouts_hz((T)))) /* prefer late expiration over early */ - -#define timeouts_i2f(T, i) \ - ((double)(i) / timeouts_hz((T))) - -#define timeouts_addf(T, to, timeout) \ - timeouts_add((T), (to), timeouts_f2i((T), (timeout))) - #endif /* TIMEOUT_H */ From 378f9c92f0cc075d7fbb8610a874e708b12527da Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 29 Jul 2018 17:00:45 -0700 Subject: [PATCH 0690/2505] Address build warnings in timeouts library --- src/lib/timeout.c | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/lib/timeout.c b/src/lib/timeout.c index ab7945ebc..64c1ae8ff 100644 --- a/src/lib/timeout.c +++ b/src/lib/timeout.c @@ -150,7 +150,9 @@ typedef uint8_t wheel_t; #endif -static inline wheel_t rotl(const wheel_t v, unsigned int c) { +static inline wheel_t rotl(const wheel_t v, int cs) { + unsigned int c = (unsigned int)cs; + if (!(c &= (sizeof v * CHAR_BIT - 1))) return v; @@ -158,7 +160,9 @@ static inline wheel_t rotl(const wheel_t v, unsigned int c) { } /* rotl() */ -static inline wheel_t rotr(const wheel_t v, unsigned int c) { +static inline wheel_t rotr(const wheel_t v, int cs) { + unsigned int c = (unsigned int)cs; + if (!(c &= (sizeof v * CHAR_BIT - 1))) return v; @@ -276,7 +280,10 @@ static inline int timeout_wheel(timeout_t timeout) { static inline int timeout_slot(int wheel, timeout_t expires) { - return WHEEL_MASK & ((expires >> (wheel * WHEEL_BIT)) - !!wheel); + const timeout_t wheel_mask = (timeout_t)WHEEL_MASK; + const timeout_t slot = wheel_mask & ((expires >> (wheel * WHEEL_BIT)) - !!wheel); + + return (int)slot; } /* timeout_slot() */ @@ -349,20 +356,21 @@ void timeouts_update(struct timeouts *T, abstime_t curtime) { if ((elapsed >> (wheel * WHEEL_BIT)) > WHEEL_MAX) { pending = (wheel_t)~WHEEL_C(0); } else { + const timeout_t wheel_mask = (timeout_t)WHEEL_MASK; wheel_t _elapsed = WHEEL_MASK & (elapsed >> (wheel * WHEEL_BIT)); - unsigned int oslot, nslot; + int oslot, nslot; /* * TODO: It's likely that at least one of the * following three bit fill operations is redundant * or can be replaced with a simpler operation. */ - oslot = WHEEL_MASK & (T->curtime >> (wheel * WHEEL_BIT)); + oslot = (int)(wheel_mask & (T->curtime >> (wheel * WHEEL_BIT))); pending = rotl(((UINT64_C(1) << _elapsed) - 1), oslot); - nslot = WHEEL_MASK & (curtime >> (wheel * WHEEL_BIT)); + nslot = (int)(wheel_mask & (curtime >> (wheel * WHEEL_BIT))); pending |= rotr(rotl(((WHEEL_C(1) << _elapsed) - 1), nslot), - (unsigned int)_elapsed); + (int)_elapsed); pending |= WHEEL_C(1) << nslot; } @@ -433,15 +441,16 @@ bool timeouts_expired(struct timeouts *T) { * We should never return a timeout larger than the lowest actual timeout. */ static timeout_t timeouts_int(struct timeouts *T) { + const timeout_t wheel_mask = (timeout_t)WHEEL_MASK; timeout_t timeout = ~TIMEOUT_C(0), _timeout; timeout_t relmask; - unsigned int wheel, slot; + int wheel, slot; relmask = 0; for (wheel = 0; wheel < WHEEL_NUM; wheel++) { if (T->pending[wheel]) { - slot = WHEEL_MASK & (T->curtime >> (wheel * WHEEL_BIT)); + slot = (int)(wheel_mask & (T->curtime >> (wheel * WHEEL_BIT))); /* ctz input cannot be zero: T->pending[wheel] is * nonzero, so rotr() is nonzero. */ From 802631d1c8c3731fd16186e5be0dbcffdfde4523 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 29 Jul 2018 23:00:19 -0700 Subject: [PATCH 0691/2505] Use simpler rotate left/right routines --- src/lib/timeout.c | 45 +++++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/src/lib/timeout.c b/src/lib/timeout.c index 64c1ae8ff..1df90cacb 100644 --- a/src/lib/timeout.c +++ b/src/lib/timeout.c @@ -23,6 +23,9 @@ * USE OR OTHER DEALINGS IN THE SOFTWARE. * ========================================================================== */ + +#include + #include /* CHAR_BIT */ #include /* NULL */ @@ -150,25 +153,26 @@ typedef uint8_t wheel_t; #endif -static inline wheel_t rotl(const wheel_t v, int cs) { - unsigned int c = (unsigned int)cs; - - if (!(c &= (sizeof v * CHAR_BIT - 1))) - return v; - - return (v << c) | (v >> (sizeof v * CHAR_BIT - c)); -} /* rotl() */ - +/* See "Safe, Efficient, and Portable Rotate in C/C++" by John Regehr + * http://blog.regehr.org/archives/1063 + * These should be recognized by the backend C compiler and turned into a rol + */ -static inline wheel_t rotr(const wheel_t v, int cs) { - unsigned int c = (unsigned int)cs; +#define WHEEL_T_BITS ((CHAR_BIT) * sizeof(wheel_t)) - if (!(c &= (sizeof v * CHAR_BIT - 1))) - return v; +static inline wheel_t rotl(const wheel_t v, uint32_t n) +{ + assert(n < WHEEL_T_BITS); + return (v << n) | (v >> (-n & (WHEEL_T_BITS - 1))); +} - return (v >> c) | (v << (sizeof v * CHAR_BIT - c)); -} /* rotr() */ +static inline wheel_t rotr(const wheel_t v, uint32_t n) +{ + assert(n < WHEEL_T_BITS); + return (v >> n) | (v << (-n & (WHEEL_T_BITS - 1))); +} +#undef WHEEL_T_BITS /* * T I M E R R O U T I N E S @@ -358,19 +362,19 @@ void timeouts_update(struct timeouts *T, abstime_t curtime) { } else { const timeout_t wheel_mask = (timeout_t)WHEEL_MASK; wheel_t _elapsed = WHEEL_MASK & (elapsed >> (wheel * WHEEL_BIT)); - int oslot, nslot; + unsigned int oslot, nslot; /* * TODO: It's likely that at least one of the * following three bit fill operations is redundant * or can be replaced with a simpler operation. */ - oslot = (int)(wheel_mask & (T->curtime >> (wheel * WHEEL_BIT))); + oslot = (unsigned int)(wheel_mask & (T->curtime >> (wheel * WHEEL_BIT))); pending = rotl(((UINT64_C(1) << _elapsed) - 1), oslot); - nslot = (int)(wheel_mask & (curtime >> (wheel * WHEEL_BIT))); + nslot = (unsigned int)(wheel_mask & (curtime >> (wheel * WHEEL_BIT))); pending |= rotr(rotl(((WHEEL_C(1) << _elapsed) - 1), nslot), - (int)_elapsed); + (unsigned int)_elapsed); pending |= WHEEL_C(1) << nslot; } @@ -444,7 +448,8 @@ static timeout_t timeouts_int(struct timeouts *T) { const timeout_t wheel_mask = (timeout_t)WHEEL_MASK; timeout_t timeout = ~TIMEOUT_C(0), _timeout; timeout_t relmask; - int wheel, slot; + unsigned int slot; + int wheel; relmask = 0; From e7712606eafa9af79db386f7d333873486b335e0 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 29 Jul 2018 23:08:10 -0700 Subject: [PATCH 0692/2505] Reindent timer wheel library --- src/lib/timeout-bitops.c | 286 +++++++++++------------ src/lib/timeout.c | 494 ++++++++++++++++++++------------------- src/lib/timeout.h | 26 ++- 3 files changed, 404 insertions(+), 402 deletions(-) diff --git a/src/lib/timeout-bitops.c b/src/lib/timeout-bitops.c index d8325db3b..2c70f1365 100644 --- a/src/lib/timeout-bitops.c +++ b/src/lib/timeout-bitops.c @@ -1,6 +1,6 @@ #include #ifdef _MSC_VER -#include /* _BitScanForward, _BitScanReverse */ +#include /* _BitScanForward, _BitScanReverse */ #endif /* First define ctz and clz functions; these are compiler-dependent if @@ -27,42 +27,42 @@ static __inline int ctz32(unsigned long val) { - DWORD zeros = 0; - _BitScanForward(&zeros, val); - return zeros; + DWORD zeros = 0; + _BitScanForward(&zeros, val); + return zeros; } static __inline int clz32(unsigned long val) { - DWORD zeros = 0; - _BitScanReverse(&zeros, val); - return zeros; + DWORD zeros = 0; + _BitScanReverse(&zeros, val); + return zeros; } #ifdef _WIN64 /* According to the documentation, these only exist on Win64. */ static __inline int ctz64(uint64_t val) { - DWORD zeros = 0; - _BitScanForward64(&zeros, val); - return zeros; + DWORD zeros = 0; + _BitScanForward64(&zeros, val); + return zeros; } static __inline int clz64(uint64_t val) { - DWORD zeros = 0; - _BitScanReverse64(&zeros, val); - return zeros; + DWORD zeros = 0; + _BitScanReverse64(&zeros, val); + return zeros; } #else static __inline int ctz64(uint64_t val) { - uint32_t lo = (uint32_t) val; - uint32_t hi = (uint32_t) (val >> 32); - return lo ? ctz32(lo) : 32 + ctz32(hi); + uint32_t lo = (uint32_t)val; + uint32_t hi = (uint32_t)(val >> 32); + return lo ? ctz32(lo) : 32 + ctz32(hi); } static __inline int clz64(uint64_t val) { - uint32_t lo = (uint32_t) val; - uint32_t hi = (uint32_t) (val >> 32); - return hi ? clz32(hi) : 32 + clz32(lo); + uint32_t lo = (uint32_t)val; + uint32_t hi = (uint32_t)(val >> 32); + return hi ? clz32(hi) : 32 + clz32(lo); } #endif @@ -72,67 +72,72 @@ static __inline int clz64(uint64_t val) /* TODO: There are more clever ways to do this in the generic case. */ - -#define process_(one, cz_bits, bits) \ - if (x < ( one << (cz_bits - bits))) { rv += bits; x <<= bits; } +#define process_(one, cz_bits, bits) \ + if (x < (one << (cz_bits - bits))) { \ + rv += bits; \ + x <<= bits; \ + } #define process64(bits) process_((UINT64_C(1)), 64, (bits)) static inline int clz64(uint64_t x) { - int rv = 0; - - process64(32); - process64(16); - process64(8); - process64(4); - process64(2); - process64(1); - return rv; + int rv = 0; + + process64(32); + process64(16); + process64(8); + process64(4); + process64(2); + process64(1); + return rv; } #define process32(bits) process_((UINT32_C(1)), 32, (bits)) static inline int clz32(uint32_t x) { - int rv = 0; - - process32(16); - process32(8); - process32(4); - process32(2); - process32(1); - return rv; + int rv = 0; + + process32(16); + process32(8); + process32(4); + process32(2); + process32(1); + return rv; } #undef process_ #undef process32 #undef process64 -#define process_(one, bits) \ - if ((x & ((one << (bits))-1)) == 0) { rv += bits; x >>= bits; } +#define process_(one, bits) \ + if ((x & ((one << (bits)) - 1)) == 0) { \ + rv += bits; \ + x >>= bits; \ + } #define process64(bits) process_((UINT64_C(1)), bits) static inline int ctz64(uint64_t x) { - int rv = 0; - - process64(32); - process64(16); - process64(8); - process64(4); - process64(2); - process64(1); - return rv; + int rv = 0; + + process64(32); + process64(16); + process64(8); + process64(4); + process64(2); + process64(1); + return rv; } #define process32(bits) process_((UINT32_C(1)), bits) static inline int ctz32(uint32_t x) { - int rv = 0; - - process32(16); - process32(8); - process32(4); - process32(2); - process32(1); - return rv; + int rv = 0; + + process32(16); + process32(8); + process32(4); + process32(2); + process32(1); + return rv; } #undef process32 @@ -147,103 +152,94 @@ static inline int ctz32(uint32_t x) #include #include -static uint64_t testcases[] = { - 13371337 * 10, - 100, - 385789752, - 82574, - (((uint64_t)1)<<63) + (((uint64_t)1)<<31) + 10101 -}; +static uint64_t testcases[] = {13371337 * 10, 100, 385789752, 82574, + (((uint64_t)1) << 63) + (((uint64_t)1) << 31) + + 10101}; -static int -naive_clz(int bits, uint64_t v) +static int naive_clz(int bits, uint64_t v) { - int r = 0; - uint64_t bit = ((uint64_t)1) << (bits-1); - while (bit && 0 == (v & bit)) { - r++; - bit >>= 1; - } - /* printf("clz(%d,%lx) -> %d\n", bits, v, r); */ - return r; + int r = 0; + uint64_t bit = ((uint64_t)1) << (bits - 1); + while (bit && 0 == (v & bit)) { + r++; + bit >>= 1; + } + /* printf("clz(%d,%lx) -> %d\n", bits, v, r); */ + return r; } -static int -naive_ctz(int bits, uint64_t v) +static int naive_ctz(int bits, uint64_t v) { - int r = 0; - uint64_t bit = 1; - while (bit && 0 == (v & bit)) { - r++; - bit <<= 1; - if (r == bits) - break; - } - /* printf("ctz(%d,%lx) -> %d\n", bits, v, r); */ - return r; + int r = 0; + uint64_t bit = 1; + while (bit && 0 == (v & bit)) { + r++; + bit <<= 1; + if (r == bits) + break; + } + /* printf("ctz(%d,%lx) -> %d\n", bits, v, r); */ + return r; } -static int -check(uint64_t vv) +static int check(uint64_t vv) { - uint32_t v32 = (uint32_t) vv; - - if (vv == 0) - return 1; /* c[tl]z64(0) is undefined. */ - - if (ctz64(vv) != naive_ctz(64, vv)) { - printf("mismatch with ctz64: %d\n", ctz64(vv)); - exit(1); - return 0; - } - if (clz64(vv) != naive_clz(64, vv)) { - printf("mismatch with clz64: %d\n", clz64(vv)); - exit(1); - return 0; - } - - if (v32 == 0) - return 1; /* c[lt]z(0) is undefined. */ - - if (ctz32(v32) != naive_ctz(32, v32)) { - printf("mismatch with ctz32: %d\n", ctz32(v32)); - exit(1); - return 0; - } - if (clz32(v32) != naive_clz(32, v32)) { - printf("mismatch with clz32: %d\n", clz32(v32)); - exit(1); - return 0; - } - return 1; + uint32_t v32 = (uint32_t)vv; + + if (vv == 0) + return 1; /* c[tl]z64(0) is undefined. */ + + if (ctz64(vv) != naive_ctz(64, vv)) { + printf("mismatch with ctz64: %d\n", ctz64(vv)); + exit(1); + return 0; + } + if (clz64(vv) != naive_clz(64, vv)) { + printf("mismatch with clz64: %d\n", clz64(vv)); + exit(1); + return 0; + } + + if (v32 == 0) + return 1; /* c[lt]z(0) is undefined. */ + + if (ctz32(v32) != naive_ctz(32, v32)) { + printf("mismatch with ctz32: %d\n", ctz32(v32)); + exit(1); + return 0; + } + if (clz32(v32) != naive_clz(32, v32)) { + printf("mismatch with clz32: %d\n", clz32(v32)); + exit(1); + return 0; + } + return 1; } -int -main(int c, char **v) +int main(int c, char **v) { - unsigned int i; - const unsigned int n = sizeof(testcases)/sizeof(testcases[0]); - int result = 0; - - for (i = 0; i <= 63; ++i) { - uint64_t x = 1 << i; - if (!check(x)) - result = 1; - --x; - if (!check(x)) - result = 1; - } - - for (i = 0; i < n; ++i) { - if (! check(testcases[i])) - result = 1; - } - if (result) { - puts("FAIL"); - } else { - puts("OK"); - } - return result; + unsigned int i; + const unsigned int n = sizeof(testcases) / sizeof(testcases[0]); + int result = 0; + + for (i = 0; i <= 63; ++i) { + uint64_t x = 1 << i; + if (!check(x)) + result = 1; + --x; + if (!check(x)) + result = 1; + } + + for (i = 0; i < n; ++i) { + if (!check(testcases[i])) + result = 1; + } + if (result) { + puts("FAIL"); + } else { + puts("OK"); + } + return result; } #endif - diff --git a/src/lib/timeout.c b/src/lib/timeout.c index 1df90cacb..8df8235df 100644 --- a/src/lib/timeout.c +++ b/src/lib/timeout.c @@ -26,17 +26,17 @@ #include -#include /* CHAR_BIT */ +#include /* CHAR_BIT */ -#include /* NULL */ -#include /* malloc(3) free(3) */ -#include /* FILE fprintf(3) */ +#include /* NULL */ +#include /* FILE fprintf(3) */ +#include /* malloc(3) free(3) */ -#include /* UINT64_C uint64_t */ +#include /* UINT64_C uint64_t */ -#include /* memset(3) */ +#include /* memset(3) */ -#include /* errno */ +#include /* errno */ #include "list.h" @@ -51,18 +51,17 @@ #define reltime_t timeout_t /* "" */ #if !defined countof -#define countof(a) (sizeof (a) / sizeof *(a)) +#define countof(a) (sizeof(a) / sizeof *(a)) #endif #if !defined MIN -#define MIN(a, b) (((a) < (b))? (a) : (b)) +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) #endif #if !defined MAX -#define MAX(a, b) (((a) > (b))? (a) : (b)) +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) #endif - /* * B I T M A N I P U L A T I O N R O U T I N E S * @@ -152,7 +151,6 @@ typedef uint8_t wheel_t; #error invalid WHEEL_BIT value #endif - /* See "Safe, Efficient, and Portable Rotate in C/C++" by John Regehr * http://blog.regehr.org/archives/1063 * These should be recognized by the backend C compiler and turned into a rol @@ -162,15 +160,15 @@ typedef uint8_t wheel_t; static inline wheel_t rotl(const wheel_t v, uint32_t n) { - assert(n < WHEEL_T_BITS); - return (v << n) | (v >> (-n & (WHEEL_T_BITS - 1))); -} + assert(n < WHEEL_T_BITS); + return (v << n) | (v >> (-n & (WHEEL_T_BITS - 1))); +} /* rotl() */ static inline wheel_t rotr(const wheel_t v, uint32_t n) { - assert(n < WHEEL_T_BITS); - return (v >> n) | (v << (-n & (WHEEL_T_BITS - 1))); -} + assert(n < WHEEL_T_BITS); + return (v >> n) | (v << (-n & (WHEEL_T_BITS - 1))); +} /* rotr() */ #undef WHEEL_T_BITS @@ -180,254 +178,257 @@ static inline wheel_t rotr(const wheel_t v, uint32_t n) * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ struct timeouts { - struct list_head wheel[WHEEL_NUM][WHEEL_LEN]; - struct list_head expired; + struct list_head wheel[WHEEL_NUM][WHEEL_LEN]; + struct list_head expired; - wheel_t pending[WHEEL_NUM]; + wheel_t pending[WHEEL_NUM]; - timeout_t curtime; - timeout_t hertz; + timeout_t curtime; + timeout_t hertz; }; /* struct timeouts */ +static struct timeouts *timeouts_init(struct timeouts *T) +{ + unsigned i, j; -static struct timeouts *timeouts_init(struct timeouts *T) { - unsigned i, j; - - for (i = 0; i < countof(T->wheel); i++) { - for (j = 0; j < countof(T->wheel[i]); j++) { - list_head_init(&T->wheel[i][j]); - } - } + for (i = 0; i < countof(T->wheel); i++) { + for (j = 0; j < countof(T->wheel[i]); j++) { + list_head_init(&T->wheel[i][j]); + } + } - list_head_init(&T->expired); + list_head_init(&T->expired); - for (i = 0; i < countof(T->pending); i++) { - T->pending[i] = 0; - } + for (i = 0; i < countof(T->pending); i++) { + T->pending[i] = 0; + } - T->curtime = 0; + T->curtime = 0; - return T; + return T; } /* timeouts_init() */ +struct timeouts *timeouts_open(timeout_error_t *error) +{ + struct timeouts *T; -struct timeouts *timeouts_open(timeout_error_t *error) { - struct timeouts *T; - - if ((T = malloc(sizeof *T))) - return timeouts_init(T); + if ((T = malloc(sizeof *T))) + return timeouts_init(T); - *error = errno; + *error = errno; - return NULL; + return NULL; } /* timeouts_open() */ +static void timeouts_reset(struct timeouts *T) +{ + struct list_head reset; + struct timeout *to; + unsigned i, j; -static void timeouts_reset(struct timeouts *T) { - struct list_head reset; - struct timeout *to; - unsigned i, j; - - list_head_init(&reset); + list_head_init(&reset); - for (i = 0; i < countof(T->wheel); i++) { - for (j = 0; j < countof(T->wheel[i]); j++) { - list_append_list(&reset, &T->wheel[i][j]); - } - } + for (i = 0; i < countof(T->wheel); i++) { + for (j = 0; j < countof(T->wheel[i]); j++) { + list_append_list(&reset, &T->wheel[i][j]); + } + } - list_append_list(&reset, &T->expired); + list_append_list(&reset, &T->expired); - list_for_each(&reset, to, tqe) { - to->pending = NULL; - } + list_for_each (&reset, to, tqe) { + to->pending = NULL; + } } /* timeouts_reset() */ +void timeouts_close(struct timeouts *T) +{ + /* + * NOTE: Delete installed timeouts so timeout_pending() and + * timeout_expired() worked as expected. + */ + timeouts_reset(T); -void timeouts_close(struct timeouts *T) { - /* - * NOTE: Delete installed timeouts so timeout_pending() and - * timeout_expired() worked as expected. - */ - timeouts_reset(T); - - free(T); + free(T); } /* timeouts_close() */ +void timeouts_del(struct timeouts *T, struct timeout *to) +{ + if (to->pending) { + list_del_from(to->pending, &to->tqe); -void timeouts_del(struct timeouts *T, struct timeout *to) { - if (to->pending) { - list_del_from(to->pending, &to->tqe); - - if (to->pending != &T->expired && list_empty(to->pending)) { - ptrdiff_t index = to->pending - &T->wheel[0][0]; - ptrdiff_t wheel = index / WHEEL_LEN; - ptrdiff_t slot = index % WHEEL_LEN; + if (to->pending != &T->expired && list_empty(to->pending)) { + ptrdiff_t index = to->pending - &T->wheel[0][0]; + ptrdiff_t wheel = index / WHEEL_LEN; + ptrdiff_t slot = index % WHEEL_LEN; - T->pending[wheel] &= ~(WHEEL_C(1) << slot); - } + T->pending[wheel] &= ~(WHEEL_C(1) << slot); + } - to->pending = NULL; - } + to->pending = NULL; + } } /* timeouts_del() */ - -static inline reltime_t timeout_rem(struct timeouts *T, struct timeout *to) { - return to->expires - T->curtime; +static inline reltime_t timeout_rem(struct timeouts *T, struct timeout *to) +{ + return to->expires - T->curtime; } /* timeout_rem() */ - -static inline int timeout_wheel(timeout_t timeout) { - /* must be called with timeout != 0, so fls input is nonzero */ - return (fls(MIN(timeout, TIMEOUT_MAX)) - 1) / WHEEL_BIT; +static inline int timeout_wheel(timeout_t timeout) +{ + /* must be called with timeout != 0, so fls input is nonzero */ + return (fls(MIN(timeout, TIMEOUT_MAX)) - 1) / WHEEL_BIT; } /* timeout_wheel() */ +static inline int timeout_slot(int wheel, timeout_t expires) +{ + const timeout_t wheel_mask = (timeout_t)WHEEL_MASK; + const timeout_t slot = + wheel_mask & ((expires >> (wheel * WHEEL_BIT)) - !!wheel); -static inline int timeout_slot(int wheel, timeout_t expires) { - const timeout_t wheel_mask = (timeout_t)WHEEL_MASK; - const timeout_t slot = wheel_mask & ((expires >> (wheel * WHEEL_BIT)) - !!wheel); - - return (int)slot; + return (int)slot; } /* timeout_slot() */ +static void +timeouts_sched(struct timeouts *T, struct timeout *to, timeout_t expires) +{ + timeout_t rem; + int wheel, slot; -static void timeouts_sched(struct timeouts *T, struct timeout *to, timeout_t expires) { - timeout_t rem; - int wheel, slot; - - timeouts_del(T, to); + timeouts_del(T, to); - to->expires = expires; + to->expires = expires; - if (expires > T->curtime) { - rem = timeout_rem(T, to); + if (expires > T->curtime) { + rem = timeout_rem(T, to); - /* rem is nonzero since: - * rem == timeout_rem(T,to), - * == to->expires - T->curtime - * and above we have expires > T->curtime. - */ - wheel = timeout_wheel(rem); - slot = timeout_slot(wheel, to->expires); + /* rem is nonzero since: + * rem == timeout_rem(T,to), + * == to->expires - T->curtime + * and above we have expires > T->curtime. + */ + wheel = timeout_wheel(rem); + slot = timeout_slot(wheel, to->expires); - to->pending = &T->wheel[wheel][slot]; + to->pending = &T->wheel[wheel][slot]; - T->pending[wheel] |= WHEEL_C(1) << slot; - } else { - to->pending = &T->expired; - } + T->pending[wheel] |= WHEEL_C(1) << slot; + } else { + to->pending = &T->expired; + } - list_add_tail(to->pending, &to->tqe); + list_add_tail(to->pending, &to->tqe); } /* timeouts_sched() */ - -void timeouts_add(struct timeouts *T, struct timeout *to, timeout_t timeout) { - if (to->flags & TIMEOUT_ABS) - timeouts_sched(T, to, timeout); - else - timeouts_sched(T, to, T->curtime + timeout); +void timeouts_add(struct timeouts *T, struct timeout *to, timeout_t timeout) +{ + if (to->flags & TIMEOUT_ABS) + timeouts_sched(T, to, timeout); + else + timeouts_sched(T, to, T->curtime + timeout); } /* timeouts_add() */ - -void timeouts_update(struct timeouts *T, abstime_t curtime) { - timeout_t elapsed = curtime - T->curtime; - struct list_head todo; - int wheel; - - list_head_init(&todo); - - /* - * There's no avoiding looping over every wheel. It's best to keep - * WHEEL_NUM smallish. - */ - for (wheel = 0; wheel < WHEEL_NUM; wheel++) { - wheel_t pending; - - /* - * Calculate the slots expiring in this wheel - * - * If the elapsed time is greater than the maximum period of - * the wheel, mark every position as expiring. - * - * Otherwise, to determine the expired slots fill in all the - * bits between the last slot processed and the current - * slot, inclusive of the last slot. We'll bitwise-AND this - * with our pending set below. - * - * If a wheel rolls over, force a tick of the next higher - * wheel. - */ - if ((elapsed >> (wheel * WHEEL_BIT)) > WHEEL_MAX) { - pending = (wheel_t)~WHEEL_C(0); - } else { - const timeout_t wheel_mask = (timeout_t)WHEEL_MASK; - wheel_t _elapsed = WHEEL_MASK & (elapsed >> (wheel * WHEEL_BIT)); - unsigned int oslot, nslot; - - /* - * TODO: It's likely that at least one of the - * following three bit fill operations is redundant - * or can be replaced with a simpler operation. - */ - oslot = (unsigned int)(wheel_mask & (T->curtime >> (wheel * WHEEL_BIT))); - pending = rotl(((UINT64_C(1) << _elapsed) - 1), oslot); - - nslot = (unsigned int)(wheel_mask & (curtime >> (wheel * WHEEL_BIT))); - pending |= rotr(rotl(((WHEEL_C(1) << _elapsed) - 1), nslot), - (unsigned int)_elapsed); - pending |= WHEEL_C(1) << nslot; - } - - while (pending & T->pending[wheel]) { - /* ctz input cannot be zero: loop condition. */ - int slot = ctz(pending & T->pending[wheel]); - list_append_list(&todo, &T->wheel[wheel][slot]); - T->pending[wheel] &= ~(UINT64_C(1) << slot); - } - - if (!(0x1 & pending)) - break; /* break if we didn't wrap around end of wheel */ - - /* if we're continuing, the next wheel must tick at least once */ - elapsed = MAX(elapsed, (WHEEL_LEN << (wheel * WHEEL_BIT))); - } - - T->curtime = curtime; - - struct timeout *to, *next; - list_for_each_safe(&todo, to, next, tqe) { - list_del_from(&todo, &to->tqe); - to->pending = NULL; - - timeouts_sched(T, to, to->expires); - } - - return; +void timeouts_update(struct timeouts *T, abstime_t curtime) +{ + timeout_t elapsed = curtime - T->curtime; + struct list_head todo; + int wheel; + + list_head_init(&todo); + + /* + * There's no avoiding looping over every wheel. It's best to keep + * WHEEL_NUM smallish. + */ + for (wheel = 0; wheel < WHEEL_NUM; wheel++) { + wheel_t pending; + + /* + * Calculate the slots expiring in this wheel + * + * If the elapsed time is greater than the maximum period of + * the wheel, mark every position as expiring. + * + * Otherwise, to determine the expired slots fill in all the + * bits between the last slot processed and the current + * slot, inclusive of the last slot. We'll bitwise-AND this + * with our pending set below. + * + * If a wheel rolls over, force a tick of the next higher + * wheel. + */ + if ((elapsed >> (wheel * WHEEL_BIT)) > WHEEL_MAX) { + pending = (wheel_t)~WHEEL_C(0); + } else { + const timeout_t wheel_mask = (timeout_t)WHEEL_MASK; + wheel_t _elapsed = WHEEL_MASK & (elapsed >> (wheel * WHEEL_BIT)); + unsigned int oslot, nslot; + + /* + * TODO: It's likely that at least one of the + * following three bit fill operations is redundant + * or can be replaced with a simpler operation. + */ + oslot = (unsigned int)(wheel_mask & + (T->curtime >> (wheel * WHEEL_BIT))); + pending = rotl(((UINT64_C(1) << _elapsed) - 1), oslot); + + nslot = + (unsigned int)(wheel_mask & (curtime >> (wheel * WHEEL_BIT))); + pending |= rotr(rotl(((WHEEL_C(1) << _elapsed) - 1), nslot), + (unsigned int)_elapsed); + pending |= WHEEL_C(1) << nslot; + } + + while (pending & T->pending[wheel]) { + /* ctz input cannot be zero: loop condition. */ + int slot = ctz(pending & T->pending[wheel]); + list_append_list(&todo, &T->wheel[wheel][slot]); + T->pending[wheel] &= ~(UINT64_C(1) << slot); + } + + if (!(0x1 & pending)) + break; /* break if we didn't wrap around end of wheel */ + + /* if we're continuing, the next wheel must tick at least once */ + elapsed = MAX(elapsed, (WHEEL_LEN << (wheel * WHEEL_BIT))); + } + + T->curtime = curtime; + + struct timeout *to, *next; + list_for_each_safe (&todo, to, next, tqe) { + list_del_from(&todo, &to->tqe); + to->pending = NULL; + + timeouts_sched(T, to, to->expires); + } + + return; } /* timeouts_update() */ - -void timeouts_step(struct timeouts *T, reltime_t elapsed) { - timeouts_update(T, T->curtime + elapsed); +void timeouts_step(struct timeouts *T, reltime_t elapsed) +{ + timeouts_update(T, T->curtime + elapsed); } /* timeouts_step() */ +bool timeouts_pending(struct timeouts *T) +{ + wheel_t pending = 0; + int wheel; -bool timeouts_pending(struct timeouts *T) { - wheel_t pending = 0; - int wheel; - - for (wheel = 0; wheel < WHEEL_NUM; wheel++) { - pending |= T->pending[wheel]; - } + for (wheel = 0; wheel < WHEEL_NUM; wheel++) { + pending |= T->pending[wheel]; + } - return !!pending; + return !!pending; } /* timeouts_pending() */ - -bool timeouts_expired(struct timeouts *T) { - return !list_empty(&T->expired); +bool timeouts_expired(struct timeouts *T) +{ + return !list_empty(&T->expired); } /* timeouts_expired() */ - /* * Calculate the interval before needing to process any timeouts pending on * any wheel. @@ -444,59 +445,62 @@ bool timeouts_expired(struct timeouts *T) { * * We should never return a timeout larger than the lowest actual timeout. */ -static timeout_t timeouts_int(struct timeouts *T) { - const timeout_t wheel_mask = (timeout_t)WHEEL_MASK; - timeout_t timeout = ~TIMEOUT_C(0), _timeout; - timeout_t relmask; - unsigned int slot; - int wheel; +static timeout_t timeouts_int(struct timeouts *T) +{ + const timeout_t wheel_mask = (timeout_t)WHEEL_MASK; + timeout_t timeout = ~TIMEOUT_C(0), _timeout; + timeout_t relmask; + unsigned int slot; + int wheel; - relmask = 0; + relmask = 0; - for (wheel = 0; wheel < WHEEL_NUM; wheel++) { - if (T->pending[wheel]) { - slot = (int)(wheel_mask & (T->curtime >> (wheel * WHEEL_BIT))); + for (wheel = 0; wheel < WHEEL_NUM; wheel++) { + if (T->pending[wheel]) { + slot = (int)(wheel_mask & (T->curtime >> (wheel * WHEEL_BIT))); - /* ctz input cannot be zero: T->pending[wheel] is - * nonzero, so rotr() is nonzero. */ - _timeout = (timeout_t)(ctz(rotr(T->pending[wheel], slot)) + !!wheel) << (wheel * WHEEL_BIT); - /* +1 to higher order wheels as those timeouts are one rotation in the future (otherwise they'd be on a lower wheel or expired) */ + /* ctz input cannot be zero: T->pending[wheel] is + * nonzero, so rotr() is nonzero. */ + _timeout = (timeout_t)(ctz(rotr(T->pending[wheel], slot)) + !!wheel) + << (wheel * WHEEL_BIT); + /* +1 to higher order wheels as those timeouts are one rotation in + * the future (otherwise they'd be on a lower wheel or expired) */ - _timeout -= relmask & T->curtime; - /* reduce by how much lower wheels have progressed */ + _timeout -= relmask & T->curtime; + /* reduce by how much lower wheels have progressed */ - timeout = MIN(_timeout, timeout); - } + timeout = MIN(_timeout, timeout); + } - relmask <<= WHEEL_BIT; - relmask |= WHEEL_MASK; - } + relmask <<= WHEEL_BIT; + relmask |= WHEEL_MASK; + } - return timeout; + return timeout; } /* timeouts_int() */ - /* * Calculate the interval our caller can wait before needing to process * events. */ -timeout_t timeouts_timeout(struct timeouts *T) { - if (!list_empty(&T->expired)) - return 0; +timeout_t timeouts_timeout(struct timeouts *T) +{ + if (!list_empty(&T->expired)) + return 0; - return timeouts_int(T); + return timeouts_int(T); } /* timeouts_timeout() */ +struct timeout *timeouts_get(struct timeouts *T) +{ + if (!list_empty(&T->expired)) { + struct timeout *to = list_top(&T->expired, struct timeout, tqe); -struct timeout *timeouts_get(struct timeouts *T) { - if (!list_empty(&T->expired)) { - struct timeout *to = list_top(&T->expired, struct timeout, tqe); - - list_del_from(&T->expired, &to->tqe); - to->pending = NULL; + list_del_from(&T->expired, &to->tqe); + to->pending = NULL; - return to; - } else { - return NULL; - } + return to; + } else { + return NULL; + } } /* timeouts_get() */ diff --git a/src/lib/timeout.h b/src/lib/timeout.h index e11967af0..fe1e92ddc 100644 --- a/src/lib/timeout.h +++ b/src/lib/timeout.h @@ -26,10 +26,10 @@ #ifndef TIMEOUT_H #define TIMEOUT_H -#include /* bool */ -#include /* FILE */ +#include /* bool */ +#include /* FILE */ -#include /* PRIu64 PRIx64 PRIX64 uint64_t */ +#include /* PRIu64 PRIx64 PRIX64 uint64_t */ #include "list.h" @@ -54,22 +54,24 @@ typedef uint64_t timeout_t; #define TIMEOUT_ABS 0x01 /* treat timeout values as absolute */ -#define TIMEOUT_INITIALIZER(flags) { (flags) } +#define TIMEOUT_INITIALIZER(flags) \ + { \ + (flags) \ + } struct timeout { - int flags; + int flags; - timeout_t expires; - /* absolute expiration time */ + timeout_t expires; + /* absolute expiration time */ - struct list_head *pending; - /* timeout list if pending on wheel or expiry queue */ + struct list_head *pending; + /* timeout list if pending on wheel or expiry queue */ - struct list_node tqe; - /* entry member for struct timeout_list lists */ + struct list_node tqe; + /* entry member for struct timeout_list lists */ }; /* struct timeout */ - struct timeout *timeout_init(struct timeout *); /* initialize timeout structure (same as TIMEOUT_INITIALIZER) */ From 8a6b4bdeeb0b27b66fc65e8ecf1b6fd9b12dc169 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 29 Jul 2018 23:10:29 -0700 Subject: [PATCH 0693/2505] Remove comment after function end in timer wheel library --- src/lib/timeout.c | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/lib/timeout.c b/src/lib/timeout.c index 8df8235df..ec9a44df2 100644 --- a/src/lib/timeout.c +++ b/src/lib/timeout.c @@ -162,13 +162,13 @@ static inline wheel_t rotl(const wheel_t v, uint32_t n) { assert(n < WHEEL_T_BITS); return (v << n) | (v >> (-n & (WHEEL_T_BITS - 1))); -} /* rotl() */ +} static inline wheel_t rotr(const wheel_t v, uint32_t n) { assert(n < WHEEL_T_BITS); return (v >> n) | (v << (-n & (WHEEL_T_BITS - 1))); -} /* rotr() */ +} #undef WHEEL_T_BITS @@ -185,7 +185,7 @@ struct timeouts { timeout_t curtime; timeout_t hertz; -}; /* struct timeouts */ +}; static struct timeouts *timeouts_init(struct timeouts *T) { @@ -206,7 +206,7 @@ static struct timeouts *timeouts_init(struct timeouts *T) T->curtime = 0; return T; -} /* timeouts_init() */ +} struct timeouts *timeouts_open(timeout_error_t *error) { @@ -218,7 +218,7 @@ struct timeouts *timeouts_open(timeout_error_t *error) *error = errno; return NULL; -} /* timeouts_open() */ +} static void timeouts_reset(struct timeouts *T) { @@ -239,7 +239,7 @@ static void timeouts_reset(struct timeouts *T) list_for_each (&reset, to, tqe) { to->pending = NULL; } -} /* timeouts_reset() */ +} void timeouts_close(struct timeouts *T) { @@ -250,7 +250,7 @@ void timeouts_close(struct timeouts *T) timeouts_reset(T); free(T); -} /* timeouts_close() */ +} void timeouts_del(struct timeouts *T, struct timeout *to) { @@ -267,18 +267,18 @@ void timeouts_del(struct timeouts *T, struct timeout *to) to->pending = NULL; } -} /* timeouts_del() */ +} static inline reltime_t timeout_rem(struct timeouts *T, struct timeout *to) { return to->expires - T->curtime; -} /* timeout_rem() */ +} static inline int timeout_wheel(timeout_t timeout) { /* must be called with timeout != 0, so fls input is nonzero */ return (fls(MIN(timeout, TIMEOUT_MAX)) - 1) / WHEEL_BIT; -} /* timeout_wheel() */ +} static inline int timeout_slot(int wheel, timeout_t expires) { @@ -287,7 +287,7 @@ static inline int timeout_slot(int wheel, timeout_t expires) wheel_mask & ((expires >> (wheel * WHEEL_BIT)) - !!wheel); return (int)slot; -} /* timeout_slot() */ +} static void timeouts_sched(struct timeouts *T, struct timeout *to, timeout_t expires) @@ -318,7 +318,7 @@ timeouts_sched(struct timeouts *T, struct timeout *to, timeout_t expires) } list_add_tail(to->pending, &to->tqe); -} /* timeouts_sched() */ +} void timeouts_add(struct timeouts *T, struct timeout *to, timeout_t timeout) { @@ -326,7 +326,7 @@ void timeouts_add(struct timeouts *T, struct timeout *to, timeout_t timeout) timeouts_sched(T, to, timeout); else timeouts_sched(T, to, T->curtime + timeout); -} /* timeouts_add() */ +} void timeouts_update(struct timeouts *T, abstime_t curtime) { @@ -405,12 +405,12 @@ void timeouts_update(struct timeouts *T, abstime_t curtime) } return; -} /* timeouts_update() */ +} void timeouts_step(struct timeouts *T, reltime_t elapsed) { timeouts_update(T, T->curtime + elapsed); -} /* timeouts_step() */ +} bool timeouts_pending(struct timeouts *T) { @@ -422,12 +422,12 @@ bool timeouts_pending(struct timeouts *T) } return !!pending; -} /* timeouts_pending() */ +} bool timeouts_expired(struct timeouts *T) { return !list_empty(&T->expired); -} /* timeouts_expired() */ +} /* * Calculate the interval before needing to process any timeouts pending on @@ -477,7 +477,7 @@ static timeout_t timeouts_int(struct timeouts *T) } return timeout; -} /* timeouts_int() */ +} /* * Calculate the interval our caller can wait before needing to process @@ -489,7 +489,7 @@ timeout_t timeouts_timeout(struct timeouts *T) return 0; return timeouts_int(T); -} /* timeouts_timeout() */ +} struct timeout *timeouts_get(struct timeouts *T) { @@ -503,4 +503,4 @@ struct timeout *timeouts_get(struct timeouts *T) } else { return NULL; } -} /* timeouts_get() */ +} From 0baf5ab7d7bf5a708fc2109756fee99f6aacb590 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 29 Jul 2018 23:26:10 -0700 Subject: [PATCH 0694/2505] Don't epoll_wait() with timeout = 0 --- src/lib/lwan-thread.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 087f2efa5..139c4800a 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -402,7 +402,7 @@ next_timeout(struct death_queue *dq, struct timeouts *wheel, int epoll_fd) wheel_timeout = timeouts_timeout(wheel); } - return (int)wheel_timeout; + return wheel_timeout ? (int)wheel_timeout : -1; } static void *thread_io_loop(void *data) From 5bfd329c4aa62ced23b164d1e80f389ba3b46de4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 30 Jul 2018 06:51:21 -0700 Subject: [PATCH 0695/2505] Fix clock sample under macOS I still don't know if this fix is necessary (or even sane) on FreeBSD. --- src/lib/missing.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/lib/missing.c b/src/lib/missing.c index e76862b98..5b958bad7 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -215,14 +215,17 @@ epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) int events = 0; /* EV_CLEAR should be set only if EPOLLET is there, but Lwan doesn't * always set EPOLLET. In the meantime, force EV_CLEAR every time. */ + uintptr_t udata = (uintptr_t)event->data.ptr; int flags = EV_ADD | EV_CLEAR; - if (event->events & EPOLLIN) + if (event->events & EPOLLIN) { events = EVFILT_READ; - else if (event->events & EPOLLOUT) + } else if (event->events & EPOLLOUT) { events = EVFILT_WRITE; - else - events = EVFILT_EXCEPT; + } else { + events = EVFILT_WRITE; + udata = 1; + } if (event->events & EPOLLONESHOT) flags |= EV_ONESHOT; @@ -231,7 +234,7 @@ epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) if (event->events & EPOLLERR) flags |= EV_ERROR; - EV_SET(&ev, fd, events, flags, 0, 0, event->data.ptr); + EV_SET(&ev, fd, events, flags, 0, 0, (void *)udata); break; } @@ -297,8 +300,14 @@ epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) } for (i = 0; i < r; i++) { - void *maskptr = hash_find(coalesce, (void*)(intptr_t)evs[i].ident); + uintptr_t udata = (uintptr_t)evs[i].udata; + void *maskptr; + + if (udata == 1) { + continue; + } + maskptr = hash_find(coalesce, (void*)(intptr_t)evs[i].ident); if (maskptr) { struct kevent *kev = &evs[i]; From 72ae74c1868576abafa90d2afcb5e57a1ed280b5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 30 Jul 2018 06:57:10 -0700 Subject: [PATCH 0696/2505] TAILQ_CONCAT() will initialize the head for the second list list_append_list() isn't sufficient; list_head_init() must be called as well. --- src/lib/timeout.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib/timeout.c b/src/lib/timeout.c index ec9a44df2..371a7774e 100644 --- a/src/lib/timeout.c +++ b/src/lib/timeout.c @@ -231,10 +231,12 @@ static void timeouts_reset(struct timeouts *T) for (i = 0; i < countof(T->wheel); i++) { for (j = 0; j < countof(T->wheel[i]); j++) { list_append_list(&reset, &T->wheel[i][j]); + list_head_init(&T->wheel[i][j]); } } list_append_list(&reset, &T->expired); + list_head_init(&T->expired); list_for_each (&reset, to, tqe) { to->pending = NULL; @@ -383,7 +385,10 @@ void timeouts_update(struct timeouts *T, abstime_t curtime) while (pending & T->pending[wheel]) { /* ctz input cannot be zero: loop condition. */ int slot = ctz(pending & T->pending[wheel]); + list_append_list(&todo, &T->wheel[wheel][slot]); + list_head_init(&T->wheel[wheel][slot]); + T->pending[wheel] &= ~(UINT64_C(1) << slot); } From 50c58c54bb7901d6a50eac57bc892aafb49a96ec Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 30 Jul 2018 07:02:23 -0700 Subject: [PATCH 0697/2505] Loop clock just once Do not store looping information in the GIF data. This also saves a few bytes in the first frame. --- src/samples/clock/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/samples/clock/main.c b/src/samples/clock/main.c index cf1aef90c..be89dc12c 100644 --- a/src/samples/clock/main.c +++ b/src/samples/clock/main.c @@ -44,7 +44,7 @@ static void destroy_gif(void *data) LWAN_HANDLER(clock) { static const uint8_t base_offsets[] = {0, 0, 2, 2, 4, 4}; - ge_GIF *gif = ge_new_gif(response->buffer, width, height, NULL, 2, 0); + ge_GIF *gif = ge_new_gif(response->buffer, width, height, NULL, 2, -1); uint8_t dot_visible = 0; if (!gif) From e21035a92300bef4b348e4912e216bc196a0b299 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 30 Jul 2018 08:07:16 -0700 Subject: [PATCH 0698/2505] Address yet another buld warning in time wheel library --- src/lib/timeout.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/timeout.c b/src/lib/timeout.c index 371a7774e..dbb270106 100644 --- a/src/lib/timeout.c +++ b/src/lib/timeout.c @@ -462,7 +462,7 @@ static timeout_t timeouts_int(struct timeouts *T) for (wheel = 0; wheel < WHEEL_NUM; wheel++) { if (T->pending[wheel]) { - slot = (int)(wheel_mask & (T->curtime >> (wheel * WHEEL_BIT))); + slot = (unsigned int)(wheel_mask & (T->curtime >> (wheel * WHEEL_BIT))); /* ctz input cannot be zero: T->pending[wheel] is * nonzero, so rotr() is nonzero. */ From 7e0779851fd01dc4f02fe0e30310cd05368dc6e9 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 30 Jul 2018 18:54:24 -0700 Subject: [PATCH 0699/2505] Merge next_timeout() and turn_timer_wheel() --- src/lib/lwan-thread.c | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 139c4800a..7351b40d9 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -340,19 +340,6 @@ static void accept_nudge(int pipe_fd, timeouts_add(wheel, &dq->timeout, 1000); } -static void turn_timer_wheel(struct lwan_thread *t) -{ - struct timespec now; - - if (UNLIKELY(clock_gettime(monotonic_clock_id, &now) < 0)) - lwan_status_critical("Could not get monotonic time"); - - timeouts_update(t->wheel, - (timeout_t)(now.tv_sec * 1000 + now.tv_nsec / 1000000)); - - update_date_cache(t); -} - static bool process_pending_timers(struct death_queue *dq, struct timeouts *wheel, int epoll_fd) @@ -390,19 +377,33 @@ static bool process_pending_timers(struct death_queue *dq, } static int -next_timeout(struct death_queue *dq, struct timeouts *wheel, int epoll_fd) +turn_timer_wheel(struct death_queue *dq, struct lwan_thread *t, int epoll_fd) { - timeout_t wheel_timeout = timeouts_timeout(wheel); + timeout_t wheel_timeout; + struct timespec now; + + if (UNLIKELY(clock_gettime(monotonic_clock_id, &now) < 0)) + lwan_status_critical("Could not get monotonic time"); + + timeouts_update(t->wheel, + (timeout_t)(now.tv_sec * 1000 + now.tv_nsec / 1000000)); + + update_date_cache(t); + wheel_timeout = timeouts_timeout(t->wheel); if (UNLIKELY((int64_t)wheel_timeout < 0)) return -1; if (wheel_timeout == 0) { - if (process_pending_timers(dq, wheel, epoll_fd)) - wheel_timeout = timeouts_timeout(wheel); + if (process_pending_timers(dq, t->wheel, epoll_fd)) { + wheel_timeout = timeouts_timeout(t->wheel); + + if (!wheel_timeout) + return -1; + } } - return wheel_timeout ? (int)wheel_timeout : -1; + return (int)wheel_timeout; } static void *thread_io_loop(void *data) @@ -429,7 +430,7 @@ static void *thread_io_loop(void *data) pthread_barrier_wait(&lwan->thread.barrier); for (;;) { - int timeout = next_timeout(&dq, t->wheel, epoll_fd); + int timeout = turn_timer_wheel(&dq, t, epoll_fd); int n_fds; n_fds = epoll_wait(epoll_fd, events, max_events, timeout); @@ -457,8 +458,6 @@ static void *thread_io_loop(void *data) if (errno == EBADF || errno == EINVAL) break; } - - turn_timer_wheel(t); } pthread_barrier_wait(&lwan->thread.barrier); From d3cbcd4234f11ddb16de8cca5dabdd6852ae958c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 31 Jul 2018 22:11:53 -0700 Subject: [PATCH 0700/2505] If t->pending_fds is full, nudge the thread In addition, close the connection if nudging and retrying to add the file descriptor to the queue a few times fails. Also, close if epoll_ctl() fails. --- src/lib/lwan-thread.c | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 7351b40d9..f02b5b451 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -517,25 +517,40 @@ static void create_thread(struct lwan *l, struct lwan_thread *thread) lwan_status_critical("Could not initialize pending fd queue"); } +void lwan_thread_nudge(struct lwan_thread *t) +{ + uint64_t event = 1; + + if (UNLIKELY(write(t->pipe_fd[1], &event, sizeof(event)) < 0)) + lwan_status_perror("write"); +} + void lwan_thread_add_client(struct lwan_thread *t, int fd) { struct epoll_event event = {.events = events_by_write_flag[1]}; + int i = 0; t->lwan->conns[fd] = (struct lwan_connection){.thread = t}; - if (UNLIKELY(epoll_ctl(t->epoll_fd, EPOLL_CTL_ADD, fd, &event) < 0)) - lwan_status_perror("epoll_ctl"); + if (UNLIKELY(epoll_ctl(t->epoll_fd, EPOLL_CTL_ADD, fd, &event) < 0)) { + lwan_status_perror("EPOLL_CTL_ADD failed"); + goto drop_connection; + } - if (!spsc_queue_push(&t->pending_fds, (void *)(intptr_t)fd)) - lwan_status_error("spsc_queue_push"); -} + do { + bool pushed = spsc_queue_push(&t->pending_fds, (void *)(intptr_t)fd); -void lwan_thread_nudge(struct lwan_thread *t) -{ - uint64_t event = 1; + if (LIKELY(pushed)) + return; - if (UNLIKELY(write(t->pipe_fd[1], &event, sizeof(event)) < 0)) - lwan_status_perror("write"); + /* Queue is full; nudge the thread to consume it. */ + lwan_thread_nudge(t); + } while (i++ < 10); + +drop_connection: + lwan_status_error("Dropping connection %d", fd); + /* FIXME: send "busy" response now, even without receiving request? */ + close(fd); } void lwan_thread_init(struct lwan *l) From 702499d19cb2b313d6034f827ebde7310beea15f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 31 Jul 2018 22:13:53 -0700 Subject: [PATCH 0701/2505] Prettify index.html in clock sample --- src/samples/clock/main.c | 41 +++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/src/samples/clock/main.c b/src/samples/clock/main.c index be89dc12c..14d9d49eb 100644 --- a/src/samples/clock/main.c +++ b/src/samples/clock/main.c @@ -99,22 +99,33 @@ LWAN_HANDLER(clock) LWAN_HANDLER(index) { - static const char index[] = "" \ - "" \ + static const char index[] = "\n" + "\n" \ + "\n" \ "Lwan Clock Sample\n" - "" \ - "" \ - "" \ - "
" \ - "
" \ - "
" \ - "" \ + "\n" \ + "\n" \ + " \n" \ + " \n" \ + " \n" \ + "
\n" \ + "
\n" \ + "
\n" \ + "\n" \ ""; response->mime_type = "text/html"; lwan_strbuf_set_static(response->buffer, index, sizeof(index) - 1); From 203b28199c634c64dde9460a457f405370dd080d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 31 Jul 2018 22:50:26 -0700 Subject: [PATCH 0702/2505] Limit each pending_fds queue to 128 elements By limiting the queue size, a few things will happen: 1) memory usage goes down significantly; 2) if a herd doesn't let go of the main thread, I/O threads will at least have a chance to add those clients to the death queue. --- src/lib/lwan-thread.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index f02b5b451..0cf813fdc 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -468,6 +468,10 @@ static void *thread_io_loop(void *data) return NULL; } +static inline size_t max_size_t(size_t a, size_t b) { + return a > b ? a : b; +} + static void create_thread(struct lwan *l, struct lwan_thread *thread) { int ignore; @@ -513,8 +517,11 @@ static void create_thread(struct lwan *l, struct lwan_thread *thread) if (pthread_attr_destroy(&attr)) lwan_status_critical_perror("pthread_attr_destroy"); - if (spsc_queue_init(&thread->pending_fds, thread->lwan->thread.max_fd) < 0) - lwan_status_critical("Could not initialize pending fd queue"); + size_t n_queue_fds = max_size_t(thread->lwan->thread.max_fd, 128); + if (spsc_queue_init(&thread->pending_fds, n_queue_fds) < 0) { + lwan_status_critical("Could not initialize pending fd " + "queue width %zu elements", n_queue_fds); + } } void lwan_thread_nudge(struct lwan_thread *t) From 616104bd46d8fc5261c7e9e7f201d451a44ab2ed Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 31 Jul 2018 23:00:03 -0700 Subject: [PATCH 0703/2505] Build clock GIF with exact size Width should be exactly 27 pixels instead of 30. Saves ~15 bytes of RAM per frame and some cycles processing each frame while looking for the bounding box. --- src/samples/clock/main.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/samples/clock/main.c b/src/samples/clock/main.c index 14d9d49eb..09b3c5c6c 100644 --- a/src/samples/clock/main.c +++ b/src/samples/clock/main.c @@ -31,7 +31,9 @@ static const uint8_t font[10][5] = { [9] = {7, 5, 7, 1, 7}, }; -static const uint16_t width = 24 + 6; +static const uint16_t width = 3 * 6 /* 6*3px wide digits */ + + 3 * 1 /* 3*1px wide decimal digit space */ + + 3 * 2 /* 2*3px wide minutes+seconds dots */; static const uint16_t height = 5; static void destroy_gif(void *data) From fb07604513f90184eac4381a8fbfa2559ff7afe7 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 1 Aug 2018 07:20:00 -0700 Subject: [PATCH 0704/2505] No need to use compound statement for ATOMIC_LOAD() --- src/lib/queue.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/queue.c b/src/lib/queue.c index f783f269f..5bf7feae8 100644 --- a/src/lib/queue.c +++ b/src/lib/queue.c @@ -44,7 +44,7 @@ #define ATOMIC_INIT(P, V) do { (P) = (V); } while(0) -#define ATOMIC_LOAD(P, O) ({ __sync_fetch_and_add((P), 0); }) +#define ATOMIC_LOAD(P, O) __sync_fetch_and_add((P), 0) #define ATOMIC_STORE(P, V, O) ({ __sync_synchronize(); __sync_lock_test_and_set((P), (V)); }) #else From 229beca6e862bd7e10a5c18eeb1d4dff71e9e72d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 1 Aug 2018 07:35:05 -0700 Subject: [PATCH 0705/2505] Really clamp number of pending_fds elements to 128 --- src/lib/lwan-thread.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 0cf813fdc..9defb31fe 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -468,10 +468,6 @@ static void *thread_io_loop(void *data) return NULL; } -static inline size_t max_size_t(size_t a, size_t b) { - return a > b ? a : b; -} - static void create_thread(struct lwan *l, struct lwan_thread *thread) { int ignore; @@ -517,7 +513,9 @@ static void create_thread(struct lwan *l, struct lwan_thread *thread) if (pthread_attr_destroy(&attr)) lwan_status_critical_perror("pthread_attr_destroy"); - size_t n_queue_fds = max_size_t(thread->lwan->thread.max_fd, 128); + size_t n_queue_fds = thread->lwan->thread.max_fd; + if (n_queue_fds > 128) + n_queue_fds = 128; if (spsc_queue_init(&thread->pending_fds, n_queue_fds) < 0) { lwan_status_critical("Could not initialize pending fd " "queue width %zu elements", n_queue_fds); From ba44987ec820eed1e6dffbafd3681100d9f56fa0 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 1 Aug 2018 07:19:15 -0700 Subject: [PATCH 0706/2505] Reduce pending_fds memory usage by storing ints rather than void* Also, use calloc() instead of posix_memalign(). calloc() ensures that memory is aligned to 4 bytes, so use the simplest/most portable API. This will also ensure that ((size + 1) * sizeof(int)) won't overflow, which is always a good thing. --- src/lib/lwan-thread.c | 8 ++++---- src/lib/queue.c | 18 +++++++----------- src/lib/queue.h | 6 +++--- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 9defb31fe..a6f9397c0 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -323,15 +323,15 @@ static void accept_nudge(int pipe_fd, struct timeouts *wheel, int epoll_fd) { - void *new_fd; uint64_t event; + int new_fd; if (read(pipe_fd, &event, sizeof(event)) < 0) { return; } - while ((new_fd = spsc_queue_pop(pending_fds))) { - struct lwan_connection *conn = &conns[(int)(intptr_t)new_fd]; + while (spsc_queue_pop(pending_fds, &new_fd)) { + struct lwan_connection *conn = &conns[new_fd]; spawn_coro(conn, switcher, dq); update_epoll_flags(dq, conn, epoll_fd, CONN_CORO_MAY_RESUME); @@ -543,7 +543,7 @@ void lwan_thread_add_client(struct lwan_thread *t, int fd) } do { - bool pushed = spsc_queue_push(&t->pending_fds, (void *)(intptr_t)fd); + bool pushed = spsc_queue_push(&t->pending_fds, fd); if (LIKELY(pushed)) return; diff --git a/src/lib/queue.c b/src/lib/queue.c index 5bf7feae8..f5600763f 100644 --- a/src/lib/queue.c +++ b/src/lib/queue.c @@ -56,15 +56,11 @@ int spsc_queue_init(struct spsc_queue *q, size_t size) { - int ret; if (size == 0) return -EINVAL; size = lwan_nextpow2(size); - ret = posix_memalign((void **)&q->buffer, sizeof(void *), (1 + size) * sizeof(void *)); - if (ret < 0) - return -errno; - + q->buffer = calloc(1 + size, sizeof(int)); if (!q->buffer) return -errno; @@ -84,7 +80,7 @@ spsc_queue_free(struct spsc_queue *q) } bool -spsc_queue_push(struct spsc_queue *q, void *input) +spsc_queue_push(struct spsc_queue *q, int input) { const size_t head = ATOMIC_LOAD(&q->head, ATOMIC_RELAXED); @@ -98,18 +94,18 @@ spsc_queue_push(struct spsc_queue *q, void *input) return false; } -void * -spsc_queue_pop(struct spsc_queue *q) +bool +spsc_queue_pop(struct spsc_queue *q, int *output) { const size_t tail = ATOMIC_LOAD(&q->tail, ATOMIC_RELAXED); if (((ATOMIC_LOAD(&q->head, ATOMIC_ACQUIRE) - tail) & q->mask) >= 1) { - void *output = q->buffer[tail & q->mask]; + *output = q->buffer[tail & q->mask]; ATOMIC_STORE(&q->tail, tail + 1, ATOMIC_RELEASE); - return output; + return true; } - return NULL; + return false; } diff --git a/src/lib/queue.h b/src/lib/queue.h index 2af1e6dd3..42c8a7c88 100644 --- a/src/lib/queue.h +++ b/src/lib/queue.h @@ -11,7 +11,7 @@ struct spsc_queue { size_t size; size_t mask; - void **buffer; + int *buffer; char cache_line_pad0[64 - sizeof(size_t) + sizeof(size_t) + sizeof(void *)]; size_t head; @@ -25,6 +25,6 @@ int spsc_queue_init(struct spsc_queue *q, size_t size); void spsc_queue_free(struct spsc_queue *q); -bool spsc_queue_push(struct spsc_queue *q, void *input); +bool spsc_queue_push(struct spsc_queue *q, int input); -void *spsc_queue_pop(struct spsc_queue *q); +bool spsc_queue_pop(struct spsc_queue *q, int *output); From d2870d6d6818c46b0f38606d6452ad9b3e3abf67 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 1 Aug 2018 20:19:36 -0700 Subject: [PATCH 0707/2505] Micro-optimizations: comparison with 0 is cheaper --- src/lib/lwan.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index d6583d9c7..f04c1a101 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -660,7 +660,7 @@ wait_herd(void) { struct pollfd fds = { .fd = (int)main_socket, .events = POLLIN }; - return poll(&fds, 1, -1) == 1; + return poll(&fds, 1, -1) > 0; } enum herd_accept { HERD_MORE = 0, HERD_GONE = -1, HERD_SHUTDOWN = 1 }; @@ -723,7 +723,9 @@ lwan_main_loop(struct lwan *l) lwan_status_info("Ready to serve"); while (LIKELY(main_socket >= 0)) { - if (UNLIKELY(accept_herd(l, &cores) == HERD_SHUTDOWN)) + enum herd_accept ha = accept_herd(l, &cores); + + if (UNLIKELY(ha > HERD_MORE)) break; if (LIKELY(cores)) { From c684e4b6ca997f7abc828e1459116cf6f520191c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 1 Aug 2018 18:25:03 -0700 Subject: [PATCH 0708/2505] Simplify coarse monotonic clock detection --- src/lib/lwan-cache.c | 31 +++++++++---------------------- src/lib/lwan-private.h | 2 ++ src/lib/lwan-thread.c | 15 --------------- src/lib/lwan.c | 13 +++++++++++++ 4 files changed, 24 insertions(+), 37 deletions(-) diff --git a/src/lib/lwan-cache.c b/src/lib/lwan-cache.c index 4f4d72a7d..7bb9b0dc8 100644 --- a/src/lib/lwan-cache.c +++ b/src/lib/lwan-cache.c @@ -74,26 +74,6 @@ struct cache { }; static bool cache_pruner_job(void *data); -static clockid_t clock_id; - -__attribute__((constructor)) static void detect_fastest_monotonic_clock(void) -{ -#ifdef CLOCK_MONOTONIC_COARSE - struct timespec ts; - - if (!clock_gettime(CLOCK_MONOTONIC_COARSE, &ts)) { - clock_id = CLOCK_MONOTONIC_COARSE; - return; - } -#endif - clock_id = CLOCK_MONOTONIC; -} - -static ALWAYS_INLINE void clock_monotonic_gettime(struct timespec *ts) -{ - if (UNLIKELY(clock_gettime(clock_id, ts) < 0)) - lwan_status_perror("clock_gettime"); -} struct cache *cache_create(cache_create_entry_cb create_entry_cb, cache_destroy_entry_cb destroy_entry_cb, @@ -231,7 +211,10 @@ struct cache_entry *cache_get_and_ref_entry(struct cache *cache, if (!hash_add_unique(cache->hash.table, entry->key, entry)) { struct timespec time_to_die; - clock_monotonic_gettime(&time_to_die); + + if (UNLIKELY(clock_gettime(monotonic_clock_id, &time_to_die) < 0)) + lwan_status_critical("clock_gettime"); + entry->time_to_die = time_to_die.tv_sec + cache->settings.time_to_live; if (LIKELY(!pthread_rwlock_wrlock(&cache->queue.lock))) { @@ -312,7 +295,11 @@ static bool cache_pruner_job(void *data) goto end; } - clock_monotonic_gettime(&now); + if (UNLIKELY(clock_gettime(monotonic_clock_id, &now) < 0)) { + lwan_status_perror("clock_gettime"); + goto end; + } + list_for_each_safe(&queue, node, next, entries) { char *key = node->key; diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 26cb421e0..4aa1d4b4c 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -76,3 +76,5 @@ const char *lwan_lua_state_last_error(lua_State *L); # define SECTION_START(name_) __start_ ## name_[] # define SECTION_END(name_) __stop_ ## name_[] #endif + +extern clockid_t monotonic_clock_id; diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index a6f9397c0..9304fa0e3 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -49,21 +49,6 @@ static const uint32_t events_by_write_flag[] = { EPOLLIN | EPOLLRDHUP | EPOLLERR | EPOLLET }; -static clockid_t monotonic_clock_id; - -__attribute__((constructor)) static void detect_fastest_monotonic_clock(void) -{ -#ifdef CLOCK_MONOTONIC_COARSE - struct timespec ts; - - if (!clock_gettime(CLOCK_MONOTONIC_COARSE, &ts)) { - monotonic_clock_id = CLOCK_MONOTONIC_COARSE; - return; - } -#endif - monotonic_clock_id = CLOCK_MONOTONIC; -} - static inline int death_queue_node_to_idx(struct death_queue *dq, struct lwan_connection *conn) { diff --git a/src/lib/lwan.c b/src/lib/lwan.c index f04c1a101..4228bfb62 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -43,6 +43,9 @@ #include "lwan-lua.h" #endif +/* See detect_fastest_monotonic_clock() */ +clockid_t monotonic_clock_id = CLOCK_MONOTONIC; + static const struct lwan_config default_config = { .listener = "localhost:8080", .keep_alive_timeout = 15, @@ -765,3 +768,13 @@ lwan_nextpow2(size_t number) return number + 1; } + +#ifdef CLOCK_MONOTONIC_COARSE +__attribute__((constructor)) static void detect_fastest_monotonic_clock(void) +{ + struct timespec ts; + + if (!clock_gettime(CLOCK_MONOTONIC_COARSE, &ts)) + monotonic_clock_id = CLOCK_MONOTONIC_COARSE; +} +#endif From 92e207809c363c251e2d0d717c1206e699cfe93e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 1 Aug 2018 22:09:31 -0700 Subject: [PATCH 0709/2505] Add more phrases found in the comment sections of the orange website --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 7b08fb22a..358642e8d 100644 --- a/README.md +++ b/README.md @@ -283,3 +283,12 @@ in no particular order. Contributions are appreciated: > "For Round 10, Lwan has taken the crown" -- > [TechEmpower](https://www.techempower.com/blog/2015/04/21/framework-benchmarks-round-10/) + +> "Jeez this is amazing. Just end to end, rock solid engineering. (...) But that sells this work short." +> [kjeetgill](https://news.ycombinator.com/item?id=17548983) + +> "I am only a spare time C coder myself and was surprised that I can follow the code. Nice!" +> [cntlzw](https://news.ycombinator.com/item?id=17550319) + +> "Impressive all and all, even more for being written in (grokkable!) C. Nice work." +> [tpaschalis](https://news.ycombinator.com/item?id=17550961) From 77d69d71cd157ce78d05cbdf7375d22119ae269c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 4 Aug 2018 08:12:28 -0700 Subject: [PATCH 0710/2505] Remove a branch from every handler function By ensuring that, when a module is initialized, the server won't start, we can remove the branches checking for a NULL private data pointer from every handler function. --- src/lib/lwan-mod-lua.c | 3 --- src/lib/lwan-mod-redirect.c | 3 --- src/lib/lwan-mod-rewrite.c | 3 --- src/lib/lwan-mod-serve-files.c | 37 +++++++++++++++++----------------- src/lib/lwan.c | 6 +++++- 5 files changed, 23 insertions(+), 29 deletions(-) diff --git a/src/lib/lwan-mod-lua.c b/src/lib/lwan-mod-lua.c index 52bd73cce..e2d5b7b86 100644 --- a/src/lib/lwan-mod-lua.c +++ b/src/lib/lwan-mod-lua.c @@ -182,9 +182,6 @@ static enum lwan_http_status lua_handle_request(struct lwan_request *request, { struct lwan_lua_priv *priv = instance; - if (UNLIKELY(!priv)) - return HTTP_INTERNAL_ERROR; - struct cache *cache = get_or_create_cache(priv); if (UNLIKELY(!cache)) return HTTP_INTERNAL_ERROR; diff --git a/src/lib/lwan-mod-redirect.c b/src/lib/lwan-mod-redirect.c index b4fe4858c..ca9b64df9 100644 --- a/src/lib/lwan-mod-redirect.c +++ b/src/lib/lwan-mod-redirect.c @@ -27,9 +27,6 @@ static enum lwan_http_status redirect_handle_request(struct lwan_request *request, struct lwan_response *response, void *instance) { - if (UNLIKELY(!instance)) - return HTTP_INTERNAL_ERROR; - struct lwan_key_value *headers = coro_malloc(request->conn->coro, sizeof(*headers) * 2); if (UNLIKELY(!headers)) return HTTP_INTERNAL_ERROR; diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index 3de5dd652..bc475ebfd 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -243,9 +243,6 @@ rewrite_handle_request(struct lwan_request *request, char final_url[PATH_MAX]; struct pattern *p; - if (UNLIKELY(!pd)) - return HTTP_INTERNAL_ERROR; - LWAN_ARRAY_FOREACH(&pd->patterns, p) { struct str_find sf[MAXCAPTURES]; const char *expanded = NULL; diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 960b9ff64..891b66437 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -1050,33 +1050,32 @@ static enum lwan_http_status serve_files_handle_request(struct lwan_request *request, struct lwan_response *response, void *instance) { - enum lwan_http_status return_status = HTTP_NOT_FOUND; struct serve_files_priv *priv = instance; + enum lwan_http_status return_status; + struct file_cache_entry *fce; struct cache_entry *ce; - if (UNLIKELY(!priv)) { - return_status = HTTP_INTERNAL_ERROR; - goto fail; - } - ce = cache_coro_get_and_ref_entry(priv->cache, request->conn->coro, request->url.value); - if (LIKELY(ce)) { - struct file_cache_entry *fce = (struct file_cache_entry *)ce; - - if (client_has_fresh_content(request, fce->last_modified.integer)) { - return_status = HTTP_NOT_MODIFIED; - } else { - response->mime_type = fce->mime_type; - response->stream.callback = fce->funcs->serve; - response->stream.data = ce; - response->stream.priv = priv; + if (UNLIKELY(!ce)) { + return_status = HTTP_NOT_FOUND; + goto out; + } - return HTTP_OK; - } + fce = (struct file_cache_entry *)ce; + if (client_has_fresh_content(request, fce->last_modified.integer)) { + return_status = HTTP_NOT_MODIFIED; + goto out; } -fail: + response->mime_type = fce->mime_type; + response->stream.callback = fce->funcs->serve; + response->stream.data = ce; + response->stream.priv = priv; + + return HTTP_OK; + +out: response->stream.callback = NULL; return return_status; } diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 4228bfb62..e7c2b0ad0 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -245,7 +245,7 @@ static void parse_listener_prefix(struct config *c, struct config_line *l, goto out; add_map: - if (module == handler && !handler) { + if (!module && !handler) { config_error(c, "Missing module or handler"); goto out; } @@ -263,6 +263,10 @@ static void parse_listener_prefix(struct config *c, struct config_line *l, hash = NULL; } else if (module && module->create_from_hash && module->handle_request) { url_map.data = module->create_from_hash(prefix, hash); + if (!url_map.data) { + config_error(c, "Could not create module instance"); + goto out; + } if (module->parse_conf && !module->parse_conf(url_map.data, isolated)) { const char *msg = config_last_error(isolated); From 82eb8cb742b29f21f393af3ee51911c16f43808f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 4 Aug 2018 08:14:08 -0700 Subject: [PATCH 0711/2505] Get rid of the legacy "prefix" sections in the "listener" sections Referencing handlers and modules by their names is easier and less confusing. --- src/lib/lwan.c | 38 +------------------------------------- testrunner.conf | 5 +---- 2 files changed, 2 insertions(+), 41 deletions(-) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index e7c2b0ad0..79e5a362c 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -200,29 +200,7 @@ static void parse_listener_prefix(struct config *c, struct config_line *l, while (config_read_line(c, l)) { switch (l->type) { case CONFIG_LINE_TYPE_LINE: - if (streq(l->key, "module")) { - if (module) { - config_error(c, "Module already specified"); - goto out; - } - module = find_module(l->value); - if (!module) { - config_error(c, "Could not find module \"%s\"", l->value); - goto out; - } - } else if (streq(l->key, "handler")) { - if (handler) { - config_error(c, "Handler already specified"); - goto out; - } - handler = find_handler(l->value); - if (!handler) { - config_error(c, "Could not find handler \"%s\"", l->value); - goto out; - } - } else { - hash_add(hash, strdup(l->key), strdup(l->value)); - } + hash_add(hash, strdup(l->key), strdup(l->value)); break; case CONFIG_LINE_TYPE_SECTION: @@ -245,15 +223,6 @@ static void parse_listener_prefix(struct config *c, struct config_line *l, goto out; add_map: - if (!module && !handler) { - config_error(c, "Missing module or handler"); - goto out; - } - if (module && handler) { - config_error(c, "Handler and module are mutually exclusive"); - goto out; - } - if (handler) { url_map.handler = handler; url_map.flags |= HANDLER_PARSE_MASK | HANDLER_DATA_IS_HASH_TABLE; @@ -324,11 +293,6 @@ static void parse_listener(struct config *c, struct config_line *l, config_error(c, "Expecting prefix section"); return; case CONFIG_LINE_TYPE_SECTION: - if (streq(l->key, "prefix")) { - parse_listener_prefix(c, l, lwan, NULL, NULL); - continue; - } - if (l->key[0] == '&') { l->key++; diff --git a/testrunner.conf b/testrunner.conf index 5b8ff00f4..3bcd87d06 100644 --- a/testrunner.conf +++ b/testrunner.conf @@ -38,10 +38,7 @@ listener *:8080 { &gif_beacon /beacon - prefix /favicon.ico { - # Use prefix+handler instead of & for testing purposes - handler = gif_beacon - } + &gif_beacon /favicon.ico &test_post_will_it_blend /post/blend From 364c36d22476ee694f0704dbc42a6e51418d8ddd Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 5 Aug 2018 07:40:37 -0700 Subject: [PATCH 0712/2505] Clean up parse_listener_prefix() a little bit more Some conditions now can't happen, so just remove the check for them. Add configuration errors for stuff that shouldn't happen with the modules shipped with Lwan, but might happen with user-provided modules. --- src/lib/lwan.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 79e5a362c..df96674d9 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -181,7 +181,8 @@ static void parse_listener_prefix_authorization(struct config *c, free(url_map->authorization.password_file); } -static void parse_listener_prefix(struct config *c, struct config_line *l, +static void parse_listener_prefix(struct config *c, + struct config_line *l, struct lwan *lwan, const struct lwan_module *module, void *handler) @@ -201,19 +202,17 @@ static void parse_listener_prefix(struct config *c, struct config_line *l, switch (l->type) { case CONFIG_LINE_TYPE_LINE: hash_add(hash, strdup(l->key), strdup(l->value)); - break; + case CONFIG_LINE_TYPE_SECTION: if (streq(l->key, "authorization")) { parse_listener_prefix_authorization(c, l, &url_map); - } else { - if (!config_skip_section(c, l)) { - config_error(c, "Could not skip section"); - goto out; - } + } else if (!config_skip_section(c, l)) { + config_error(c, "Could not skip section"); + goto out; } - break; + case CONFIG_LINE_TYPE_SECTION_END: goto add_map; } @@ -230,7 +229,7 @@ static void parse_listener_prefix(struct config *c, struct config_line *l, url_map.module = NULL; hash = NULL; - } else if (module && module->create_from_hash && module->handle_request) { + } else if (module->create_from_hash && module->handle_request) { url_map.data = module->create_from_hash(prefix, hash); if (!url_map.data) { config_error(c, "Could not create module instance"); @@ -247,8 +246,12 @@ static void parse_listener_prefix(struct config *c, struct config_line *l, url_map.handler = module->handle_request; url_map.flags |= module->flags; url_map.module = module; - } else { - config_error(c, "Invalid handler"); + } else if (UNLIKELY(!module->create_from_hash)) { + config_error(c, "Module isn't prepared to load settings from a file; " + "create_from_hash() method isn't present"); + goto out; + } else if (UNLIKELY(!module->handle_request)) { + config_error(c, "Module does not have handle_request() method"); goto out; } From f276b24bf42dabf3577a7a4627352fde32bbdd29 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 5 Aug 2018 09:57:36 -0700 Subject: [PATCH 0713/2505] Remove timeout-bitops.c: Lwan supports only GCC-compatible compilers No need to carry around support for platforms that are unsupported by Lwan. If needed these can be brought back... but I highly doubt it. --- src/lib/timeout-bitops.c | 245 --------------------------------------- src/lib/timeout.c | 13 ++- 2 files changed, 12 insertions(+), 246 deletions(-) delete mode 100644 src/lib/timeout-bitops.c diff --git a/src/lib/timeout-bitops.c b/src/lib/timeout-bitops.c deleted file mode 100644 index 2c70f1365..000000000 --- a/src/lib/timeout-bitops.c +++ /dev/null @@ -1,245 +0,0 @@ -#include -#ifdef _MSC_VER -#include /* _BitScanForward, _BitScanReverse */ -#endif - -/* First define ctz and clz functions; these are compiler-dependent if - * you want them to be fast. */ -#if defined(__GNUC__) && !defined(TIMEOUT_DISABLE_GNUC_BITOPS) - -/* On GCC and clang and some others, we can use __builtin functions. They - * are not defined for n==0, but timeout.s never calls them with n==0. */ - -#define ctz64(n) __builtin_ctzll(n) -#define clz64(n) __builtin_clzll(n) -#if LONG_BITS == 32 -#define ctz32(n) __builtin_ctzl(n) -#define clz32(n) __builtin_clzl(n) -#else -#define ctz32(n) __builtin_ctz(n) -#define clz32(n) __builtin_clz(n) -#endif - -#elif defined(_MSC_VER) && !defined(TIMEOUT_DISABLE_MSVC_BITOPS) - -/* On MSVC, we have these handy functions. We can ignore their return - * values, since we will never supply val == 0. */ - -static __inline int ctz32(unsigned long val) -{ - DWORD zeros = 0; - _BitScanForward(&zeros, val); - return zeros; -} -static __inline int clz32(unsigned long val) -{ - DWORD zeros = 0; - _BitScanReverse(&zeros, val); - return zeros; -} -#ifdef _WIN64 -/* According to the documentation, these only exist on Win64. */ -static __inline int ctz64(uint64_t val) -{ - DWORD zeros = 0; - _BitScanForward64(&zeros, val); - return zeros; -} -static __inline int clz64(uint64_t val) -{ - DWORD zeros = 0; - _BitScanReverse64(&zeros, val); - return zeros; -} -#else -static __inline int ctz64(uint64_t val) -{ - uint32_t lo = (uint32_t)val; - uint32_t hi = (uint32_t)(val >> 32); - return lo ? ctz32(lo) : 32 + ctz32(hi); -} -static __inline int clz64(uint64_t val) -{ - uint32_t lo = (uint32_t)val; - uint32_t hi = (uint32_t)(val >> 32); - return hi ? clz32(hi) : 32 + clz32(lo); -} -#endif - -/* End of MSVC case. */ - -#else - -/* TODO: There are more clever ways to do this in the generic case. */ - -#define process_(one, cz_bits, bits) \ - if (x < (one << (cz_bits - bits))) { \ - rv += bits; \ - x <<= bits; \ - } - -#define process64(bits) process_((UINT64_C(1)), 64, (bits)) -static inline int clz64(uint64_t x) -{ - int rv = 0; - - process64(32); - process64(16); - process64(8); - process64(4); - process64(2); - process64(1); - return rv; -} -#define process32(bits) process_((UINT32_C(1)), 32, (bits)) -static inline int clz32(uint32_t x) -{ - int rv = 0; - - process32(16); - process32(8); - process32(4); - process32(2); - process32(1); - return rv; -} - -#undef process_ -#undef process32 -#undef process64 -#define process_(one, bits) \ - if ((x & ((one << (bits)) - 1)) == 0) { \ - rv += bits; \ - x >>= bits; \ - } - -#define process64(bits) process_((UINT64_C(1)), bits) -static inline int ctz64(uint64_t x) -{ - int rv = 0; - - process64(32); - process64(16); - process64(8); - process64(4); - process64(2); - process64(1); - return rv; -} - -#define process32(bits) process_((UINT32_C(1)), bits) -static inline int ctz32(uint32_t x) -{ - int rv = 0; - - process32(16); - process32(8); - process32(4); - process32(2); - process32(1); - return rv; -} - -#undef process32 -#undef process64 -#undef process_ - -/* End of generic case */ - -#endif /* End of defining ctz */ - -#ifdef TEST_BITOPS -#include -#include - -static uint64_t testcases[] = {13371337 * 10, 100, 385789752, 82574, - (((uint64_t)1) << 63) + (((uint64_t)1) << 31) + - 10101}; - -static int naive_clz(int bits, uint64_t v) -{ - int r = 0; - uint64_t bit = ((uint64_t)1) << (bits - 1); - while (bit && 0 == (v & bit)) { - r++; - bit >>= 1; - } - /* printf("clz(%d,%lx) -> %d\n", bits, v, r); */ - return r; -} - -static int naive_ctz(int bits, uint64_t v) -{ - int r = 0; - uint64_t bit = 1; - while (bit && 0 == (v & bit)) { - r++; - bit <<= 1; - if (r == bits) - break; - } - /* printf("ctz(%d,%lx) -> %d\n", bits, v, r); */ - return r; -} - -static int check(uint64_t vv) -{ - uint32_t v32 = (uint32_t)vv; - - if (vv == 0) - return 1; /* c[tl]z64(0) is undefined. */ - - if (ctz64(vv) != naive_ctz(64, vv)) { - printf("mismatch with ctz64: %d\n", ctz64(vv)); - exit(1); - return 0; - } - if (clz64(vv) != naive_clz(64, vv)) { - printf("mismatch with clz64: %d\n", clz64(vv)); - exit(1); - return 0; - } - - if (v32 == 0) - return 1; /* c[lt]z(0) is undefined. */ - - if (ctz32(v32) != naive_ctz(32, v32)) { - printf("mismatch with ctz32: %d\n", ctz32(v32)); - exit(1); - return 0; - } - if (clz32(v32) != naive_clz(32, v32)) { - printf("mismatch with clz32: %d\n", clz32(v32)); - exit(1); - return 0; - } - return 1; -} - -int main(int c, char **v) -{ - unsigned int i; - const unsigned int n = sizeof(testcases) / sizeof(testcases[0]); - int result = 0; - - for (i = 0; i <= 63; ++i) { - uint64_t x = 1 << i; - if (!check(x)) - result = 1; - --x; - if (!check(x)) - result = 1; - } - - for (i = 0; i < n; ++i) { - if (!check(testcases[i])) - result = 1; - } - if (result) { - puts("FAIL"); - } else { - puts("OK"); - } - return result; -} -#endif diff --git a/src/lib/timeout.c b/src/lib/timeout.c index dbb270106..c403a054e 100644 --- a/src/lib/timeout.c +++ b/src/lib/timeout.c @@ -104,7 +104,18 @@ #define WHEEL_MASK (WHEEL_LEN - 1) #define TIMEOUT_MAX ((TIMEOUT_C(1) << (WHEEL_BIT * WHEEL_NUM)) - 1) -#include "timeout-bitops.c" +/* On GCC and clang and some others, we can use __builtin functions. They + * are not defined for n==0, but these are never called with n==0. */ + +#define ctz64(n) __builtin_ctzll(n) +#define clz64(n) __builtin_clzll(n) +#if LONG_BITS == 32 +#define ctz32(n) __builtin_ctzl(n) +#define clz32(n) __builtin_clzl(n) +#else +#define ctz32(n) __builtin_ctz(n) +#define clz32(n) __builtin_clz(n) +#endif #if WHEEL_BIT == 6 #define ctz(n) ctz64(n) From 8ad10e8f0de9378c621ef4defcab2b6374821559 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 5 Aug 2018 10:01:12 -0700 Subject: [PATCH 0714/2505] Remove configuration knobs for timer wheel The defaults of WHEEL_BIT=6 and WHEEL_NUM=4 are OK; no need to tune them, so no need to carry code to make this configurable. --- src/lib/timeout.c | 40 ---------------------------------------- 1 file changed, 40 deletions(-) diff --git a/src/lib/timeout.c b/src/lib/timeout.c index c403a054e..c4d1af60b 100644 --- a/src/lib/timeout.c +++ b/src/lib/timeout.c @@ -91,13 +91,8 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -#if !defined WHEEL_BIT #define WHEEL_BIT 6 -#endif - -#if !defined WHEEL_NUM #define WHEEL_NUM 4 -#endif #define WHEEL_LEN (1U << WHEEL_BIT) #define WHEEL_MAX (WHEEL_LEN - 1) @@ -117,51 +112,16 @@ #define clz32(n) __builtin_clz(n) #endif -#if WHEEL_BIT == 6 #define ctz(n) ctz64(n) #define clz(n) clz64(n) #define fls(n) ((int)(64 - clz64(n))) -#else -#define ctz(n) ctz32(n) -#define clz(n) clz32(n) -#define fls(n) ((int)(32 - clz32(n))) -#endif -#if WHEEL_BIT == 6 #define WHEEL_C(n) UINT64_C(n) #define WHEEL_PRIu PRIu64 #define WHEEL_PRIx PRIx64 typedef uint64_t wheel_t; -#elif WHEEL_BIT == 5 - -#define WHEEL_C(n) UINT32_C(n) -#define WHEEL_PRIu PRIu32 -#define WHEEL_PRIx PRIx32 - -typedef uint32_t wheel_t; - -#elif WHEEL_BIT == 4 - -#define WHEEL_C(n) UINT16_C(n) -#define WHEEL_PRIu PRIu16 -#define WHEEL_PRIx PRIx16 - -typedef uint16_t wheel_t; - -#elif WHEEL_BIT == 3 - -#define WHEEL_C(n) UINT8_C(n) -#define WHEEL_PRIu PRIu8 -#define WHEEL_PRIx PRIx8 - -typedef uint8_t wheel_t; - -#else -#error invalid WHEEL_BIT value -#endif - /* See "Safe, Efficient, and Portable Rotate in C/C++" by John Regehr * http://blog.regehr.org/archives/1063 * These should be recognized by the backend C compiler and turned into a rol From 4bfff9810e7e97e35e2b46045da6bea166c5e0da Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 5 Aug 2018 10:02:50 -0700 Subject: [PATCH 0715/2505] timeout.c does not use stdio --- src/lib/timeout.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/timeout.c b/src/lib/timeout.c index c4d1af60b..531b95541 100644 --- a/src/lib/timeout.c +++ b/src/lib/timeout.c @@ -29,7 +29,6 @@ #include /* CHAR_BIT */ #include /* NULL */ -#include /* FILE fprintf(3) */ #include /* malloc(3) free(3) */ #include /* UINT64_C uint64_t */ From 08cad9a6c9e47619cb8177365257a91d8860465d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 5 Aug 2018 10:12:26 -0700 Subject: [PATCH 0716/2505] Use UINT64_C(n) instead of assuming 64bit ints always use ULL suffix --- src/lib/lwan.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index df96674d9..69d094c35 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -645,7 +645,7 @@ accept_one(struct lwan *l, uint64_t *cores) int fd = accept4((int)main_socket, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC); if (LIKELY(fd >= 0)) { - *cores |= 1ULL<<(unsigned)schedule_client(l, fd); + *cores |= UINT64_C(1)<<(unsigned)schedule_client(l, fd); return HERD_MORE; } @@ -704,7 +704,7 @@ lwan_main_loop(struct lwan *l) if (LIKELY(cores)) { for (unsigned short t = 0; t < l->thread.count; t++) { - if (cores & 1ULL<thread.threads[t]); } From cd8935bbe7846b4792ccf8e2f250109c7293e863 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 5 Aug 2018 10:30:26 -0700 Subject: [PATCH 0717/2505] Only use jemalloc or tcmalloc if asked Pass -DUSE_ALTERNATIVE_MALLOC=ON to try detecting these malloc implementations; otherwise, just use the one provided by the C library. The glibc malloc underwent through a lot of optimizations in the last years and is generally pretty good on multithreaded programs such as Lwan. This makes it easier to use tools like Valgrind memcheck as well. --- CMakeLists.txt | 26 ++++++++++++++------------ README.md | 10 ++++++++-- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 293b4dd35..523d5c5d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,20 +53,22 @@ else () set(HAVE_LUA 1) endif () -find_library(TCMALLOC_LIBRARY NAMES tcmalloc_minimal tcmalloc) -if (TCMALLOC_LIBRARY) - message(STATUS "tcmalloc found: ${TCMALLOC_LIBRARY}") - list(APPEND ADDITIONAL_LIBRARIES ${TCMALLOC_LIBRARY}) -else () - find_library(JEMALLOC_LIBRARY NAMES jemalloc) - if (JEMALLOC_LIBRARY) - message(STATUS "jemalloc found: ${JEMALLOC_LIBRARY}") - list(APPEND ADDITIONAL_LIBRARIES ${JEMALLOC_LIBRARY}) +option(USE_ALTERNATIVE_MALLOC "Use alternative malloc implementations" OFF) +if (USE_ALTERNATIVE_MALLOC) + find_library(TCMALLOC_LIBRARY NAMES tcmalloc_minimal tcmalloc) + if (TCMALLOC_LIBRARY) + message(STATUS "tcmalloc found: ${TCMALLOC_LIBRARY}") + list(APPEND ADDITIONAL_LIBRARIES ${TCMALLOC_LIBRARY}) else () - message(STATUS "jemalloc and tcmalloc were not found, using system malloc") + find_library(JEMALLOC_LIBRARY NAMES jemalloc) + if (JEMALLOC_LIBRARY) + message(STATUS "jemalloc found: ${JEMALLOC_LIBRARY}") + list(APPEND ADDITIONAL_LIBRARIES ${JEMALLOC_LIBRARY}) + else () + message(STATUS "jemalloc and tcmalloc were not found, using system malloc") + endif() endif() -endif() - +endif () # # Look for C library functions diff --git a/README.md b/README.md index 358642e8d..452395a29 100644 --- a/README.md +++ b/README.md @@ -34,9 +34,10 @@ package management tool that's used by your distribution. The build system will look for these libraries and enable/link if available. - [Lua 5.1](http://www.lua.org) or [LuaJIT 2.0](http://luajit.org) - - [TCMalloc](https://github.com/gperftools/gperftools) - - [jemalloc](http://jemalloc.net/) - [Valgrind](http://valgrind.org) + - Alternative memory allocators can be used by passing `-DUSE_ALTERNATIVE_MALLOC=ON` to CMake: + - [TCMalloc](https://github.com/gperftools/gperftools) + - [jemalloc](http://jemalloc.net/) - To run test suite: - [Python](https://www.python.org/) (2.6+) with Requests - [Lua 5.1](http://www.lua.org) @@ -122,6 +123,11 @@ following arguments to the CMake invocation line: - `-DSANITIZER=address` selects the Address Sanitizer. - `-DSANITIZER=thread` selects the Thread Sanitizer. +Alternative memory allocators can be selected as well. Lwan currently +supports [TCMalloc](https://github.com/gperftools/gperftools) and +[jemalloc](http://jemalloc.net/) out of the box. To use either one of them, +pass `-DALTERNATIVE_MALLOC=ON` to the CMake invocation line. + ### Tests ~/lwan/build$ make teststuite From 85f7357447440bc52b15a427ad48481559608fa1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 5 Aug 2018 11:51:28 -0700 Subject: [PATCH 0718/2505] Begin documenting module options --- README.md | 130 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 124 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 452395a29..c442600af 100644 --- a/README.md +++ b/README.md @@ -112,12 +112,12 @@ mutex. The default build (i.e. not passing `-DCMAKE_BUILD_TYPE=Release`) will build a version suitable for debugging purposes. This version can be used under -Valgrind *(if its headers are present)*, is built with Undefined Behavior -Sanitizer, and includes debugging messages that are stripped in the release -version. Debugging messages are printed for each and every request. +Valgrind *(if its headers are present)* and includes debugging messages that +are stripped in the release version. Debugging messages are printed for +each and every request. -Which sanitizer will be used in a debug build can be selected by passing the -following arguments to the CMake invocation line: +On debug builds, sanitizers can be enabled. To select which one to build Lwan +with, specify one of the following options to the CMake invocation line: - `-DSANITIZER=ubsan` selects the Undefined Behavior Sanitizer. - `-DSANITIZER=address` selects the Address Sanitizer. @@ -164,11 +164,129 @@ the `./wwwroot` directory. Lwan will listen on port 8080 on all interfaces. Lwan will detect the number of CPUs, will increase the maximum number of open file descriptors and generally try its best to autodetect reasonable -settings for the environment it's running on. +settings for the environment it's running on. Many of these settings can +be tweaked in the configuration file, but it's usually a good idea to not +mess with them. Optionally, the `lwan` binary can be used for one-shot static file serving without any configuration file. Run it with `--help` for help on that. +Built-in modules +---------------- + +A list of built-in modules can be obtained by executing Lwan with the `-m` +command-line argument. + +### Built-in modules + +A list of built-in modules can be obtained by executing Lwan with the `-m` +command-line argument. The following is some basic documentation for the modules shipped with Lwan. + +Note that, for options in configuration files, `this_key` is equivalent to `this key`. + +#### File Serving + +The `serve_files` module will serve static files, and automatically create +directory indices or serve pre-compressed files. It'll generally try its +best to serve files in the fastest way possible according to some heuristics. + + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `path` | `str` | `NULL` | Path to a directory containing files to be served | +| `index_path` | `str` | `index.html` | File name to serve as an index for a directory | +| `serve_precompressed_path` | `bool` | `true` | If $FILE.gz exists, is smaller and newer than $FILE, and the client accepts `gzip` encoding, transfer it | +| `auto_index` | `bool` | `true` | Generate a directory list automatically if no `index_path` file present. Otherwise, yields 404 | +| `directory_list_template` | `str` | `NULL` | Path to a Mustache template for the directory list; by default, use an internal template | + +#### Lua + +The `lua` module will allow requests to be serviced by scripts written in +the [Lua](https://www.lua.org/) programming language. Although the +functionality provided by this module is quite spartan, it's able to run +frameworks such as [Sailor](https://github.com/lpereira/sailor-hello-lwan). + +Scripts can be served from files or embedded in the configuration file, and +the results of loading them, the standard Lua modules, and (optionally, if +using LuaJIT) optimizing the code will be cached for a while. Each I/O +thread in Lwan will create an instance of a Lua VM (i.e. one `lua_State` +struct for every I/O thread), and each Lwan coroutine will spawn a Lua +thread (with `lua_newthread()`) per request. Because of this, Lua scripts +can't use global variables, as they may be not only serviced by different +threads, but the state will be available only for the amount of time +specified in the `cache_period` configuration option. + +There's no need to have one instance of the Lua module for each endpoint; a +single script, embeded in the configuration file or otherwise, can service +many different endpoints. Scripts are supposed to implement functions with +the following signature: `handle_${METHOD}_${ENDPOINT}(req)`, where +`${METHOD}` can be a HTTP method (i.e. `get`, `post`, `head`, etc.), and +`${ENDPOINT}` is the desired endpoint to be handled by that function. The +special `${ENDPOINT}` `root` can be specified to act as a catchall. The +`req` parameter points to a metatable that contains methods to obtain +information from the request, or to set the response, as seen below: + + - `req:query_param(param)` returns the query parameter (from the query string) with the key `param`, or `nil` if not found + - `req:post_param(param)` returns the post parameter (only for `${POST}` handlers) with the key `param`, or `nil` if not found + - `req:set_response(str)` sets the response to the string `str` + - `req:say(str)` sends a response chunk (using chunked encoding in HTTP) + - `req:send_event(event, str)` sends an event (using server-sent events) + - `req:cookie(param)` returns the cookie named `param`, or `nil` is not found + - `req:set_headers(tbl)` sets the response headers from the table `tbl`; a header may be specified multiple times by using a table, rather than a string, in the table value (`{'foo'={'bar', 'baz'}}`); must be called before sending any response with `say()` or `send_event()` + - `req:sleep(ms)` pauses the current handler for the specified amount of milliseconds + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `default_type` | `str` | `text/plain` | Default MIME-Type for responses | +| `script_file` | `str` | `NULL` | Path to Lua script| +| `cache_period` | `time` | `15s` | Time to keep Lua state loaded in memory | +| `script` | `str` | `NULL` | Inline lua script | + +Rewrite +------- + +The `rewrite` module will match +[patterns](https://man.openbsd.org/patterns.7) in URLs and give the option +to either redirect to another URL, or rewrite the request in a way that Lwan +will handle the request as if it were made in that way originally. The +patterns are a special kind of regular expressions, forked from Lua 5.3.1, +that do not contain backreferences and other features that could create +denial-of-service issues in Lwan. The new URL can be specified using a +simple text substitution syntax, or use Lua scripts; Lua scripts will +contain the same metamethods available in the `req` metatable provided by +the Lua module, so it can be quite powerful. + +Each instance of the rewrite module will require a `pattern` and the action +to execute when such pattern is matched. Patterns are evaluated in the +order they appear in the configuration file, and are specified using nested +sections in the configuration file. For instance, consider the following +example, where two patterns are specified: + +``` +rewrite /some/base/endpoint { + pattern posts/(%d+) { + rewrite_as = /cms/view-post?id=%1 + } + pattern imgur/(%a+)/(%g+) { + redirect_to = https://i.imgur.com/%2.%1 + } +} +``` + +This example defines two patterns, one providing a nicer URL that's hidden +from the user, and another providing a dufferent way to obtain a direct link +to an image hosted on a popular image hosting service. + +The value of `rewrite_as` or `redirect_to` can be Lua scripts as well; in +which case, the option `expand_with_lua` must be set to `true`, and, instead +of using the simple text substitution syntax as the example above, a +function named `handle_rewrite(req, captures)` has to be defined instead. +The `req` parameter is documented in the Lua module section; the `captures` +parameter is a table containing all the captures, in order. This function +returns the new URL to redirect to. + +This module has no options by itself. + Portability ----------- From f8f5870fd54cb916ee614b7bd357b7c362f8eebc Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 5 Aug 2018 14:56:11 -0700 Subject: [PATCH 0719/2505] Add documentation for rewrite and redirect modules Also, fix some typos nearby. --- README.md | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c442600af..a4fb730f0 100644 --- a/README.md +++ b/README.md @@ -242,8 +242,7 @@ information from the request, or to set the response, as seen below: | `cache_period` | `time` | `15s` | Time to keep Lua state loaded in memory | | `script` | `str` | `NULL` | Inline lua script | -Rewrite -------- +#### Rewrite The `rewrite` module will match [patterns](https://man.openbsd.org/patterns.7) in URLs and give the option @@ -265,17 +264,21 @@ example, where two patterns are specified: ``` rewrite /some/base/endpoint { pattern posts/(%d+) { + # Matches /some/base/endpointposts/2600 rewrite_as = /cms/view-post?id=%1 } pattern imgur/(%a+)/(%g+) { + # Matches /some/base/endpointimgur/gif/mpT94Ld redirect_to = https://i.imgur.com/%2.%1 } } ``` This example defines two patterns, one providing a nicer URL that's hidden -from the user, and another providing a dufferent way to obtain a direct link -to an image hosted on a popular image hosting service. +from the user, and another providing a different way to obtain a direct link +to an image hosted on a popular image hosting service (i.e. requesting +`/some/base/endpoint/imgur/mp4/4kOZNYX` will redirect directly to a resource +in the Imgur service). The value of `rewrite_as` or `redirect_to` can be Lua scripts as well; in which case, the option `expand_with_lua` must be set to `true`, and, instead @@ -285,7 +288,48 @@ The `req` parameter is documented in the Lua module section; the `captures` parameter is a table containing all the captures, in order. This function returns the new URL to redirect to. -This module has no options by itself. +This module has no options by itself. Options are specified in each and +every pattern. + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `rewrite_as` | `str` | `NULL` | Rewrite the URL following this pattern | +| `redirect_to` | `str` | `NULL` | Redirect to a new URL following this pattern | +| `expand_with_lua` | `bool` | `false` | Use Lua scripts to redirect to or rewrite a request | + +`redirect_to` and `rewrite_as` are mutually exclusive, and one of them must be +specified at least. + +#### Redirect + +The `redirect` module will, as it says in the tin, generate a `301 +Moved permanently` response, according to the options specified in its +configuration. Generally, the `rewrite` module should be used instead +as it packs more features; however, this module serves also as an +example of how to write Lwan modules (weighing at ~40 lines of code). + +If the `to` option is not specified, it always generates a `500 +Internal Server Error` response. + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `to` | `str` | `NULL` | The location to redirect to | + +#### Response + +The `response` module will generate an artificial response of any HTTP code. +In addition to also serving as an example of how to write a Lwan module, +it can be used to carve out voids from other modules (e.g. generating a +`405 Not Allowed` response for files in `/.git`, if `/` is served with +the `serve_files` module). + +If the supplied `code` falls outside the response codes known by Lwan, +a `404 Not Found` error will be sent instead. + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `code` | `int` | `999` | A HTTP response code | + Portability ----------- @@ -299,6 +343,7 @@ implemented on top of [kqueue](https://en.wikipedia.org/wiki/Kqueue), and Linux-only syscalls and GNU extensions have been implemented for the supported systems. + Performance ----------- From 627d2870321dfaa483856dfb2b0f0ceffc1ee6c1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 5 Aug 2018 15:55:31 -0700 Subject: [PATCH 0720/2505] Document the configuration file format, global settings, & more --- README.md | 164 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 156 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index a4fb730f0..f04549de2 100644 --- a/README.md +++ b/README.md @@ -171,18 +171,154 @@ mess with them. Optionally, the `lwan` binary can be used for one-shot static file serving without any configuration file. Run it with `--help` for help on that. -Built-in modules +Configuration File ---------------- -A list of built-in modules can be obtained by executing Lwan with the `-m` -command-line argument. +### Format -### Built-in modules +Lwan uses a familiar `key = value` configuration file syntax. Comments are +supported with the `#` character (similar to e.g. shell scripts, Python, +and Perl). Nested sections can be created with curly brackets. Sections +can be empty; in this case, curly brackets are optional. -A list of built-in modules can be obtained by executing Lwan with the `-m` -command-line argument. The following is some basic documentation for the modules shipped with Lwan. +`some_key_name` is equivalent to `some key name` in configuration files (as +an implementation detail, code reading configuration options will only be +given the version with underscores). + +``` +sound volume = 11 # This one is 1 louder + +playlist metal { + files = ''' + /multi/line/strings/are/supported.mp3 + ''' +} + +playlist chiptune { + files = """ + /if/it/starts/with/single/quotes/it/ends/with/single/quotes.mod + /but/it/can/use/double/quotes.s3m + """ +} +``` + +#### Value types + +| Type | Description | +|--------|-------------| +| `str` | Any kind of free-form text, usually application specific | +| `int` | Integer number. Range is application specific | +| `time` | Time interval. See table below for units | +| `bool` | Boolean value. See table below for valid values | + +#### Time Intervals + +Time fields can be specified using multipliers. Multiple can be specified, they're +just added together; for instance, "1M 1w" specifies "1 month and 1 week". The following +table lists all known multipliers: + +| Multiplier | Description | +|------------|-------------| +| `s` | Seconds | +| `m` | Minutes | +| `h` | Hours | +| `d` | Days | +| `w` | Weeks | +| `M` | Months | +| `y` | Years | + +A number with a multiplier not in this table is ignored; a warning is issued while +reading the configuration file. No spaces must exist between the number and its +multiplier. + +#### Boolean Values + +Valid values for "true": + + - Any integer number different than 0 + - `on` + - `true` + - `yes` + +Valid values for "false": -Note that, for options in configuration files, `this_key` is equivalent to `this key`. + - 0 + - `off` + - `false` + - `no` + +### Global Settings + +It's generally a good idea to let Lwan decide the best settings for your environment. +However, not environment is the same, and not all uses can be decided automatically, +so some configuration options are provided. + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `keep alive timeout` | `time` | `15` | Timeout to keep a connection alive | +| `quiet` | `bool` | `false` | Set to true to not print any debugging messages. Only effective in release builds. | +| `reuse port` | `bool` | `false` | Sets `SO_REUSEPORT` to `1` in the master socket | +| `expires` | `time` | `1M 1w` | Value of the "Expires" header. Default is 1 month and 1 week | +| `threads` | `int` | `0` | Number of I/O threads. Default (0) is the number of online CPUs | +| `proxy protocol` | `bool` | `false` | Enables the [PROXY protocol](https://www.haproxy.com/blog/haproxy/proxy-protocol/). Versions 1 and 2 are supported. Only enable this setting if using Lwan behind a proxy, and the proxy supports this protocol; otherwise, this allows anybody to spoof origin IP addresses | +| `max post data size` | `int` | `40960` | Sets the maximum number of data size for POST requests, in bytes | + +### Straitjacket + +Lwan can drop its privileges to a user in the system, and limit its +filesystem view with a chroot. While not bulletproof, this provides a +first layer of security in the case there's a bug in Lwan. + +In order to use this feature, declare a `straitjacket` section, and set +some options. This requires Lwan to be executed as `root`. + +Although this section can be written anywhere in the file (as long as +it is a top level declaration), if any directories are open, due to +e.g. instantiating the `serve_files` module, Lwan will refuse to +start. (This check is only performed on Linux as a safeguard for +malconfiguration.) + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `user` | `str` | `NULL` | Drop privileges to this user name | +| `chroot` | `str` | `NULL` | Path to `chroot()` | + +### Listeners + +In order to specify which interfaces Lwan should listen on, a `listener` section +must be specified. Only one listener per Lwan process is accepted at the moment. +The only parameter to a listener block is the interface address and the port to +listen on; anything inside a listener section are instances of modules. + +The syntax for the listener parameter is `${ADDRESS}:${PORT}`, where `${ADDRESS}` +can either be `*` (binding to all interfaces), an IPv6 address (if surrounded by +square brackets), an IPv4 address, or a hostname. If systemd's socket activation +is used, `systemd` can be specified as a parameter. + +### Routing URLs Using Modules or Handlers + +In order to route URLs, Lwan matches the largest common prefix from the request +URI with a set of prefixes specified in the listener section. How a request to +a particular prefix will be handled depends on which handler or module has been +declared in the listener section. Handlers and modules are similar internally; +handlers are merely functions and hold no state, and modules holds state (named +instance). Multiple instances of a module can appear in a listener section. + +There is no special syntax to attach a prefix to a handler or module; all the +configuration parser rules apply here. Use `${NAME} ${PREFIX}` to link the +`${PREFIX}` prefix path to either a handler named `${NAME}` (if `${NAME}` +begins with `&`, as with C's "address of" operator), or a module named +`${NAME}`. Empty sections can be used here. + +Each module will have its specific set of options, and they're listed in the +next sections. In addition to configuration options, a special `authorization` +section can be present in the declaration of a module instance. Handlers do +not take any configuration options, but may include the `authorization` +section. + +A list of built-in modules can be obtained by executing Lwan with the `-m` +command-line argument. The following is some basic documentation for the +modules shipped with Lwan. #### File Serving @@ -319,7 +455,7 @@ Internal Server Error` response. The `response` module will generate an artificial response of any HTTP code. In addition to also serving as an example of how to write a Lwan module, -it can be used to carve out voids from other modules (e.g. generating a +it can be used to carve out voids from other modules (e.g. generating a `405 Not Allowed` response for files in `/.git`, if `/` is served with the `serve_files` module). @@ -330,6 +466,18 @@ a `404 Not Found` error will be sent instead. |--------|------|---------|-------------| | `code` | `int` | `999` | A HTTP response code | +### Authorization Section + +Authorization sections can be declared in any module instance or handler, +and provides a way to authorize the fulfillment of that request through +the standard HTTP authorization mechanism. In order to require authorization +to access a certain module instance or handler, declare an `authorization` +section with a `basic` parameter, and set one of its options. + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `realm` | `str` | `Lwan` | Realm for authorization. This is usually shown in the user/password UI in browsers | +| `password file` | `str` | `NULL` | Path for a file containing username and passwords (in clear text). The file format is the same as the configuration file format used by Lwan | Portability ----------- From 7fd5eafe80ec57641f3f7e74bdf6ee26cde467c4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 5 Aug 2018 17:03:50 -0700 Subject: [PATCH 0721/2505] Export lwan_request_sleep() in liblwan.so --- src/lib/liblwan.sym | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/liblwan.sym b/src/lib/liblwan.sym index 171e6ece6..d091bdce2 100644 --- a/src/lib/liblwan.sym +++ b/src/lib/liblwan.sym @@ -27,6 +27,7 @@ global: lwan_request_get_post_param; lwan_request_get_query_param; lwan_request_get_remote_address; + lwan_request_sleep; lwan_response; lwan_response_send_chunk; From 604cee57ae018cabee7a9ece03d217ba9c8eb9c0 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 5 Aug 2018 17:38:18 -0700 Subject: [PATCH 0722/2505] Add links to Clock and FreeGeoIP samples --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f04549de2..376b7a1e5 100644 --- a/README.md +++ b/README.md @@ -96,8 +96,9 @@ This will generate a few binaries: - `src/bin/lwan/lwan`: The main Lwan executable. May be executed with `--help` for guidance. - `src/bin/testrunner/testrunner`: Contains code to execute the test suite. - - `src/samples/freegeoip/freegeoip`: FreeGeoIP sample implementation. Requires SQLite. + - `src/samples/freegeoip/freegeoip`: [FreeGeoIP sample implementation](https://freegeoip.lwan.ws). Requires SQLite. - `src/samples/techempower/techempower`: Code for the Techempower Web Framework benchmark. Requires SQLite and MySQL libraries. + - `src/samples/clock/clock`: [Clock sample](https://time.lwan.ws). Generates a GIF file that always shows the local time. - `src/bin/tools/mimegen`: Builds the extension-MIME type table. Used during build process. - `src/bin/tools/bin2hex`: Generates a C file from a binary file, suitable for use with #include. From 026ef20ab1b3337267da387e190e18298d4f88a9 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 5 Aug 2018 17:38:34 -0700 Subject: [PATCH 0723/2505] Fix some typos and other miscelaneous stuff in README --- README.md | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 376b7a1e5..6fc961d10 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,7 @@ Running ------- Set up the server by editing the provided `lwan.conf`; the format is -very simple and should be self-explanatory. +explained in details below. Configuration files are loaded from the current directory. If no changes are made to this file, running Lwan will serve static files located in @@ -192,6 +192,7 @@ sound volume = 11 # This one is 1 louder playlist metal { files = ''' /multi/line/strings/are/supported.mp3 + /anything/inside/these/are/stored/verbatim.mp3 ''' } @@ -236,23 +237,23 @@ multiplier. Valid values for "true": - - Any integer number different than 0 - - `on` - - `true` - - `yes` + - Any integer number different than 0 + - `on` + - `true` + - `yes` Valid values for "false": - - 0 - - `off` - - `false` - - `no` + - 0 + - `off` + - `false` + - `no` ### Global Settings -It's generally a good idea to let Lwan decide the best settings for your environment. -However, not environment is the same, and not all uses can be decided automatically, -so some configuration options are provided. +It's generally a good idea to let Lwan decide the best settings for your +environment. However, not every environment is the same, and not all uses +can be decided automatically, so some configuration options are provided. | Option | Type | Default | Description | |--------|------|---------|-------------| @@ -434,8 +435,8 @@ every pattern. | `redirect_to` | `str` | `NULL` | Redirect to a new URL following this pattern | | `expand_with_lua` | `bool` | `false` | Use Lua scripts to redirect to or rewrite a request | -`redirect_to` and `rewrite_as` are mutually exclusive, and one of them must be -specified at least. +`redirect_to` and `rewrite_as` options are mutually exclusive, and one of +them must be specified at least. #### Redirect From ae9ca54d41893068092ee8d0a72e07d4367dbf5e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 5 Aug 2018 17:39:05 -0700 Subject: [PATCH 0724/2505] Add "Hacking" section to README --- README.md | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 104 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6fc961d10..b2da1c9d9 100644 --- a/README.md +++ b/README.md @@ -481,6 +481,108 @@ section with a `basic` parameter, and set one of its options. | `realm` | `str` | `Lwan` | Realm for authorization. This is usually shown in the user/password UI in browsers | | `password file` | `str` | `NULL` | Path for a file containing username and passwords (in clear text). The file format is the same as the configuration file format used by Lwan | +Hacking +------- + +## Coding Style + +Lwan tries to follow a consistent coding style throughout the project. If you're +considering contributing a patch to the project, please respect this style by trying +to match the style of the surrounding code. In general: + + - `global_variables_are_named_like_this`, even though they tend to be rare and, with rare exceptions, marked as `static` + - Local variables are usually shorter `local_var` + - Struct names are often as short as they're descriptive. `typedef` for structs are rarely used in Lwan + - Header files should use `#pragma once` instead of the usual include guard hackery + - Functions that are used between .c files but are not APIs to be exposed to liblwan should have their prototype added to `lwan-private.h` + - Functions should be short and sweet. Exceptions may apply + - Public functions should be prefixed with `lwan_` + - Public types should be prefixed with `lwan_` + - Private functions must be static, and can be named without the `lwan_` prefix + - Code is indented with 4 spaces; don't use tabs + - There's a suggested line break at column 80, but it's not enforced + - `/* Old C-style comments are preferred */` + - `clang-format` can be used to format the source code in an acceptable way; a `.clang-format` file is provided + +## Tests + +If modifying well-tested areas of the code (e.g. the event loop, HTTP parser, +etc.), please add a new integration test and make sure that, before you send a +pull request, all tests (including the new ones you've sent) are working. +Tests can be added by modifying `src/scripts/testsuite.py`, and executed by +either invoking that script directly from the source root, or executing the +`testsuite` build target. + +Some tests will only work on Linux, and won't be executed on other platforms. + +## Exporting APIs + +The shared object version of `liblwan` on ELF targets (e.g. Linux) will use +a symbol filter script to hide symbols that are considered private to the +library. Please edit `src/lib/liblwan.sym` to add new symbols that should +be exported to `liblwan.so`. + +## Using Git and Pull Requests + +Lwan tries to maintain a source history that's as flat as possible, devoid of +merge commits. This means that pull requests should be rebased on top of the +current master before they can be merged; sometimes this can be made +automatically by the GitHub interface, sometimes they need some manual work to +fix conflicts. It is appreciated if the contributor fixes these conflicts when +asked. + +It is advisable to push your changes to your fork on a branch-per-pull request, +rather than pushing to the `master` branch; the reason is explained below. + +Please ensure that Git is configured properly with your name (it doesn't really +matter if it is your legal name or a nickname, but it should be enough to credit +you) and a valid email address. There's no need to add `Signed-off-by` lines, +even though it's fine to send commits with them. + +If a change is requested in a pull request, you have two choices: + + - Reply asking for clarification. Maybe the intentions were not clear enough, +and whoever asked for changes didn't fully understand what you were trying to +achieve + - Fix the issue. When fixing issues found in pull requests, *please* use +[interactive rebases](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History) to +squash or fixup commits; don't add your fixes on top of your tree. After rewriting +the history locally, force-push to your PR branch; the PR will update automatically +with your changes. + +It is not enforced, but it is recommended to create smaller commits. How +commits are split in Lwan is pretty much arbitrary, so please take a look at +the commit history to get the idea on how the division should be made. Git +offers a plethora of commands to achieve this result: the already mentioned +interactive rebase, the `-p` option to `git add`, and `git commit --amend` +are good examples. + +Commit messages should have one line of summary (~72 chars), followed by an +empty line, followed by paragraphs of 80-lines explaining the change. The +paragraphs explaining the changes are usually not necessary if the summary +is good enough. Try to [write good commit messages](https://chris.beams.io/posts/git-commit/). + +## Licensing + +Lwan is licensed under the GNU General Public License, version 2, or (at your option), +any later version. Therefore: + + - Code must be either LGPLv2.1, GPLv2, a permissive "copyfree" license that is compatible +with GPLv2 (e.g. MIT, BSD 3-clause), or public domain code (e.g. CC0) + - Although the program can be distributed and used as if it were licensed as GPLv3, +its code must be compatible with GPLv2 as well; no new code can be licensed under versions +of GPL newer than 2 + - Likewise, licenses that are compatible with GPLv3 but incompatible with GPLv2 (e.g. Apache 2) +are not suitable for inclusion in Lwan + - Even if the license does not specify that credit should be given (e.g. CC0-licensed code), +please give credit to the original author for that piece of code + - Contrary to popular belief, it is possible to use a GPL'd piece of code on a server without +having to share the code for your application. It is only when the binary of that server is +shared that source must be available to whoever has that binary. Merely accessing a Lwan +server through HTTP does not qualify as having access to the binary program that's running +on the server. + - When in doubt, please consult a lawyer that understands free software licensing + Portability ----------- @@ -491,8 +593,8 @@ and build support library functions as appropriate. For instance, [epoll](https://en.wikipedia.org/wiki/Epoll) has been implemented on top of [kqueue](https://en.wikipedia.org/wiki/Kqueue), and Linux-only syscalls and GNU extensions have been implemented for the -supported systems. - +supported systems. [This blog post](https://tia.mat.br/posts/2018/06/28/include_next_and_portability.html) +explains the details and how `#include_next` is used. Performance ----------- From 74a7665907462e7a5874c4c9694cfede40a8905a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 5 Aug 2018 17:42:57 -0700 Subject: [PATCH 0725/2505] Use correct header levels for the Hacking section in README --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b2da1c9d9..b57dd48b6 100644 --- a/README.md +++ b/README.md @@ -484,7 +484,7 @@ section with a `basic` parameter, and set one of its options. Hacking ------- -## Coding Style +### Coding Style Lwan tries to follow a consistent coding style throughout the project. If you're considering contributing a patch to the project, please respect this style by trying @@ -504,7 +504,7 @@ to match the style of the surrounding code. In general: - `/* Old C-style comments are preferred */` - `clang-format` can be used to format the source code in an acceptable way; a `.clang-format` file is provided -## Tests +### Tests If modifying well-tested areas of the code (e.g. the event loop, HTTP parser, etc.), please add a new integration test and make sure that, before you send a @@ -515,14 +515,14 @@ either invoking that script directly from the source root, or executing the Some tests will only work on Linux, and won't be executed on other platforms. -## Exporting APIs +### Exporting APIs The shared object version of `liblwan` on ELF targets (e.g. Linux) will use a symbol filter script to hide symbols that are considered private to the library. Please edit `src/lib/liblwan.sym` to add new symbols that should be exported to `liblwan.so`. -## Using Git and Pull Requests +### Using Git and Pull Requests Lwan tries to maintain a source history that's as flat as possible, devoid of merge commits. This means that pull requests should be rebased on top of the @@ -562,7 +562,7 @@ empty line, followed by paragraphs of 80-lines explaining the change. The paragraphs explaining the changes are usually not necessary if the summary is good enough. Try to [write good commit messages](https://chris.beams.io/posts/git-commit/). -## Licensing +### Licensing Lwan is licensed under the GNU General Public License, version 2, or (at your option), any later version. Therefore: From f3259d1dfb3567180c1b4c50a36be36ab5abfa8c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 5 Aug 2018 17:45:14 -0700 Subject: [PATCH 0726/2505] Clarify use of `static` keyword --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b57dd48b6..63041349f 100644 --- a/README.md +++ b/README.md @@ -490,7 +490,7 @@ Lwan tries to follow a consistent coding style throughout the project. If you'r considering contributing a patch to the project, please respect this style by trying to match the style of the surrounding code. In general: - - `global_variables_are_named_like_this`, even though they tend to be rare and, with rare exceptions, marked as `static` + - `global_variables_are_named_like_this`, even though they tend to be rare and should be marked as `static` (with rare exceptions) - Local variables are usually shorter `local_var` - Struct names are often as short as they're descriptive. `typedef` for structs are rarely used in Lwan - Header files should use `#pragma once` instead of the usual include guard hackery From 843691d30305bd64623044bfce0bd132f5000f06 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 5 Aug 2018 17:48:23 -0700 Subject: [PATCH 0727/2505] Document possible boolean values as a table --- README.md | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 63041349f..b9eb32d67 100644 --- a/README.md +++ b/README.md @@ -235,19 +235,12 @@ multiplier. #### Boolean Values -Valid values for "true": - - - Any integer number different than 0 - - `on` - - `true` - - `yes` - -Valid values for "false": - - - 0 - - `off` - - `false` - - `no` +| True Values | False Values | +|-------------|--------------| +| Any integer number different than 0 | 0 | +| `on` | `off` | +| `true` | `false` | +| `yes` | `no` | ### Global Settings From 2a161ae19a4659c5aa7d9aa7b95c801d6a4dd6e1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 5 Aug 2018 18:12:53 -0700 Subject: [PATCH 0728/2505] Clarify the Licensing section in README --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b9eb32d67..f0028a62d 100644 --- a/README.md +++ b/README.md @@ -565,16 +565,18 @@ with GPLv2 (e.g. MIT, BSD 3-clause), or public domain code (e.g. CC0) - Although the program can be distributed and used as if it were licensed as GPLv3, its code must be compatible with GPLv2 as well; no new code can be licensed under versions of GPL newer than 2 - - Likewise, licenses that are compatible with GPLv3 but incompatible with GPLv2 (e.g. Apache 2) -are not suitable for inclusion in Lwan + - Likewise, code licensed under licenses compatible with GPLv3 but +incompatible with GPLv2 (e.g. Apache 2) are not suitable for inclusion in +Lwan - Even if the license does not specify that credit should be given (e.g. CC0-licensed code), please give credit to the original author for that piece of code - Contrary to popular belief, it is possible to use a GPL'd piece of code on a server without having to share the code for your application. It is only when the binary of that server is shared that source must be available to whoever has that binary. Merely accessing a Lwan server through HTTP does not qualify as having access to the binary program that's running -on the server. - - When in doubt, please consult a lawyer that understands free software licensing +on the server + - When in doubt, don't take legal advice from a README file: please consult +a lawyer that understands free software licensing Portability ----------- From 489fc3cf38c71f1d10e62a15eb3bcae45d85cd67 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 5 Aug 2018 18:20:43 -0700 Subject: [PATCH 0729/2505] Mention brew packages to install macOS dependencies --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index f0028a62d..c9f0ce9aa 100644 --- a/README.md +++ b/README.md @@ -55,11 +55,13 @@ The build system will look for these libraries and enable/link if available. - ArchLinux: `pacman -S cmake zlib` - FreeBSD: `pkg install cmake pkgconf` - Ubuntu 14+: `apt-get update && apt-get install git cmake zlib1g-dev pkg-config` + - macOS: `brew install cmake` #### Build all examples - ArchLinux: `pacman -S cmake zlib sqlite luajit libmariadbclient gperftools valgrind` - FreeBSD: `pkg install cmake pkgconf sqlite3 lua51` - Ubuntu 14+: `apt-get update && apt-get install git cmake zlib1g-dev pkg-config lua5.1-dev libsqlite3-dev libmysqlclient-dev` + - macOS: `brew install cmake mysql-connector-c sqlite lua@5.1 pkg-config` ### Build commands From ab84f2cf643d3de5efd2ed1aaf6aae1ff18b3190 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 5 Aug 2018 18:21:32 -0700 Subject: [PATCH 0730/2505] Clarify local variables bullet in coding style section --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c9f0ce9aa..53d676142 100644 --- a/README.md +++ b/README.md @@ -486,7 +486,7 @@ considering contributing a patch to the project, please respect this style by tr to match the style of the surrounding code. In general: - `global_variables_are_named_like_this`, even though they tend to be rare and should be marked as `static` (with rare exceptions) - - Local variables are usually shorter `local_var` + - Local variables are usually shorter, e.g. `local_var`, `i`, `conn` - Struct names are often as short as they're descriptive. `typedef` for structs are rarely used in Lwan - Header files should use `#pragma once` instead of the usual include guard hackery - Functions that are used between .c files but are not APIs to be exposed to liblwan should have their prototype added to `lwan-private.h` From 61bf363652bd6bb42b4039bc108eae1409fe10f7 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 5 Aug 2018 21:12:46 -0700 Subject: [PATCH 0731/2505] Refine the Hacking section in the README --- README.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 53d676142..39c138ba5 100644 --- a/README.md +++ b/README.md @@ -479,6 +479,11 @@ section with a `basic` parameter, and set one of its options. Hacking ------- +Please read this section (and follow it) if you're planning on contributing +to Lwan. There's nothing unexpected here; this mostly follows the rules and +expectations of many other FOSS projects, but every one expects things a +little bit different from one another. + ### Coding Style Lwan tries to follow a consistent coding style throughout the project. If you're @@ -534,16 +539,18 @@ matter if it is your legal name or a nickname, but it should be enough to credit you) and a valid email address. There's no need to add `Signed-off-by` lines, even though it's fine to send commits with them. -If a change is requested in a pull request, you have two choices: +If a change is requested in a pull request, you have three choices: - - Reply asking for clarification. Maybe the intentions were not clear enough, + - *Reply asking for clarification.* Maybe the intentions were not clear enough, and whoever asked for changes didn't fully understand what you were trying to achieve - - Fix the issue. When fixing issues found in pull requests, *please* use + - *Fix the issue.* When fixing issues found in pull requests, *please* use [interactive rebases](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History) to -squash or fixup commits; don't add your fixes on top of your tree. After rewriting +squash or fixup commits; don't add your fixes on top of your tree. Do not create +another pull request just to accomodate the changes. After rewriting the history locally, force-push to your PR branch; the PR will update automatically -with your changes. +with your changes. Rewriting the history of development branches is fine, and +force-pushing them is normal and expected. It is not enforced, but it is recommended to create smaller commits. How commits are split in Lwan is pretty much arbitrary, so please take a look at From 497c117826a4d94cb93be261bd62c1cb0625ec5b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 8 Aug 2018 06:25:50 -0700 Subject: [PATCH 0732/2505] Use CRC32 for integer hashes if available, as well --- src/lib/hash.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/lib/hash.c b/src/lib/hash.c index 15e914b67..1ad8a5784 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -58,8 +58,11 @@ struct hash { #define STEPS 64 #define DEFAULT_ODD_CONSTANT 0x27d4eb2d +static inline unsigned int hash_int_shift_mult(const void *keyptr); + static unsigned int odd_constant = DEFAULT_ODD_CONSTANT; static unsigned (*hash_str)(const void *key) = murmur3_simple; +static unsigned (*hash_int)(const void *key) = hash_int_shift_mult; static unsigned int get_random_unsigned(void) { @@ -84,7 +87,7 @@ static unsigned int get_random_unsigned(void) return value; } -static inline unsigned int hash_int(const void *keyptr) +static inline unsigned int hash_int_shift_mult(const void *keyptr) { /* http://www.concentric.net/~Ttwang/tech/inthash.htm */ unsigned int key = (unsigned int)(long)keyptr; @@ -99,7 +102,7 @@ static inline unsigned int hash_int(const void *keyptr) } #if defined(HAVE_BUILTIN_CPU_INIT) && defined(HAVE_BUILTIN_IA32_CRC32) -static inline unsigned int hash_crc32(const void *keyptr) +static inline unsigned int hash_str_crc32(const void *keyptr) { unsigned int hash = odd_constant; const char *key = keyptr; @@ -134,6 +137,12 @@ static inline unsigned int hash_crc32(const void *keyptr) return hash; } + +static inline unsigned int hash_int_crc32(const void *keyptr) +{ + return __builtin_ia32_crc32si(odd_constant, + (unsigned int)(uintptr_t)keyptr); +} #endif __attribute__((constructor)) static void initialize_odd_constant(void) @@ -146,7 +155,8 @@ __attribute__((constructor)) static void initialize_odd_constant(void) #if defined(HAVE_BUILTIN_CPU_INIT) && defined(HAVE_BUILTIN_IA32_CRC32) __builtin_cpu_init(); if (__builtin_cpu_supports("sse4.2")) { - hash_str = hash_crc32; + hash_str = hash_str_crc32; + hash_int = hash_int_crc32; } #endif } From e79d02c56cda5b7ee6f35816d47e81babb5cb331 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 8 Aug 2018 08:10:57 -0700 Subject: [PATCH 0733/2505] It's likely that, on PROXY v2, cmd_ver will be PROXY --- src/lib/lwan-request.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 0b7a37e2d..0494a03bf 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -218,12 +218,7 @@ parse_proxy_protocol_v2(struct lwan_request *request, char *buffer) if (UNLIKELY(size > (unsigned int)sizeof(*hdr))) return NULL; - if (hdr->cmd_ver == LOCAL) { - struct sockaddr_in *from = &proxy->from.ipv4; - struct sockaddr_in *to = &proxy->to.ipv4; - - from->sin_family = to->sin_family = AF_UNSPEC; - } else if (hdr->cmd_ver == PROXY) { + if (LIKELY(hdr->cmd_ver == PROXY)) { if (hdr->fam == TCP4) { struct sockaddr_in *from = &proxy->from.ipv4; struct sockaddr_in *to = &proxy->to.ipv4; @@ -249,6 +244,11 @@ parse_proxy_protocol_v2(struct lwan_request *request, char *buffer) } else { return NULL; } + } else if (hdr->cmd_ver == LOCAL) { + struct sockaddr_in *from = &proxy->from.ipv4; + struct sockaddr_in *to = &proxy->to.ipv4; + + from->sin_family = to->sin_family = AF_UNSPEC; } else { return NULL; } From 2b2718c79f56bb653ed4a7938e7aa3994f260be2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 8 Aug 2018 08:30:38 -0700 Subject: [PATCH 0734/2505] Use LWAN_ARRAY_FOREACH() macro in testrunner --- src/bin/testrunner/main.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/bin/testrunner/main.c b/src/bin/testrunner/main.c index 39ab43d8c..5c8801d89 100644 --- a/src/bin/testrunner/main.c +++ b/src/bin/testrunner/main.c @@ -156,6 +156,7 @@ LWAN_HANDLER(test_post_big) LWAN_HANDLER(hello_world) { + struct lwan_key_value *iter; static struct lwan_key_value headers[] = { { .key = "X-The-Answer-To-The-Universal-Question", .value = "42" }, { NULL, NULL } @@ -176,17 +177,18 @@ LWAN_HANDLER(hello_world) lwan_strbuf_append_str(response->buffer, "\n\nCookies\n", 0); lwan_strbuf_append_str(response->buffer, "-------\n\n", 0); - struct lwan_key_value *qs = request->cookies.base.base; - for (; qs && qs->key; qs++) + LWAN_ARRAY_FOREACH(&request->cookies, iter) { lwan_strbuf_append_printf(response->buffer, - "Key = \"%s\"; Value = \"%s\"\n", qs->key, qs->value); + "Key = \"%s\"; Value = \"%s\"\n", iter->key, iter->value); + } lwan_strbuf_append_str(response->buffer, "\n\nQuery String Variables\n", 0); lwan_strbuf_append_str(response->buffer, "----------------------\n\n", 0); - for (qs = request->query_params.base.base; qs && qs->key; qs++) + LWAN_ARRAY_FOREACH(&request->query_params, iter) { lwan_strbuf_append_printf(response->buffer, - "Key = \"%s\"; Value = \"%s\"\n", qs->key, qs->value); + "Key = \"%s\"; Value = \"%s\"\n", iter->key, iter->value); + } if (lwan_request_get_method(request) != REQUEST_METHOD_POST) goto end; @@ -194,9 +196,10 @@ LWAN_HANDLER(hello_world) lwan_strbuf_append_str(response->buffer, "\n\nPOST data\n", 0); lwan_strbuf_append_str(response->buffer, "---------\n\n", 0); - for (qs = request->post_data.base.base; qs && qs->key; qs++) + LWAN_ARRAY_FOREACH(&request->post_data, iter) { lwan_strbuf_append_printf(response->buffer, - "Key = \"%s\"; Value = \"%s\"\n", qs->key, qs->value); + "Key = \"%s\"; Value = \"%s\"\n", iter->key, iter->value); + } end: return HTTP_OK; From 46f638bb07fbba1975965353e5d5daaf79100c98 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 8 Aug 2018 08:11:28 -0700 Subject: [PATCH 0735/2505] There's no need to append a sentinel value while parsing key/values This is a remnant of refactoring this code to use lwan_array. --- src/lib/lwan-array.c | 2 +- src/lib/lwan-request.c | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/lib/lwan-array.c b/src/lib/lwan-array.c index e00d3c289..906ef84e5 100644 --- a/src/lib/lwan-array.c +++ b/src/lib/lwan-array.c @@ -90,7 +90,7 @@ void lwan_array_sort(struct lwan_array *a, int (*cmp)(const void *a, const void *b)) { if (LIKELY(a->elements)) - qsort(a->base, a->elements - 1, element_size, cmp); + qsort(a->base, a->elements, element_size, cmp); } static void coro_lwan_array_free(void *data) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 0494a03bf..500a5228c 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -378,11 +378,6 @@ parse_key_values(struct lwan_request *request, kv->value = value; } while (ptr); - kv = lwan_key_value_array_append(array); - if (UNLIKELY(!kv)) - goto error; - kv->key = kv->value = NULL; - lwan_key_value_array_sort(array, key_value_compare); return; @@ -1206,7 +1201,7 @@ value_lookup(const struct lwan_key_value_array *array, const char *key) struct lwan_key_value k = { .key = (char *)key }; struct lwan_key_value *entry; - entry = bsearch(&k, la->base, la->elements - 1, sizeof(k), key_value_compare); + entry = bsearch(&k, la->base, la->elements, sizeof(k), key_value_compare); if (LIKELY(entry)) return entry->value; } From 5b57c5f23f5d0ab8f717ff24febb5bfc1d0e444e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 8 Aug 2018 22:51:42 -0700 Subject: [PATCH 0736/2505] Simplify ring buffer implementation Use only two indices rather than two indices+size. Also, use uint32_t instead of size_t for the indices, which further decreases the struct size. --- src/lib/lwan-config.c | 49 ++++++++--------------- src/lib/lwan-template.c | 28 ++++--------- src/lib/ringbuffer.h | 89 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 52 deletions(-) create mode 100644 src/lib/ringbuffer.h diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 89cd925b6..a163b512c 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -37,6 +37,8 @@ #include "lwan-config.h" #include "lwan-strbuf.h" +#include "ringbuffer.h" + enum lexeme_type { LEXEME_ERROR, LEXEME_STRING, @@ -68,15 +70,8 @@ struct lexeme { } value; }; -struct lexeme_ring_buffer { - struct lexeme lexemes[16]; - size_t first, last, population; -}; - -struct config_ring_buffer { - struct config_line items[16]; - size_t first, last, population; -}; +DEFINE_RING_BUFFER_TYPE(lexeme_ring_buffer, struct lexeme, 16) +DEFINE_RING_BUFFER_TYPE(config_ring_buffer, struct config_line, 16) struct lexer { void *(*state)(struct lexer *); @@ -197,52 +192,40 @@ bool config_error(struct config *conf, const char *fmt, ...) static bool config_buffer_consume(struct config_ring_buffer *buf, struct config_line **line) { - if (!buf->population) + if (config_ring_buffer_empty(buf)) return false; - *line = &buf->items[buf->first]; - buf->first = (buf->first + 1) % 16; - buf->population--; - + *line = config_ring_buffer_get_ptr(buf); return true; } static bool config_buffer_emit(struct config_ring_buffer *buf, struct config_line *line) { - if (buf->population == 16) + if (config_ring_buffer_full(buf)) return false; - buf->items[buf->last] = *line; - buf->last = (buf->last + 1) % 16; - buf->population++; - + config_ring_buffer_put(buf, line); return true; } static bool lexeme_buffer_consume(struct lexeme_ring_buffer *buf, struct lexeme **lexeme) { - if (!buf->population) + if (lexeme_ring_buffer_empty(buf)) return false; - *lexeme = &buf->lexemes[buf->first]; - buf->first = (buf->first + 1) % 16; - buf->population--; - + *lexeme = lexeme_ring_buffer_get_ptr(buf); return true; } static bool lexeme_buffer_emit(struct lexeme_ring_buffer *buf, struct lexeme *lexeme) { - if (buf->population == 16) + if (lexeme_ring_buffer_full(buf)) return false; - buf->lexemes[buf->last] = *lexeme; - buf->last = (buf->last + 1) % 16; - buf->population++; - + lexeme_ring_buffer_put(buf, lexeme); return true; } @@ -504,7 +487,7 @@ static void *parse_key_value(struct parser *parser) while (lexeme_buffer_consume(&parser->buffer, &lexeme)) { lwan_strbuf_append_str(&parser->strbuf, lexeme->value.value, lexeme->value.len); - if (parser->buffer.population >= 1) + if (!lexeme_ring_buffer_empty(&parser->buffer)) lwan_strbuf_append_char(&parser->strbuf, '_'); } key_size = lwan_strbuf_get_length(&parser->strbuf); @@ -572,7 +555,7 @@ static void *parse_section(struct parser *parser) while (lexeme_buffer_consume(&parser->buffer, &lexeme)) { lwan_strbuf_append_str(&parser->strbuf, lexeme->value.value, lexeme->value.len); - if (parser->buffer.population >= 1) + if (!lexeme_ring_buffer_empty(&parser->buffer)) lwan_strbuf_append_char(&parser->strbuf, ' '); } @@ -618,7 +601,7 @@ static void *parse_config(struct parser *parser) return parse_section; case LEXEME_LINEFEED: - if (parser->buffer.population) + if (!lexeme_ring_buffer_empty(&parser->buffer)) return parse_section_shorthand; return parse_config; @@ -704,6 +687,8 @@ struct config *config_open(const char *path) config->error_message = NULL; lwan_strbuf_init(&config->parser.strbuf); + config_ring_buffer_init(&config->parser.items); + lexeme_ring_buffer_init(&config->parser.buffer); return config; } diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index 62d18eea8..4eaea2e7c 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -47,6 +47,7 @@ #include "lwan-array.h" #include "lwan-strbuf.h" #include "lwan-template.h" +#include "ringbuffer.h" /* Define this and build a debug version to have the template * chunks printed out after compilation. */ @@ -134,16 +135,13 @@ struct lexeme { } value; }; +DEFINE_RING_BUFFER_TYPE(lexeme_ring_buffer, struct lexeme, 4) + struct lexer { void *(*state)(struct lexer *); const char *start, *pos, *end; - struct { - struct lexeme lexemes[4]; - size_t first; - size_t last; - size_t population; - } ring_buffer; + struct lexeme_ring_buffer ring_buffer; }; struct parser { @@ -271,27 +269,16 @@ static void symtab_pop(struct parser *parser) static void emit_lexeme(struct lexer *lexer, struct lexeme *lexeme) { - assert(lexer->ring_buffer.population < - N_ELEMENTS(lexer->ring_buffer.lexemes)); - - lexer->ring_buffer.lexemes[lexer->ring_buffer.last] = *lexeme; - lexer->ring_buffer.last = - (lexer->ring_buffer.last + 1) % N_ELEMENTS(lexer->ring_buffer.lexemes); - lexer->ring_buffer.population++; - + lexeme_ring_buffer_put(&lexer->ring_buffer, lexeme); lexer->start = lexer->pos; } static bool consume_lexeme(struct lexer *lexer, struct lexeme **lexeme) { - if (!lexer->ring_buffer.population) + if (lexeme_ring_buffer_empty(&lexer->ring_buffer)) return false; - *lexeme = &lexer->ring_buffer.lexemes[lexer->ring_buffer.first]; - lexer->ring_buffer.first = - (lexer->ring_buffer.first + 1) % N_ELEMENTS(lexer->ring_buffer.lexemes); - lexer->ring_buffer.population--; - + *lexeme = lexeme_ring_buffer_get_ptr(&lexer->ring_buffer); return true; } @@ -521,6 +508,7 @@ static void lex_init(struct lexer *lexer, const char *input) lexer->state = lex_text; lexer->pos = lexer->start = input; lexer->end = rawmemchr(input, '\0'); + lexeme_ring_buffer_init(&lexer->ring_buffer); } static void *unexpected_lexeme(struct lexeme *lexeme) diff --git a/src/lib/ringbuffer.h b/src/lib/ringbuffer.h new file mode 100644 index 000000000..06cfd09f1 --- /dev/null +++ b/src/lib/ringbuffer.h @@ -0,0 +1,89 @@ +/* + * lwan - simple web server + * Copyright (c) 2018 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/* + * Inspired by blog post by Juho Snellman + * https://www.snellman.net/blog/archive/2016-12-13-ring-buffers/ + */ + +#pragma once + +#include +#include +#include + +#define DEFINE_RING_BUFFER_TYPE(type_name_, element_type_, size_) \ + static_assert((size_) && !((size_) & ((size_)-1)), \ + "size is a power of two"); \ + \ + struct type_name_ { \ + uint32_t read, write; \ + element_type_ array[size_]; \ + }; \ + \ + __attribute__((unused)) static inline uint32_t type_name_##_mask( \ + uint32_t value) \ + { \ + return value & ((size_)-1); \ + } \ + \ + __attribute__((unused)) static inline uint32_t type_name_##_size( \ + const struct type_name_ *rb) \ + { \ + return rb->write - rb->read; \ + } \ + \ + __attribute__((unused)) static inline bool type_name_##_full( \ + const struct type_name_ *rb) \ + { \ + return type_name_##_size(rb) == (size_); \ + } \ + \ + __attribute__((unused)) static inline bool type_name_##_empty( \ + const struct type_name_ *rb) \ + { \ + return rb->write == rb->read; \ + } \ + \ + __attribute__((unused)) static inline void type_name_##_init( \ + struct type_name_ *rb) \ + { \ + rb->write = rb->read = 0; \ + } \ + \ + __attribute__((unused)) static inline void type_name_##_put( \ + struct type_name_ *rb, element_type_ *e) \ + { \ + assert(!type_name_##_full(rb)); \ + memcpy(&rb->array[type_name_##_mask(rb->write++)], e, sizeof(*e)); \ + } \ + \ + __attribute__((unused)) static inline element_type_ type_name_##_get( \ + struct type_name_ *rb) \ + { \ + assert(!type_name_##_empty(rb)); \ + return rb->array[type_name_##_mask(rb->read++)]; \ + } \ + \ + __attribute__((unused)) static inline element_type_ *type_name_##_get_ptr( \ + struct type_name_ *rb) \ + { \ + assert(!type_name_##_empty(rb)); \ + return &rb->array[type_name_##_mask(rb->read++)]; \ + } From ba55669011dc595737d082f6dfad870baeebe523 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 9 Aug 2018 07:14:12 -0700 Subject: [PATCH 0737/2505] Configuration parser ring buffer doesn't need to be large 4 pending elements is more than enough. It should work with less, but be on the safe side for maintenance reasons. (It's not much memory anyway for something that's rarely used in the program lifetime.) --- src/lib/lwan-config.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index a163b512c..8b90b2b5b 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -70,8 +70,8 @@ struct lexeme { } value; }; -DEFINE_RING_BUFFER_TYPE(lexeme_ring_buffer, struct lexeme, 16) -DEFINE_RING_BUFFER_TYPE(config_ring_buffer, struct config_line, 16) +DEFINE_RING_BUFFER_TYPE(lexeme_ring_buffer, struct lexeme, 4) +DEFINE_RING_BUFFER_TYPE(config_ring_buffer, struct config_line, 4) struct lexer { void *(*state)(struct lexer *); From 8ca1b79a9bf65734f058e98b0191b5b868d6dad7 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 9 Aug 2018 23:30:10 -0700 Subject: [PATCH 0738/2505] Check if epoll(2) exists instead of assuming supported platforms Makes the epoll shim work on OpenBSD as well --- CMakeLists.txt | 1 + src/cmake/lwan-build-config.h.cmake | 1 + src/lib/missing.c | 2 +- src/lib/missing/sys/epoll.h | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 523d5c5d1..dd548d2c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -84,6 +84,7 @@ set(CMAKE_EXTRA_INCLUDE_FILES unistd.h ) check_include_file(sys/auxv.h HAVE_SYS_AUXV) +check_include_file(sys/epoll.h HAVE_EPOLL) check_include_file(alloca.h HAVE_ALLOCA_H) if (HAVE_SYS_AUXV) set(CMAKE_EXTRA_INCLUDE_FILES diff --git a/src/cmake/lwan-build-config.h.cmake b/src/cmake/lwan-build-config.h.cmake index 058ffff03..5cde393f0 100644 --- a/src/cmake/lwan-build-config.h.cmake +++ b/src/cmake/lwan-build-config.h.cmake @@ -34,6 +34,7 @@ #cmakedefine HAVE_READAHEAD #cmakedefine HAVE_REALLOCARRAY #cmakedefine HAVE_EVENTFD +#cmakedefine HAVE_EPOLL /* Compiler builtins for specific CPU instruction support */ #cmakedefine HAVE_BUILTIN_CLZLL diff --git a/src/lib/missing.c b/src/lib/missing.c index 5b958bad7..599997e60 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -191,7 +191,7 @@ clock_gettime(clockid_t clk_id, struct timespec *ts) } #endif -#if defined(__FreeBSD__) || defined(__APPLE__) +#if !defined(HAVE_EPOLL) #include #include #include diff --git a/src/lib/missing/sys/epoll.h b/src/lib/missing/sys/epoll.h index 71e3d8bb2..e93bd4a34 100644 --- a/src/lib/missing/sys/epoll.h +++ b/src/lib/missing/sys/epoll.h @@ -17,7 +17,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#if defined(__FreeBSD__) || defined(__APPLE__) +#if !defined(HAVE_EPOLL) #pragma once #include From 37c0c95d72c2ead856826ee1679a2cd90dd75cbc Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 9 Aug 2018 23:33:13 -0700 Subject: [PATCH 0739/2505] Implement a hackish proc_pidpath() for OpenBSD --- src/lib/missing.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/lib/missing.c b/src/lib/missing.c index 599997e60..8eba644c2 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -386,6 +386,26 @@ proc_pidpath(pid_t pid, void *buffer, size_t buffersize) return 0; } +#elif defined(__OpenBSD__) +int +proc_pidpath(pid_t pid, void *buffer, size_t buffersize) +{ + /* FIXME: there doesn't seem to be a way to do this on OpenBSD */ + if (getpid() != pid) { + errno = EACCES; + + return -1; + } + + if (buffersize < sizeof("lwan")) { + errno = ENOMEM; + + return -1; + } + + memcpy(buffer, "lwan", sizeof("lwan")); + return 0; +} #elif !defined(__APPLE__) #error proc_pidpath() not implemented for this architecture #endif From ce549f0f2ad35f1ad0cdbcfdb151d81104e2f602 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 10 Aug 2018 00:03:54 -0700 Subject: [PATCH 0740/2505] Provide a pread()+send() based sendfile implementation This makes Lwan work on OpenBSD --- src/lib/lwan-io-wrappers.c | 55 +++++++++++++++++++++++++++++++++- src/lib/missing/sys/sendfile.h | 3 ++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index 2e08a9627..f2dfb18cc 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -189,5 +189,58 @@ lwan_sendfile(struct lwan_request *request, int in_fd, off_t offset, size_t coun } while (total_written < count); } #else -#error No sendfile() implementation for this platform +static inline size_t min_size(size_t a, size_t b) { return (a > b) ? b : a; } + +static off_t +try_pread(struct coro *coro, int fd, void *buffer, size_t len, off_t offset) +{ + ssize_t total_read = 0; + + for (int tries = MAX_FAILED_TRIES; tries;) { + ssize_t r = pread(fd, buffer, len, offset); + + if (UNLIKELY(r < 0)) { + tries--; + + switch (errno) { + case EAGAIN: + case EINTR: + goto try_again; + default: + goto out; + } + } + + total_read += r; + offset += r; + if ((size_t)total_read == len) + return offset; + try_again: + coro_yield(coro, CONN_CORO_MAY_RESUME); + } + +out: + coro_yield(coro, CONN_CORO_ABORT); + __builtin_unreachable(); +} + +void lwan_sendfile(struct lwan_request *request, + int in_fd, + off_t offset, + size_t count, + const char *header, + size_t header_len) +{ + unsigned char buffer[512]; + + lwan_send(request, header, header_len, MSG_MORE); + + while (count) { + size_t to_read = min_size(count, sizeof(buffer)); + + offset = try_pread(request->conn->coro, in_fd, buffer, to_read, offset); + lwan_send(request, buffer, to_read, 0); + count -= to_read; + } +} #endif diff --git a/src/lib/missing/sys/sendfile.h b/src/lib/missing/sys/sendfile.h index 2d5a03304..01f776ccb 100644 --- a/src/lib/missing/sys/sendfile.h +++ b/src/lib/missing/sys/sendfile.h @@ -19,6 +19,9 @@ #if defined(__FreeBSD__) || defined(__APPLE__) # include +#elif defined(__OpenBSD__) +/* OpenBSD has no sendfile(); implement the Lwan wrapper directly in + * lwan-io-wrappers.c */ #else # include_next #endif From ad791e802798d1f84744b7d74250e1208612fa0c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 10 Aug 2018 06:29:22 -0700 Subject: [PATCH 0741/2505] Do not assume kqueue() is available if epoll() isn't --- CMakeLists.txt | 9 +++++++++ src/cmake/lwan-build-config.h.cmake | 1 + src/lib/missing.c | 4 +++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dd548d2c2..f7cc20f42 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,7 @@ include(CheckCCompilerFlag) include(CheckCSourceCompiles) include(CheckFunctionExists) include(CheckIncludeFile) +include(CheckIncludeFiles) include(CodeCoverage) include(EnableCFlag) include(FindPkgConfig) @@ -85,6 +86,14 @@ set(CMAKE_EXTRA_INCLUDE_FILES ) check_include_file(sys/auxv.h HAVE_SYS_AUXV) check_include_file(sys/epoll.h HAVE_EPOLL) +check_include_files("sys/time.h;sys/types.h;sys/event.h" HAVE_SYS_EVENT) +if (HAVE_SYS_EVENT) + set(CMAKE_EXTRA_INCLUDE_FILES + ${CMAKE_EXTRA_INCLUDE_FILES} + sys/event.h sys/types.h sys/time.h + ) + check_function_exists(kqueue HAVE_KQUEUE) +endif () check_include_file(alloca.h HAVE_ALLOCA_H) if (HAVE_SYS_AUXV) set(CMAKE_EXTRA_INCLUDE_FILES diff --git a/src/cmake/lwan-build-config.h.cmake b/src/cmake/lwan-build-config.h.cmake index 5cde393f0..24521c1a4 100644 --- a/src/cmake/lwan-build-config.h.cmake +++ b/src/cmake/lwan-build-config.h.cmake @@ -35,6 +35,7 @@ #cmakedefine HAVE_REALLOCARRAY #cmakedefine HAVE_EVENTFD #cmakedefine HAVE_EPOLL +#cmakedefine HAVE_KQUEUE /* Compiler builtins for specific CPU instruction support */ #cmakedefine HAVE_BUILTIN_CLZLL diff --git a/src/lib/missing.c b/src/lib/missing.c index 8eba644c2..58e61af36 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -191,7 +191,7 @@ clock_gettime(clockid_t clk_id, struct timespec *ts) } #endif -#if !defined(HAVE_EPOLL) +#if !defined(HAVE_EPOLL) && defined(HAVE_KQUEUE) #include #include #include @@ -320,6 +320,8 @@ epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) hash_free(coalesce); return (int)(intptr_t)(ev - events); } +#elif !defined(HAVE_EPOLL) +#error epoll() not implemented for this platform #endif #if defined(__linux__) || defined(__CYGWIN__) From e1f12b6535d25205678de1873b8ac0c70069e2c7 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 10 Aug 2018 17:55:13 -0700 Subject: [PATCH 0742/2505] Use dladdr() to implement proc_pidpath() for OpenBSD --- CMakeLists.txt | 7 +++++++ src/cmake/lwan-build-config.h.cmake | 1 + src/lib/missing.c | 25 +++++++++++++++++++------ 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f7cc20f42..8e0e4cd52 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,6 +83,7 @@ set(CMAKE_EXTRA_INCLUDE_FILES string.h time.h unistd.h + dlfcn.h ) check_include_file(sys/auxv.h HAVE_SYS_AUXV) check_include_file(sys/epoll.h HAVE_EPOLL) @@ -118,6 +119,12 @@ check_function_exists(clock_gettime HAVE_CLOCK_GETTIME) check_function_exists(pthread_barrier_init HAVE_PTHREADBARRIER) check_function_exists(eventfd HAVE_EVENTFD) +# This is available on -ldl in glibc, but some systems (such as OpenBSD) +# will bundle these in the C library. This isn't required for glibc anyway, +# as there's getauxval(), with a fallback to reading the link +# /proc/self/exe. +check_function_exists(dladdr HAVE_DLADDR) + if (NOT HAVE_CLOCK_GETTIME AND ${CMAKE_SYSTEM_NAME} MATCHES "Linux") list(APPEND ADDITIONAL_LIBRARIES rt) endif () diff --git a/src/cmake/lwan-build-config.h.cmake b/src/cmake/lwan-build-config.h.cmake index 24521c1a4..50e6f6591 100644 --- a/src/cmake/lwan-build-config.h.cmake +++ b/src/cmake/lwan-build-config.h.cmake @@ -36,6 +36,7 @@ #cmakedefine HAVE_EVENTFD #cmakedefine HAVE_EPOLL #cmakedefine HAVE_KQUEUE +#cmakedefine HAVE_DLADDR /* Compiler builtins for specific CPU instruction support */ #cmakedefine HAVE_BUILTIN_CLZLL diff --git a/src/lib/missing.c b/src/lib/missing.c index 58e61af36..072877931 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -388,24 +388,37 @@ proc_pidpath(pid_t pid, void *buffer, size_t buffersize) return 0; } -#elif defined(__OpenBSD__) +#elif defined(HAVE_DLADDR) +#include + int proc_pidpath(pid_t pid, void *buffer, size_t buffersize) { - /* FIXME: there doesn't seem to be a way to do this on OpenBSD */ + Dl_info info; + if (getpid() != pid) { errno = EACCES; - return -1; } - if (buffersize < sizeof("lwan")) { - errno = ENOMEM; + extern int main(); + if (dladdr(main, &info)) { + if (!info.dli_fname) + goto fallback; + + if (buffersize < PATH_MAX - 1) + goto fallback; + if (realpath(info.dli_fname, buffer)) + return 0; + } + +fallback: + if (strlcpy(buffer, "lwan", buffersize) >= buffersize) { + errno = ENOMEM; return -1; } - memcpy(buffer, "lwan", sizeof("lwan")); return 0; } #elif !defined(__APPLE__) From c099be9ba6be9e23d8d1dffb3cdabc846ba3ce6e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 10 Aug 2018 22:40:00 -0700 Subject: [PATCH 0743/2505] Pre-schedule all client connections to reduce MOD operations Pre-calculate the mapping between file descriptors and threads during startup and only reference that. In the future this should learn about the CPU topology and build the correct map (using the current scheme as the fallback mechanism for Linux without /proc or /sys, or non-Linux systems). --- src/lib/lwan-thread.c | 31 +++++++++++++++++++++++++++++++ src/lib/lwan.c | 23 ++++++++--------------- src/lib/lwan.h | 6 +++++- 3 files changed, 44 insertions(+), 16 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 9304fa0e3..d6e97a6c1 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -559,6 +559,33 @@ void lwan_thread_init(struct lwan *l) for (short i = 0; i < l->thread.count; i++) create_thread(l, &l->thread.threads[i]); +#ifdef __x86_64__ + static_assert(sizeof(struct lwan_connection) == 32, + "Two connections per cache line"); + /* + * Pre-schedule each file descriptor, to reduce some operations in the + * fast path. + * + * Since struct lwan_connection is guaranteed to be 32-byte long, two of + * them can fill up a cache line. This formula will group two connections + * per thread in a way that false-sharing is avoided. + */ + l->thread.fd_to_thread_mask = + (unsigned int)lwan_nextpow2((size_t)((l->thread.count - 1) * 2)); + l->thread.fd_to_thread = + calloc(l->thread.fd_to_thread_mask, sizeof(unsigned int)); + + if (!l->thread.fd_to_thread) + lwan_status_critical("Could not allocate fd_to_thread array"); + + for (unsigned int i = 0; i < l->thread.fd_to_thread_mask; i++) { + /* TODO: do not assume the CPU topology */ + l->thread.fd_to_thread[i] = (i / 2) % l->thread.count; + } + + l->thread.fd_to_thread_mask--; +#endif + pthread_barrier_wait(&l->thread.barrier); lwan_status_debug("IO threads created and ready to serve"); @@ -592,4 +619,8 @@ void lwan_thread_shutdown(struct lwan *l) } free(l->thread.threads); + +#ifdef __x86_64__ + free(l->thread.fd_to_thread); +#endif } diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 69d094c35..f133ae2a6 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -590,25 +590,18 @@ void lwan_shutdown(struct lwan *l) lwan_http_authorize_shutdown(); } -static ALWAYS_INLINE int schedule_client(struct lwan *l, int fd) +static ALWAYS_INLINE unsigned int schedule_client(struct lwan *l, int fd) { - int thread; + unsigned int thread; + #ifdef __x86_64__ - static_assert(sizeof(struct lwan_connection) == 32, - "Two connections per cache line"); - /* Since struct lwan_connection is guaranteed to be 32-byte long, two of - * them can fill up a cache line. This formula will group two connections - * per thread in a way that false-sharing is avoided. This gives wrong - * results when fd=0, but this shouldn't happen (as 0 is either the - * standard input or the main socket, but even if that changes, - * scheduling will still work). */ - thread = ((fd - 1) / 2) % l->thread.count; + thread = l->thread.fd_to_thread[(unsigned int)fd & l->thread.fd_to_thread_mask]; #else - static int counter = 0; + static unsigned int counter = 0; thread = counter++ % l->thread.count; #endif - struct lwan_thread *t = &l->thread.threads[thread]; - lwan_thread_add_client(t, fd); + + lwan_thread_add_client(&l->thread.threads[thread], fd); return thread; } @@ -645,7 +638,7 @@ accept_one(struct lwan *l, uint64_t *cores) int fd = accept4((int)main_socket, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC); if (LIKELY(fd >= 0)) { - *cores |= UINT64_C(1)<<(unsigned)schedule_client(l, fd); + *cores |= UINT64_C(1)< Date: Sun, 12 Aug 2018 07:47:27 -0700 Subject: [PATCH 0744/2505] Assert that either module or handler has been passed to add_url_map() Previous code would test that, but after some cleanups this wasn't the case. It's not really necessary to do -- but the assertion won't kill. --- src/lib/lwan.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index f133ae2a6..21c0fc2df 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -222,6 +222,8 @@ static void parse_listener_prefix(struct config *c, goto out; add_map: + assert((handler && !module) || (!handler && module)); + if (handler) { url_map.handler = handler; url_map.flags |= HANDLER_PARSE_MASK | HANDLER_DATA_IS_HASH_TABLE; From f5cc650ff8c4c53d123269450b75b640a12b0898 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 12 Aug 2018 07:55:27 -0700 Subject: [PATCH 0745/2505] Parse range bytes as uint64_t and check the bounds later Parse using sscanf(SCNu64), and check if value exceeds OFF_MAX. OFF_MAX isn't defined on all platforms, so just add it to `missing/limits.h`. --- src/lib/lwan-request.c | 8 ++++---- src/lib/missing/limits.h | 5 +++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 500a5228c..07ac5e857 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -612,22 +612,22 @@ parse_range(struct lwan_request *request, struct request_parser_helper *helper) return; range += sizeof("bytes=") - 1; - unsigned long from, to; + uint64_t from, to; if (sscanf(range, "%"SCNu64"-%"SCNu64, &from, &to) == 2) { - if (UNLIKELY(from > LONG_MAX || to > LONG_MAX)) + if (UNLIKELY(from > OFF_MAX || to > OFF_MAX)) goto invalid_range; request->header.range.from = (off_t)from; request->header.range.to = (off_t)to; } else if (sscanf(range, "-%"SCNu64, &to) == 1) { - if (UNLIKELY(to > LONG_MAX)) + if (UNLIKELY(to > OFF_MAX)) goto invalid_range; request->header.range.from = 0; request->header.range.to = (off_t)to; } else if (sscanf(range, "%"SCNu64"-", &from) == 1) { - if (UNLIKELY(from > LONG_MAX)) + if (UNLIKELY(from > OFF_MAX)) goto invalid_range; request->header.range.from = (off_t)from; diff --git a/src/lib/missing/limits.h b/src/lib/missing/limits.h index 61ffc27b1..781af6484 100644 --- a/src/lib/missing/limits.h +++ b/src/lib/missing/limits.h @@ -30,4 +30,9 @@ # define OPEN_MAX 65535 #endif +#ifndef OFF_MAX +#include +# define OFF_MAX ~((off_t)1 << (sizeof(off_t) * CHAR_BIT - 1)) +#endif + #endif /* MISSING_LIMITS_H */ From 33eb15e0e6d18c597290c3eff3c3ef1a5e82b74d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 12 Aug 2018 08:06:30 -0700 Subject: [PATCH 0746/2505] Mark request and data as unused in hello-no-meta sample --- src/samples/hello-no-meta/main.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/samples/hello-no-meta/main.c b/src/samples/hello-no-meta/main.c index f82c4b119..98d96da8b 100644 --- a/src/samples/hello-no-meta/main.c +++ b/src/samples/hello-no-meta/main.c @@ -23,9 +23,10 @@ /* Defining a handler like this will make it invisible for configuration * files, but it can be referenced by a url_map and will work just the * same. */ -static enum lwan_http_status hello_world(struct lwan_request *request, +static enum lwan_http_status hello_world(struct lwan_request *request + __attribute__((unused)), struct lwan_response *response, - void *data) + void *data __attribute__((unused))) { static const char message[] = "Hello, World!"; From eaa346fb3f06fc1220c551111a72bb40a2944984 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 12 Aug 2018 08:08:59 -0700 Subject: [PATCH 0747/2505] Use the correct formatting parameter for diff_ms --- src/bin/testrunner/main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bin/testrunner/main.c b/src/bin/testrunner/main.c index 5c8801d89..74e929343 100644 --- a/src/bin/testrunner/main.c +++ b/src/bin/testrunner/main.c @@ -228,14 +228,14 @@ LWAN_HANDLER(sleep) diff_ms = (t2.tv_sec - t1.tv_sec) * 1000; diff_ms += (t2.tv_nsec - t1.tv_nsec) / 1000000; - lwan_strbuf_printf(response->buffer, "Returned from sleep. diff_ms = %ld", diff_ms); + lwan_strbuf_printf(response->buffer, + "Returned from sleep. diff_ms = %"PRIi64, diff_ms); } else { lwan_strbuf_set_static(response->buffer, "Did not sleep", 0); } return HTTP_OK; } - int main() { From 6a9ee50845f6d3f4f4b8f17af43efa9aad409328 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 14 Aug 2018 07:44:31 -0700 Subject: [PATCH 0748/2505] Simplify module information printing --- src/bin/lwan/main.c | 55 ++++++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/src/bin/lwan/main.c b/src/bin/lwan/main.c index 71af9b72c..d7bf5f8fd 100644 --- a/src/bin/lwan/main.c +++ b/src/bin/lwan/main.c @@ -32,11 +32,22 @@ enum args { ARGS_SERVE_FILES }; -static void -print_module_info(void) +static void print_module_info(void) { extern const struct lwan_module_info SECTION_START(lwan_module); extern const struct lwan_module_info SECTION_END(lwan_module); + static const struct { + enum lwan_handler_flags flag; + const char *str; + } flag2str[] = { + {.flag = HANDLER_PARSE_QUERY_STRING, .str = "parse-query-string"}, + {.flag = HANDLER_PARSE_IF_MODIFIED_SINCE, .str = "parse-if-modified-since"}, + {.flag = HANDLER_PARSE_RANGE, .str = "parse-range"}, + {.flag = HANDLER_PARSE_ACCEPT_ENCODING, .str = "parse-accept-encoding"}, + {.flag = HANDLER_PARSE_POST_DATA, .str = "parse-post-data"}, + {.flag = HANDLER_CAN_REWRITE_URL, .str = "can-rewrite"}, + {.flag = HANDLER_PARSE_COOKIES, .str = "parse-cookies"}, + }; const struct lwan_module_info *module; struct lwan_strbuf buf; @@ -46,40 +57,22 @@ print_module_info(void) printf("Available modules:\n"); for (module = __start_lwan_module; module < __stop_lwan_module; module++) { size_t len; + size_t i; - if (module->module->flags & HANDLER_PARSE_QUERY_STRING) { - if (!lwan_strbuf_append_str(&buf, "parse-query-string, ", 0)) - goto next_module; - } - if (module->module->flags & HANDLER_PARSE_IF_MODIFIED_SINCE) { - if (!lwan_strbuf_append_str(&buf, "parse-if-modified-since, ", 0)) - goto next_module; - } - if (module->module->flags & HANDLER_PARSE_RANGE) { - if (!lwan_strbuf_append_str(&buf, "parse-range, ", 0)) - goto next_module; - } - if (module->module->flags & HANDLER_PARSE_ACCEPT_ENCODING) { - if (!lwan_strbuf_append_str(&buf, "parse-accept-encoding, ", 0)) - goto next_module; - } - if (module->module->flags & HANDLER_PARSE_POST_DATA) { - if (!lwan_strbuf_append_str(&buf, "parse-post-data, ", 0)) - goto next_module; - } - if (module->module->flags & HANDLER_CAN_REWRITE_URL) { - if (!lwan_strbuf_append_str(&buf, "can-rewrite, ", 0)) - goto next_module; - } - if (module->module->flags & HANDLER_PARSE_COOKIES) { - if (!lwan_strbuf_append_str(&buf, "parse-cookies, ", 0)) + for (i = 0; i < N_ELEMENTS(flag2str); i++) { + if (!(module->module->flags & flag2str[i].flag)) + continue; + if (!lwan_strbuf_append_printf(&buf, "%s, ", flag2str[i].str)) goto next_module; } len = lwan_strbuf_get_length(&buf); - printf(" * %s%s%.*s%s\n", module->name, len ? " (" : "", - (int)(len ? (len - 2) : 0), lwan_strbuf_get_buffer(&buf), - len ? ")" : ""); + if (len) { + printf(" * %s (%.*s)\n", module->name, (int)(len - 2), + lwan_strbuf_get_buffer(&buf)); + } else { + printf(" * %s\n", module->name); + } next_module: lwan_strbuf_reset(&buf); } From 5172258ed6230e32d522ff7c3dc5caeb9b9ef18f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 17 Aug 2018 21:13:51 -0700 Subject: [PATCH 0749/2505] Mention that environment variables may be used in config files --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 39c138ba5..d87445eca 100644 --- a/README.md +++ b/README.md @@ -188,6 +188,9 @@ can be empty; in this case, curly brackets are optional. an implementation detail, code reading configuration options will only be given the version with underscores). +Values can contain environment variables. Use the syntax +`${VARIABLE_NAME}`. + ``` sound volume = 11 # This one is 1 louder From 06ebcb1becbefc1d264b8af5300170ebe168b038 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 17 Aug 2018 21:29:42 -0700 Subject: [PATCH 0750/2505] Update the date cache whenever dq timer expires The dq expires every 1000ms, so let's update the date cache only then instead of every time the timer wheel turns. Also, there's no need to check if time(NULL) != last time: this is now implicit. --- src/lib/lwan-thread.c | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index d6e97a6c1..b309f6f73 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -271,13 +271,12 @@ static void death_queue_kill_all(struct death_queue *dq) static void update_date_cache(struct lwan_thread *thread) { time_t now = time(NULL); - if (now != thread->date.last) { - thread->date.last = now; - lwan_format_rfc_time(now, thread->date.date); - lwan_format_rfc_time(now + (time_t)thread->lwan->config.expires, - thread->date.expires); - } + thread->date.last = now; + + lwan_format_rfc_time(now, thread->date.date); + lwan_format_rfc_time(now + (time_t)thread->lwan->config.expires, + thread->date.expires); } static ALWAYS_INLINE void spawn_coro(struct lwan_connection *conn, @@ -326,13 +325,13 @@ static void accept_nudge(int pipe_fd, } static bool process_pending_timers(struct death_queue *dq, - struct timeouts *wheel, + struct lwan_thread *t, int epoll_fd) { struct timeout *timeout; bool processed_dq_timeout = false; - while ((timeout = timeouts_get(wheel))) { + while ((timeout = timeouts_get(t->wheel))) { struct lwan_request *request; if (timeout == &dq->timeout) { @@ -350,11 +349,15 @@ static bool process_pending_timers(struct death_queue *dq, if (processed_dq_timeout) { if (death_queue_empty(dq)) { - timeouts_del(wheel, &dq->timeout); + timeouts_del(t->wheel, &dq->timeout); return false; } - timeouts_add(wheel, &dq->timeout, 1000); + /* dq timeout expires every 1000ms if there are connections, so + * update the date cache at this point as well. */ + update_date_cache(t); + + timeouts_add(t->wheel, &dq->timeout, 1000); return true; } @@ -373,14 +376,12 @@ turn_timer_wheel(struct death_queue *dq, struct lwan_thread *t, int epoll_fd) timeouts_update(t->wheel, (timeout_t)(now.tv_sec * 1000 + now.tv_nsec / 1000000)); - update_date_cache(t); - wheel_timeout = timeouts_timeout(t->wheel); if (UNLIKELY((int64_t)wheel_timeout < 0)) return -1; if (wheel_timeout == 0) { - if (process_pending_timers(dq, t->wheel, epoll_fd)) { + if (process_pending_timers(dq, t, epoll_fd)) { wheel_timeout = timeouts_timeout(t->wheel); if (!wheel_timeout) @@ -410,6 +411,8 @@ static void *thread_io_loop(void *data) if (UNLIKELY(!events)) lwan_status_critical("Could not allocate memory for events"); + update_date_cache(t); + death_queue_init(&dq, lwan); pthread_barrier_wait(&lwan->thread.barrier); From 6ee4cc000442d05a336f3cb5319f25bb60f9e658 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 18 Aug 2018 12:18:41 -0700 Subject: [PATCH 0751/2505] Remove `ended` member from struct coro This was introduced to avoid a memcpy() in the last context switch of a coroutine. However, most coroutines that matter (i.e. coroutines handling requests) will be freed before ended would be true, as that function never returns. The only case where that memcpy() would be avoided would be in templates with sequences; however, that's not a hot path, so this optimization has no point. Saves 4 bytes per coroutine (due to alignment), and a (hopefully well-predicted) branch every context switch, as the memcpy() to the switcher is always necessary. --- src/lib/lwan-coro.c | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 94e894cdd..cad03255a 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -63,8 +63,6 @@ struct coro { #if !defined(NDEBUG) && defined(HAVE_VALGRIND) unsigned int vg_stack_id; #endif - - bool ended; }; #if defined(__APPLE__) @@ -151,7 +149,6 @@ static void coro_entry_point(struct coro *coro, coro_function_t func, void *data) { int return_value = func(coro, data); - coro->ended = true; coro_yield(coro, return_value); } #else @@ -167,7 +164,6 @@ coro_entry_point(struct coro *coro, coro_function_t func, void *data); "movq %r15, %rsi\n\t" /* data = r15 */ "call *%rdx\n\t" /* eax = func(coro, data) */ "movq (%rbx), %rsi\n\t" - "movb $1, 0x6c(%rbx)\n\t" /* coro->ended = true */ "movl %eax, 0x68(%rbx)\n\t" /* coro->yield_value = eax */ "popq %rbx\n\t" "leaq 0x50(%rsi), %rdi\n\t" /* get coro context from coro */ @@ -202,8 +198,6 @@ coro_reset(struct coro *coro, coro_function_t func, void *data) { unsigned char *stack = (unsigned char *)(coro + 1); - coro->ended = false; - coro_deferred_run(coro, 0); coro_defer_array_reset(&coro->defer); @@ -278,24 +272,20 @@ ALWAYS_INLINE int coro_resume(struct coro *coro) { assert(coro); - assert(coro->ended == false); #if defined(__x86_64__) || defined(__i386__) coro_swapcontext(&coro->switcher->caller, &coro->context); - if (!coro->ended) - memcpy(&coro->context, &coro->switcher->callee, - sizeof(coro->context)); + memcpy(&coro->context, &coro->switcher->callee, + sizeof(coro->context)); #else coro_context prev_caller; memcpy(&prev_caller, &coro->switcher->caller, sizeof(prev_caller)); coro_swapcontext(&coro->switcher->caller, &coro->context); - if (!coro->ended) { - memcpy(&coro->context, &coro->switcher->callee, - sizeof(coro->context)); - memcpy(&coro->switcher->caller, &prev_caller, - sizeof(coro->switcher->caller)); - } + memcpy(&coro->context, &coro->switcher->callee, + sizeof(coro->context)); + memcpy(&coro->switcher->caller, &prev_caller, + sizeof(coro->switcher->caller)); #endif return coro->yield_value; @@ -305,7 +295,6 @@ ALWAYS_INLINE int coro_resume_value(struct coro *coro, int value) { assert(coro); - assert(coro->ended == false); coro->yield_value = value; return coro_resume(coro); From 5762e101e2bfa8ee1993b64242a2746d19e888e0 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 18 Aug 2018 12:23:43 -0700 Subject: [PATCH 0752/2505] Use flexible array member to reference to the stack Makes the code slightly cleaner and explicit. --- src/lib/lwan-coro.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index cad03255a..f865589db 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -63,6 +63,8 @@ struct coro { #if !defined(NDEBUG) && defined(HAVE_VALGRIND) unsigned int vg_stack_id; #endif + + unsigned char stack[]; }; #if defined(__APPLE__) @@ -196,7 +198,7 @@ coro_deferred_get_generation(const struct coro *coro) void coro_reset(struct coro *coro, coro_function_t func, void *data) { - unsigned char *stack = (unsigned char *)(coro + 1); + unsigned char *stack = coro->stack; coro_deferred_run(coro, 0); coro_defer_array_reset(&coro->defer); @@ -261,7 +263,7 @@ coro_new(struct coro_switcher *switcher, coro_function_t function, void *data) coro_reset(coro, function, data); #if !defined(NDEBUG) && defined(HAVE_VALGRIND) - char *stack = (char *)(coro + 1); + unsigned char *stack = coro->stack; coro->vg_stack_id = VALGRIND_STACK_REGISTER(stack, stack + CORO_STACK_MIN); #endif From 6962e2ad155c7f2d92f0dc4b5b46020a23c0afcf Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 19 Aug 2018 16:19:17 -0700 Subject: [PATCH 0753/2505] lwan_thread.thread.last is not used any longer: get rid of it --- src/lib/lwan-thread.c | 2 -- src/lib/lwan.h | 1 - 2 files changed, 3 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index b309f6f73..4f45ded0d 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -272,8 +272,6 @@ static void update_date_cache(struct lwan_thread *thread) { time_t now = time(NULL); - thread->date.last = now; - lwan_format_rfc_time(now, thread->date.date); lwan_format_rfc_time(now + (time_t)thread->lwan->config.expires, thread->date.expires); diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 0a8ea4e1c..fa2bc5ae8 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -356,7 +356,6 @@ struct lwan_thread { struct { char date[30]; char expires[30]; - time_t last; } date; struct spsc_queue pending_fds; struct timeouts *wheel; From 2294156bf1a3431c18517ee107f7fa11dbe687b1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 20 Aug 2018 18:20:09 -0700 Subject: [PATCH 0754/2505] Remove more unused functions from timeouts library --- src/lib/timeout.c | 22 ---------------------- src/lib/timeout.h | 9 --------- 2 files changed, 31 deletions(-) diff --git a/src/lib/timeout.c b/src/lib/timeout.c index 531b95541..62f1e9738 100644 --- a/src/lib/timeout.c +++ b/src/lib/timeout.c @@ -382,28 +382,6 @@ void timeouts_update(struct timeouts *T, abstime_t curtime) return; } -void timeouts_step(struct timeouts *T, reltime_t elapsed) -{ - timeouts_update(T, T->curtime + elapsed); -} - -bool timeouts_pending(struct timeouts *T) -{ - wheel_t pending = 0; - int wheel; - - for (wheel = 0; wheel < WHEEL_NUM; wheel++) { - pending |= T->pending[wheel]; - } - - return !!pending; -} - -bool timeouts_expired(struct timeouts *T) -{ - return !list_empty(&T->expired); -} - /* * Calculate the interval before needing to process any timeouts pending on * any wheel. diff --git a/src/lib/timeout.h b/src/lib/timeout.h index fe1e92ddc..627bfa6b0 100644 --- a/src/lib/timeout.h +++ b/src/lib/timeout.h @@ -91,9 +91,6 @@ void timeouts_close(struct timeouts *); void timeouts_update(struct timeouts *, timeout_t); /* update timing wheel with current absolute time */ -void timeouts_step(struct timeouts *, timeout_t); -/* step timing wheel by relative time */ - timeout_t timeouts_timeout(struct timeouts *); /* return interval to next required update */ @@ -106,10 +103,4 @@ void timeouts_del(struct timeouts *, struct timeout *); struct timeout *timeouts_get(struct timeouts *); /* return any expired timeout (caller should loop until NULL-return) */ -bool timeouts_pending(struct timeouts *); -/* return true if any timeouts pending on timing wheel */ - -bool timeouts_expired(struct timeouts *); -/* return true if any timeouts on expired queue */ - #endif /* TIMEOUT_H */ From 309df00ec3856aa5acfb094dc997cbd1d048e4ab Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 20 Aug 2018 18:18:35 -0700 Subject: [PATCH 0755/2505] Optimize coro context switch on non-x86 architectures Not all calls to memcpy() are strictly necessary, even if using the ucontext fallback. --- src/lib/lwan-coro.c | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index f865589db..a9d5cccc7 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -275,20 +275,9 @@ coro_resume(struct coro *coro) { assert(coro); -#if defined(__x86_64__) || defined(__i386__) coro_swapcontext(&coro->switcher->caller, &coro->context); memcpy(&coro->context, &coro->switcher->callee, sizeof(coro->context)); -#else - coro_context prev_caller; - - memcpy(&prev_caller, &coro->switcher->caller, sizeof(prev_caller)); - coro_swapcontext(&coro->switcher->caller, &coro->context); - memcpy(&coro->context, &coro->switcher->callee, - sizeof(coro->context)); - memcpy(&coro->switcher->caller, &prev_caller, - sizeof(coro->switcher->caller)); -#endif return coro->yield_value; } From 1402361a17695f2db18675fcfa3835067b373a9b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 20 Aug 2018 18:19:31 -0700 Subject: [PATCH 0756/2505] Update date cache even if death queue emptied after processing --- src/lib/lwan-thread.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 4f45ded0d..1b9b27548 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -346,15 +346,15 @@ static bool process_pending_timers(struct death_queue *dq, } if (processed_dq_timeout) { + /* dq timeout expires every 1000ms if there are connections, so + * update the date cache at this point as well. */ + update_date_cache(t); + if (death_queue_empty(dq)) { timeouts_del(t->wheel, &dq->timeout); return false; } - /* dq timeout expires every 1000ms if there are connections, so - * update the date cache at this point as well. */ - update_date_cache(t); - timeouts_add(t->wheel, &dq->timeout, 1000); return true; } From 59ac7095b64e72efe4618958747afe9e1ce23d58 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 20 Aug 2018 22:33:59 -0700 Subject: [PATCH 0757/2505] Define CLOCK_MONOTONIC_COARSE for FreeBSD and macOS --- src/lib/missing/time.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib/missing/time.h b/src/lib/missing/time.h index 817a00684..551da5685 100644 --- a/src/lib/missing/time.h +++ b/src/lib/missing/time.h @@ -33,6 +33,11 @@ int clock_gettime(clockid_t clk_id, struct timespec *ts); # ifndef CLOCK_MONOTONIC # define CLOCK_MONOTONIC 1 # endif + +#elif !defined(CLOCK_MONOTONIC_COARSE) && defined(CLOCK_MONOTONIC_FAST) +# define CLOCK_MONOTONIC_COARSE CLOCK_MONOTONIC_FAST /* FreeBSD */ +#elif !defined(CLOCK_MONOTONIC_COARSE) && defined(CLOCK_MONOTONIC_RAW_APPROX) +# define CLOCK_MONOTONIC_COARSE CLOCK_MONOTONIC_RAW_APPROX /* macOS */ #endif #endif /* MISSING_TIME_H */ From a57e4c0702193966b2fadb5953d8c39312fd57d8 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 20 Aug 2018 22:40:38 -0700 Subject: [PATCH 0758/2505] Fix build on macOS afte re1f12b --- src/lib/missing.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/missing.c b/src/lib/missing.c index 072877931..bbad74673 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -388,7 +388,7 @@ proc_pidpath(pid_t pid, void *buffer, size_t buffersize) return 0; } -#elif defined(HAVE_DLADDR) +#elif defined(HAVE_DLADDR) && !defined(__APPLE__) #include int From 4d6ddaab9f3e4339f8f40dce4a9aad02f3a94bd1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 21 Aug 2018 19:12:17 -0700 Subject: [PATCH 0759/2505] Remove one branch per main loop iteration --- src/lib/lwan-thread.c | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 1b9b27548..013183a21 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -417,32 +417,32 @@ static void *thread_io_loop(void *data) for (;;) { int timeout = turn_timer_wheel(&dq, t, epoll_fd); - int n_fds; + int n_fds = epoll_wait(epoll_fd, events, max_events, timeout); - n_fds = epoll_wait(epoll_fd, events, max_events, timeout); - if (LIKELY(n_fds > 0)) { - for (struct epoll_event *ep_event = events; n_fds--; ep_event++) { - struct lwan_connection *conn; + if (UNLIKELY(n_fds < 0)) { + if (errno == EBADF || errno == EINVAL) + break; + continue; + } - if (UNLIKELY(!ep_event->data.ptr)) { - accept_nudge(read_pipe_fd, &t->pending_fds, lwan->conns, - &dq, &switcher, t->wheel, epoll_fd); - continue; - } + for (struct epoll_event *event = events; n_fds--; event++) { + struct lwan_connection *conn; - conn = ep_event->data.ptr; + if (UNLIKELY(!event->data.ptr)) { + accept_nudge(read_pipe_fd, &t->pending_fds, lwan->conns, + &dq, &switcher, t->wheel, epoll_fd); + continue; + } - if (UNLIKELY(ep_event->events & (EPOLLRDHUP | EPOLLHUP))) { - destroy_coro(&dq, conn); - continue; - } + conn = event->data.ptr; - resume_coro_if_needed(&dq, conn, epoll_fd); - death_queue_move_to_last(&dq, conn); + if (UNLIKELY(event->events & (EPOLLRDHUP | EPOLLHUP))) { + destroy_coro(&dq, conn); + continue; } - } else if (UNLIKELY(n_fds < 0)) { - if (errno == EBADF || errno == EINVAL) - break; + + resume_coro_if_needed(&dq, conn, epoll_fd); + death_queue_move_to_last(&dq, conn); } } From d460008206def6b662d6f108fb24da3e14dab81d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 21 Aug 2018 19:12:54 -0700 Subject: [PATCH 0760/2505] Slightly simplify process_pending_timers() --- src/lib/lwan-thread.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 013183a21..393399774 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -350,13 +350,12 @@ static bool process_pending_timers(struct death_queue *dq, * update the date cache at this point as well. */ update_date_cache(t); - if (death_queue_empty(dq)) { - timeouts_del(t->wheel, &dq->timeout); - return false; + if (!death_queue_empty(dq)) { + timeouts_add(t->wheel, &dq->timeout, 1000); + return true; } - timeouts_add(t->wheel, &dq->timeout, 1000); - return true; + timeouts_del(t->wheel, &dq->timeout); } return false; From 564d6a433920bf4ac4c341cd04baa7cb3dc354dd Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 21 Aug 2018 19:13:15 -0700 Subject: [PATCH 0761/2505] Use posix_fadvise() where readahead() isn't available On Linux, readahead() and posix_fadvise(POSIX_FADV_WILLNEED) are exactly the same underneath. The way these functions are used need to change, as it doesn't really make any sense to readahead a whole file, especially if it's larger than the amount of RAM in the server. --- CMakeLists.txt | 1 + src/cmake/lwan-build-config.h.cmake | 1 + src/lib/missing.c | 13 ++++++++++--- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e0e4cd52..25eb0af6e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -118,6 +118,7 @@ check_function_exists(mkostemp HAVE_MKOSTEMP) check_function_exists(clock_gettime HAVE_CLOCK_GETTIME) check_function_exists(pthread_barrier_init HAVE_PTHREADBARRIER) check_function_exists(eventfd HAVE_EVENTFD) +check_function_exists(posix_fadvise HAVE_POSIX_FADVISE) # This is available on -ldl in glibc, but some systems (such as OpenBSD) # will bundle these in the C library. This isn't required for glibc anyway, diff --git a/src/cmake/lwan-build-config.h.cmake b/src/cmake/lwan-build-config.h.cmake index 50e6f6591..52cea450f 100644 --- a/src/cmake/lwan-build-config.h.cmake +++ b/src/cmake/lwan-build-config.h.cmake @@ -37,6 +37,7 @@ #cmakedefine HAVE_EPOLL #cmakedefine HAVE_KQUEUE #cmakedefine HAVE_DLADDR +#cmakedefine HAVE_POSIX_FADVISE /* Compiler builtins for specific CPU instruction support */ #cmakedefine HAVE_BUILTIN_CLZLL diff --git a/src/lib/missing.c b/src/lib/missing.c index bbad74673..aa43755a8 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -611,11 +611,18 @@ void *reallocarray(void *optr, size_t nmemb, size_t size) #endif /* HAVE_REALLOCARRAY */ #if !defined(HAVE_READAHEAD) -ssize_t readahead(int fd __attribute__((unused)), - off_t offset __attribute__((unused)), - size_t count __attribute__((unused))) +ssize_t readahead(int fd, off_t offset, size_t count) { +#if defined(HAVE_POSIX_FADVISE) + return (ssize_t)posix_fadvise(fd, offset, (off_t)count, + POSIX_FADV_WILLNEED); +#else + (void)fd; + (void)offset; + (void)count; + return 0; +#endif } #endif From 28925a2c1c5fedd0e28221ee58fa3b1c5a08e725 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 21 Aug 2018 19:31:12 -0700 Subject: [PATCH 0762/2505] Limit readahead size to 128k (and make it configurable) Since readahead might block, let's just make it a tunable setting with a reasonable default. --- README.md | 1 + src/lib/lwan-mod-serve-files.c | 21 ++++++++++++++++++--- src/lib/lwan-mod-serve-files.h | 4 ++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d87445eca..0cce5d481 100644 --- a/README.md +++ b/README.md @@ -334,6 +334,7 @@ best to serve files in the fastest way possible according to some heuristics. | `serve_precompressed_path` | `bool` | `true` | If $FILE.gz exists, is smaller and newer than $FILE, and the client accepts `gzip` encoding, transfer it | | `auto_index` | `bool` | `true` | Generate a directory list automatically if no `index_path` file present. Otherwise, yields 404 | | `directory_list_template` | `str` | `NULL` | Path to a Mustache template for the directory list; by default, use an internal template | +| `read_ahead` | `int` | `131702` | Maximum amount of bytes to read ahead when caching open files. A value of `0` disables readahead. Larger values can block until all the filesystem metadata is loaded from the disk. | #### Lua diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 891b66437..f8df1866e 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -61,6 +61,8 @@ struct serve_files_priv { struct lwan_tpl *directory_list_tpl; + size_t read_ahead; + bool serve_precompressed_files; bool auto_index; }; @@ -371,6 +373,16 @@ static bool is_world_readable(mode_t mode) return (mode & world_readable) == world_readable; } +static void +try_readahead(const struct serve_files_priv *priv, int fd, size_t size) +{ + if (size > priv->read_ahead) + size = priv->read_ahead; + + if (LIKELY(size)) + readahead(fd, 0, size); +} + static int try_open_compressed(const char *relpath, const struct serve_files_priv *priv, const struct stat *uncompressed, @@ -403,7 +415,7 @@ static int try_open_compressed(const char *relpath, (size_t)uncompressed->st_size))) { *compressed_sz = (size_t)st.st_size; - readahead(fd, 0, *compressed_sz); + try_readahead(priv, fd, *compressed_sz); return fd; } @@ -451,7 +463,7 @@ static bool sendfile_init(struct file_cache_entry *ce, } sd->uncompressed.size = (size_t)st->st_size; - readahead(sd->uncompressed.fd, 0, sd->uncompressed.size); + try_readahead(priv, sd->uncompressed.fd, sd->uncompressed.size); return true; } @@ -745,6 +757,7 @@ static void *serve_files_create(const char *prefix, void *args) settings->index_html ? settings->index_html : "index.html"; priv->serve_precompressed_files = settings->serve_precompressed_files; priv->auto_index = settings->auto_index; + priv->read_ahead = settings->read_ahead; return priv; @@ -770,7 +783,9 @@ static void *serve_files_create_from_hash(const char *prefix, .serve_precompressed_files = parse_bool(hash_find(hash, "serve_precompressed_files"), true), .auto_index = parse_bool(hash_find(hash, "auto_index"), true), - .directory_list_template = hash_find(hash, "directory_list_template")}; + .directory_list_template = hash_find(hash, "directory_list_template"), + .read_ahead = (size_t)parse_long("read_ahead", SERVE_FILES_READ_AHEAD_BYTES), + }; return serve_files_create(prefix, &settings); } diff --git a/src/lib/lwan-mod-serve-files.h b/src/lib/lwan-mod-serve-files.h index d8cd63d5a..d8866cfa9 100644 --- a/src/lib/lwan-mod-serve-files.h +++ b/src/lib/lwan-mod-serve-files.h @@ -25,10 +25,13 @@ extern "C" { #include "lwan.h" +#define SERVE_FILES_READ_AHEAD_BYTES (128 * 1024) + struct lwan_serve_files_settings { const char *root_path; const char *index_html; const char *directory_list_template; + size_t read_ahead; bool serve_precompressed_files; bool auto_index; }; @@ -38,6 +41,7 @@ LWAN_MODULE_FORWARD_DECL(serve_files); #define SERVE_FILES_SETTINGS(root_path_, index_html_, serve_precompressed_files_) \ .module = LWAN_MODULE_REF(serve_files), \ .args = ((struct lwan_serve_files_settings[]) {{ \ + .read_ahead = SERVE_FILES_READ_AHEAD_BYTES, \ .root_path = root_path_, \ .index_html = index_html_, \ .serve_precompressed_files = serve_precompressed_files_, \ From 6b9fa89ae51b089934ece39a429ae060baf8eb89 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 21 Aug 2018 19:34:10 -0700 Subject: [PATCH 0763/2505] Fix compile warnings with Clang when 0-initializing structs For some reason, when building Lwan with Clang, structs initialized with { 0 } will generate a warning saying that some struct member is missing from the initialization. This is not the case: {0} actually initializes the whole struct to 0. Use an alternative syntax, {}, that is used elsewhere in the code. --- src/lib/lwan-request.c | 2 +- src/lib/lwan-thread.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 07ac5e857..6648e7dce 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1285,7 +1285,7 @@ void lwan_request_sleep(struct lwan_request *request, uint64_t ms) assert(!(conn->flags & CONN_SUSPENDED_BY_TIMER)); conn->flags |= CONN_SUSPENDED_BY_TIMER; - request->timeout = (struct timeout) { 0 }; + request->timeout = (struct timeout) {}; timeouts_add(wheel, &request->timeout, ms); coro_defer2(conn->coro, remove_sleep, wheel, &request->timeout); coro_yield(conn->coro, CONN_CORO_MAY_RESUME); diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 393399774..dd2fcfd4d 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -114,7 +114,7 @@ static void death_queue_init(struct death_queue *dq, const struct lwan *lwan) dq->time = 0; dq->keep_alive_timeout = lwan->config.keep_alive_timeout; dq->head.next = dq->head.prev = -1; - dq->timeout = (struct timeout) { 0 }; + dq->timeout = (struct timeout) {}; } static ALWAYS_INLINE void destroy_coro(struct death_queue *dq, From 01b58b9b130783e181ccca233cd9fc442e028db3 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 21 Aug 2018 20:51:46 -0700 Subject: [PATCH 0764/2505] Drop all capabilities with capset(2) when enabling straitjacket Can be disabled by specifying `drop_capabilities = no` in the straitjacket section. This is of course a no-op on anything but Linux. --- README.md | 1 + src/lib/lwan-straitjacket.c | 22 +++++++++-- src/lib/lwan.h | 1 + src/lib/missing/linux/capability.h | 62 ++++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 src/lib/missing/linux/capability.h diff --git a/README.md b/README.md index 0cce5d481..21d9d9157 100644 --- a/README.md +++ b/README.md @@ -282,6 +282,7 @@ malconfiguration.) |--------|------|---------|-------------| | `user` | `str` | `NULL` | Drop privileges to this user name | | `chroot` | `str` | `NULL` | Path to `chroot()` | +| `drop_capabilities` | `bool` | `true` | Drop all capabilities with capset(2). Only effective under Linux. | ### Listeners diff --git a/src/lib/lwan-straitjacket.c b/src/lib/lwan-straitjacket.c index 5c0a8f5a4..2a0ef1420 100644 --- a/src/lib/lwan-straitjacket.c +++ b/src/lib/lwan-straitjacket.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -179,7 +180,7 @@ void lwan_straitjacket_enforce(const struct lwan_straitjacket *sj) bool got_uid_gid = false; if (!sj->user_name && !sj->chroot_path) - return; + goto out; if (geteuid() != 0) lwan_status_critical("Straitjacket requires root privileges"); @@ -195,7 +196,7 @@ void lwan_straitjacket_enforce(const struct lwan_straitjacket *sj) if (chroot(sj->chroot_path) < 0) { lwan_status_critical_perror("Could not chroot() to %s", - sj->chroot_path); + sj->chroot_path); } if (chdir("/") < 0) @@ -207,7 +208,18 @@ void lwan_straitjacket_enforce(const struct lwan_straitjacket *sj) if (got_uid_gid) { if (!switch_to_user(uid, gid, sj->user_name)) lwan_status_critical("Could not drop privileges to %s, aborting", - sj->user_name); + sj->user_name); + } + +out: + if (sj->drop_capabilities) { + struct __user_cap_header_struct header = { + .version = _LINUX_CAPABILITY_VERSION_1 + }; + struct __user_cap_data_struct data = {}; + + if (capset(&header, &data) < 0) + lwan_status_critical_perror("Could not drop capabilities"); } } @@ -216,6 +228,7 @@ void lwan_straitjacket_enforce_from_config(struct config *c) struct config_line l; char *user_name = NULL; char *chroot_path = NULL; + bool drop_capabilities = true; while (config_read_line(c, &l)) { switch (l.type) { @@ -225,6 +238,8 @@ void lwan_straitjacket_enforce_from_config(struct config *c) user_name = strdupa(l.value); } else if (streq(l.key, "chroot")) { chroot_path = strdupa(l.value); + } else if (streq(l.key, "drop_capabilities")) { + drop_capabilities = parse_bool(l.value, true); } else { config_error(c, "Invalid key: %s", l.key); return; @@ -237,6 +252,7 @@ void lwan_straitjacket_enforce_from_config(struct config *c) lwan_straitjacket_enforce(&(struct lwan_straitjacket) { .user_name = user_name, .chroot_path = chroot_path, + .drop_capabilities = drop_capabilities, }); return; diff --git a/src/lib/lwan.h b/src/lib/lwan.h index fa2bc5ae8..5efed3ed1 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -367,6 +367,7 @@ struct lwan_thread { struct lwan_straitjacket { const char *user_name; const char *chroot_path; + bool drop_capabilities; }; struct lwan_config { diff --git a/src/lib/missing/linux/capability.h b/src/lib/missing/linux/capability.h new file mode 100644 index 000000000..7a1694e0c --- /dev/null +++ b/src/lib/missing/linux/capability.h @@ -0,0 +1,62 @@ +/* + * lwan - simple web server + * Copyright (c) 2018 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#ifndef __MISSING_CAPABILITY_H__ +#define __MISSING_CAPABILITY_H__ + +#if defined(__linux__) + +#include_next +#include +#include + +#include + +static inline int capset(struct __user_cap_header_struct *header, + struct __user_cap_data_struct *data) +{ +#if defined(SYS_capset) + return (int)syscall(SYS_capset, header, data); +#else + return 0; +#endif +} + +#else +struct __user_cap_data_struct { + unsigned int effective, permitted, inheritable; +}; + +struct __user_cap_header_struct { +#define _LINUX_CAPABILITY_VERSION_1 0 + unsigned int version; + int pid; +}; + +static inline int capset(struct __user_cap_header_struct *header + __attribute__((unused)), + struct __user_cap_data_struct *data + __attribute__((unused))) +{ + return 0; +} +#endif + +#endif /* __MISSING_CAPABILITY_H__ */ From 84b907da2626d3d377fe85cfb3234c79651bf4fb Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 21 Aug 2018 21:16:56 -0700 Subject: [PATCH 0765/2505] Honor the location for valgrind.h --- CMakeLists.txt | 2 ++ src/lib/lwan-coro.c | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 25eb0af6e..52962f041 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -157,6 +157,8 @@ check_c_source_compiles("int main(void) { _Static_assert(1, \"\"); }" HAVE_STATI find_path(VALGRIND_INCLUDE_DIR valgrind.h /usr/include /usr/include/valgrind /usr/local/include /usr/local/include/valgrind) if (VALGRIND_INCLUDE_DIR) message(STATUS "Building with Valgrind support") + include_directories(${VALGRIND_INCLUDE_DIR}) + set(HAVE_VALGRIND 1) else () message(STATUS "Valgrind headers not found -- disabling valgrind support") diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index a9d5cccc7..1473d3fb3 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -31,7 +31,7 @@ #include "lwan-coro.h" #ifdef HAVE_VALGRIND -#include +#include #endif #if PTHREAD_STACK_MIN <= 16384 From d1b45980fd0934e0f24a7ddda409b660e3e6bb27 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 22 Aug 2018 18:51:39 -0700 Subject: [PATCH 0766/2505] Use SIGSTKSZ to calculate the stack size for a coroutine Also increase stack size a little bit (4 * SIGSTKSZ). This makes Lwan work fine on OpenBSD now. --- src/lib/lwan-coro.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 1473d3fb3..d87c967f8 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -20,6 +20,7 @@ #define _GNU_SOURCE #include #include +#include #include #include #include @@ -31,16 +32,16 @@ #include "lwan-coro.h" #ifdef HAVE_VALGRIND -#include +# include #endif -#if PTHREAD_STACK_MIN <= 16384 -# undef PTHREAD_STACK_MIN -# define PTHREAD_STACK_MIN 16384 +#if !defined(SIGSTKSZ) +# define SIGSTKSZ 16384 #endif -#define CORO_STACK_MIN ((3 * (PTHREAD_STACK_MIN)) / 2) -static_assert(DEFAULT_BUFFER_SIZE < (CORO_STACK_MIN + PTHREAD_STACK_MIN), +#define CORO_STACK_MIN (4 * SIGSTKSZ) + +static_assert(DEFAULT_BUFFER_SIZE < (CORO_STACK_MIN + SIGSTKSZ), "Request buffer fits inside coroutine stack"); typedef void (*defer_func)(); From 36cb0c865dcf62f12e65dfda48ff4a88b9fd7517 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 22 Aug 2018 22:45:37 -0700 Subject: [PATCH 0767/2505] Use pledge(2) in OpenBSD to implement capset() While not the same thing by a long shot, this will do at least for the use case in Lwan. This syscall is actually pretty nice to use; shame it's kind of hard to do the same thing (exactly) in Linux. --- src/lib/missing.c | 21 +++++++++++++++++++++ src/lib/missing/linux/capability.h | 11 +++-------- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/lib/missing.c b/src/lib/missing.c index aa43755a8..8d1dc11b0 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -638,3 +639,23 @@ char *get_current_dir_name(void) return strdup(ret ? ret : "/"); } #endif + +#ifndef __linux__ +int capset(struct __user_cap_header_struct *header, + struct __user_cap_data_struct *data) +{ +#ifdef __OpenBSD__ + if (header->version != _LINUX_CAPABILITY_VERSION_1) + return -EINVAL; + if (header->pid != 0) + return -EINVAL; + if (data->effective == 0 && data->permitted == 0) + return pledge("stdio rpath tmppath inet error", NULL); +#else + (void)header; + (void)data; +#endif + + return 0; +} +#endif diff --git a/src/lib/missing/linux/capability.h b/src/lib/missing/linux/capability.h index 7a1694e0c..7742ce230 100644 --- a/src/lib/missing/linux/capability.h +++ b/src/lib/missing/linux/capability.h @@ -26,7 +26,6 @@ #include_next #include #include - #include static inline int capset(struct __user_cap_header_struct *header, @@ -50,13 +49,9 @@ struct __user_cap_header_struct { int pid; }; -static inline int capset(struct __user_cap_header_struct *header - __attribute__((unused)), - struct __user_cap_data_struct *data - __attribute__((unused))) -{ - return 0; -} +int capset(struct __user_cap_header_struct *header, + struct __user_cap_data_struct *data); + #endif #endif /* __MISSING_CAPABILITY_H__ */ From 459eb7cc683d5ed6115826df6c93c9e0081841f8 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 24 Aug 2018 20:53:16 -0700 Subject: [PATCH 0768/2505] Fix build when linux/capability.h isn't found A dummy capset() will be provided in this case --- CMakeLists.txt | 1 + src/cmake/lwan-build-config.h.cmake | 1 + src/lib/missing/linux/capability.h | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 52962f041..afe9ca744 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -85,6 +85,7 @@ set(CMAKE_EXTRA_INCLUDE_FILES unistd.h dlfcn.h ) +check_include_file(linux/capability.h HAVE_LINUX_CAPABILITY) check_include_file(sys/auxv.h HAVE_SYS_AUXV) check_include_file(sys/epoll.h HAVE_EPOLL) check_include_files("sys/time.h;sys/types.h;sys/event.h" HAVE_SYS_EVENT) diff --git a/src/cmake/lwan-build-config.h.cmake b/src/cmake/lwan-build-config.h.cmake index 52cea450f..c725be4ff 100644 --- a/src/cmake/lwan-build-config.h.cmake +++ b/src/cmake/lwan-build-config.h.cmake @@ -38,6 +38,7 @@ #cmakedefine HAVE_KQUEUE #cmakedefine HAVE_DLADDR #cmakedefine HAVE_POSIX_FADVISE +#cmakedefine HAVE_LINUX_CAPABILITY /* Compiler builtins for specific CPU instruction support */ #cmakedefine HAVE_BUILTIN_CLZLL diff --git a/src/lib/missing/linux/capability.h b/src/lib/missing/linux/capability.h index 7742ce230..f5c8a72ff 100644 --- a/src/lib/missing/linux/capability.h +++ b/src/lib/missing/linux/capability.h @@ -21,7 +21,7 @@ #ifndef __MISSING_CAPABILITY_H__ #define __MISSING_CAPABILITY_H__ -#if defined(__linux__) +#if defined(__linux__) && defined(HAVE_LINUX_CAPABILITY) #include_next #include From e38e0adee6d0d9cc2b90f14fbc5c87d74ecde024 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 24 Aug 2018 20:54:34 -0700 Subject: [PATCH 0769/2505] Install list.h Since the timeouts library requires list.h, and it needs to be installed as some of its structs are embedded in Lwan structs, also install list.h. Closes #239. --- src/lib/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index 5ae398d5a..a68d89c77 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -129,4 +129,5 @@ install(FILES lwan-strbuf.h queue.h timeout.h + list.h DESTINATION "${CMAKE_INSTALL_FULL_INCLUDEDIR}/lwan") From 42994cee7588807dd62daaf978f22aa2ed32fb2e Mon Sep 17 00:00:00 2001 From: halosghost Date: Sat, 25 Aug 2018 13:50:50 -0500 Subject: [PATCH 0770/2505] expose some needed symbols for template authoring --- src/lib/liblwan.sym | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/lib/liblwan.sym b/src/lib/liblwan.sym index d091bdce2..bd4f3762f 100644 --- a/src/lib/liblwan.sym +++ b/src/lib/liblwan.sym @@ -75,6 +75,15 @@ global: lwan_strbuf_set; lwan_strbuf_set_static; + lwan_append_int_to_strbuf; + lwan_append_double_to_strbuf; + lwan_append_str_to_strbuf; + lwan_append_str_escaped_to_strbuf; + + lwan_tpl_int_is_empty; + lwan_tpl_double_is_empty; + lwan_tpl_str_is_empty; + local: *; }; From 85a7261bc4dfae50eacbed4ce98a6df3f949b927 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 25 Aug 2018 14:19:09 -0700 Subject: [PATCH 0771/2505] Not being able to raise number of file descriptors isn't an error This is required to use pledge() on OpenBSD: without relaxing the pledge too much by enabling the "proc" promise, which would also enable fork(2) -- which is definitely not wanted -- make it a warning instead. So, instead of being a critical error, just use system defaults from limits.h. Also, if for some reason the system does not provide OPEN_MAX, use the oldschool NOFILE from . (I have not tested on all OSes supported by Lwan if this header is available everywhere. If it fails, the build system will have to figure it out.) --- src/lib/lwan.c | 28 ++++++++++++++++++++++------ src/lib/missing/limits.h | 11 ++++++++++- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 21c0fc2df..b4aad6963 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -458,18 +458,34 @@ static rlim_t setup_open_file_count_limits(void) { struct rlimit r; - if (getrlimit(RLIMIT_NOFILE, &r) < 0) - lwan_status_critical_perror("getrlimit"); + if (getrlimit(RLIMIT_NOFILE, &r) < 0) { + lwan_status_perror("Could not obtain maximum number of file " + "descriptors. Assuming %d", + OPEN_MAX); + return OPEN_MAX; + } if (r.rlim_max != r.rlim_cur) { - if (r.rlim_max == RLIM_INFINITY) + const rlim_t current = r.rlim_cur; + + if (r.rlim_max == RLIM_INFINITY) { r.rlim_cur = OPEN_MAX; - else if (r.rlim_cur < r.rlim_max) + } else if (r.rlim_cur < r.rlim_max) { r.rlim_cur = r.rlim_max; - if (setrlimit(RLIMIT_NOFILE, &r) < 0) - lwan_status_critical_perror("setrlimit"); + } else { + /* Shouldn't happen, so just return the current value. */ + goto out; + } + + if (setrlimit(RLIMIT_NOFILE, &r) < 0) { + lwan_status_perror("Could not raise maximum number of file " + "descriptors to %" PRIu64 ". Leaving at " + "%" PRIu64, r.rlim_max, current); + r.rlim_cur = current; + } } +out: return r.rlim_cur; } diff --git a/src/lib/missing/limits.h b/src/lib/missing/limits.h index 781af6484..647dd7143 100644 --- a/src/lib/missing/limits.h +++ b/src/lib/missing/limits.h @@ -26,8 +26,17 @@ # define PATH_MAX 4096 #endif + #ifndef OPEN_MAX -# define OPEN_MAX 65535 + +# include + +# ifdef NOFILE +# define OPEN_MAX NOFILE +# else +# define OPEN_MAX 65535 +# endif + #endif #ifndef OFF_MAX From ac15774f8fa038453d767887e068507f8f33f812 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 25 Aug 2018 14:54:11 -0700 Subject: [PATCH 0772/2505] Enable straitjacket by default in {lwan,testrunner}.conf This will either use capset(2) (Linux), or pledge(2) (OpenBSD) by default. --- lwan.conf | 4 ++++ testrunner.conf | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/lwan.conf b/lwan.conf index 9ca48d738..77b70df87 100644 --- a/lwan.conf +++ b/lwan.conf @@ -17,6 +17,10 @@ threads = 0 # Disable HAProxy's PROXY protocol by default. Only enable if needed. proxy_protocol = false +# Enable straitjacket by default. The `drop_capabilities` option is `true` +# by default. Other options may require more privileges. +straitjacket + listener *:8080 { serve_files / { path = ./wwwroot diff --git a/testrunner.conf b/testrunner.conf index 3bcd87d06..23a22594e 100644 --- a/testrunner.conf +++ b/testrunner.conf @@ -23,6 +23,10 @@ proxy_protocol = true # small for testing purposes. max_post_data_size = 1000000 +# Enable straitjacket by default. The `drop_capabilities` option is `true` +# by default. Other options may require more privileges. +straitjacket + listener *:8080 { &sleep /sleep From 791c10802f9e33e625ffa3676fee7ae586e1dca2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 25 Aug 2018 17:49:19 -0700 Subject: [PATCH 0773/2505] Ensure job thread has lowest I/O priority under Linux The job thread already has SCHED_IDLE priority if supported by the operating system, so it should be fine to turn this knob and further reduce the priority for this thread to not interfere with the rest of the server. --- src/lib/lwan-job.c | 8 ++++++- src/lib/missing/ioprio.h | 49 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 src/lib/missing/ioprio.h diff --git a/src/lib/lwan-job.c b/src/lib/lwan-job.c index feee50476..c5aea8c7b 100644 --- a/src/lib/lwan-job.c +++ b/src/lib/lwan-job.c @@ -20,12 +20,13 @@ #define _GNU_SOURCE #include #include +#include #include #include #include #include -#include #include +#include #include "lwan.h" #include "lwan-status.h" @@ -65,6 +66,11 @@ timedwait(bool had_job) static void* job_thread(void *data __attribute__((unused))) { + /* Idle priority for the calling thread. Magic value of `7` obtained from + * sample program in linux/Documentation/block/ioprio.txt. This is a no-op + * on anything but Linux. */ + ioprio_set(IOPRIO_WHO_PROCESS, 0, IOPRIO_PRIO_VALUE(IOPRIO_CLASS_IDLE, 7)); + if (pthread_mutex_lock(&job_wait_mutex)) lwan_status_critical("Could not lock job wait mutex"); diff --git a/src/lib/missing/ioprio.h b/src/lib/missing/ioprio.h new file mode 100644 index 000000000..a04d94842 --- /dev/null +++ b/src/lib/missing/ioprio.h @@ -0,0 +1,49 @@ +/* + * lwan - simple web server + * Copyright (c) 2018 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include + +#if defined(__linux__) && defined(SYS_ioprio_set) + +#define IOPRIO_WHO_PROCESS 1 +#define IOPRIO_CLASS_IDLE 3 +#define IOPRIO_PRIO_VALUE(class, data) (((class) << 13) | (data)) + +static inline int ioprio_set(int which, int who, int ioprio) +{ + return (int)syscall(SYS_ioprio_set, which, who, ioprio); +} + +#else + +#define IOPRIO_WHO_PROCESS 0 +#define IOPRIO_PRIO_VALUE(arg1, arg2) 0 +#define IOPRIO_CLASS_IDLE 0 + +static inline int ioprio_set(int which __attribute__((unused), + int who __attribute__((unused)), + int ioprio __attribute__((unused))) +{ + return 0; +} + +#endif From 811d59eb72304f6af3e2e288534f036d068e737d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 25 Aug 2018 18:47:35 -0700 Subject: [PATCH 0774/2505] Move calls to readahead() to a low priority thread This thread has SCHED_IDLE priority, and, if on Linux, will also have IDLE I/O class. The idea is that readahead should never have any kind of impact on any of the worker threads (which can't block, as they perform cooperative context switches). The file serving module, that is now making use of this, will call this function every time it adds a file to its cache. Items in cache are alive for a few seconds only, so this will play well in keeping the operating system cache fresh. Threads can request a file descriptor to be readahead by calling lwan_readahead_queue(fd, size); this writes the request to a pipe, which is consumed by the readahead thread. Only the read side of that pipe is blocking; if the writer side can't write the readahead command, it's not the end of the world: this is just an optimization. --- src/lib/lwan-mod-serve-files.c | 2 +- src/lib/lwan-private.h | 2 + src/lib/lwan.c | 93 ++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index f8df1866e..19e13ec0c 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -380,7 +380,7 @@ try_readahead(const struct serve_files_priv *priv, int fd, size_t size) size = priv->read_ahead; if (LIKELY(size)) - readahead(fd, 0, size); + lwan_readahead_queue(fd, size); } static int try_open_compressed(const char *relpath, diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 4aa1d4b4c..5b9dd1885 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -43,6 +43,8 @@ void lwan_job_del(bool (*cb)(void *data), void *data); void lwan_tables_init(void); void lwan_tables_shutdown(void); +void lwan_readahead_queue(int fd, size_t size); + char *lwan_process_request(struct lwan *l, struct lwan_request *request, struct lwan_value *buffer, char *next_request); size_t lwan_prepare_response_header_full(struct lwan_request *request, diff --git a/src/lib/lwan.c b/src/lib/lwan.c index b4aad6963..d4cd8cc20 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include #include #include @@ -527,6 +529,95 @@ static char *dup_or_null(const char *s) return s ? strdup(s) : NULL; } +struct lwan_readahead_cmd { + size_t size; + int fd; +} __attribute__((packed)); + +static int readahead_pipe_fd[2]; +static pthread_t readahead_self; + +static void lwan_readahead_shutdown(void) +{ + unsigned char quit_cmd = 0; + + if (readahead_pipe_fd[0] != readahead_pipe_fd[1]) { + lwan_status_debug("Shutting down readahead thread"); + + /* Writing only 1 byte will signal the thread to end. */ + write(readahead_pipe_fd[1], &quit_cmd, sizeof(quit_cmd)); + pthread_join(readahead_self, NULL); + + close(readahead_pipe_fd[0]); + close(readahead_pipe_fd[1]); + readahead_pipe_fd[0] = readahead_pipe_fd[1] = 0; + } +} + +void lwan_readahead_queue(int fd, size_t size) +{ + struct lwan_readahead_cmd cmd = {.size = size, .fd = fd}; + + /* Readahead is just a hint. Failing to write is not an error. */ + write(readahead_pipe_fd[1], &cmd, sizeof(cmd)); +} + +static void *lwan_readahead_loop(void *data __attribute__((unused))) +{ + /* Idle priority for the calling thread. Magic value of `7` obtained from + * sample program in linux/Documentation/block/ioprio.txt. This is a no-op + * on anything but Linux. */ + ioprio_set(IOPRIO_WHO_PROCESS, 0, IOPRIO_PRIO_VALUE(IOPRIO_CLASS_IDLE, 7)); + + while (true) { + struct lwan_readahead_cmd cmd; + ssize_t n_bytes = read(readahead_pipe_fd[0], &cmd, sizeof(cmd)); + + if (LIKELY(n_bytes == (ssize_t)sizeof(cmd))) { + lwan_status_debug("Got request to readahead fd %d, size %zu", + cmd.fd, cmd.size); + readahead(cmd.fd, 0, cmd.size); + } else if (UNLIKELY(n_bytes < 0)) { + if (errno == EAGAIN || errno == EINTR) + continue; + break; + } else { + break; + } + } + + return NULL; +} + +static void lwan_readahead_init(void) +{ + int flags; + + if (readahead_pipe_fd[0] != readahead_pipe_fd[1]) + return; + + lwan_status_debug("Initializing low priority readahead thread"); + + if (pipe2(readahead_pipe_fd, O_CLOEXEC) < 0) + lwan_status_critical_perror("pipe2"); + + /* Only write side should be non-blocking. */ + flags = fcntl(readahead_pipe_fd[1], F_GETFL); + if (flags < 0) + lwan_status_critical_perror("fcntl"); + if (fcntl(readahead_pipe_fd[1], F_SETFL, flags | O_NONBLOCK) < 0) + lwan_status_critical_perror("fcntl"); + + if (pthread_create(&readahead_self, NULL, lwan_readahead_loop, NULL)) + lwan_status_critical_perror("pthread_create"); + +#ifdef SCHED_IDLE + struct sched_param sched_param = {.sched_priority = 0}; + if (pthread_setschedparam(readahead_self, SCHED_IDLE, &sched_param) < 0) + lwan_status_perror("pthread_setschedparam"); +#endif /* SCHED_IDLE */ +} + void lwan_init_with_config(struct lwan *l, const struct lwan_config *config) { /* Load defaults */ @@ -581,6 +672,7 @@ void lwan_init_with_config(struct lwan *l, const struct lwan_config *config) signal(SIGPIPE, SIG_IGN); + lwan_readahead_init(); lwan_thread_init(l); lwan_socket_init(l); lwan_http_authorize_init(); @@ -606,6 +698,7 @@ void lwan_shutdown(struct lwan *l) lwan_tables_shutdown(); lwan_status_shutdown(l); lwan_http_authorize_shutdown(); + lwan_readahead_shutdown(); } static ALWAYS_INLINE unsigned int schedule_client(struct lwan *l, int fd) From 41a0676089c6a645b90f5a9265f2ff8a12bfcf02 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 25 Aug 2018 21:26:47 -0700 Subject: [PATCH 0775/2505] Fix build on non-Linux platforms --- src/lib/missing/ioprio.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/missing/ioprio.h b/src/lib/missing/ioprio.h index a04d94842..046878309 100644 --- a/src/lib/missing/ioprio.h +++ b/src/lib/missing/ioprio.h @@ -39,7 +39,7 @@ static inline int ioprio_set(int which, int who, int ioprio) #define IOPRIO_PRIO_VALUE(arg1, arg2) 0 #define IOPRIO_CLASS_IDLE 0 -static inline int ioprio_set(int which __attribute__((unused), +static inline int ioprio_set(int which __attribute__((unused)), int who __attribute__((unused)), int ioprio __attribute__((unused))) { From a98160134a81f626cf77d269cf6ea9f8c9bf0c72 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 26 Aug 2018 10:04:41 -0700 Subject: [PATCH 0776/2505] No need to check for HTTP_NOT_MODIFIED return status Freshness is now checked globally. --- src/lib/lwan-mod-serve-files.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 19e13ec0c..f2a642510 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -924,8 +924,7 @@ static enum lwan_http_status sendfile_serve(struct lwan_request *request, if (UNLIKELY(!header_len)) return HTTP_INTERNAL_ERROR; - if (lwan_request_get_method(request) == REQUEST_METHOD_HEAD || - return_status == HTTP_NOT_MODIFIED) { + if (lwan_request_get_method(request) == REQUEST_METHOD_HEAD) { lwan_send(request, headers, header_len, 0); } else { lwan_sendfile(request, fd, from, (size_t)to, headers, header_len); @@ -949,8 +948,7 @@ static enum lwan_http_status serve_buffer(struct lwan_request *request, if (UNLIKELY(!header_len)) return HTTP_INTERNAL_ERROR; - if (lwan_request_get_method(request) == REQUEST_METHOD_HEAD || - return_status == HTTP_NOT_MODIFIED) { + if (lwan_request_get_method(request) == REQUEST_METHOD_HEAD) { lwan_send(request, headers, header_len, 0); } else { struct iovec response_vec[] = { From b6eb7308a06c10e8b30831d85221b0ec1cf7d209 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 26 Aug 2018 10:58:59 -0700 Subject: [PATCH 0777/2505] Fix integer addition overflow in realpathat() This mirrors the change made by Paul Pluzhnikov to glibc in upstream commit 5460617d (bugzilla 22786[1]), fixing CVE-2018-11236[2]. [1] https://sourceware.org/bugzilla/show_bug.cgi?id=22786 [2] https://nvd.nist.gov/vuln/detail/CVE-2018-11236 --- src/lib/realpathat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/realpathat.c b/src/lib/realpathat.c index 8984fce6b..6c08ca856 100644 --- a/src/lib/realpathat.c +++ b/src/lib/realpathat.c @@ -160,7 +160,7 @@ realpathat2(int dirfd, char *dirfdpath, const char *name, char *resolved, buf[n] = '\0'; len = strlen(end); - if (UNLIKELY((long int) (n + (long int)len) >= PATH_MAX)) { + if (UNLIKELY(PATH_MAX - n <= len)) { errno = ENAMETOOLONG; goto error; } From 5cc0fb844e5a979d4dbe7374f36f392b1aba4068 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 26 Aug 2018 11:16:20 -0700 Subject: [PATCH 0778/2505] Fix comparison between signed and unsigned ints after b6eb730 --- src/lib/realpathat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/realpathat.c b/src/lib/realpathat.c index 6c08ca856..e4e3fc7ff 100644 --- a/src/lib/realpathat.c +++ b/src/lib/realpathat.c @@ -160,7 +160,7 @@ realpathat2(int dirfd, char *dirfdpath, const char *name, char *resolved, buf[n] = '\0'; len = strlen(end); - if (UNLIKELY(PATH_MAX - n <= len)) { + if (UNLIKELY((size_t)(PATH_MAX - n) <= len)) { errno = ENAMETOOLONG; goto error; } From 7512f2cfd96fd7f77951addce7205c5e6cccb3d9 Mon Sep 17 00:00:00 2001 From: halosghost Date: Mon, 27 Aug 2018 00:05:13 -0500 Subject: [PATCH 0779/2505] Expose symbols needed to instantiate modules --- src/lib/liblwan.sym | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/liblwan.sym b/src/lib/liblwan.sym index bd4f3762f..2f32d0b6e 100644 --- a/src/lib/liblwan.sym +++ b/src/lib/liblwan.sym @@ -84,6 +84,8 @@ global: lwan_tpl_double_is_empty; lwan_tpl_str_is_empty; + lwan_module_info_*; + local: *; }; From c43521d549dc8e529c33a65bc54037855a29b808 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 27 Aug 2018 07:44:50 -0700 Subject: [PATCH 0780/2505] Ignore all read(2) errors in readahead thread --- src/lib/lwan.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index d4cd8cc20..04e17aad1 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -577,11 +577,8 @@ static void *lwan_readahead_loop(void *data __attribute__((unused))) lwan_status_debug("Got request to readahead fd %d, size %zu", cmd.fd, cmd.size); readahead(cmd.fd, 0, cmd.size); - } else if (UNLIKELY(n_bytes < 0)) { - if (errno == EAGAIN || errno == EINTR) - continue; - break; - } else { + } else if (UNLIKELY(n_bytes == 1)) { + /* Shutdown request received. */ break; } } From 1a1a906aa1da82ff81fc85fc7ce70ac8ec16f742 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 27 Aug 2018 18:28:07 -0700 Subject: [PATCH 0781/2505] Move readahead thread to its own file --- src/lib/CMakeLists.txt | 7 +-- src/lib/lwan-private.h | 2 + src/lib/lwan-readahead.c | 114 +++++++++++++++++++++++++++++++++++++++ src/lib/lwan.c | 88 ------------------------------ 4 files changed, 120 insertions(+), 91 deletions(-) create mode 100644 src/lib/lwan-readahead.c diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index a68d89c77..a000dd9d1 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -20,23 +20,24 @@ set(SOURCES lwan-mod-response.c lwan-mod-rewrite.c lwan-mod-serve-files.c + lwan-readahead.c lwan-request.c lwan-response.c lwan-socket.c lwan-status.c lwan-straitjacket.c + lwan-strbuf.c lwan-tables.c lwan-template.c lwan-thread.c - lwan-trie.c lwan-time.c + lwan-trie.c missing.c murmur3.c patterns.c + queue.c realpathat.c sd-daemon.c - lwan-strbuf.c - queue.c timeout.c ) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 5b9dd1885..03b82b975 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -43,6 +43,8 @@ void lwan_job_del(bool (*cb)(void *data), void *data); void lwan_tables_init(void); void lwan_tables_shutdown(void); +void lwan_readahead_init(void); +void lwan_readahead_shutdown(void); void lwan_readahead_queue(int fd, size_t size); char *lwan_process_request(struct lwan *l, struct lwan_request *request, diff --git a/src/lib/lwan-readahead.c b/src/lib/lwan-readahead.c new file mode 100644 index 000000000..2a0a6f06c --- /dev/null +++ b/src/lib/lwan-readahead.c @@ -0,0 +1,114 @@ +/* + * lwan - simple web server + * Copyright (c) 2018 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#include "lwan-private.h" + +struct lwan_readahead_cmd { + size_t size; + int fd; +} __attribute__((packed)); + +static int readahead_pipe_fd[2]; +static pthread_t readahead_self; + +void lwan_readahead_shutdown(void) +{ + unsigned char quit_cmd = 0; + + if (readahead_pipe_fd[0] != readahead_pipe_fd[1]) { + lwan_status_debug("Shutting down readahead thread"); + + /* Writing only 1 byte will signal the thread to end. */ + write(readahead_pipe_fd[1], &quit_cmd, sizeof(quit_cmd)); + pthread_join(readahead_self, NULL); + + close(readahead_pipe_fd[0]); + close(readahead_pipe_fd[1]); + readahead_pipe_fd[0] = readahead_pipe_fd[1] = 0; + } +} + +void lwan_readahead_queue(int fd, size_t size) +{ + struct lwan_readahead_cmd cmd = {.size = size, .fd = fd}; + + /* Readahead is just a hint. Failing to write is not an error. */ + write(readahead_pipe_fd[1], &cmd, sizeof(cmd)); +} + +static void *lwan_readahead_loop(void *data __attribute__((unused))) +{ + /* Idle priority for the calling thread. Magic value of `7` obtained from + * sample program in linux/Documentation/block/ioprio.txt. This is a no-op + * on anything but Linux. */ + ioprio_set(IOPRIO_WHO_PROCESS, 0, IOPRIO_PRIO_VALUE(IOPRIO_CLASS_IDLE, 7)); + + while (true) { + struct lwan_readahead_cmd cmd; + ssize_t n_bytes = read(readahead_pipe_fd[0], &cmd, sizeof(cmd)); + + if (LIKELY(n_bytes == (ssize_t)sizeof(cmd))) { + lwan_status_debug("Got request to readahead fd %d, size %zu", + cmd.fd, cmd.size); + readahead(cmd.fd, 0, cmd.size); + } else if (UNLIKELY(n_bytes == 1)) { + /* Shutdown request received. */ + break; + } + } + + return NULL; +} + +void lwan_readahead_init(void) +{ + int flags; + + if (readahead_pipe_fd[0] != readahead_pipe_fd[1]) + return; + + lwan_status_debug("Initializing low priority readahead thread"); + + if (pipe2(readahead_pipe_fd, O_CLOEXEC) < 0) + lwan_status_critical_perror("pipe2"); + + /* Only write side should be non-blocking. */ + flags = fcntl(readahead_pipe_fd[1], F_GETFL); + if (flags < 0) + lwan_status_critical_perror("fcntl"); + if (fcntl(readahead_pipe_fd[1], F_SETFL, flags | O_NONBLOCK) < 0) + lwan_status_critical_perror("fcntl"); + + if (pthread_create(&readahead_self, NULL, lwan_readahead_loop, NULL)) + lwan_status_critical_perror("pthread_create"); + +#ifdef SCHED_IDLE + struct sched_param sched_param = {.sched_priority = 0}; + if (pthread_setschedparam(readahead_self, SCHED_IDLE, &sched_param) < 0) + lwan_status_perror("pthread_setschedparam"); +#endif /* SCHED_IDLE */ +} diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 04e17aad1..667606a2b 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -23,8 +23,6 @@ #include #include #include -#include -#include #include #include #include @@ -529,92 +527,6 @@ static char *dup_or_null(const char *s) return s ? strdup(s) : NULL; } -struct lwan_readahead_cmd { - size_t size; - int fd; -} __attribute__((packed)); - -static int readahead_pipe_fd[2]; -static pthread_t readahead_self; - -static void lwan_readahead_shutdown(void) -{ - unsigned char quit_cmd = 0; - - if (readahead_pipe_fd[0] != readahead_pipe_fd[1]) { - lwan_status_debug("Shutting down readahead thread"); - - /* Writing only 1 byte will signal the thread to end. */ - write(readahead_pipe_fd[1], &quit_cmd, sizeof(quit_cmd)); - pthread_join(readahead_self, NULL); - - close(readahead_pipe_fd[0]); - close(readahead_pipe_fd[1]); - readahead_pipe_fd[0] = readahead_pipe_fd[1] = 0; - } -} - -void lwan_readahead_queue(int fd, size_t size) -{ - struct lwan_readahead_cmd cmd = {.size = size, .fd = fd}; - - /* Readahead is just a hint. Failing to write is not an error. */ - write(readahead_pipe_fd[1], &cmd, sizeof(cmd)); -} - -static void *lwan_readahead_loop(void *data __attribute__((unused))) -{ - /* Idle priority for the calling thread. Magic value of `7` obtained from - * sample program in linux/Documentation/block/ioprio.txt. This is a no-op - * on anything but Linux. */ - ioprio_set(IOPRIO_WHO_PROCESS, 0, IOPRIO_PRIO_VALUE(IOPRIO_CLASS_IDLE, 7)); - - while (true) { - struct lwan_readahead_cmd cmd; - ssize_t n_bytes = read(readahead_pipe_fd[0], &cmd, sizeof(cmd)); - - if (LIKELY(n_bytes == (ssize_t)sizeof(cmd))) { - lwan_status_debug("Got request to readahead fd %d, size %zu", - cmd.fd, cmd.size); - readahead(cmd.fd, 0, cmd.size); - } else if (UNLIKELY(n_bytes == 1)) { - /* Shutdown request received. */ - break; - } - } - - return NULL; -} - -static void lwan_readahead_init(void) -{ - int flags; - - if (readahead_pipe_fd[0] != readahead_pipe_fd[1]) - return; - - lwan_status_debug("Initializing low priority readahead thread"); - - if (pipe2(readahead_pipe_fd, O_CLOEXEC) < 0) - lwan_status_critical_perror("pipe2"); - - /* Only write side should be non-blocking. */ - flags = fcntl(readahead_pipe_fd[1], F_GETFL); - if (flags < 0) - lwan_status_critical_perror("fcntl"); - if (fcntl(readahead_pipe_fd[1], F_SETFL, flags | O_NONBLOCK) < 0) - lwan_status_critical_perror("fcntl"); - - if (pthread_create(&readahead_self, NULL, lwan_readahead_loop, NULL)) - lwan_status_critical_perror("pthread_create"); - -#ifdef SCHED_IDLE - struct sched_param sched_param = {.sched_priority = 0}; - if (pthread_setschedparam(readahead_self, SCHED_IDLE, &sched_param) < 0) - lwan_status_perror("pthread_setschedparam"); -#endif /* SCHED_IDLE */ -} - void lwan_init_with_config(struct lwan *l, const struct lwan_config *config) { /* Load defaults */ From e31e4b60591a52984288152b720f4b6f0dab172f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 27 Aug 2018 18:38:07 -0700 Subject: [PATCH 0782/2505] Error events shouldn't be ignored while coalescing kevents When watching events that aren't EPOLLIN or EPOLLOUT (e.g. only to keep the file descriptor in the epoll set to be notified of errors when a coroutine is suspended by a timer), udata is set to 1. When coalescing the events, udata should only be checked if the filter is EVFILT_WRITE; ignoring anything that happened if udata is 1, regardless of the filter in the kevent, will actually ignore EV_ERROR and EV_EOF events. --- src/lib/missing.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/lib/missing.c b/src/lib/missing.c index 8d1dc11b0..972c95ec8 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -294,20 +294,15 @@ epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) if (kev->filter == EVFILT_READ) mask |= EPOLLIN; - else if (kev->filter == EVFILT_WRITE) + else if (kev->filter == EVFILT_WRITE && (uintptr_t)evs[i].udata != 1) mask |= EPOLLOUT; hash_add(coalesce, (void*)(intptr_t)evs[i].ident, (void *)(uintptr_t)mask); } for (i = 0; i < r; i++) { - uintptr_t udata = (uintptr_t)evs[i].udata; void *maskptr; - if (udata == 1) { - continue; - } - maskptr = hash_find(coalesce, (void*)(intptr_t)evs[i].ident); if (maskptr) { struct kevent *kev = &evs[i]; From a27ffd6b3e6053b6df77aa40f1dc9e18e6830a67 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 27 Aug 2018 19:39:58 -0700 Subject: [PATCH 0783/2505] Use wildcards to simplify liblwan.sym --- src/lib/liblwan.sym | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/lib/liblwan.sym b/src/lib/liblwan.sym index 2f32d0b6e..fb77a8a15 100644 --- a/src/lib/liblwan.sym +++ b/src/lib/liblwan.sym @@ -56,6 +56,8 @@ global: lwan_tpl_compile_string; lwan_tpl_compile_string_full; lwan_tpl_free; + lwan_append_*_to_strbuf; + lwan_tpl_*_is_empty; lwan_writev; lwan_send; @@ -75,15 +77,6 @@ global: lwan_strbuf_set; lwan_strbuf_set_static; - lwan_append_int_to_strbuf; - lwan_append_double_to_strbuf; - lwan_append_str_to_strbuf; - lwan_append_str_escaped_to_strbuf; - - lwan_tpl_int_is_empty; - lwan_tpl_double_is_empty; - lwan_tpl_str_is_empty; - lwan_module_info_*; local: From 7083573aacd30e2394b19b115e834ce6c2bc040b Mon Sep 17 00:00:00 2001 From: halosghost Date: Wed, 29 Aug 2018 15:36:54 -0500 Subject: [PATCH 0784/2505] include stddef and stdbool for this header --- src/lib/queue.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/queue.h b/src/lib/queue.h index 42c8a7c88..7c78ce7d1 100644 --- a/src/lib/queue.h +++ b/src/lib/queue.h @@ -8,6 +8,9 @@ #pragma once +#include +#include + struct spsc_queue { size_t size; size_t mask; From 22757c13935a4039aeb06db83c009169de313d0d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 30 Aug 2018 07:23:49 -0700 Subject: [PATCH 0785/2505] madvise() may also block, so move it to the readahead thread At least under Linux, a call to madvise(MADV_WILLNEED) will eventually reach the same path that readahead() reaches. So move it to the readaheaad thread. --- src/lib/lwan-mod-serve-files.c | 4 +- src/lib/lwan-private.h | 1 + src/lib/lwan-readahead.c | 76 +++++++++++++++++++++++++--------- 3 files changed, 59 insertions(+), 22 deletions(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index f2a642510..77338aa9b 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -348,9 +348,7 @@ static bool mmap_init(struct file_cache_entry *ce, goto close_file; } - if (UNLIKELY(madvise(md->uncompressed.contents, (size_t)st->st_size, - MADV_WILLNEED) < 0)) - lwan_status_perror("madvise"); + lwan_madvise_queue(md->uncompressed.contents, (size_t)st->st_size); md->uncompressed.size = (size_t)st->st_size; compress_cached_entry(md); diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 03b82b975..a11d95833 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -46,6 +46,7 @@ void lwan_tables_shutdown(void); void lwan_readahead_init(void); void lwan_readahead_shutdown(void); void lwan_readahead_queue(int fd, size_t size); +void lwan_madvise_queue(void *addr, size_t size); char *lwan_process_request(struct lwan *l, struct lwan_request *request, struct lwan_value *buffer, char *next_request); diff --git a/src/lib/lwan-readahead.c b/src/lib/lwan-readahead.c index 2a0a6f06c..06589524e 100644 --- a/src/lib/lwan-readahead.c +++ b/src/lib/lwan-readahead.c @@ -24,12 +24,28 @@ #include #include #include +#include #include "lwan-private.h" +enum readahead_cmd { + READAHEAD, + MADVISE, + SHUTDOWN, +}; + struct lwan_readahead_cmd { - size_t size; - int fd; + enum readahead_cmd cmd; + union { + struct { + size_t size; + int fd; + } readahead; + struct { + void *addr; + size_t length; + } madvise; + }; } __attribute__((packed)); static int readahead_pipe_fd[2]; @@ -37,24 +53,40 @@ static pthread_t readahead_self; void lwan_readahead_shutdown(void) { - unsigned char quit_cmd = 0; + struct lwan_readahead_cmd cmd = { + .cmd = SHUTDOWN, + }; - if (readahead_pipe_fd[0] != readahead_pipe_fd[1]) { - lwan_status_debug("Shutting down readahead thread"); + if (readahead_pipe_fd[0] == readahead_pipe_fd[1]) + return; - /* Writing only 1 byte will signal the thread to end. */ - write(readahead_pipe_fd[1], &quit_cmd, sizeof(quit_cmd)); - pthread_join(readahead_self, NULL); + lwan_status_debug("Shutting down readahead thread"); - close(readahead_pipe_fd[0]); - close(readahead_pipe_fd[1]); - readahead_pipe_fd[0] = readahead_pipe_fd[1] = 0; - } + write(readahead_pipe_fd[1], &cmd, sizeof(cmd)); + pthread_join(readahead_self, NULL); + + close(readahead_pipe_fd[0]); + close(readahead_pipe_fd[1]); + readahead_pipe_fd[0] = readahead_pipe_fd[1] = 0; } void lwan_readahead_queue(int fd, size_t size) { - struct lwan_readahead_cmd cmd = {.size = size, .fd = fd}; + struct lwan_readahead_cmd cmd = { + .readahead = {.size = size, .fd = fd}, + .cmd = READAHEAD, + }; + + /* Readahead is just a hint. Failing to write is not an error. */ + write(readahead_pipe_fd[1], &cmd, sizeof(cmd)); +} + +void lwan_madvise_queue(void *addr, size_t length) +{ + struct lwan_readahead_cmd cmd = { + .madvise = {.addr = addr, .length = length}, + .cmd = MADVISE, + }; /* Readahead is just a hint. Failing to write is not an error. */ write(readahead_pipe_fd[1], &cmd, sizeof(cmd)); @@ -71,16 +103,22 @@ static void *lwan_readahead_loop(void *data __attribute__((unused))) struct lwan_readahead_cmd cmd; ssize_t n_bytes = read(readahead_pipe_fd[0], &cmd, sizeof(cmd)); - if (LIKELY(n_bytes == (ssize_t)sizeof(cmd))) { - lwan_status_debug("Got request to readahead fd %d, size %zu", - cmd.fd, cmd.size); - readahead(cmd.fd, 0, cmd.size); - } else if (UNLIKELY(n_bytes == 1)) { - /* Shutdown request received. */ + if (UNLIKELY(n_bytes < 0)) + continue; + + switch (cmd.cmd) { + case READAHEAD: + readahead(cmd.readahead.fd, 0, cmd.readahead.size); + break; + case MADVISE: + madvise(cmd.madvise.addr, cmd.madvise.length, MADV_WILLNEED); break; + case SHUTDOWN: + goto out; } } +out: return NULL; } From 7e617553a7a2da26a8c8668751a9972e7629fd8d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 30 Aug 2018 07:39:00 -0700 Subject: [PATCH 0786/2505] Perform readaheads while sending large files with sendfile() --- src/lib/lwan-io-wrappers.c | 3 ++- src/lib/lwan-mod-serve-files.c | 2 +- src/lib/lwan-private.h | 2 +- src/lib/lwan-readahead.c | 7 ++++--- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index f2dfb18cc..769d7b68a 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -25,7 +25,7 @@ #include #include -#include "lwan.h" +#include "lwan-private.h" #include "lwan-io-wrappers.h" static const int MAX_FAILED_TRIES = 5; @@ -140,6 +140,7 @@ lwan_sendfile(struct lwan_request *request, int in_fd, off_t offset, size_t coun to_be_written -= (size_t)written; chunk_size = min_size(to_be_written, 1<<19); + lwan_readahead_queue(in_fd, offset, chunk_size); coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); } while (to_be_written > 0); } diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 77338aa9b..9000283c9 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -378,7 +378,7 @@ try_readahead(const struct serve_files_priv *priv, int fd, size_t size) size = priv->read_ahead; if (LIKELY(size)) - lwan_readahead_queue(fd, size); + lwan_readahead_queue(fd, 0, size); } static int try_open_compressed(const char *relpath, diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index a11d95833..8eb2e5748 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -45,7 +45,7 @@ void lwan_tables_shutdown(void); void lwan_readahead_init(void); void lwan_readahead_shutdown(void); -void lwan_readahead_queue(int fd, size_t size); +void lwan_readahead_queue(int fd, off_t off, size_t size); void lwan_madvise_queue(void *addr, size_t size); char *lwan_process_request(struct lwan *l, struct lwan_request *request, diff --git a/src/lib/lwan-readahead.c b/src/lib/lwan-readahead.c index 06589524e..a1186a7d4 100644 --- a/src/lib/lwan-readahead.c +++ b/src/lib/lwan-readahead.c @@ -39,6 +39,7 @@ struct lwan_readahead_cmd { union { struct { size_t size; + off_t off; int fd; } readahead; struct { @@ -70,10 +71,10 @@ void lwan_readahead_shutdown(void) readahead_pipe_fd[0] = readahead_pipe_fd[1] = 0; } -void lwan_readahead_queue(int fd, size_t size) +void lwan_readahead_queue(int fd, off_t off, size_t size) { struct lwan_readahead_cmd cmd = { - .readahead = {.size = size, .fd = fd}, + .readahead = {.size = size, .fd = fd, .off = off}, .cmd = READAHEAD, }; @@ -108,7 +109,7 @@ static void *lwan_readahead_loop(void *data __attribute__((unused))) switch (cmd.cmd) { case READAHEAD: - readahead(cmd.readahead.fd, 0, cmd.readahead.size); + readahead(cmd.readahead.fd, cmd.readahead.off, cmd.readahead.size); break; case MADVISE: madvise(cmd.madvise.addr, cmd.madvise.length, MADV_WILLNEED); From b71756f80e5d545bca2073a2e1442521021f2b1b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 30 Aug 2018 08:36:21 -0700 Subject: [PATCH 0787/2505] Add test case for lwan_request_sleep() --- src/scripts/testsuite.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index 8e75edd9c..00cbae55b 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -750,5 +750,14 @@ def test_brew_coffee(self): self.assertEqual(r.status_code, 418) + +class TestSleep(LwanTest): + def test_sleep(self): + now = time.time() + requests.get('http://127.0.0.1:8080/sleep?ms=1500') + diff = time.time() - now + + self.assertTrue(1.450 < diff < 1.550) + if __name__ == '__main__': unittest.main() From a21b0e33a48dceb4e1bf69acc410badeb6797d3b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 30 Aug 2018 19:55:04 -0700 Subject: [PATCH 0788/2505] Warn if VLAs are used --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index afe9ca744..afa159678 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -224,6 +224,7 @@ enable_warning_if_supported(-Wrestrict) enable_warning_if_supported(-Wdouble-promotion) enable_warning_if_supported(-Wno-unused-parameter) enable_warning_if_supported(-Wstringop-truncation) +enable_warning_if_supported(-Wvla) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wshadow -Wconversion -std=gnu99") set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${C_FLAGS_REL}") From 9201a6991ca134632d843d21ba2e110d70082ddb Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 30 Aug 2018 19:55:46 -0700 Subject: [PATCH 0789/2505] Give threads more descriptive names --- src/lib/lwan-job.c | 4 +++- src/lib/lwan-private.h | 2 ++ src/lib/lwan-readahead.c | 2 ++ src/lib/lwan-thread.c | 1 + src/lib/lwan.c | 27 +++++++++++++++++++++++++++ 5 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-job.c b/src/lib/lwan-job.c index c5aea8c7b..1a4d56918 100644 --- a/src/lib/lwan-job.c +++ b/src/lib/lwan-job.c @@ -28,7 +28,7 @@ #include #include -#include "lwan.h" +#include "lwan-private.h" #include "lwan-status.h" #include "list.h" @@ -71,6 +71,8 @@ job_thread(void *data __attribute__((unused))) * on anything but Linux. */ ioprio_set(IOPRIO_WHO_PROCESS, 0, IOPRIO_PRIO_VALUE(IOPRIO_CLASS_IDLE, 7)); + lwan_set_thread_name("job"); + if (pthread_mutex_lock(&job_wait_mutex)) lwan_status_critical("Could not lock job wait mutex"); diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 8eb2e5748..4e1509c8e 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -21,6 +21,8 @@ #include "lwan.h" +void lwan_set_thread_name(const char *name); + void lwan_response_init(struct lwan *l); void lwan_response_shutdown(struct lwan *l); diff --git a/src/lib/lwan-readahead.c b/src/lib/lwan-readahead.c index a1186a7d4..0fa433f8b 100644 --- a/src/lib/lwan-readahead.c +++ b/src/lib/lwan-readahead.c @@ -100,6 +100,8 @@ static void *lwan_readahead_loop(void *data __attribute__((unused))) * on anything but Linux. */ ioprio_set(IOPRIO_WHO_PROCESS, 0, IOPRIO_PRIO_VALUE(IOPRIO_CLASS_IDLE, 7)); + lwan_set_thread_name("readahead"); + while (true) { struct lwan_readahead_cmd cmd; ssize_t n_bytes = read(readahead_pipe_fd[0], &cmd, sizeof(cmd)); diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index dd2fcfd4d..aed55b771 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -403,6 +403,7 @@ static void *thread_io_loop(void *data) lwan_status_debug("Starting IO loop on thread #%d", (unsigned short)(ptrdiff_t)(t - t->lwan->thread.threads) + 1); + lwan_set_thread_name("worker"); events = calloc((size_t)max_events, sizeof(*events)); if (UNLIKELY(!events)) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 667606a2b..230c03b4f 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -762,3 +762,30 @@ __attribute__((constructor)) static void detect_fastest_monotonic_clock(void) monotonic_clock_id = CLOCK_MONOTONIC_COARSE; } #endif + +#ifdef __linux__ +#include + +void lwan_set_thread_name(const char *name) +{ + char thread_name[16]; + char process_name[PATH_MAX]; + char *tmp; + int ret; + + if (proc_pidpath(getpid(), process_name, sizeof(process_name)) < 0) + return; + + tmp = strrchr(process_name, '/'); + if (!tmp) + return; + + ret = snprintf(thread_name, sizeof(thread_name), "%s %s", tmp + 1, name); + if (ret < 0) + return; + + prctl(PR_SET_NAME, thread_name, 0, 0, 0); +} +#else +void lwan_set_thread_name(const char *name) {} +#endif From a83a1a4874aeb4fd25c5056eb8d885eb7740d5d0 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 30 Aug 2018 19:56:35 -0700 Subject: [PATCH 0790/2505] Better handle error conditions in readahead thread --- src/lib/lwan-readahead.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-readahead.c b/src/lib/lwan-readahead.c index 0fa433f8b..105a60002 100644 --- a/src/lib/lwan-readahead.c +++ b/src/lib/lwan-readahead.c @@ -106,8 +106,17 @@ static void *lwan_readahead_loop(void *data __attribute__((unused))) struct lwan_readahead_cmd cmd; ssize_t n_bytes = read(readahead_pipe_fd[0], &cmd, sizeof(cmd)); - if (UNLIKELY(n_bytes < 0)) + if (UNLIKELY(n_bytes < 0)) { + if (errno == EAGAIN || errno == EINTR) + continue; + lwan_status_perror("Ignoring error while reading from pipe (%d)", + readahead_pipe_fd[0]); continue; + } else if (UNLIKELY(n_bytes != (ssize_t)sizeof(cmd))) { + lwan_status_debug( + "Ignoring incomplete command for readahead thread"); + continue; + } switch (cmd.cmd) { case READAHEAD: From db99d207e39008e9052d7a9b563da56efb2a6422 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 30 Aug 2018 20:30:29 -0700 Subject: [PATCH 0791/2505] Make HTTP response code for `redirect` module be a user setting --- README.md | 14 +++++--- src/lib/lwan-mod-redirect.c | 72 ++++++++++++++++++++++++++++++++----- src/lib/lwan-mod-redirect.h | 20 ++++++----- 3 files changed, 84 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 21d9d9157..0005eb57b 100644 --- a/README.md +++ b/README.md @@ -441,17 +441,21 @@ them must be specified at least. #### Redirect The `redirect` module will, as it says in the tin, generate a `301 -Moved permanently` response, according to the options specified in its -configuration. Generally, the `rewrite` module should be used instead -as it packs more features; however, this module serves also as an -example of how to write Lwan modules (weighing at ~40 lines of code). +Moved permanently` (by default; the code can be changed, see below) +response, according to the options specified in its configuration. +Generally, the `rewrite` module should be used instead as it packs more +features; however, this module serves also as an example of how to +write Lwan modules (less than 100 lines of code). If the `to` option is not specified, it always generates a `500 -Internal Server Error` response. +Internal Server Error` response. Specifying an invalid HTTP code, or a +code that Lwan doesn't know about (see `enum lwan_http_status`), will +produce a `301 Moved Permanently` response. | Option | Type | Default | Description | |--------|------|---------|-------------| | `to` | `str` | `NULL` | The location to redirect to | +| `code` | `int` | `301` | The HTTP code to perform a redirect | #### Response diff --git a/src/lib/lwan-mod-redirect.c b/src/lib/lwan-mod-redirect.c index ca9b64df9..a2f18950d 100644 --- a/src/lib/lwan-mod-redirect.c +++ b/src/lib/lwan-mod-redirect.c @@ -14,46 +14,100 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ -#include #include +#include #include "lwan.h" #include "lwan-mod-redirect.h" +struct redirect_priv { + char *to; + enum lwan_http_status code; +}; + static enum lwan_http_status redirect_handle_request(struct lwan_request *request, - struct lwan_response *response, void *instance) + struct lwan_response *response, + void *instance) { - struct lwan_key_value *headers = coro_malloc(request->conn->coro, sizeof(*headers) * 2); + struct redirect_priv *priv = instance; + struct lwan_key_value *headers; + + headers = coro_malloc(request->conn->coro, sizeof(*headers) * 2); if (UNLIKELY(!headers)) return HTTP_INTERNAL_ERROR; headers[0].key = "Location"; - headers[0].value = instance; + headers[0].value = priv->to; headers[1].key = NULL; headers[1].value = NULL; response->headers = headers; - return HTTP_MOVED_PERMANENTLY; + return priv->code; } static void *redirect_create(const char *prefix __attribute__((unused)), void *instance) { struct lwan_redirect_settings *settings = instance; + struct redirect_priv *priv = malloc(sizeof(*priv)); + + if (!priv) + return NULL; + + priv->to = strdup(settings->to); + if (!priv->to) { + free(priv); + return NULL; + } + + priv->code = settings->code; + + return priv; +} + +static void redirect_destroy(void *data) +{ + struct redirect_priv *priv = data; + + if (priv) { + free(priv->to); + free(priv); + } +} + +static enum lwan_http_status parse_http_code(const char *code, + enum lwan_http_status fallback) +{ + const char *known; + int as_int; + + if (!code) + return fallback; + + as_int = parse_int(code, 999); + if (as_int == 999) + return fallback; + + known = lwan_http_status_as_string_with_code(as_int); + if (!strncmp(known, "999", 3)) + return fallback; - return (settings->to) ? strdup(settings->to) : NULL; + return as_int; } static void *redirect_create_from_hash(const char *prefix, const struct hash *hash) { struct lwan_redirect_settings settings = { - .to = hash_find(hash, "to") + .to = hash_find(hash, "to"), + .code = + parse_http_code(hash_find(hash, "code"), HTTP_MOVED_PERMANENTLY), }; return redirect_create(prefix, &settings); @@ -62,7 +116,7 @@ static void *redirect_create_from_hash(const char *prefix, static const struct lwan_module module = { .create = redirect_create, .create_from_hash = redirect_create_from_hash, - .destroy = free, + .destroy = redirect_destroy, .handle_request = redirect_handle_request, }; diff --git a/src/lib/lwan-mod-redirect.h b/src/lib/lwan-mod-redirect.h index 569557b4d..03925772a 100644 --- a/src/lib/lwan-mod-redirect.h +++ b/src/lib/lwan-mod-redirect.h @@ -14,7 +14,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #pragma once @@ -22,15 +23,18 @@ #include "lwan.h" struct lwan_redirect_settings { - char *to; + char *to; + enum lwan_http_status code; }; LWAN_MODULE_FORWARD_DECL(redirect) -#define REDIRECT(to_) \ - .module = LWAN_MODULE_REF(redirect), \ - .args = ((struct lwan_redirect_settings[]) {{ \ - .to = to_ \ - }}), \ - .flags = 0 +#define REDIRECT_CODE(to_, code_) \ + .module = LWAN_MODULE_REF(redirect), \ + .args = &(struct lwan_redirect_settings[]){ \ + .to = (to_), \ + .code = (code_), \ + }), \ + .flags = 0 +#define REDIRECT(to_) REDIRECT_CODE((to_), HTTP_MOVED_PERMANENTLY) From 53c8cd394908424e6cf198d299b23eec5c4d1dc5 Mon Sep 17 00:00:00 2001 From: halosghost Date: Fri, 31 Aug 2018 17:57:35 -0500 Subject: [PATCH 0792/2505] Fix REDIRECT() and REDIRECT_CODE() macros --- src/lib/lwan-mod-redirect.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-mod-redirect.h b/src/lib/lwan-mod-redirect.h index 03925772a..031d0c5cd 100644 --- a/src/lib/lwan-mod-redirect.h +++ b/src/lib/lwan-mod-redirect.h @@ -31,10 +31,10 @@ LWAN_MODULE_FORWARD_DECL(redirect) #define REDIRECT_CODE(to_, code_) \ .module = LWAN_MODULE_REF(redirect), \ - .args = &(struct lwan_redirect_settings[]){ \ + .args = ((struct lwan_redirect_settings[]) {{ \ .to = (to_), \ .code = (code_), \ - }), \ - .flags = 0 + }}), \ + .flags = (enum lwan_handler_flags)0 #define REDIRECT(to_) REDIRECT_CODE((to_), HTTP_MOVED_PERMANENTLY) From 000ae395e153f98d7bd1846138f9642d6401a368 Mon Sep 17 00:00:00 2001 From: halosghost Date: Fri, 31 Aug 2018 18:01:10 -0500 Subject: [PATCH 0793/2505] Add support for HTTP 307 Temporary Redirect --- src/lib/lwan-tables.c | 1 + src/lib/lwan.h | 1 + 2 files changed, 2 insertions(+) diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index c98fde7f1..02fb3a05e 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -119,6 +119,7 @@ static const struct { STATUS(206, "Partial content", "Delivering part of requested resource."), STATUS(301, "Moved permanently", "This content has moved to another place."), STATUS(304, "Not modified", "The content has not changed since previous request."), + STATUS(307, "Temporary Redirect", "This content can be temporarily found at a different location."), STATUS(400, "Bad request", "The client has issued a bad request."), STATUS(401, "Not authorized", "Client has no authorization to access this resource."), STATUS(403, "Forbidden", "Access to this resource has been denied."), diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 5efed3ed1..305ea34f5 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -160,6 +160,7 @@ enum lwan_http_status { HTTP_OK = 200, HTTP_PARTIAL_CONTENT = 206, HTTP_MOVED_PERMANENTLY = 301, + HTTP_TEMPORARY_REDIRECT = 307, HTTP_NOT_MODIFIED = 304, HTTP_BAD_REQUEST = 400, HTTP_NOT_AUTHORIZED = 401, From 09402cad70366dde7464179276a4e20998a834a8 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 31 Aug 2018 08:12:58 -0700 Subject: [PATCH 0794/2505] Make lwan_set_thread_name() portable across all supported OSes --- CMakeLists.txt | 1 + src/cmake/lwan-build-config.h.cmake | 1 + src/lib/CMakeLists.txt | 1 + src/lib/lwan.c | 8 +-- src/lib/missing-pthread.c | 97 +++++++++++++++++++++++++++++ src/lib/missing.c | 56 ----------------- src/lib/missing/pthread.h | 4 ++ 7 files changed, 105 insertions(+), 63 deletions(-) create mode 100644 src/lib/missing-pthread.c diff --git a/CMakeLists.txt b/CMakeLists.txt index afa159678..ca1ac3e4a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -118,6 +118,7 @@ check_function_exists(readahead HAVE_READAHEAD) check_function_exists(mkostemp HAVE_MKOSTEMP) check_function_exists(clock_gettime HAVE_CLOCK_GETTIME) check_function_exists(pthread_barrier_init HAVE_PTHREADBARRIER) +check_function_exists(pthread_set_name_np HAVE_PTHREAD_SET_NAME_NP) check_function_exists(eventfd HAVE_EVENTFD) check_function_exists(posix_fadvise HAVE_POSIX_FADVISE) diff --git a/src/cmake/lwan-build-config.h.cmake b/src/cmake/lwan-build-config.h.cmake index c725be4ff..e555c46da 100644 --- a/src/cmake/lwan-build-config.h.cmake +++ b/src/cmake/lwan-build-config.h.cmake @@ -39,6 +39,7 @@ #cmakedefine HAVE_DLADDR #cmakedefine HAVE_POSIX_FADVISE #cmakedefine HAVE_LINUX_CAPABILITY +#cmakedefine HAVE_PTHREAD_SET_NAME_NP /* Compiler builtins for specific CPU instruction support */ #cmakedefine HAVE_BUILTIN_CLZLL diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index a000dd9d1..fe689e23e 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -33,6 +33,7 @@ set(SOURCES lwan-time.c lwan-trie.c missing.c + missing-pthread.c murmur3.c patterns.c queue.c diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 230c03b4f..f7617f8d5 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -763,9 +763,6 @@ __attribute__((constructor)) static void detect_fastest_monotonic_clock(void) } #endif -#ifdef __linux__ -#include - void lwan_set_thread_name(const char *name) { char thread_name[16]; @@ -784,8 +781,5 @@ void lwan_set_thread_name(const char *name) if (ret < 0) return; - prctl(PR_SET_NAME, thread_name, 0, 0, 0); + pthread_set_name_np(pthread_self(), thread_name); } -#else -void lwan_set_thread_name(const char *name) {} -#endif diff --git a/src/lib/missing-pthread.c b/src/lib/missing-pthread.c new file mode 100644 index 000000000..8c04558de --- /dev/null +++ b/src/lib/missing-pthread.c @@ -0,0 +1,97 @@ +/* + * lwan - simple web server + * Copyright (c) 2018 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#define _GNU_SOURCE +#include +#include + +#include "lwan-private.h" + +#ifndef HAVE_PTHREADBARRIER +#define PTHREAD_BARRIER_SERIAL_THREAD -1 + +int +pthread_barrier_init(pthread_barrier_t *restrict barrier, + const pthread_barrierattr_t *restrict attr __attribute__((unused)), + unsigned int count) { + if (count == 0) { + return -1; + } + + barrier->count = count; + barrier->in = 0; + + if (pthread_mutex_init(&barrier->mutex, NULL) < 0) + return -1; + + if (pthread_cond_init(&barrier->cond, NULL) < 0) { + pthread_mutex_destroy(&barrier->mutex); + return -1; + } + + return 0; +} + +int +pthread_barrier_destroy(pthread_barrier_t *barrier) +{ + pthread_mutex_destroy(&barrier->mutex); + pthread_cond_destroy(&barrier->cond); + barrier->in = 0; + + return 0; +} + +int +pthread_barrier_wait(pthread_barrier_t *barrier) +{ + pthread_mutex_lock(&barrier->mutex); + + barrier->in++; + if (barrier->in >= barrier->count) { + barrier->in = 0; + pthread_cond_broadcast(&barrier->cond); + pthread_mutex_unlock(&barrier->mutex); + + return PTHREAD_BARRIER_SERIAL_THREAD; + } + + pthread_cond_wait(&barrier->cond, &barrier->mutex); + pthread_mutex_unlock(&barrier->mutex); + + return 0; +} +#endif + +#if defined(__linux__) +int pthread_set_name_np(pthread_t thread, const char *name) +{ + return pthread_setname_np(thread, name); +} +#elif defined(__APPLE__) +int pthread_set_name_np(pthread_t thread, const char *name) +{ + if (!pthread_equal(thread, pthread_self()) + return EPERM; + + /* macOS takes a char*; I don't know if it's modified or not, so + * copy it on the stack. */ + return pthread_setname_np(strdupa(name)); +} +#endif diff --git a/src/lib/missing.c b/src/lib/missing.c index 972c95ec8..8c61fb61e 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -32,62 +32,6 @@ #include "lwan.h" -#ifndef HAVE_PTHREADBARRIER -#define PTHREAD_BARRIER_SERIAL_THREAD -1 - -int -pthread_barrier_init(pthread_barrier_t *restrict barrier, - const pthread_barrierattr_t *restrict attr __attribute__((unused)), - unsigned int count) { - if (count == 0) { - return -1; - } - - barrier->count = count; - barrier->in = 0; - - if (pthread_mutex_init(&barrier->mutex, NULL) < 0) - return -1; - - if (pthread_cond_init(&barrier->cond, NULL) < 0) { - pthread_mutex_destroy(&barrier->mutex); - return -1; - } - - return 0; -} - -int -pthread_barrier_destroy(pthread_barrier_t *barrier) -{ - pthread_mutex_destroy(&barrier->mutex); - pthread_cond_destroy(&barrier->cond); - barrier->in = 0; - - return 0; -} - -int -pthread_barrier_wait(pthread_barrier_t *barrier) -{ - pthread_mutex_lock(&barrier->mutex); - - barrier->in++; - if (barrier->in >= barrier->count) { - barrier->in = 0; - pthread_cond_broadcast(&barrier->cond); - pthread_mutex_unlock(&barrier->mutex); - - return PTHREAD_BARRIER_SERIAL_THREAD; - } - - pthread_cond_wait(&barrier->cond, &barrier->mutex); - pthread_mutex_unlock(&barrier->mutex); - - return 0; -} -#endif - #ifndef HAVE_MEMPCPY void * mempcpy(void *dest, const void *src, size_t len) diff --git a/src/lib/missing/pthread.h b/src/lib/missing/pthread.h index dd2926cbe..3862f882e 100644 --- a/src/lib/missing/pthread.h +++ b/src/lib/missing/pthread.h @@ -36,4 +36,8 @@ int pthread_barrier_destroy(pthread_barrier_t *barrier); int pthread_barrier_wait(pthread_barrier_t *barrier); #endif +#ifndef HAVE_PTHREAD_SET_NAME_NP +int pthread_set_name_np(pthread_t thread, const char *name); +#endif + #endif /* MISSING_PTHREAD_H */ From c662e108329c63d1062c70cc55481471182ec829 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 31 Aug 2018 18:57:10 -0700 Subject: [PATCH 0795/2505] Add tests for the redirect module --- src/scripts/testsuite.py | 15 +++++++++++++++ testrunner.conf | 5 +++++ 2 files changed, 20 insertions(+) diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index 00cbae55b..dfbd99e8c 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -312,6 +312,21 @@ def test_directory_without_trailing_slash_redirects(self): self.assertTrue('location' in r.headers) self.assertEqual(r.headers['location'], '/icons/') +class TestRedirect(LwanTest): + def test_redirect_default(self): + r = requests.get('http://127.0.0.1:8080/elsewhere', allow_redirects=False) + + self.assertResponseHtml(r, 301) + self.assertTrue('location' in r.headers) + self.assertEqual(r.headers['location'], 'http://lwan.ws/') + + def test_redirect_307(self): + r = requests.get('http://127.0.0.1:8080/redirect307', allow_redirects=False) + + self.assertResponseHtml(r, 307) + self.assertTrue('location' in r.headers) + self.assertEqual(r.headers['location'], 'http://lwan.ws/') + class TestRewrite(LwanTest): def test_pattern_redirect_to(self): r = requests.get('http://127.0.0.1:8080/pattern/foo/1234x5678', allow_redirects=False) diff --git a/testrunner.conf b/testrunner.conf index 23a22594e..5c8b8d451 100644 --- a/testrunner.conf +++ b/testrunner.conf @@ -50,6 +50,11 @@ listener *:8080 { redirect /elsewhere { to = http://lwan.ws } + redirect /redirect307 { + to = http://lwan.ws + code = 307 + } + rewrite /read-env { pattern user { rewrite as = /hello?name=${USER} } } From d362adf48749c04e3fb14a5ebe43f33b5166f23c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 31 Aug 2018 19:00:42 -0700 Subject: [PATCH 0796/2505] Address compile warnings when building with clang --- src/lib/lwan-mod-redirect.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-mod-redirect.c b/src/lib/lwan-mod-redirect.c index a2f18950d..81baf7415 100644 --- a/src/lib/lwan-mod-redirect.c +++ b/src/lib/lwan-mod-redirect.c @@ -94,11 +94,11 @@ static enum lwan_http_status parse_http_code(const char *code, if (as_int == 999) return fallback; - known = lwan_http_status_as_string_with_code(as_int); + known = lwan_http_status_as_string_with_code((enum lwan_http_status)as_int); if (!strncmp(known, "999", 3)) return fallback; - return as_int; + return (enum lwan_http_status)as_int; } static void *redirect_create_from_hash(const char *prefix, From 308341d3fb60bb89682a77bde463e468c65b52e1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 31 Aug 2018 19:03:15 -0700 Subject: [PATCH 0797/2505] Remember to #include on FreeBSD --- src/lib/missing-pthread.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/missing-pthread.c b/src/lib/missing-pthread.c index 8c04558de..4c96f1210 100644 --- a/src/lib/missing-pthread.c +++ b/src/lib/missing-pthread.c @@ -21,6 +21,10 @@ #include #include +#ifdef __FreeBSD__ +#include +#endif + #include "lwan-private.h" #ifndef HAVE_PTHREADBARRIER From 33fc107abc2d116234ef7263107df4fb62edf32b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 31 Aug 2018 19:05:46 -0700 Subject: [PATCH 0798/2505] On FreeBSD, also include in --- src/lib/missing/pthread.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/missing/pthread.h b/src/lib/missing/pthread.h index 3862f882e..5b3e5e6ec 100644 --- a/src/lib/missing/pthread.h +++ b/src/lib/missing/pthread.h @@ -40,4 +40,8 @@ int pthread_barrier_wait(pthread_barrier_t *barrier); int pthread_set_name_np(pthread_t thread, const char *name); #endif +#ifdef __FreeBSD__ +#include +#endif + #endif /* MISSING_PTHREAD_H */ From 78a0d04fe4e9a98bafebfa2873fb2028f6f366b7 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 1 Sep 2018 10:41:16 -0700 Subject: [PATCH 0799/2505] Tweak directory list template by alternating background colors --- src/lib/lwan-mod-serve-files.c | 63 ++++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 22 deletions(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 9000283c9..bdacbab71 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -123,6 +123,8 @@ struct file_list { int size; const char *unit; + + const char *zebra_class; } file_list; }; @@ -160,68 +162,82 @@ static const struct cache_funcs mmap_funcs = { .init = mmap_init, .free = mmap_free, .serve = mmap_serve, - .struct_size = sizeof(struct mmap_cache_data)}; + .struct_size = sizeof(struct mmap_cache_data), +}; static const struct cache_funcs sendfile_funcs = { .init = sendfile_init, .free = sendfile_free, .serve = sendfile_serve, - .struct_size = sizeof(struct sendfile_cache_data)}; + .struct_size = sizeof(struct sendfile_cache_data), +}; static const struct cache_funcs dirlist_funcs = { .init = dirlist_init, .free = dirlist_free, .serve = dirlist_serve, - .struct_size = sizeof(struct dir_list_cache_data)}; + .struct_size = sizeof(struct dir_list_cache_data), +}; static const struct cache_funcs redir_funcs = { .init = redir_init, .free = redir_free, .serve = redir_serve, - .struct_size = sizeof(struct redir_cache_data)}; + .struct_size = sizeof(struct redir_cache_data), +}; static const struct lwan_var_descriptor file_list_desc[] = { TPL_VAR_STR_ESCAPE(struct file_list, full_path), TPL_VAR_STR_ESCAPE(struct file_list, rel_path), - TPL_VAR_SEQUENCE( - struct file_list, file_list, directory_list_generator, - ((const struct lwan_var_descriptor[]){ - TPL_VAR_STR(struct file_list, file_list.icon), - TPL_VAR_STR(struct file_list, file_list.icon_alt), - TPL_VAR_STR(struct file_list, file_list.name), - TPL_VAR_STR(struct file_list, file_list.type), - TPL_VAR_INT(struct file_list, file_list.size), - TPL_VAR_STR(struct file_list, file_list.unit), TPL_VAR_SENTINEL})), - TPL_VAR_SENTINEL}; + TPL_VAR_SEQUENCE(struct file_list, + file_list, + directory_list_generator, + ((const struct lwan_var_descriptor[]){ + TPL_VAR_STR(struct file_list, file_list.icon), + TPL_VAR_STR(struct file_list, file_list.icon_alt), + TPL_VAR_STR(struct file_list, file_list.name), + TPL_VAR_STR(struct file_list, file_list.type), + TPL_VAR_INT(struct file_list, file_list.size), + TPL_VAR_STR(struct file_list, file_list.unit), + TPL_VAR_STR(struct file_list, file_list.zebra_class), + TPL_VAR_SENTINEL, + })), + TPL_VAR_SENTINEL, +}; static const char *directory_list_tpl_str = "\n" "\n" "{{rel_path?}} Index of {{rel_path}}{{/rel_path?}}\n" "{{^rel_path?}} Index of /{{/rel_path?}}\n" + "\n" "\n" "\n" "{{rel_path?}}

Index of {{rel_path}}

{{/rel_path?}}\n" "{{^rel_path?}}

Index of /

{{/rel_path?}}\n" " \n" " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" " \n" " \n" " \n" - "{{#file_list}}" " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "{{#file_list}}" + " \n" " \n" " \n" " \n" - " \n" + " \n" " \n" "{{/file_list}}" "{{^#file_list}}" @@ -235,8 +251,10 @@ static const char *directory_list_tpl_str = static int directory_list_generator(struct coro *coro, void *data) { + static const char *zebra_classes[] = {"odd", "even"}; struct file_list *fl = data; struct dirent *entry; + int zebra_class = 0; DIR *dir; int fd; @@ -285,6 +303,7 @@ static int directory_list_generator(struct coro *coro, void *data) } fl->file_list.name = entry->d_name; + fl->file_list.zebra_class = zebra_classes[zebra_class++ % 2]; if (coro_yield(coro, 1)) break; From da40c33d7191c44b6b89b5ebd9c46cf98eaa2ff9 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 1 Sep 2018 15:24:49 -0700 Subject: [PATCH 0800/2505] Port jwz's Dali Clock to Lwan This is a quick and dirty port from the Pebble port of Dali Clock, which was based off of Jamie Zawinski's famous original, that has been porteed to inumerous platforms[1]. I think this is the first time this clock has been ported to the Web without JavaScript, though. Have fun! [1] https://www.jwz.org/xdaliclock/ --- src/samples/clock/CMakeLists.txt | 2 + src/samples/clock/font/colonA.xbm | 3153 ++++++++++++ src/samples/clock/font/colonB.xbm | 790 +++ src/samples/clock/font/colonB2.xbm | 287 ++ src/samples/clock/font/colonC.xbm | 208 + src/samples/clock/font/colonC2.xbm | 171 + src/samples/clock/font/colonC3.xbm | 56 + src/samples/clock/font/colonD.xbm | 55 + src/samples/clock/font/colonD2.xbm | 49 + src/samples/clock/font/colonD3.xbm | 34 + src/samples/clock/font/colonD4.xbm | 25 + src/samples/clock/font/colonE.xbm | 21 + src/samples/clock/font/colonF.xbm | 8 + src/samples/clock/font/colonG.xbm | 6 + src/samples/clock/font/dalifont.ai | 473 ++ src/samples/clock/font/dalifont.png | Bin 0 -> 197077 bytes src/samples/clock/font/dalifontF.gif | Bin 0 -> 1960 bytes src/samples/clock/font/dalifontF.psd.gz | Bin 0 -> 20137 bytes src/samples/clock/font/dalifontG.gif | Bin 0 -> 827 bytes src/samples/clock/font/dalifontG.psd.gz | Bin 0 -> 17497 bytes src/samples/clock/font/eightA.xbm | 6302 +++++++++++++++++++++++ src/samples/clock/font/eightB.xbm | 1577 ++++++ src/samples/clock/font/eightB2.xbm | 551 ++ src/samples/clock/font/eightC.xbm | 396 ++ src/samples/clock/font/eightC2.xbm | 324 ++ src/samples/clock/font/eightC3.xbm | 108 + src/samples/clock/font/eightD.xbm | 106 + src/samples/clock/font/eightD2.xbm | 80 + src/samples/clock/font/eightD3.xbm | 59 + src/samples/clock/font/eightD4.xbm | 41 + src/samples/clock/font/eightE.xbm | 29 + src/samples/clock/font/eightF.xbm | 10 + src/samples/clock/font/eightG.xbm | 6 + src/samples/clock/font/fiveA.xbm | 6302 +++++++++++++++++++++++ src/samples/clock/font/fiveB.xbm | 1577 ++++++ src/samples/clock/font/fiveB2.xbm | 551 ++ src/samples/clock/font/fiveC.xbm | 396 ++ src/samples/clock/font/fiveC2.xbm | 324 ++ src/samples/clock/font/fiveC3.xbm | 108 + src/samples/clock/font/fiveD.xbm | 106 + src/samples/clock/font/fiveD2.xbm | 80 + src/samples/clock/font/fiveD3.xbm | 59 + src/samples/clock/font/fiveD4.xbm | 41 + src/samples/clock/font/fiveE.xbm | 29 + src/samples/clock/font/fiveF.xbm | 10 + src/samples/clock/font/fiveG.xbm | 6 + src/samples/clock/font/fourA.xbm | 6302 +++++++++++++++++++++++ src/samples/clock/font/fourB.xbm | 1577 ++++++ src/samples/clock/font/fourB2.xbm | 551 ++ src/samples/clock/font/fourC.xbm | 396 ++ src/samples/clock/font/fourC2.xbm | 324 ++ src/samples/clock/font/fourC3.xbm | 108 + src/samples/clock/font/fourD.xbm | 106 + src/samples/clock/font/fourD2.xbm | 80 + src/samples/clock/font/fourD3.xbm | 59 + src/samples/clock/font/fourD4.xbm | 41 + src/samples/clock/font/fourE.xbm | 29 + src/samples/clock/font/fourF.xbm | 10 + src/samples/clock/font/fourG.xbm | 6 + src/samples/clock/font/nineA.xbm | 6302 +++++++++++++++++++++++ src/samples/clock/font/nineB.xbm | 1577 ++++++ src/samples/clock/font/nineB2.xbm | 551 ++ src/samples/clock/font/nineC.xbm | 396 ++ src/samples/clock/font/nineC2.xbm | 324 ++ src/samples/clock/font/nineC3.xbm | 108 + src/samples/clock/font/nineD.xbm | 106 + src/samples/clock/font/nineD2.xbm | 80 + src/samples/clock/font/nineD3.xbm | 59 + src/samples/clock/font/nineD4.xbm | 41 + src/samples/clock/font/nineE.xbm | 29 + src/samples/clock/font/nineF.xbm | 10 + src/samples/clock/font/nineG.xbm | 6 + src/samples/clock/font/oneA.xbm | 6302 +++++++++++++++++++++++ src/samples/clock/font/oneB.xbm | 1577 ++++++ src/samples/clock/font/oneB2.xbm | 551 ++ src/samples/clock/font/oneC.xbm | 396 ++ src/samples/clock/font/oneC2.xbm | 324 ++ src/samples/clock/font/oneC3.xbm | 108 + src/samples/clock/font/oneD.xbm | 106 + src/samples/clock/font/oneD2.xbm | 80 + src/samples/clock/font/oneD3.xbm | 59 + src/samples/clock/font/oneD4.xbm | 41 + src/samples/clock/font/oneE.xbm | 29 + src/samples/clock/font/oneF.xbm | 10 + src/samples/clock/font/oneG.xbm | 6 + src/samples/clock/font/sevenA.xbm | 6302 +++++++++++++++++++++++ src/samples/clock/font/sevenB.xbm | 1577 ++++++ src/samples/clock/font/sevenB2.xbm | 551 ++ src/samples/clock/font/sevenC.xbm | 396 ++ src/samples/clock/font/sevenC2.xbm | 324 ++ src/samples/clock/font/sevenC3.xbm | 108 + src/samples/clock/font/sevenD.xbm | 106 + src/samples/clock/font/sevenD2.xbm | 80 + src/samples/clock/font/sevenD3.xbm | 59 + src/samples/clock/font/sevenD4.xbm | 41 + src/samples/clock/font/sevenE.xbm | 29 + src/samples/clock/font/sevenF.xbm | 10 + src/samples/clock/font/sevenG.xbm | 6 + src/samples/clock/font/sixA.xbm | 6302 +++++++++++++++++++++++ src/samples/clock/font/sixB.xbm | 1577 ++++++ src/samples/clock/font/sixB2.xbm | 551 ++ src/samples/clock/font/sixC.xbm | 396 ++ src/samples/clock/font/sixC2.xbm | 324 ++ src/samples/clock/font/sixC3.xbm | 108 + src/samples/clock/font/sixD.xbm | 106 + src/samples/clock/font/sixD2.xbm | 80 + src/samples/clock/font/sixD3.xbm | 59 + src/samples/clock/font/sixD4.xbm | 41 + src/samples/clock/font/sixE.xbm | 29 + src/samples/clock/font/sixF.xbm | 10 + src/samples/clock/font/sixG.xbm | 6 + src/samples/clock/font/slashA.xbm | 3153 ++++++++++++ src/samples/clock/font/slashB.xbm | 790 +++ src/samples/clock/font/slashB2.xbm | 287 ++ src/samples/clock/font/slashC.xbm | 208 + src/samples/clock/font/slashC2.xbm | 171 + src/samples/clock/font/slashC3.xbm | 56 + src/samples/clock/font/slashD.xbm | 55 + src/samples/clock/font/slashD2.xbm | 49 + src/samples/clock/font/slashD3.xbm | 34 + src/samples/clock/font/slashD4.xbm | 25 + src/samples/clock/font/slashE.xbm | 21 + src/samples/clock/font/slashF.xbm | 8 + src/samples/clock/font/slashG.xbm | 6 + src/samples/clock/font/threeA.xbm | 6302 +++++++++++++++++++++++ src/samples/clock/font/threeB.xbm | 1577 ++++++ src/samples/clock/font/threeB2.xbm | 551 ++ src/samples/clock/font/threeC.xbm | 396 ++ src/samples/clock/font/threeC2.xbm | 324 ++ src/samples/clock/font/threeC3.xbm | 108 + src/samples/clock/font/threeD.xbm | 106 + src/samples/clock/font/threeD2.xbm | 80 + src/samples/clock/font/threeD3.xbm | 59 + src/samples/clock/font/threeD4.xbm | 41 + src/samples/clock/font/threeE.xbm | 29 + src/samples/clock/font/threeF.xbm | 10 + src/samples/clock/font/threeG.xbm | 6 + src/samples/clock/font/twoA.xbm | 6302 +++++++++++++++++++++++ src/samples/clock/font/twoB.xbm | 1577 ++++++ src/samples/clock/font/twoB2.xbm | 551 ++ src/samples/clock/font/twoC.xbm | 396 ++ src/samples/clock/font/twoC2.xbm | 324 ++ src/samples/clock/font/twoC3.xbm | 108 + src/samples/clock/font/twoD.xbm | 106 + src/samples/clock/font/twoD2.xbm | 80 + src/samples/clock/font/twoD3.xbm | 59 + src/samples/clock/font/twoD4.xbm | 41 + src/samples/clock/font/twoE.xbm | 29 + src/samples/clock/font/twoF.xbm | 10 + src/samples/clock/font/twoG.xbm | 6 + src/samples/clock/font/zeroA.xbm | 6302 +++++++++++++++++++++++ src/samples/clock/font/zeroB.xbm | 1577 ++++++ src/samples/clock/font/zeroB2.xbm | 551 ++ src/samples/clock/font/zeroC.xbm | 396 ++ src/samples/clock/font/zeroC2.xbm | 324 ++ src/samples/clock/font/zeroC3.xbm | 108 + src/samples/clock/font/zeroD.xbm | 106 + src/samples/clock/font/zeroD2.xbm | 80 + src/samples/clock/font/zeroD3.xbm | 59 + src/samples/clock/font/zeroD4.xbm | 41 + src/samples/clock/font/zeroE.xbm | 29 + src/samples/clock/font/zeroF.xbm | 10 + src/samples/clock/font/zeroG.xbm | 6 + src/samples/clock/main.c | 48 + src/samples/clock/numbers.c | 33 + src/samples/clock/numbers.h | 42 + src/samples/clock/xdaliclock.c | 377 ++ src/samples/clock/xdaliclock.h | 14 + 168 files changed, 106605 insertions(+) create mode 100644 src/samples/clock/font/colonA.xbm create mode 100644 src/samples/clock/font/colonB.xbm create mode 100644 src/samples/clock/font/colonB2.xbm create mode 100644 src/samples/clock/font/colonC.xbm create mode 100644 src/samples/clock/font/colonC2.xbm create mode 100644 src/samples/clock/font/colonC3.xbm create mode 100644 src/samples/clock/font/colonD.xbm create mode 100644 src/samples/clock/font/colonD2.xbm create mode 100644 src/samples/clock/font/colonD3.xbm create mode 100644 src/samples/clock/font/colonD4.xbm create mode 100644 src/samples/clock/font/colonE.xbm create mode 100644 src/samples/clock/font/colonF.xbm create mode 100644 src/samples/clock/font/colonG.xbm create mode 100644 src/samples/clock/font/dalifont.ai create mode 100644 src/samples/clock/font/dalifont.png create mode 100644 src/samples/clock/font/dalifontF.gif create mode 100644 src/samples/clock/font/dalifontF.psd.gz create mode 100644 src/samples/clock/font/dalifontG.gif create mode 100644 src/samples/clock/font/dalifontG.psd.gz create mode 100644 src/samples/clock/font/eightA.xbm create mode 100644 src/samples/clock/font/eightB.xbm create mode 100644 src/samples/clock/font/eightB2.xbm create mode 100644 src/samples/clock/font/eightC.xbm create mode 100644 src/samples/clock/font/eightC2.xbm create mode 100644 src/samples/clock/font/eightC3.xbm create mode 100644 src/samples/clock/font/eightD.xbm create mode 100644 src/samples/clock/font/eightD2.xbm create mode 100644 src/samples/clock/font/eightD3.xbm create mode 100644 src/samples/clock/font/eightD4.xbm create mode 100644 src/samples/clock/font/eightE.xbm create mode 100644 src/samples/clock/font/eightF.xbm create mode 100644 src/samples/clock/font/eightG.xbm create mode 100644 src/samples/clock/font/fiveA.xbm create mode 100644 src/samples/clock/font/fiveB.xbm create mode 100644 src/samples/clock/font/fiveB2.xbm create mode 100644 src/samples/clock/font/fiveC.xbm create mode 100644 src/samples/clock/font/fiveC2.xbm create mode 100644 src/samples/clock/font/fiveC3.xbm create mode 100644 src/samples/clock/font/fiveD.xbm create mode 100644 src/samples/clock/font/fiveD2.xbm create mode 100644 src/samples/clock/font/fiveD3.xbm create mode 100644 src/samples/clock/font/fiveD4.xbm create mode 100644 src/samples/clock/font/fiveE.xbm create mode 100644 src/samples/clock/font/fiveF.xbm create mode 100644 src/samples/clock/font/fiveG.xbm create mode 100644 src/samples/clock/font/fourA.xbm create mode 100644 src/samples/clock/font/fourB.xbm create mode 100644 src/samples/clock/font/fourB2.xbm create mode 100644 src/samples/clock/font/fourC.xbm create mode 100644 src/samples/clock/font/fourC2.xbm create mode 100644 src/samples/clock/font/fourC3.xbm create mode 100644 src/samples/clock/font/fourD.xbm create mode 100644 src/samples/clock/font/fourD2.xbm create mode 100644 src/samples/clock/font/fourD3.xbm create mode 100644 src/samples/clock/font/fourD4.xbm create mode 100644 src/samples/clock/font/fourE.xbm create mode 100644 src/samples/clock/font/fourF.xbm create mode 100644 src/samples/clock/font/fourG.xbm create mode 100644 src/samples/clock/font/nineA.xbm create mode 100644 src/samples/clock/font/nineB.xbm create mode 100644 src/samples/clock/font/nineB2.xbm create mode 100644 src/samples/clock/font/nineC.xbm create mode 100644 src/samples/clock/font/nineC2.xbm create mode 100644 src/samples/clock/font/nineC3.xbm create mode 100644 src/samples/clock/font/nineD.xbm create mode 100644 src/samples/clock/font/nineD2.xbm create mode 100644 src/samples/clock/font/nineD3.xbm create mode 100644 src/samples/clock/font/nineD4.xbm create mode 100644 src/samples/clock/font/nineE.xbm create mode 100644 src/samples/clock/font/nineF.xbm create mode 100644 src/samples/clock/font/nineG.xbm create mode 100644 src/samples/clock/font/oneA.xbm create mode 100644 src/samples/clock/font/oneB.xbm create mode 100644 src/samples/clock/font/oneB2.xbm create mode 100644 src/samples/clock/font/oneC.xbm create mode 100644 src/samples/clock/font/oneC2.xbm create mode 100644 src/samples/clock/font/oneC3.xbm create mode 100644 src/samples/clock/font/oneD.xbm create mode 100644 src/samples/clock/font/oneD2.xbm create mode 100644 src/samples/clock/font/oneD3.xbm create mode 100644 src/samples/clock/font/oneD4.xbm create mode 100644 src/samples/clock/font/oneE.xbm create mode 100644 src/samples/clock/font/oneF.xbm create mode 100644 src/samples/clock/font/oneG.xbm create mode 100644 src/samples/clock/font/sevenA.xbm create mode 100644 src/samples/clock/font/sevenB.xbm create mode 100644 src/samples/clock/font/sevenB2.xbm create mode 100644 src/samples/clock/font/sevenC.xbm create mode 100644 src/samples/clock/font/sevenC2.xbm create mode 100644 src/samples/clock/font/sevenC3.xbm create mode 100644 src/samples/clock/font/sevenD.xbm create mode 100644 src/samples/clock/font/sevenD2.xbm create mode 100644 src/samples/clock/font/sevenD3.xbm create mode 100644 src/samples/clock/font/sevenD4.xbm create mode 100644 src/samples/clock/font/sevenE.xbm create mode 100644 src/samples/clock/font/sevenF.xbm create mode 100644 src/samples/clock/font/sevenG.xbm create mode 100644 src/samples/clock/font/sixA.xbm create mode 100644 src/samples/clock/font/sixB.xbm create mode 100644 src/samples/clock/font/sixB2.xbm create mode 100644 src/samples/clock/font/sixC.xbm create mode 100644 src/samples/clock/font/sixC2.xbm create mode 100644 src/samples/clock/font/sixC3.xbm create mode 100644 src/samples/clock/font/sixD.xbm create mode 100644 src/samples/clock/font/sixD2.xbm create mode 100644 src/samples/clock/font/sixD3.xbm create mode 100644 src/samples/clock/font/sixD4.xbm create mode 100644 src/samples/clock/font/sixE.xbm create mode 100644 src/samples/clock/font/sixF.xbm create mode 100644 src/samples/clock/font/sixG.xbm create mode 100644 src/samples/clock/font/slashA.xbm create mode 100644 src/samples/clock/font/slashB.xbm create mode 100644 src/samples/clock/font/slashB2.xbm create mode 100644 src/samples/clock/font/slashC.xbm create mode 100644 src/samples/clock/font/slashC2.xbm create mode 100644 src/samples/clock/font/slashC3.xbm create mode 100644 src/samples/clock/font/slashD.xbm create mode 100644 src/samples/clock/font/slashD2.xbm create mode 100644 src/samples/clock/font/slashD3.xbm create mode 100644 src/samples/clock/font/slashD4.xbm create mode 100644 src/samples/clock/font/slashE.xbm create mode 100644 src/samples/clock/font/slashF.xbm create mode 100644 src/samples/clock/font/slashG.xbm create mode 100644 src/samples/clock/font/threeA.xbm create mode 100644 src/samples/clock/font/threeB.xbm create mode 100644 src/samples/clock/font/threeB2.xbm create mode 100644 src/samples/clock/font/threeC.xbm create mode 100644 src/samples/clock/font/threeC2.xbm create mode 100644 src/samples/clock/font/threeC3.xbm create mode 100644 src/samples/clock/font/threeD.xbm create mode 100644 src/samples/clock/font/threeD2.xbm create mode 100644 src/samples/clock/font/threeD3.xbm create mode 100644 src/samples/clock/font/threeD4.xbm create mode 100644 src/samples/clock/font/threeE.xbm create mode 100644 src/samples/clock/font/threeF.xbm create mode 100644 src/samples/clock/font/threeG.xbm create mode 100644 src/samples/clock/font/twoA.xbm create mode 100644 src/samples/clock/font/twoB.xbm create mode 100644 src/samples/clock/font/twoB2.xbm create mode 100644 src/samples/clock/font/twoC.xbm create mode 100644 src/samples/clock/font/twoC2.xbm create mode 100644 src/samples/clock/font/twoC3.xbm create mode 100644 src/samples/clock/font/twoD.xbm create mode 100644 src/samples/clock/font/twoD2.xbm create mode 100644 src/samples/clock/font/twoD3.xbm create mode 100644 src/samples/clock/font/twoD4.xbm create mode 100644 src/samples/clock/font/twoE.xbm create mode 100644 src/samples/clock/font/twoF.xbm create mode 100644 src/samples/clock/font/twoG.xbm create mode 100644 src/samples/clock/font/zeroA.xbm create mode 100644 src/samples/clock/font/zeroB.xbm create mode 100644 src/samples/clock/font/zeroB2.xbm create mode 100644 src/samples/clock/font/zeroC.xbm create mode 100644 src/samples/clock/font/zeroC2.xbm create mode 100644 src/samples/clock/font/zeroC3.xbm create mode 100644 src/samples/clock/font/zeroD.xbm create mode 100644 src/samples/clock/font/zeroD2.xbm create mode 100644 src/samples/clock/font/zeroD3.xbm create mode 100644 src/samples/clock/font/zeroD4.xbm create mode 100644 src/samples/clock/font/zeroE.xbm create mode 100644 src/samples/clock/font/zeroF.xbm create mode 100644 src/samples/clock/font/zeroG.xbm create mode 100644 src/samples/clock/numbers.c create mode 100644 src/samples/clock/numbers.h create mode 100644 src/samples/clock/xdaliclock.c create mode 100644 src/samples/clock/xdaliclock.h diff --git a/src/samples/clock/CMakeLists.txt b/src/samples/clock/CMakeLists.txt index dc1b94cfb..bd3751113 100644 --- a/src/samples/clock/CMakeLists.txt +++ b/src/samples/clock/CMakeLists.txt @@ -1,6 +1,8 @@ add_executable(clock main.c gifenc.c + xdaliclock.c + numbers.c ) target_link_libraries(clock diff --git a/src/samples/clock/font/colonA.xbm b/src/samples/clock/font/colonA.xbm new file mode 100644 index 000000000..7d12500f4 --- /dev/null +++ b/src/samples/clock/font/colonA.xbm @@ -0,0 +1,3153 @@ +#define colonA_width 368 +#define colonA_height 1027 +static unsigned char colonA_bits[] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0x3f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0x1f, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff, + 0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff, + 0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xfc,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xfc,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0x7f, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff, + 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff, + 0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff, + 0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00}; diff --git a/src/samples/clock/font/colonB.xbm b/src/samples/clock/font/colonB.xbm new file mode 100644 index 000000000..8c66c5b4d --- /dev/null +++ b/src/samples/clock/font/colonB.xbm @@ -0,0 +1,790 @@ +#define colonB_width 184 +#define colonB_height 513 +static unsigned char colonB_bits[] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x3f,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff, + 0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff, + 0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00, + 0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00, + 0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00, + 0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01, + 0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00, + 0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00, + 0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff, + 0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff, + 0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, + 0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x7f,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xf8,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x7f,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff, + 0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00, + 0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00, + 0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00, + 0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00, + 0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01, + 0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe, + 0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0x0f,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xe0,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x7f,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xf8,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x7f,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; diff --git a/src/samples/clock/font/colonB2.xbm b/src/samples/clock/font/colonB2.xbm new file mode 100644 index 000000000..98a7632e4 --- /dev/null +++ b/src/samples/clock/font/colonB2.xbm @@ -0,0 +1,287 @@ +#define colonB2_width 107 +#define colonB2_height 304 +static unsigned char colonB2_bits[] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0x03,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x80,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xe0,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf8,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc, + 0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, + 0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff, + 0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff, + 0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff, + 0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0x01, + 0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00, + 0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00, + 0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00, + 0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00, + 0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0xc0, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0xe0,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x01,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x01,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01, + 0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00, + 0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00, + 0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00, + 0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00, + 0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfe, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfe,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfe,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x03,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x03,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03, + 0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00, + 0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00, + 0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00, + 0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00, + 0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0xf8, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0xf8,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x1f,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f, + 0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00, + 0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, + 0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00, + 0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00, + 0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00, + 0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf0,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0, + 0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff, + 0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, + 0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff, + 0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff, + 0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0x7f, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0x1f,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0x07,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0x07,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xe0,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe, + 0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff, + 0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff, + 0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0x03, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x07,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00, + 0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x7f,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f, + 0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00, + 0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00, + 0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00, + 0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00, + 0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00, + 0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0xfc, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfc,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfc,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x03,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x03,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03, + 0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00, + 0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00, + 0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00, + 0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00, + 0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfc, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfc,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfc,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f, + 0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00, + 0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00, + 0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00, + 0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00, + 0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00, + 0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0xfe, + 0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff, + 0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff, + 0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff, + 0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff, + 0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff, + 0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0x1f, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x0f,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xf8,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00}; diff --git a/src/samples/clock/font/colonC.xbm b/src/samples/clock/font/colonC.xbm new file mode 100644 index 000000000..72188ed64 --- /dev/null +++ b/src/samples/clock/font/colonC.xbm @@ -0,0 +1,208 @@ +#define colonC_width 91 +#define colonC_height 256 +static unsigned char colonC_bits[] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xc0,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff, + 0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0x0f,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf0,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff, + 0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0x07, + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00, + 0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x80, + 0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff, + 0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0x7f, + 0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00, + 0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0xf8, + 0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff, + 0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff, + 0x03,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, + 0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0xfe, + 0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff, + 0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x0f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x1f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00, + 0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x80,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x1f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x0f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00, + 0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0xfe, + 0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff, + 0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff, + 0x07,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00, + 0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xf8, + 0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff, + 0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff, + 0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00, + 0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0xc0, + 0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff, + 0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x0f, + 0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00, + 0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf8,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff, + 0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0x3f,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xf0,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff, + 0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0x01,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0x7f,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xfe,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff, + 0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0x7f,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfe,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff, + 0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0x1f, + 0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00, + 0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0xe0, + 0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff, + 0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff, + 0x01,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00, + 0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfc, + 0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff, + 0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff, + 0x07,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, + 0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x0f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x1f,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00, + 0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0x1f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff, + 0x0f,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, + 0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0xfc, + 0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff, + 0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff, + 0x03,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00, + 0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0xf0, + 0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff, + 0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0x7f, + 0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00, + 0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff, + 0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0x03, + 0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc0,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, + 0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0x07,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xff,0x0f,0x00,0x00,0x00,0x00,0x00}; diff --git a/src/samples/clock/font/colonC2.xbm b/src/samples/clock/font/colonC2.xbm new file mode 100644 index 000000000..1705eda7e --- /dev/null +++ b/src/samples/clock/font/colonC2.xbm @@ -0,0 +1,171 @@ +#define colonC2_width 81 +#define colonC2_height 229 +static unsigned char colonC2_bits[] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xfe,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff, + 0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0x1f,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfe,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff, + 0x03,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00, + 0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0xf0,0xff, + 0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0x3f, + 0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00, + 0x00,0xfc,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff, + 0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0x00, + 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff, + 0xff,0xff,0x03,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00, + 0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0xc0,0xff, + 0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff, + 0xff,0x07,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, + 0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0xe0,0xff,0xff, + 0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff, + 0x0f,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00, + 0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0xe0,0xff,0xff,0xff, + 0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0x0f, + 0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0xe0, + 0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff, + 0xff,0xff,0x0f,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00, + 0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0xc0,0xff, + 0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff, + 0xff,0x07,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, + 0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x80,0xff,0xff, + 0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff, + 0x03,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00, + 0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0xfe,0xff,0xff, + 0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0x7f,0x00, + 0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00, + 0xf8,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff, + 0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0x0f,0x00,0x00, + 0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x80, + 0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0x7f,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xf0,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0, + 0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x01,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0x07,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xf0,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0x0f,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfc,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff, + 0x01,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00, + 0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0xe0,0xff, + 0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0x1f, + 0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00, + 0x00,0xfc,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff, + 0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0x00, + 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff, + 0xff,0xff,0x03,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00, + 0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0xc0,0xff, + 0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff, + 0xff,0x07,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, + 0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0xe0,0xff,0xff, + 0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff, + 0x0f,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00, + 0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0xe0,0xff,0xff,0xff, + 0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0x0f, + 0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0xe0, + 0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff, + 0xff,0xff,0x0f,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00, + 0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0xc0,0xff, + 0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff, + 0xff,0x07,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, + 0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x80,0xff,0xff, + 0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff, + 0x03,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00, + 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0xfe,0xff,0xff, + 0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0x00, + 0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00, + 0xf8,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff, + 0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0x1f,0x00,0x00, + 0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0xc0, + 0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xfc,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0, + 0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0x07,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0x7f,0x00,0x00,0x00,0x00,0x00}; diff --git a/src/samples/clock/font/colonC3.xbm b/src/samples/clock/font/colonC3.xbm new file mode 100644 index 000000000..56ef8f80a --- /dev/null +++ b/src/samples/clock/font/colonC3.xbm @@ -0,0 +1,56 @@ +#define colonC3_width 47 +#define colonC3_height 131 +static unsigned char colonC3_bits[] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0x1f,0x00,0x00, + 0x00,0x00,0xfe,0x7f,0x00,0x00,0x00,0x80,0xff,0xff,0x01,0x00,0x00,0xc0,0xff, + 0xff,0x03,0x00,0x00,0xe0,0xff,0xff,0x07,0x00,0x00,0xf0,0xff,0xff,0x0f,0x00, + 0x00,0xf8,0xff,0xff,0x1f,0x00,0x00,0xf8,0xff,0xff,0x1f,0x00,0x00,0xfc,0xff, + 0xff,0x3f,0x00,0x00,0xfc,0xff,0xff,0x3f,0x00,0x00,0xfe,0xff,0xff,0x7f,0x00, + 0x00,0xfe,0xff,0xff,0x7f,0x00,0x00,0xfe,0xff,0xff,0x7f,0x00,0x00,0xfe,0xff, + 0xff,0x7f,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0x00, + 0x00,0xff,0xff,0xff,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0xfe,0xff, + 0xff,0x7f,0x00,0x00,0xfe,0xff,0xff,0x7f,0x00,0x00,0xfe,0xff,0xff,0x7f,0x00, + 0x00,0xfe,0xff,0xff,0x7f,0x00,0x00,0xfc,0xff,0xff,0x3f,0x00,0x00,0xfc,0xff, + 0xff,0x3f,0x00,0x00,0xf8,0xff,0xff,0x1f,0x00,0x00,0xf0,0xff,0xff,0x0f,0x00, + 0x00,0xe0,0xff,0xff,0x07,0x00,0x00,0xc0,0xff,0xff,0x03,0x00,0x00,0x80,0xff, + 0xff,0x01,0x00,0x00,0x00,0xff,0xff,0x00,0x00,0x00,0x00,0xfc,0x3f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0x0f,0x00,0x00, + 0x00,0x00,0xfe,0x7f,0x00,0x00,0x00,0x80,0xff,0xff,0x01,0x00,0x00,0xc0,0xff, + 0xff,0x03,0x00,0x00,0xe0,0xff,0xff,0x07,0x00,0x00,0xf0,0xff,0xff,0x0f,0x00, + 0x00,0xf8,0xff,0xff,0x1f,0x00,0x00,0xf8,0xff,0xff,0x1f,0x00,0x00,0xfc,0xff, + 0xff,0x3f,0x00,0x00,0xfc,0xff,0xff,0x3f,0x00,0x00,0xfe,0xff,0xff,0x7f,0x00, + 0x00,0xfe,0xff,0xff,0x7f,0x00,0x00,0xfe,0xff,0xff,0x7f,0x00,0x00,0xfe,0xff, + 0xff,0x7f,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0x00, + 0x00,0xff,0xff,0xff,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0xfe,0xff, + 0xff,0x7f,0x00,0x00,0xfe,0xff,0xff,0x7f,0x00,0x00,0xfe,0xff,0xff,0x7f,0x00, + 0x00,0xfe,0xff,0xff,0x7f,0x00,0x00,0xfc,0xff,0xff,0x3f,0x00,0x00,0xfc,0xff, + 0xff,0x3f,0x00,0x00,0xf8,0xff,0xff,0x1f,0x00,0x00,0xf8,0xff,0xff,0x1f,0x00, + 0x00,0xf0,0xff,0xff,0x0f,0x00,0x00,0xe0,0xff,0xff,0x07,0x00,0x00,0xc0,0xff, + 0xff,0x03,0x00,0x00,0x00,0xff,0xff,0x00,0x00,0x00,0x00,0xfc,0x3f,0x00,0x00, + 0x00,0x00,0xe0,0x07,0x00,0x00}; diff --git a/src/samples/clock/font/colonD.xbm b/src/samples/clock/font/colonD.xbm new file mode 100644 index 000000000..782194def --- /dev/null +++ b/src/samples/clock/font/colonD.xbm @@ -0,0 +1,55 @@ +#define colonD_width 46 +#define colonD_height 128 +static unsigned char colonD_bits[] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xfc,0x07,0x00,0x00,0x00,0x00,0xff,0x3f,0x00,0x00, + 0x00,0xc0,0xff,0x7f,0x00,0x00,0x00,0xe0,0xff,0xff,0x00,0x00,0x00,0xf0,0xff, + 0xff,0x03,0x00,0x00,0xf8,0xff,0xff,0x03,0x00,0x00,0xfc,0xff,0xff,0x07,0x00, + 0x00,0xfe,0xff,0xff,0x0f,0x00,0x00,0xfe,0xff,0xff,0x0f,0x00,0x00,0xff,0xff, + 0xff,0x1f,0x00,0x00,0xff,0xff,0xff,0x1f,0x00,0x00,0xff,0xff,0xff,0x1f,0x00, + 0x00,0xff,0xff,0xff,0x3f,0x00,0x80,0xff,0xff,0xff,0x3f,0x00,0x80,0xff,0xff, + 0xff,0x3f,0x00,0x80,0xff,0xff,0xff,0x3f,0x00,0x80,0xff,0xff,0xff,0x3f,0x00, + 0x80,0xff,0xff,0xff,0x3f,0x00,0x00,0xff,0xff,0xff,0x3f,0x00,0x00,0xff,0xff, + 0xff,0x1f,0x00,0x00,0xff,0xff,0xff,0x1f,0x00,0x00,0xff,0xff,0xff,0x1f,0x00, + 0x00,0xfe,0xff,0xff,0x0f,0x00,0x00,0xfe,0xff,0xff,0x0f,0x00,0x00,0xfc,0xff, + 0xff,0x07,0x00,0x00,0xf8,0xff,0xff,0x03,0x00,0x00,0xf0,0xff,0xff,0x01,0x00, + 0x00,0xe0,0xff,0xff,0x00,0x00,0x00,0xc0,0xff,0x7f,0x00,0x00,0x00,0x00,0xff, + 0x1f,0x00,0x00,0x00,0x00,0xf8,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc, + 0x07,0x00,0x00,0x00,0x00,0xff,0x3f,0x00,0x00,0x00,0xc0,0xff,0x7f,0x00,0x00, + 0x00,0xe0,0xff,0xff,0x01,0x00,0x00,0xf0,0xff,0xff,0x03,0x00,0x00,0xf8,0xff, + 0xff,0x03,0x00,0x00,0xfc,0xff,0xff,0x07,0x00,0x00,0xfe,0xff,0xff,0x0f,0x00, + 0x00,0xfe,0xff,0xff,0x0f,0x00,0x00,0xff,0xff,0xff,0x1f,0x00,0x00,0xff,0xff, + 0xff,0x1f,0x00,0x00,0xff,0xff,0xff,0x1f,0x00,0x00,0xff,0xff,0xff,0x3f,0x00, + 0x80,0xff,0xff,0xff,0x3f,0x00,0x80,0xff,0xff,0xff,0x3f,0x00,0x80,0xff,0xff, + 0xff,0x3f,0x00,0x80,0xff,0xff,0xff,0x3f,0x00,0x80,0xff,0xff,0xff,0x3f,0x00, + 0x00,0xff,0xff,0xff,0x3f,0x00,0x00,0xff,0xff,0xff,0x1f,0x00,0x00,0xff,0xff, + 0xff,0x1f,0x00,0x00,0xff,0xff,0xff,0x1f,0x00,0x00,0xfe,0xff,0xff,0x0f,0x00, + 0x00,0xfe,0xff,0xff,0x0f,0x00,0x00,0xfc,0xff,0xff,0x07,0x00,0x00,0xf8,0xff, + 0xff,0x03,0x00,0x00,0xf0,0xff,0xff,0x01,0x00,0x00,0xe0,0xff,0xff,0x00,0x00, + 0x00,0xc0,0xff,0x7f,0x00,0x00,0x00,0x00,0xff,0x1f,0x00,0x00,0x00,0x00,0xf8, + 0x03,0x00,0x00}; diff --git a/src/samples/clock/font/colonD2.xbm b/src/samples/clock/font/colonD2.xbm new file mode 100644 index 000000000..03e032d5e --- /dev/null +++ b/src/samples/clock/font/colonD2.xbm @@ -0,0 +1,49 @@ +#define colonD2_width 41 +#define colonD2_height 115 +static unsigned char colonD2_bits[] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0x01,0x00,0x00, + 0x00,0xc0,0xff,0x0f,0x00,0x00,0x00,0xe0,0xff,0x1f,0x00,0x00,0x00,0xf0,0xff, + 0x3f,0x00,0x00,0x00,0xf8,0xff,0x7f,0x00,0x00,0x00,0xfc,0xff,0xff,0x00,0x00, + 0x00,0xfe,0xff,0xff,0x01,0x00,0x00,0xfe,0xff,0xff,0x01,0x00,0x00,0xff,0xff, + 0xff,0x03,0x00,0x00,0xff,0xff,0xff,0x03,0x00,0x00,0xff,0xff,0xff,0x03,0x00, + 0x80,0xff,0xff,0xff,0x03,0x00,0x80,0xff,0xff,0xff,0x07,0x00,0x80,0xff,0xff, + 0xff,0x07,0x00,0x80,0xff,0xff,0xff,0x07,0x00,0x80,0xff,0xff,0xff,0x07,0x00, + 0x80,0xff,0xff,0xff,0x03,0x00,0x00,0xff,0xff,0xff,0x03,0x00,0x00,0xff,0xff, + 0xff,0x03,0x00,0x00,0xff,0xff,0xff,0x03,0x00,0x00,0xfe,0xff,0xff,0x01,0x00, + 0x00,0xfe,0xff,0xff,0x01,0x00,0x00,0xfc,0xff,0xff,0x00,0x00,0x00,0xf8,0xff, + 0x7f,0x00,0x00,0x00,0xf0,0xff,0x3f,0x00,0x00,0x00,0xe0,0xff,0x1f,0x00,0x00, + 0x00,0x80,0xff,0x07,0x00,0x00,0x00,0x00,0xfe,0x01,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x78, + 0x00,0x00,0x00,0x00,0x80,0xff,0x07,0x00,0x00,0x00,0xe0,0xff,0x0f,0x00,0x00, + 0x00,0xf0,0xff,0x3f,0x00,0x00,0x00,0xf8,0xff,0x7f,0x00,0x00,0x00,0xfc,0xff, + 0xff,0x00,0x00,0x00,0xfe,0xff,0xff,0x00,0x00,0x00,0xfe,0xff,0xff,0x01,0x00, + 0x00,0xff,0xff,0xff,0x01,0x00,0x00,0xff,0xff,0xff,0x03,0x00,0x00,0xff,0xff, + 0xff,0x03,0x00,0x80,0xff,0xff,0xff,0x03,0x00,0x80,0xff,0xff,0xff,0x07,0x00, + 0x80,0xff,0xff,0xff,0x07,0x00,0x80,0xff,0xff,0xff,0x07,0x00,0x80,0xff,0xff, + 0xff,0x07,0x00,0x80,0xff,0xff,0xff,0x07,0x00,0x80,0xff,0xff,0xff,0x03,0x00, + 0x00,0xff,0xff,0xff,0x03,0x00,0x00,0xff,0xff,0xff,0x03,0x00,0x00,0xff,0xff, + 0xff,0x01,0x00,0x00,0xfe,0xff,0xff,0x01,0x00,0x00,0xfc,0xff,0xff,0x00,0x00, + 0x00,0xfc,0xff,0x7f,0x00,0x00,0x00,0xf8,0xff,0x7f,0x00,0x00,0x00,0xf0,0xff, + 0x1f,0x00,0x00,0x00,0xc0,0xff,0x0f,0x00,0x00,0x00,0x00,0xff,0x03,0x00,0x00}; diff --git a/src/samples/clock/font/colonD3.xbm b/src/samples/clock/font/colonD3.xbm new file mode 100644 index 000000000..12ff2de1e --- /dev/null +++ b/src/samples/clock/font/colonD3.xbm @@ -0,0 +1,34 @@ +#define colonD3_width 34 +#define colonD3_height 93 +static unsigned char colonD3_bits[] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0x3f,0x00,0x00,0x00,0xf8,0x7f,0x00,0x00, + 0x00,0xfe,0xff,0x01,0x00,0x00,0xff,0xff,0x03,0x00,0x00,0xff,0xff,0x03,0x00, + 0x80,0xff,0xff,0x07,0x00,0x80,0xff,0xff,0x07,0x00,0xc0,0xff,0xff,0x0f,0x00, + 0xc0,0xff,0xff,0x0f,0x00,0xc0,0xff,0xff,0x0f,0x00,0xc0,0xff,0xff,0x0f,0x00, + 0xc0,0xff,0xff,0x0f,0x00,0xc0,0xff,0xff,0x0f,0x00,0xc0,0xff,0xff,0x0f,0x00, + 0xc0,0xff,0xff,0x0f,0x00,0x80,0xff,0xff,0x07,0x00,0x80,0xff,0xff,0x07,0x00, + 0x00,0xff,0xff,0x03,0x00,0x00,0xfe,0xff,0x01,0x00,0x00,0xfc,0xff,0x00,0x00, + 0x00,0xf8,0x7f,0x00,0x00,0x00,0xe0,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0x1f,0x00,0x00, + 0x00,0xf8,0x7f,0x00,0x00,0x00,0xfc,0xff,0x00,0x00,0x00,0xfe,0xff,0x01,0x00, + 0x00,0xff,0xff,0x03,0x00,0x80,0xff,0xff,0x07,0x00,0x80,0xff,0xff,0x07,0x00, + 0xc0,0xff,0xff,0x0f,0x00,0xc0,0xff,0xff,0x0f,0x00,0xc0,0xff,0xff,0x0f,0x00, + 0xc0,0xff,0xff,0x0f,0x00,0xc0,0xff,0xff,0x0f,0x00,0xc0,0xff,0xff,0x0f,0x00, + 0xc0,0xff,0xff,0x0f,0x00,0xc0,0xff,0xff,0x0f,0x00,0x80,0xff,0xff,0x07,0x00, + 0x80,0xff,0xff,0x07,0x00,0x00,0xff,0xff,0x03,0x00,0x00,0xff,0xff,0x03,0x00, + 0x00,0xfe,0xff,0x01,0x00,0x00,0xf8,0x7f,0x00,0x00,0x00,0xf0,0x3f,0x00,0x00}; diff --git a/src/samples/clock/font/colonD4.xbm b/src/samples/clock/font/colonD4.xbm new file mode 100644 index 000000000..031a05997 --- /dev/null +++ b/src/samples/clock/font/colonD4.xbm @@ -0,0 +1,25 @@ +#define colonD4_width 30 +#define colonD4_height 80 +static unsigned char colonD4_bits[] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0x03,0x00,0x00,0xfc,0x0f,0x00,0x00, + 0xff,0x1f,0x00,0x00,0xff,0x3f,0x00,0x80,0xff,0x7f,0x00,0xc0,0xff,0xff,0x00, + 0xc0,0xff,0xff,0x00,0xc0,0xff,0xff,0x00,0xc0,0xff,0xff,0x00,0xc0,0xff,0xff, + 0x00,0xc0,0xff,0xff,0x00,0xc0,0xff,0xff,0x00,0xc0,0xff,0xff,0x00,0xc0,0xff, + 0xff,0x00,0x80,0xff,0x7f,0x00,0x80,0xff,0x3f,0x00,0x00,0xff,0x3f,0x00,0x00, + 0xfc,0x0f,0x00,0x00,0xf8,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xf8,0x07,0x00,0x00,0xfc,0x0f,0x00,0x00,0xff,0x3f, + 0x00,0x80,0xff,0x3f,0x00,0x80,0xff,0x7f,0x00,0xc0,0xff,0xff,0x00,0xc0,0xff, + 0xff,0x00,0xc0,0xff,0xff,0x00,0xc0,0xff,0xff,0x00,0xc0,0xff,0xff,0x00,0xc0, + 0xff,0xff,0x00,0xc0,0xff,0xff,0x00,0xc0,0xff,0xff,0x00,0xc0,0xff,0xff,0x00, + 0x80,0xff,0x7f,0x00,0x00,0xff,0x3f,0x00,0x00,0xff,0x1f,0x00,0x00,0xfc,0x0f, + 0x00,0x00,0xf0,0x03,0x00}; diff --git a/src/samples/clock/font/colonE.xbm b/src/samples/clock/font/colonE.xbm new file mode 100644 index 000000000..6ca3d5dcb --- /dev/null +++ b/src/samples/clock/font/colonE.xbm @@ -0,0 +1,21 @@ +#define colonE_width 25 +#define colonE_height 64 +static unsigned char colonE_bits[] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xfe,0x00,0x00,0x00,0xff,0x03,0x00,0x80,0xff,0x07,0x00,0xc0,0xff, + 0x07,0x00,0xc0,0xff,0x0f,0x00,0xc0,0xff,0x0f,0x00,0xe0,0xff,0x0f,0x00,0xe0, + 0xff,0x0f,0x00,0xe0,0xff,0x0f,0x00,0xc0,0xff,0x0f,0x00,0xc0,0xff,0x0f,0x00, + 0xc0,0xff,0x07,0x00,0x80,0xff,0x07,0x00,0x00,0xff,0x03,0x00,0x00,0xfc,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x00, + 0x00,0x00,0xfe,0x01,0x00,0x00,0xff,0x03,0x00,0x80,0xff,0x07,0x00,0xc0,0xff, + 0x0f,0x00,0xc0,0xff,0x0f,0x00,0xe0,0xff,0x0f,0x00,0xe0,0xff,0x0f,0x00,0xe0, + 0xff,0x0f,0x00,0xe0,0xff,0x0f,0x00,0xc0,0xff,0x0f,0x00,0xc0,0xff,0x0f,0x00, + 0x80,0xff,0x07,0x00,0x80,0xff,0x03,0x00,0x00,0xfe,0x01,0x00,0x00,0x78,0x00, + 0x00}; diff --git a/src/samples/clock/font/colonF.xbm b/src/samples/clock/font/colonF.xbm new file mode 100644 index 000000000..04f3e3d8a --- /dev/null +++ b/src/samples/clock/font/colonF.xbm @@ -0,0 +1,8 @@ +#define colonF_width 11 +#define colonF_height 35 +static unsigned char colonF_bits[] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x70,0x00,0xf8,0x00,0xfc,0x01, + 0xfc,0x01,0xfc,0x01,0xfc,0x01,0xfc,0x01,0xf8,0x00,0x70,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x70,0x00,0xf8,0x00,0xfc,0x01,0xfc,0x01, + 0xfc,0x01,0xfc,0x01,0xfc,0x01,0xf8,0x00,0x70,0x00}; diff --git a/src/samples/clock/font/colonG.xbm b/src/samples/clock/font/colonG.xbm new file mode 100644 index 000000000..4a10cfaf2 --- /dev/null +++ b/src/samples/clock/font/colonG.xbm @@ -0,0 +1,6 @@ +#define colonG_width 10 +#define colonG_height 16 +static unsigned char colonG_bits[] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x70,0x00,0x70,0x00,0x70, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x70,0x00,0x70,0x00, + 0x70,0x00}; diff --git a/src/samples/clock/font/dalifont.ai b/src/samples/clock/font/dalifont.ai new file mode 100644 index 000000000..a4a9b4143 --- /dev/null +++ b/src/samples/clock/font/dalifont.ai @@ -0,0 +1,473 @@ +%PDF-1.5 %���� +1 0 obj <>/OCGs[5 0 R 31 0 R 56 0 R 81 0 R 106 0 R 131 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <>stream + + + + + application/pdf + + + Adobe Illustrator CS6 (Macintosh) + 2013-07-15T11:48:25-07:00 + 2013-07-15T13:11:36-07:00 + 2013-07-15T13:11:36-07:00 + + + + 256 + 40 + JPEG + /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgAKAEAAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9Ma95i0Hy/YNqGuahb6b ZJsZ7mRYlJpXiORHJvADfFUk8g/mf5S8+x6jN5ankubXTZlt5bh4miR2ZeQMYejkU8VGKssxV5v+ YX/OQX5a+RNT/ROr3ktxqigNPZWUfrSQhhyX1SWRFJBqF5VpvSmKsl8ifmF5T89aN+lvLd6Lq2Vv TnjZTHLDJSvCSNqFT+B7E4qyGSSOKNpJGCRoCzuxAVVAqSSegGKvHr//AJyy/Jyz1b9H/Xbu4iDc W1GC2Z7YbkFgaiRlFOqoa9q4q9b03UbDU7C31DT50urG7jWa2uIjyR43FVZSOxGKojFWFeY/zf8A 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 dirsVdirsVdirsVdirsVdirsVdirsVf/2Q== + + + + + + xmp.did:F97F117407206811822AB33B9AB6B0AC + uuid:9b02df42-3b96-bb4c-a8f3-4f96e0b6335f + uuid:B4FB81C46090DF11AB3682B45BA6D570 + proof:pdf + + uuid:B5FB81C46090DF11AB3682B45BA6D570 + uuid:B4FB81C46090DF11AB3682B45BA6D570 + uuid:B4FB81C46090DF11AB3682B45BA6D570 + + + + + saved + xmp.iid:F97F117407206811822AB33B9AB6B0AC + 2013-07-15T11:48:27-07:00 + Adobe Illustrator CS6 (Macintosh) + / + + + + + + 3 + + + + 1 + 720000/10000 + 720000/10000 + 2 + + + 1 + 12960 + 1112 + + + 1 + True + False + + 180.000000 + 15.444444 + Inches + + + + Cyan + Magenta + Yellow + Black + + + + + + Default Swatch Group + 0 + + + + + + Document + + + + + + + + + + + + + + + + + + + + + + + + + endstream endobj 3 0 obj <> endobj 7 0 obj <>/Resources<>/ExtGState<>/ProcSet[/PDF/ImageC/ImageI]/Properties<>/XObject<>>>/Thumb 141 0 R/TrimBox[0.0 0.0 12960.0 1112.0]/Type/Page>> endobj 133 0 obj <>stream +H�dWK�%� ��)��-꯭{� �0f�<��j�q�pD��W]�^�S� C�����������/���#�鴼ںך���X��y���߿���t��ǁ9�s�d���q|��G:���w��^��S/��|����o?N�����;76n��i�-�%+wN��=������q�z�b'~�����F>K�Ǵs����Gop��ak��{���ڨ�J�����8/~����V���k<�YX�X Kl"b{ �S+�g��Ƽ+������)k����ִq\��q��x�H�����ą��:^n>��&�%����P��t��� +O����YW�I� ?i~���Ұ-; +\_�=����^�S:jힱv�q�װ�G<�:�I�����|�ȫܭ�O��}����~��>y؆��X�>m�����=���m��F��g�bE��|��� f�&V$�@�J�Tc�}.��K��e*�y�Ȑ;�VM'�����9�]�k�p������(� ��'!1*V8�ϻã�n���#� 8��g��1懥��@�?��-��t"Xc���Eh&C�T\% �F�Z���9�]��Q�=|;p�J. �H�U�36������D$�'Ƀzc.�5������[�=�aiJ�e��4,��Y�R��L�0Q0 �>� �6�Ql��,�?��y��W�{[rͷU���)Jj��X�oGA��>}ۚ�g��;ѻW[��Ѕ%W�N�y x�+��,T�k� ���wuG�8Y*�Lc\�*N��R� �mXwU� ���&R�o@� +��2^hh�,&NtT�pr弔��[��!�)�U����� ���Jc0a��y=#\���zk X�ڗ|{�� +y1��6��]E�O�#^@q6dB ?��7����Ɛ���c�x�&��!h.̛�b{b A�qㆌcY�X�����,� z�)շ�Y���A�Y��nM�����݌��A��@L���)k�p^`�u!yDb�~� ͅ^�1W����)d�%���<�F7�.2C\�R���t<���+: ��"��� ëaL<6v��V� +�Qa��`Y��I/��0��=�pyȂN��2�*b�i�R�@��J�b�����3x��r�QP��(?�Ą*��{2�,-��y���� 2���Jy컒�'��S�8���x��� :�8��u���,T��F{��1�ra` щ������ +  B��}����#p���3Z�x�@�;_>�5�=P�p���8��(U��@C��_��Pm$O��;B{����V��>-{�f����^P�����*/Q��^bluiV�wR�lXlz�����um�;M]*KI!� +)�% ^��;/���j3wV���(�i���j&���l#ԏQ<>riA頡U��4�0QIg�p�T]j� �Q��eWw2�cD���%�@@a��`yMR�E��O��Jwr^�/��F"[��xz[}��� *�*-��G�����]ݪ�t���g��iQ���B�ѐ�;]"{Q���?H��E �u�($ ���,�xU ���„�����=��ĎȲ�h��t�,y�F�q��,QL���ư.��۱ l:Bo +�j��p���ͩA� +:�K�G~�� "� �/�l��1��:4��S,���A5m.��M��$��,�y,��ৗ �%���-����-���}:M �"���zH���h<=�w�%XnZ&�����U�3h�J�M'W�5���LJ�B��� +�+W��@��t ���a�%=��"���\3����kl­��h�!�J��B*����劼%a���r�j��>���5kg ��8Y�%�"@+��{�v$���&$��n�P3K!�?9O�����N�|�&UA�����.���tqj|;�riE +��B�ݓ.~+L +��L��ޏQ� �����-'�=�B�z�a�����-@Tʈˆ���#��%�(*�6��i͹�/�1�ig@��<���� +��j",b����"���{<@"�(u7��� �S���� ��B�ޗ��B*�a��`��e��u��RZ��Ө�� g����>4��1U�ސ1�[C"�j:nr�fH�k��ؗz?Zz��Dݡ@�����!��S�^=j��c�V�Č= ���p�w.�*1�����r�M>$j)g5�Jg�� �>C��暷��WS�Bc�r�8�cD���Ζ�⠞�� ]!��Ӡ��V�)�+�������3��.�� +�C��V��Jq;/�)�cY�Y���ڜ�)�ŀ����ț�����F���@�����)g"��6Y��`����)�\�5���'�� �;l�+���dI����V4����l,?+�qxp�ۜ!��Ϥ�J����ƪ�]}g�+��I<"�]��?1x����O��M p��Vx�ma0�%�*Ř����j���(�pU�=�,U!��J�6�T"��T��-~��6ؙ7<�.H��k�<:+g{TW��mX����U�/�c�̱$�a ��)���}����)���*>Y]ӒUI�R$�|(�i9k�@ ���e�pS/�k��PŕH����-˱�𔎠bS��&t��Z��m�8\�C�Z�Z�%�C�NȮ|�3���_���_?���mf�:W?9SE�D�mFBut9ӆ�kV��}8��0G2�)p������5T�Q�zV[��g\���gf:G�\-��9���Ș;��YRc�:�(���g�h�3a��?S� '��8Y�R�T]5%F�h��W�!��+ 1��D�&��fgv��X ދ��w!v DQ?pЛ�"���@��oX�k�e��uBZ��D vQ�.�%[���Z8L�IR*��4�C�V��D;�ـ"��O.g���;�#�x�*Q]��WF)�>7o��ey��z�f�?�Z���,��/����QoSY��<�,{ܯȏ��ˇr#_�!�# M?��p��F�&wr��H\X�VD�ɫVd���aM����̑[4�k��:=���J5g��ƒ��^v���ݕ�םAΌ,�:��<� d��INE��=N�zU�F��Y}��DƆ���3^�8Z�H����c1�bT=��w e� ��{�ku/+*.W�Õ������q� juB�:�f�m�-��#����p[� U�yBqGji�l���k+��S�K�Z�B R���B� �t*�-LTOz����<��������iZ*�r>��~�M��1�h�� ��Յ��Sj�a�W��ہ���V�t���� uÕ�7��L0n�Ɲٺ�6��SDP����q��x=uc��P)�Qg�׏�� ��k#ɦ�R�7�?��ڏ6_���Ko� `��w�5|�XB8f7 PS������zý� +�@Ow���Б*i�l�=sl�?�H?X�d� }BD]O��'ذ�=�LZbdd9�w��bЖ�>�C�$�b(ii?��G�m���&��?��'^�M8�>���d|�W�*__m)-�J�� ��0�m��Z�H ���P�I�B���B<�|��>Z���%:�s�m�MU6�֦�U@����S�d;�֩)�fR����$C�3FkF�g�&b#m�jb��1a���z����5���;?��s �Tp��np�YNO������t���-���t� +)�[ϘP�X�33�e�.��t ����y�dg�W�8j׻��'Fk�q��şij����}��\�����D e{ȍ��������-TYx=��kf� ���l�f R����כּ.[�_i'��]DS��IY���X��H<����gVy���U|Y,��F�,T��WR�)���-i3�!���_�(�5\���0ҸZ���m��:m�X��MH���p8������r[�#e�gHOg�� �/ z����*(��b#J���V�DJB�y��O�������-��JK�+� &)'s�LB�0�@I(��m����A��E��<��:���������: endstream endobj 134 0 obj <> endobj 141 0 obj <>stream +8;V^l9,<17%-%7&j>t"Pg[[)J'ug:u%1.RZ_*T+=i3(]s5_5+/gQ"k@^ucd$+@c9J +S=II;8s1^m=q=_q%l!r)o0\!/O.o\[UR(@bZVaE(U]!n4UKs,:J_g%Yqc9Xm4d?kt +2p+Yq,t[J>Lg5/~> endstream endobj 143 0 obj [/Indexed/DeviceRGB 255 144 0 R] endobj 144 0 obj <>stream +8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 +b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` +E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn +6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( +l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~> endstream endobj 140 0 obj <>stream +H�������� +�ك ��FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa��_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU�=8�mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU��@���TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ ��FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa��_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU�=8�mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU��@���TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ ��FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa��_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU�=8�mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU��@���TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ ��FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa��_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU�=8�mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU��@���TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ ��FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa��_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU�=8�mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU��@���TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ ��FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa��_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU�=8�mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU��@���TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ ��FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa��_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU�=8$���7 ��� endstream endobj 136 0 obj [/Indexed 137 0 R 0 146 0 R] endobj 145 0 obj <>/Filter/FlateDecode/Height 1112/Intent/RelativeColorimetric/Length 14328/Name/X/Subtype/Image/Type/XObject/Width 12960>>stream +H�������� +�ك ��FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa��_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU�=8�mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU��@���TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ ��FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa��_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU�=8�mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU��@���TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ ��FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa��_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU�=8�mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU��@���TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ ��FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa��_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU�=8�mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU��@���TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ ��FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa��_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU�=8�mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU��@���TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ ��FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa��_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU�=8�mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU��@���TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ ��FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa��_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU�=8$���7 �i��Z endstream endobj 137 0 obj [/ICCBased 147 0 R] endobj 146 0 obj <>stream +��� endstream endobj 147 0 obj <>stream +H���yTSw�oɞ����c [���5, �� �BHBK!aP�V�X��=u����:X��K�è���Z\;v^�����N�����߽��~��w��.M�h�aUZ�>31[�_��& (�D���Ԭ�l�K/�jq'�VO��V����?�:�O��s�RU�����z��d���WRab5�? Ζ&Vϳ���S�7���T�rc�3�MQ]Ym�c�:�B� :Ŀ9��ᝩ*U�UZ<"�2�V��[��4�ZL��OM��a?��\�⎽�"����?.�KH�6|zӷJ.Hǟy���~N�ϳ�}��V����dfc +��n~��Y�&�+`��;�A4�I d�|�(@�zPZ@;�=`=���v0v��� ����<�\��$�� x +^AD��� W� ��P$�@�P>T �!-d�Z�P� C;������ �t � +��@�A/a��<�v�}a1'���X ��Mp'��G�}�a�|�O��Y 4��8"BD�H�4�)E�H+ҍ "��~�r��L"��(�*D�Q)��*���E��]�a�4z�Bg�����E#��jB=��0H�I��p�p�0MxJ$�D1�(%�ˉ��^�V��q�%�]�,�D�"y�"Hi$9�@�"m!�#}F�L�&='��dr���%w��{ȟ�/��_QXWJ%���4R�(c�c���i�+*�*�FP����v�u?� �6� �Fs���2h�r����iS�tݓ.�ҍ�u����_џ0 7F4��a`�c�f�b|�xn�5�1��)���F��]6{̤0]�1̥�&� ��"���rcIXrV+k�u�u�5��E�4v����}�}�C�q�9JN'��)�].�u�J� +� +�� w�G� x2^9���{�oƜch�k�`>b���$��e�J~� �:����E���b��~���,m,�-U�ݖ,�Y��¬�*�6X�[ݱF�=�3�뭷Y��~dó �Q�t���i �z�f�6�~`{�v���.�Ng����#{�}�}��������������c1X%6���fm��F�����N9NN��8S��Υ��'�g\\R]Z\���t���]�\7��u}�&p�s[�6�v_`)� �{���Q�5��W=�b� +��_zžA�e�#��`�`/��V�K��Po���� !]#��N��}R|:|�}����n�=���/ȯ�o�#Ju�������W���_ `$� �6�+P�-�AܠԠUA'����� �%�8佐b�8]�+�<���q苰�0C���� �+����_ X�Z0��n�S�PE��U�J#J�K�#��ʢ��i$�aͷ������*�*>���2��@���ꨖ��О���n�����u�&k�j6����;k��%�G <�g��ݸ�8UY7R��>��P�A�p�ѳqM㽦���5�͊�-�-�-S�b��h�ZKZO�9�u�M/O\����^��������W�8�i׹����ĕ{�̺�]7V��ھ]�Y=�&`͖5����_��� ��Ы��b�h���ו��� �۶��^����� ����M�w7�n<<� t|��hӹ���훩���'�� �Z�L���$�����h�՛B��������d�Ҟ@��������i�ءG���&����v��V�ǥ8��������n��R�ĩ7�������u��\�ЭD���-�������u��`�ֲK�³8���%�������y��h��Y�ѹJ�º;���.���!������ +�����z���p���g���_���X���Q���K���F���Aǿ�=ȼ�:ɹ�8ʷ�6˶�5̵�5͵�6ζ�7ϸ�9к�<Ѿ�?���D���I���N���U���\���d���l���v��ۀ�܊�ݖ�ޢ�)߯�6��D���S���c���s���� ����2��F���[���p������(��@���X���r������4���P���m��������8���W���w����)���K���m�� ��� endstream endobj 131 0 obj <> endobj 148 0 obj [/View/Design] endobj 149 0 obj <>>> endobj 138 0 obj <> endobj 139 0 obj <> endobj 135 0 obj <> endobj 150 0 obj <> endobj 151 0 obj <>stream +%!PS-Adobe-3.0 %%Creator: Adobe Illustrator(R) 16.0 %%AI8_CreatorVersion: 16.0.0 %%For: (Jamie Zawinski) () %%Title: (dalifont.ai) %%CreationDate: 7/15/13 1:11 PM %%Canvassize: 16383 %%BoundingBox: -6084 -516 6876 1399 %%HiResBoundingBox: -6084 -515.9189 6876 1398.6221 %%DocumentProcessColors: Cyan Magenta Yellow Black %AI5_FileFormat 12.0 %AI12_BuildNumber: 682 %AI3_ColorUsage: Color %AI7_ImageSettings: 0 %%RGBProcessColor: 0 0 0 ([Registration]) %AI3_Cropmarks: -6084 56 6876 1168 %AI3_TemplateBox: 395.5 612.5 395.5 612.5 %AI3_TileBox: 12.2402 318.2402 779.7598 905.7598 %AI3_DocumentPreview: None %AI5_ArtSize: 14400 14400 %AI5_RulerUnits: 0 %AI9_ColorModel: 1 %AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 %AI5_TargetResolution: 800 %AI5_NumLayers: 1 %AI9_OpenToView: -6104 3579 0.15 1970 896 18 1 0 117 134 0 0 0 1 1 1 1 1 0 1 %AI5_OpenViewLayers: 7 %%PageOrigin:0 0 %AI7_GridSettings: 72 8 72 8 1 0 0.8 0.8 0.8 0.9 0.9 0.9 %AI9_Flatten: 1 %AI12_CMSettings: 00.MS %%EndComments endstream endobj 152 0 obj <>stream +%%BoundingBox: -6084 -516 6876 1399 %%HiResBoundingBox: -6084 -515.9189 6876 1398.6221 %AI7_Thumbnail: 128 20 8 %%BeginData: 3580 Hex Bytes %0000330000660000990000CC0033000033330033660033990033CC0033FF %0066000066330066660066990066CC0066FF009900009933009966009999 %0099CC0099FF00CC0000CC3300CC6600CC9900CCCC00CCFF00FF3300FF66 %00FF9900FFCC3300003300333300663300993300CC3300FF333300333333 %3333663333993333CC3333FF3366003366333366663366993366CC3366FF %3399003399333399663399993399CC3399FF33CC0033CC3333CC6633CC99 %33CCCC33CCFF33FF0033FF3333FF6633FF9933FFCC33FFFF660000660033 %6600666600996600CC6600FF6633006633336633666633996633CC6633FF %6666006666336666666666996666CC6666FF669900669933669966669999 %6699CC6699FF66CC0066CC3366CC6666CC9966CCCC66CCFF66FF0066FF33 %66FF6666FF9966FFCC66FFFF9900009900339900669900999900CC9900FF %9933009933339933669933999933CC9933FF996600996633996666996699 %9966CC9966FF9999009999339999669999999999CC9999FF99CC0099CC33 %99CC6699CC9999CCCC99CCFF99FF0099FF3399FF6699FF9999FFCC99FFFF %CC0000CC0033CC0066CC0099CC00CCCC00FFCC3300CC3333CC3366CC3399 %CC33CCCC33FFCC6600CC6633CC6666CC6699CC66CCCC66FFCC9900CC9933 %CC9966CC9999CC99CCCC99FFCCCC00CCCC33CCCC66CCCC99CCCCCCCCCCFF %CCFF00CCFF33CCFF66CCFF99CCFFCCCCFFFFFF0033FF0066FF0099FF00CC %FF3300FF3333FF3366FF3399FF33CCFF33FFFF6600FF6633FF6666FF6699 %FF66CCFF66FFFF9900FF9933FF9966FF9999FF99CCFF99FFFFCC00FFCC33 %FFCC66FFCC99FFCCCCFFCCFFFFFF33FFFF66FFFF99FFFFCC110000001100 %000011111111220000002200000022222222440000004400000044444444 %550000005500000055555555770000007700000077777777880000008800 %000088888888AA000000AA000000AAAAAAAABB000000BB000000BBBBBBBB %DD000000DD000000DDDDDDDDEE000000EE000000EEEEEEEE0000000000FF %00FF0000FFFFFF0000FF00FFFFFF00FFFFFF %524C45FDFCFFFD09FF7DFD08FFA87DA8FD08FF7D7DA8FD0AFF7DA8FD06FF %A8FD047DFD09FFA87DFD05FFFD067DFD06FF7D7D7DFD07FFA87D7DA8FD08 %FF52A8FD1BFF7D52F827FD07FF27F8F8F87DFD06FF27F8F8F87DFD08FF52 %F87DFD06FFFD05F8FD07FF522752A8FD04FF52FD05F827FD05FFF8277D27 %F8A8FD04FFA8F82752F87DFD05FFA8F852F827FD19FFA87DF8F827FD06FF %7D27A827F8F8FD05FF52A8FF52F827FD07FF7DF8F87DFD05FF7DFD04527D %FD06FF27F87DFD06FF52F8522752F87DFD04FF52F87DFF7DF852FD04FF27 %F8FFFFF8F8FD05FF27F8FFA8F87DFD1AFF52F827FD06FF52FFFFA8F8F8A8 %FFFFFFA8FFFFFF52F852FD06FFA82727F87DFD05FF5252FD09FF52F852FD %07FF52FFFFFF7DF8A8FD04FF52F827A87DF87DFD04FFF8F8A8FFF8F87DFF %FFFF7DF8F8FF7DF827FD0DFFA87DA8FD0AFF52F827FD09FFA8F8F8FD07FF %7D27F8A8FD06FF27A827F87DFD05FF27F8F8277DFD05FFA8F8F85252A8FD %09FF2727FD05FFA8F8F827277DFD04FFA8F8F8A8FF27F87DFFFFFF7DF827 %FFA8F8F8A8FD0CFF52F852FD0AFF52F852FD09FF7DF87DFD06FFA827F8F8 %F8FD05FF527DFF27F87DFD04FFA827FD04F852FD04FF7DF82752F8F87DFD %07FFA8F87DFD04FFA8FF7DF8F8F852FD05FF27F852FFF8F852FFFFFF52F8 %F8FF7DF8F8A8FD0CFF52F852FD0AFF52F827FD09FFF852FD09FF7DF8F87D %FFFFFF7D52FFFF27F87DFD07FFA852F827FD04FF52F827FF7DF8F8FD07FF %52F8A8FD06FF522752F8F852FD05FF272727F8F8A8FFFFFF7DF827FFA8F8 %F8A8FFFFFF7D52527DFD06FFA8FD0BFF52F852FD08FF2752FFFFA8FD08FF %52F87DFFFFFF52FD06F87DFD08FF27F8FD04FF7DF827FFA8F8F8A8FD06FF %2727FD06FF52F8A8FF27F827FD07FF7DF827FD04FF7DF8F8FF7DF8F8FD04 %FF52F8F8F8FD12FF52F827FD07FF5227A8A85252FD04FF7DFFFFFF7DF8A8 %FFFFFF7D525252F8F827A8FD04FF52FFFFFF5227FD04FF7DF8F8FFA8F8F8 %FD06FFA8F8A8FD06FF27F8A8FF7DF827FD07FFF8F8A8FD05FFF827FFA8F8 %52FD04FFA8FFA8FD06FFA827A8FD0AFF52F827FD06FF7DFD05F87DFFFFFF %7DF8F87DA82752FD08FF27F87DFD04FF52F8F87D5227A8FD05FF27F8A87D %F87DFD06FF27F8A8FD06FF7DF87DFF52F87DFD05FFA8F827A8FD06FF7DF8 %A852F8A8FD0DFF27F827FD08FFA87D2727277DA8FFFFFFA827F8FD0427FD %05FF5227F852A8FD09FF27277DFD05FF52272752A8FD07FF5252277DFD07 %FF2752FD08FF7D275227A8FD05FF7D527DFD09FF7D2752A8FD0EFFA827A8 %FD09FFA8FFFFFFA8FD33FFA8FD15FFA8FD07FFA8FDFCFFFDFCFFFDFCFFFD %32FFFF %%EndData endstream endobj 153 0 obj <>stream +%AI12_CompressedDatax�ܽ��H(������3M�96� ��`�af��q���� ��6�a��sv���,U�T*I%U�F۞���q�j�^�&q�H�a5����ٜ�# 2��j�߁�d18f{8I��]�~D?���M%l����߫�^��&3x�Y78x8�6�9�;:���L �`G�4�t��.��v�ԍ*|����(j�/�x��[�8�f��"E�V��hЫ��\~�?�]�P�V-��9B�`�t��nx%CLO[|wl����4�!H*�N��v�*�O0��l��թ 6]s^Ɂ��j�S���q��p|��D �E�8����i[�v�! �E�{�:�6��i;�;�A7��3�ԥ@K@��g�s`\܂_���: +(�ah�S�N���L�-|���p|hfђ�~��k��"ߙ�.����� �GO������>�g�?���[�q��@������?0/\�������k�gx�$�mfܽ^e�2OZ� Nvw�#��d1�0�J�� �����`4O��\׿ @#�� �9iA�)�W�?8C���w�G���w�^���Q�˧v��:, ��]��,���?��4� �:��Ak�a����jf[����:��:���;�r�:��i-�������JW9"�:�m@1����-�>g��� �`�]>�O�맽�S� 9'�S�W�������:��N�'�3I������6̙Ŧ�#��\���L��^�����Ħ+�|���Į0���|zF��o��8�*�y���bЯ�fu����UN��%X�K\` �)'�Ā���*��;� �t�@q�9�vRG���^P���sr�l�s��������v��p���-�6sN�� $ˣL�'��iÅ�cGg�ܙ�:������O{�ѷ��� +t���snOŸU7p� r6������5=m��3~g�N8;9#��9��lKҵ�b���9�ū�m3@#n�-֐������ɫ=�9�Ǧ�3� F�6�����2���&9�-�-Ȁ�4��'���;��l�� + +ȅi�.��$ƌh�DtK�ri7��F�r�W�ìx��=�C̠��c}�g��/ ����8Q@DT�$GD���IF2���e��ߋ\q-^�� P��s��]g � D����<���˶��63[lJBy��æ�`�43�U��i����v���`�%a�b�=U'KkP_iܡ��q]/'��p��elj�� ����|���� ++�d����0����l'�f�d����ǰq ��cW Um��;���I�/����؉=���������X|�Q��n�_`�ٷ7ˢ��s`����U����/|C����_�P�1ruc��0RM�~�\Y�ի���^q_��,�$�Y��� +���=\�:�4O��H!�v+P��7��m����Sha��y���Al.h��.�ٟ�_!����®�ctK�R���-M<�_ƞ��*N�>����J̳G�V|+j{�'�/ �����][Ń��,Pr�X��7v�.+� l[\v�� � �~��:�W�=�w��%���n�9�}A� W����Z��B����P�,GQ�U�O�߱��84��5l��m*g{�K� ���*��;�c��� +~#��������v��߃��g��=�:{{���>����V�PuU�9D�3��{KE�&�DTj�����tO@ׯ��ˌ,x��@����� +�r�����n���T�H]�q���`T:�����ͨ�P�3� ���� �(��m�9 ����J?v�����V�q������CQ���c����x�"t��-x��KnCdۭ�%�JÛ����A�x�����vhE�i�td�.6�4�)�FoE�&� ��G �[��+�VB^�r�b�m:D�>� E�Ė��� +�b �|�Xn�c���&,R��6>��c��V$u� -��$�ڥ;� ��/������.eC Q����Z�3\t�EԦ����v���� p)��$Ib �� �s��K঒���q�~ +����6v��/��"`1k��� ۜΰ�wJnB�m ���)wv�5W�� 1]�3%�9C�H0ۯɊ�Up)��6~�ѝ��?.��IN�d9���+l�����Q��i���R<�!�Y�a��' +��]M����0�AZ�`�)����m�j�� �����?�����YM�o���|ڤ���%��翭{�@��+�(���vW�t�B��=���)���;��N�\^���7dK:�Jzx��/� &����߫�q��O,����O��6q"�8��__����n�?b3` =ێГ���������<8��Ԧu���H{�I�}�P8��Ղ��2Ϊx���!�˝6�s��MgO�<�? �˓����ԥ�~���m��[ߥw�p�=6]ٞy}��9l�'a��=h�d��A�v��%��̊:b�)7�$d�d�ٜH���{i��&�p��rP��4[��GX�.�� x墈��� �*wAH Ǔ ���j,����`(�� � ���ҫ9��w�4�S�YJ$�F��@ó�I�&�,�nff,�g}vKt�s�z��J]~�����I5�;���f�Q�p(�8a��:��W|���aVg�ı$��['E`Z�`�8����S�K��~���T1r���$y�9S����S��Ϫ�ӺN�d��'�l��Rn��b�2���Vu�f g:Y&�*~�%�S�Թ��X�v�;Cм�,=4�Ab0K���좞`��b2�s����l&�-F�M���Q����Y,g���US�/SbC^��dQ�< u�n�e +p�˥>/����������.� �"`~�z�G�pH����C�j{�К�o�@�,?S��˟o����v����h�j��.]��$.���t�Q��`ɳ���"�.N�rp���~g!�6 �>�v.��5H��ɿpu�:5c6�n��b�?�F���{���ʡ鳊�V����S=��4�˶.�W��]��m:�9 +㪠!�8��0-Ijg��{�~:�˿Ù�7��`�� �5��y"H�f�Y����X�?j��K��GgQ�Y���������X�n1��r5] +!/���629� :*:��� ��<0ym]�v� O��r��L=��ΐ`�"�b�q��-�����@\ʠ"8�� 2؍�P�A.�z�ƈ� �1p�d�>���;|�]��@S��+ ���<��kJ��7���f��RXwa8a�va�(6>T@L���w�T�(�"IA<����A.��p�*x�L���� w�C�eM�$ �c<T��Oe� + @Ɍ8 0;=��.of߉��F�P��n*#% �ȟ%���@���v�x���JC�s�[��d�:�u����'g�/S�_o|����8j[`�qL>1�@W���( +s]N�dd q\�s�SdZ�[���]�U�<�' +���*�V'�/rkn|�w��6V���N��鑣�V&̔�U���2AZ��,�R�n`� |�B _�Sb�4��b޺O˙Z�c +�v�X �28�I�A��)�(��� �d��B(���d,՞W�O�� dE �B+�'��U���o�>8-s�|e���A�wA,F��TҨ�A_%[���?�N�͢ |Б�k'.6��� Q����dX��Dox0�F#�5�=-����5� F���:������)�5lz���IqG����|2��׎J�7:�6���4��~]�O�|;�^Q'�J7ԗq���`w�Osi�<��]�՞��L���|)�rB�_龄@�����y.=������줃jr��)��T�,����Hv�>�MY܀ߦav� f�Ƃ>S>yF4�hfzTzz�I-�tg���;�� ��=W�� {���K���c|���=�\n���x���/=���;gϲ�`~�<Ց�5Z �uL��?�@7���y�:�j�pt�c�غ�6@KL��~ z�jW>5�h%[�7C*��za{L��ad��ױ��x��S_���:<�%�U�#JW�u g/��3�����S.�+����^� +ak�Zq%oӀ5[����i���u��X�G2�rhW +'۶�.�Z̎*}`�wdp�-����R�p�g�,ϭ�������)R�zv��N��@;#�cM�C��LqV4���Mb�Im|�I ��$9-����NM�(W�d��,���{�O�H�5�6N�A�C��ۿ<�T�)3���2�/�5��h{D3+� H��2K��t�'+;Vf�P��k��Yh��@�3�4�ߴ9D����k�2/���6b@4�a��25D�N}L]C���o �y����D��6�X�bmjR��7�!�����͈s��!��D\TG<ڣ�~�g���M;B�1�U�y H�����t +F��$/Ց�^l�$v.+� +[2H�S;F�R���R�￐b}�E��S�`�1R{�����_D��� +i�^���w���G���,�1�-���n57_�w�gC&�V���u�|� K��#kZ@���!� �UҤ���^�P}_� �-f�h�' ]y ��i�e���n� T�n���w��7t#�ȗ1U&��b�=36��o��ʴ7�N˪q�X��Jql<�o@��1�䘇�M��?l����l.�n��h�����65�J��i�B��c��1S���L[�@�7��(�9�P�c=3���}�7��ͫxP16A�E�9X-ç��f���wm)xt��i 0ˀ�G�����W�OˑZ�����ck���X3���Z�e6�~���bJo��֣�P�m��6es�5[�N|ڲTne�O)�m0lxl�oOֶ���vͮ����q�=�'��ڡo/o愽���ؿ��U���Z�O��aM}V~�d�H,�����3��JT���݂cS�m���'괷Zgp��9ӧ��Y=Ώni���s� j2%����]�o>,��� -�[d����;@ץ/�**��U3;]��y�*bh��%n�ĕ&\�B���VI�;f���Dv�.�G��őw�?�>T�F�9��y�^�'���z�T�3 ����"����J�u�Vo,0xK���n���@Gy)���Yז�/�h|�tZ��:�f�Ծ@}�%�XRo����ϥ�m���'�� ˗��g9���G��@/�^f$a P3��T��`�P���7��Ԃ�r�'��'�!7^n�R��>��.��o\�{0j [V�f8�� @g���|��:��Jѕ7Dě��"����t2�Jd�/#G �GQ��M�V��rZ�b�/s�0������X�23�j�r�t�ؾ���m�2�e&�x�l)��U0���jox����x��������e��=J8�yg"�m�I̶�} �I��Jfg�E���ϓ?Fl�2F�?�P�M��(����Ԟ�ZU���f��J<�>K��$����O�L�^�2��Ñ��������: �U6����/$�]Y�����C�"�e(W���ܤ~��H�����Q�h�*��~Z��f�����B�A� ��l���lE[�/&��b�s�.���d�|r�Jq\k-�;�ai���eS��*�>F�2�B@_��S�TbcwJ����IKe�����]H�_���Nw���"H�X,kI����ik�m�+SwTs�zf��럚��~Ȯ� ��6�o_���8:6���� �M�2�U����7�L��z��Z��󭵭uKm'n���֤=���D�e�;o�N#l�w���k�����g9��h'�]�Y�|���W]ƫ��!��7��^���\̻���^�'��컿0�y�[M��ź:X�u�Af؝ ��������e���c ۬���S�ϲg1���&�/sl�|�������>>D��J?��“a�ٮ ��&0�b~���|�G���;vt��q�� +���K��[<}�w�����8�t��������D������$�j4&�Y�1!-��4X;�T�i�r}N�r��m��Ys���m�ӌ{K�o�>R�&i�=��t^￟��y³�=�E�m/�+�,=�]d�pn{�͚$W�/sd��?W�ZX�?�ƴ����n��bl2lf�^&XS?{l�6���Ʌo5���6mo�#w��3�B�]����M�h��G�T���$ݻѺv�h����)���w�w�1 �x8�p {���Sf�<������ę0�:C'�+� ������X�z֍L�9z3u���:K����P��W���j�=~R��ߩ)�/H�qO�U�}�(�3{jN&wI2t�����#�C��� x�ة�� +>�I3��j�58�lj@����:�W���L�����Gxd�j��Q��6����ۚW4 ��Q�@�����R�vkx�C��p�!7�)��� $�yO�w:r[)g�F��f�+�ۺ�������`S�H�D�i^tH��K�� `���f)Iӿ$��e���ˤ9�v��fB�D�Ϡ���eWK��95/I�7�K� �߀��4U���P���2�+��L��y3C|gጩJ��$�x��[}��P�`-'L(x̜� '&Tm L�?��Q܁$S�I-PSt����Z�csv ;��i;�a�����u���L�}�ׄC�vK������� q�g��{��j�i�o5O�uܾ��aԼ�g�! �?�s���c���/��R����o*g��D���+�N��4K����2FV3zP#rjx$!�꾏��ſ�:t:U�(� r]��З|/uo�H���o�z�.\�*�ZwI�]*0H���Tu�vGU��`E�,_�W�=y���m(�� + +$f��� �Ֆ)�'Uf��=�}}�����;2���_��5���Ã.޺.�r� L(x� <�|�֣^_��O.�vMi����8@�g�t�߽[��v��+��g,�9�xW��$L_�I�� �8�6n�߭�����.2��˅�q���r۶޷����nj��V���T��o���)��ɘ^aJ���~m8��ǫ޷p��XM��i �uW�z�-�R���†���Ò���T�*��*}"RA�3P�Z��Dԋ�ӫX|�q8��R��� dr�p�#�� S��-�%*T��>.�q����xu�����W�3s�4E� ��^��+�I;z�D�p�a"�� aʱ�fl�SΔ��Uz�Y�o ����;q��yjyL/.;� ���rHDߌ}h2�-F�d8��<$e��m�vǴ���T�Џ�:�N}MSz�G�I��`M�l�p��K�'K�W�.#�)���V�켲d(�P' W���jT��6�7���~������oNDJK�J���nj�ZcՈ3����x�a��4S�z�m#�{�Yx�vb`|]՘�n�S�@c��`P�'�Ũ����|��_���5ڛ}���F��A��~�ɧ��o� �⩵�ogE�7������qbQC�~���>�Xh��h�^��5������l�O۲� 2��]���Z��͝y[�X<�X42���葼4૧|,�*]0�6�e�� ��!�̘f5����?�?�d'�c�̝�CÌ���mF �q<�YN�ɆnZMv�0���p���>���o� ����h}E�8��y�Df���h~����F��A�*}v��R��R.A�ˮ7\���#��H?<�͚"B#!+�����'f`!*�>b��L��ƕBK�0��GW ��B$"��&�)O���$���'�[豃c���� +�9�aoN/�����Z28I�P��\°�ŒJ>9.�hi�ڿsp����Oc�>3���a[N�G�j.=/QWy2˥g��k#j��~c~ +e���L�� �Q̆Oc�ǘ1���-\�&V��� ��G�_�V����扬�3�.6�`b����C?e�AV��A:�r�`�E�>3��ea�1��jB�ڔP���� �Vo����Ǯ��M�,�3K�e�,�|* b!��=~���\�Ж�wXH�}�lZ��/��O�P�~������Ǵ������a@ 0���g�̓@\<4�mF�������,ö�il�BS��Bw����Sm�7�+� +|[�"�ɷD+Q�7� +�����y�t�GP� ��LrY��g.F�0��0��XF�K_x%3� �`�\��� e�?�g>����]�2�BK��kݗ�X���dH�0�νb�r�*R�qO,>� +�WWQ��YHE�Y �iN����3e*-�>�sgI�&�K��tw?�<� J�˅ ��2���6N�M���58�B��A���V&/4���$�α�\��3 �_�!-�����i����'�C�LO��.�&P�?v� %w��|Qfey/X0��^y�K�\k�>�Ϗ�Y�3�9�ɩ����ǎ%A�5�{/��2��cNp���ąS�iǀ1_�l�f�pX�)��~�OIo*��䒞��D��YiA�%4�����L����RI��dk��&���6k�8M���\"�ߟ�����|1���C��N5� �ZW[c� +�0~O9�F 2�����R+S"-�b��°�,���Nf[�կ�T'Yӯ�O�e��q�򾃼�������!^�̛T۹j|��Ҍ��qk>�!IWH��:CW �]c<��d=���ӫ�32������O�߁@kF��ٯ�kƶ7�+* �,���< ���gv�C��C�nL/���s��ٱ��独��;rWGh�4 +4�Q�*�̳��i��Sa�3r~���xW��o�sZ��4��̚��C�� 0�x֍��ުm1 �FƲg���̼kͣ�$�q�gYfҞ��fJ��5��R�*� -�\vw�v�C� ;�Q��� L+�T��}lk����Z��˘g�Wa' h`}�4�Jy����˜��c �%��B��x~<>K��F�QN��V`�7��9���Qj�!��@�m7(RW r�����WS��K�T�&���f��áy���µ��+S.G����n����knʅ�-�y@�&��;N,������/S�6���i�o�-�K�rQ�ڒ?���Sc�K��1��$�Xl.��v�;!7�9�o0 ߂����8��N`t޻J���o,"9Sq� -�j�\�Qm��{/P.}|��>�׹ȚA������𒁶6��/$�v�Y� +2�a�� e=�sL�NzM�n�DM�P����B)�=`���Tڕ��<pK3�2��IJn�zNr3����'+����'� �ʝ�����>? yY�8��JnF?��Mz����D5�/����mD�b�����W"��7#xh�c����.���ě;�i����q���l�3��!� Ӳ�����d{��3ޢۯY�����RJ��󬼮/4��'�"�z#�>uݷ ����+ +,[�-h�Ɵ���C�@=m�Ug��p�m�`�pN��`��1�Y컨Im��}�1���V{#���� �q��MO�2�A,X��.��+2�>H�{�p��)�)<�is|rdk + ��nR??�D�8k3%�)�A�̾Qr�mi�({4�3�� X��kÊe�XR;�rgK6-c�BN�.�:.��h�3��/�@�vµ�����P�<����(��G��y��A��?���n�� {��X>�r��ɣ��`4(K <���~�U8.�� /���D��/;������nP�6|7��B!7>�����7���Da4��1h��9a_=[>��|e'�d?��V�uQ\g�uY�=�I� ��ނL/?��\��؇�ԉ�����+��L��:cY9���'�g?���%o@E�)�I�註i:By������Ԯ-�w����l�2%d�r{��]��a������ p_�O�����.3'�V:t$�fU}*�Ý�ɱ%���u�a +�1;=iF�z�� yl�Hnl�}���d �����^,sF���5��`��'�����p5�rw�� + \�z�k�yntؿ՜1����8H}�#S�j}\ʛ�.;��f0���~c�j p���^��"��@"2����t` ���v,I$�ܵq �)ID��T`W�2��� ���&�<#��1�o晢Y���k:�1�z7�J�f�.�蓠�Sj��\��i��j�2�ξ��w����O��s�;��ȩ��ᕄq���`�k��|Ox#0�I�\ �̽���e����a�'sg3B�m��'+����z +�a_2���Qgl`���uSU��t�<��'T���������O��O�d<6I���R���� Ѿ#G|x{Y���|��\,���v�u�7.� b�:ڰWA�k��g>ļZo�1��]3s�浵�h�W��F�\�_T�����u;O�!F�ͧI��w��TX�34�'� ���tg��%���a�h�h����9����7@$'Y��~��L�f��vT�C��ѵ��J��$�d�54��":%GA׻8�Z֢=�OU1��v�ʩ�R}-��w�q�^���tk�D59�' ��sG��LI�k����M��`��NG�h^S� |��+�o{�=5�ǒ!��Z�x�Fc�� �Zh�tz��la���* � �V��!Kt`7 z�9�D�0���m�d�D��S�2�E��7xA�h�q�ۮDaX�C�ӊ�+��8ѲgT���Q���(IT�oW��o��~f�: +��ص���%�Ũ+Q0.|�=>�K��}�[J�h��OɴQ0.��H��?�N%%A�ˏ�o�8�Zw��k6��J/��h�>�$J�o���*���dĈ��`�v]��c�(�W��ٵ,щ�,�4֐%�a�b�c��҄ɓ'�B�6:���k=f:��� @4F +W�01��D�!���~����Dݩ��x%��C�B���<$k�U�5�y�I���f �7S��#�[gVV'��]8]�B� �>!Q�-Q�^ +�h Eܩ��%n:�D�N�}�!b�5M��֭T� ��Y�� �F���}�"��n"��d@<��]�o�{�`��}��SCu%�X�h=�<�˲@�|��J�㦟�*��7��z�J=M��Y�/����4ZE�A���h}ZlI=����(��ӆ�{Z���s�ITMRO}h98ğF�khߟMK��@?�����t��a�x�@�?sۙc��C��H=M�c�8$�tL�����T�1�� +}��oO���a��z�v����$Ǧ���v�$ޞ�]���A��o��D���n��ͥ�1�˘ +��I�QHę�|�g��zK?�O��/kԽh^� +8fZ��m��v�ni]����4�XSUc�W<��s\���s�Xς$�\���hL�:o��\f>�Q� ͥS�i:�r��������7�5����cp�-��t��O��wF�C��hw(�y��j���� f�& ��p���I��k���N��Q֐(m��d}K�>��D��wi����)I�uRM޺��+0��*&E�[�h��� ���%������}_V"g�� �������%O�.o݇d9 ��&Ax��w�'�| +���<oݒDi�A�(�fRDq�K�+X���D�="�^h���DA_xd�7�� �X��'V�� +�"4�u�KAz�*�0z� ����:{|���;O���f�k�h��Q.g�^g|�e�l�?�\W������uQ�8q�x�@��A[���F� ��O������s h��%�ړ��c��E���w�A����9�� F�?3���s�{�]�&dzz���J0�al~A��H`���K� 48�\�=�� -�Mb9&֨ �Q�&ٲ6��O�X`�f� P��t��?�'�_��J�!��%9���+�zo��'˹�?Ԅ��M�� K0K��E�4=:֧�Y2�l�|W)wV���C�u�+�LK:wi�s^��W!=k��sU �*If�y��Aܨ��m�#p����9G%F# ��砸4��z�Z*�y7���=�n�����2���~�)�-5+�G�ko�r��qV1�9�V�Q��0����^����V�+�*v�y�+q5:ʢ8��?�! d��Fx��<_6lu�0e�W��2�+ƌ�Wi?v���p$��oD�M(��DP��1�o��f�M���Nv�AO<�e�r|2[�j�h%�m9����S�,6�*=w@���^�=E����'���m�]V�uˡ��S�����&6$���{��u��׵JU��;��K-L +l����Z��,�ޫ���]��Xx�z���r�Y��IA�ܩ�PIj���G�I�i%�t:�^��u���V~�8:9Ϥ{`�Pw��hI~��S�.�+@�_Pn�eO"��/�ߪ{�c���eh���嵵�q�c� p\ݩ��K���YĜAq�V|�.�ݙ���آ���Z�YY�{(�Ge��H. +]�\��o�)��n_�b[ +�3v(#H�E�D拒a+�Z� a$��)w�������xrg�����j��sE�,t"lQ�: [r����]�x�,� w�3�Z#g+|J��L�p�C.�U���/؟�7�b�WTi� +"����E[Uz�x+@����T8i\�t����Q3�䱨��<�fO��c}I�lbXX�����'6.@�xb��������|�l���� o�G|*Xn`�d6}���$��@��A�h4�*&�h�u��.�z\��EH޻��h�����,x!��M��ir�G<��γ�֞��>܈�D����E��.h��h��N�\ �G�{�,D��}?wi|�X�0M�%����x�ߚd�x�.ޫ8���P~-�ƥ�?�r�°�&X !���A/B") �y�����%O]�0�l�.�d�x�d�S��7�,<���d����}m��ܖ͵�����T�%="0�=垪�b~�-y�ȕ�t:A��%z|w��`ar��v��t�9q?�,�0 �4��5q�l)�~tr��We�Bd�K�Y�<�{�=&IJh ��V���7�Jb� 2��>��oY�����Q_�&ԝ�Ŭ�{�X³nrcQqfǀ9����oz�8������.1�u⍬X�J��:� +:~���k謲tP'���N�([A'U-�h �|ݵZ�:I�t��{��N���[-�L �|�L��C5t�tl���5t�pt�� j�n&.����Y<[C� S�T�AI�[C�OH�){�Ju�́��:<�$�H/hԽ��*��^�b��&�2�3÷~E� +#� �C���{|�~����$���׊��5��r��)������ �������r�rNq�$E��ק��c�2Q�&�W>ދ\�4I�dNP�u�hN��e�C���;TuQ�[2��1�d���acɮ�Sh��W��}i�m��b7~&�ÁA(ɰ��و1,v����{�n�6�� �����IẦw���W���V������ A�J,T�;�ҳ�uwv/ + +����w��;�ݝ�g�Y +����-wQ�)���(t��C�r�����G����t�`+�$١8� � hJ��^%�!g��y�;��Wr��� )��R���ߕd�t>�u�hK�^��P�HWDI���c?E�3��% { ���4��2;A���t��O���"p�2N�I���hDF�I|i�����IyDF.;]IQ�L��gwĎ��/]��̶DGd��R���&��� ��'�6����\��m�� ��$�ݡ� �wA�`��tD�� Ӄ�+R���=��������9��so� w_�eI8o��}�j,K�+1��J��ek��e2����n����NY2s��"+*C3�2I�����N{4I��ݦ.�U ޭ�{8i�&C��I��F,� �eZ}�^ ކ�wK'釟2���c�|_��(F��M>G���n&�0���Qj8Q.�yug�QGp/O���&��Z����%Wa�ɍ�D�k�%������� +��\��& +/վ�b�Ug�(��� ʔ�5-QA��jZ@�w_�o�K�� ���$Q47,�U�����D�(&C4��D���-JZI�ȗ�ߕb�M��E��^R'��n��\�w�)��kq18:�����c���i��s�N��C� ��(/�����g/�.���+!T�0�J�Qw�h���A{�/�IN�I�g�܏.��$�H�aԼ��^���{��L^��t�9�����v������{�߽g��?K����)�/���R�'n�S�t���_U��&�x5���D�^YM��|�'���b����j:�Z�_V>�TӉ��E3ퟪ���2g��{��N �� !����݊�h5��> �V���N,����_TM'VK�(��j:�X;{�����Nlt�_ZM'�+��j:�Z:��柩�6��d�TӉ�b����������g�����u�U�t�嫫�s�j:>*�+9�y�c�WӉm�H�\��j:1LF�+���j�<�WVӉ��-�WT���^_SM'�3�j�{��k��Ęq��_VM'� �������j���{� SW�{��k +��.��͡��I��8Gs$��'�6Iֺ��}u�օ2>ݽ�V�'�:�!����+A��hճt��M�[��ϴ��]��1��&��4��,�$�D +) #�'��*�r_ҹQ]h��{D%�Gt�f}'2w�-K��;�5k���s���俻�N�v9�Ew�����4�J.E�t/8=�$� ��5w +n��-SRv�ݽ�"}��/+��;�)�B�p[�;����Ew�6�������s��/��ؗ_�����Z���\��|JUև�����;� F�s/���NQ�J��P&�Cy}���4eId�;E����-ӭ� +��:���$�{`h���]�l(�P���l(��U6]����~?2�$ҍ`�� �B�����W�G�ݮbX��]ϳ3��r[�.R)|���A������d kp�Ma�~g/I�9"Sd ���2��[)3��%���N��w�{�͡��n���D2��>3 /��6���x�)�)��z��MyOOC���uI +�C~uÝ�}���;aa��D �C-^����.��� k]%��*e��.�S��_+q�÷�U>J���9W4��D��gD�_RX�$:I�b��H� ',bbeL���&�����.�]�2���X,Օ_��ĵ�3���1�/�y+b��b�ʘx��� �]Q��"]�d�-� �q��Y ���J�p�����+��CEL�x��D���1Ð�H}���a8�n � EW ���5�>;I lz��H�8���JI{lB��h*ȱ�ոN(ɣ��a���dpR�n���$����ݯz�_� +�tG"��pr5�W�(��D�cP�H�}Ա%z����u`��'�X��,�K��}O%�� �A�<��4��\z�;?y�i@�h���Ew�d=y�vH�Y��E�����I߮�|�]s��$ +�~n^kK�կ5�J�љ�=l�6/v�����K�����n{驠�,]���DŽw�q��B�����S]k�I�q� {�_Gy���m�=I�F�X����E�\^�(I���qD�(��;��.@�@bœ��my�/7:mw�p��J��0ʾ��b�-Y�c�IX"��爌iK/����܉�VH��Na�$g7�{����IEj5�9A��N�D�V<��*�&��2�z(�J��)G��f���%(���H�gF>�Jr�7�dL��)���'J�34�\R)�'����;׌���IWZuw��ş/ʙ�P6�E�#�(�8�cMr�ܚ���67���D�e��o�L�>t���?��-X��-����C��ٗ�� ��)5��c�W k%��8ך��]@��; +���y?{%�H��=�%+���(>�Oq���L܇{r�L�`�W���F�i� +��1MU��+��q��:>��q~%!;>�Q�up/�VF`Tf��z�y1��z��Lx��ײE�9�e���s�M�/��9 +�q^���Nf�����|}/U���R�)�w���켯���B��@*�+M9�����T��!��<±��<�;)ع|�cw��y�c2i7�¢�!ñ�V*�Tx�d��2�S:;]I��@a<��*@.�\ ��_T*�����*@���z��<�G����qy� +Pi �J;#�G��ȿ�o�m g��+ޫW\ʧ�F��.���?y)�0�🹔O�F�W]�kF�(/��O��� +_)�0 +����O�~�^ʧ��ʧ.��F`�p���`Xt�4X�V��φb��{��Kٽ~JΆz�^�k�^p6��~�)��^?����l(�{��B���/��!���~s��}I~Ž~�!��ճ���Vr��Ŕ�:�����t��������������+n��O�C������v�������R��RgA��^?���X���{����6��{����@%w����+���{�n�9x��=u�I��s��{��o�{�}|6y,*�x�_� ��"�H���~��&�V?�� O��'��̮��S: +��^�]����O�V��$��y��|���"��{�.�[�}�s��{��e������������+���S<���=_� }��c�����o�g�>~����-t�� ���X��[���J�SM3��I�W~t�^?��f�]�O������˿����$ ����>L�{�����)���p{��CW��T�>y�7-��V?:��{�ΥR��=\��+3GT�~q��t��W��M�{���{Uv��������O�V����e._[����D1<�V� �'����y���'u�٣��ɧw�W����'��ď���^?�Z���.{.���'�^�_�-��s��r��{�.�K�V��f +����� O��'o��� ��S�"?���7�� �����'�|��?{��2�={��u$�6p��k��O~q���|aA,���7;��o�6�.�d|Ε�w�N:��_+�a��,�x/([�IӞ���� �F�:�y!/뎻B�e�(L�K��<�b_�iX/o��c��t�m�1���@���`�40�Y  !�Qa�8b?Ik$~Y�����Y� 4��q��{͕�I4G�*h����bm 4Le;_�� �D�UO�W=�K-�h=n��(vB��� ��u��p��������y�D-�JK�d�{k���0eo�\��Gu� �� hmy�C�q��K) +��V{j��\��#NI0+��8���� �R�[%� ++߭Q�1��R���OKh�^�Rڑ+W�^���Ƃ�Oԙ#�&��f{�1}����M0�����4��o�t���p�p4 �hͫ�#v��kn3�.t"6s"+!֦��nk��bOzf���^@,-�)v�2�0lah� +���eI�"��t�^�� +B��A��j|j�]��">��[�:���G�Q��p�_�R4��~p x�`f��V���~ ��4������eQ-p�Nׇ���䖨P�r�?Z�b���=�Ǭ��.S�Ϗ���#w��v~Pt^�k�=�j{��v��Q-|j[�9�\��}�� ��a�8��!�������;�j���~��;��,aI�*�laSDDpaS�P��y�~�|����N��C��9��y�v��U����et��g.d&un�u�Ƶz�Z�}�TC��5�6x�jT����|����ى�����/�֔�Y�������r����o�}~a���k�Q��6v�k����^���CuE�:t��ڛf���biwq�#z����y{w7ˡu�P����N_�՗����_���W�я����P����r�nT.>K�����������澭٭�/��/[�s��=Ǖ����{�_��ξ��5�h8�U�l�߯��MN(�O�����(����Ծ��4��lKs�����N���K9����^����g��o3p�n����ӡw�_FW�e듸V�{�uW��է��̻�����G�gRU�i�bq$�d�T�Tt.��Ý�f��jd=l1<ŵ�接��i��7�3<����?~=k�>x�1��'-\�^���$���ɓ�q�K�Т�����a�6��T������uR ��ma�a���4Z�蜋�z‘�¨Ή�O� Ǯx��st����IOқ��k�7��86����ݾ�wK/�\���j�)CaM���5.Q\�~�]q���p./z φ��Oo�O~9U�Ł��o�E)+��������۫譥��v$̓����wa��$��q3�?�9����li�{��?���J�r�;ϭ��@dU볱��Y���edl�v]u�����S�[���mj�{<3}����Zbn���LBoܪ�.�@���(?��o�϶��:�ڼ������驞byfmjci��oqb�����xa��dן��ٷ�C���KS�/צ�� �9��|�~���ܯw�'�>'z��~�s�}xCV �Z�����f� +�_fez��������������{O��'��k�>t8��8����3�����q�����$���4}�w��Y\����1����zf~�Gi�p�w������l�Wi*m.�W~��_B�|� +��³����/Z*+ssc��o+}���6��q�ሽ٭w����D�����-�D���T�(��3��O�}�vt���\.Om��������^+WzzF�f�N>�&�>�_�:��&���g��݉�w��v�z�Ib�i|�-Y�#]�#�U�N|��e�X<�L�Z��4[؞|��g{e������N�����J�az_��g�[�3����vS��}���??�;����7x��u�ɩ_p��d�do��O�'��ə���^�vsy����0�Z������щ�o�>�9���j���fK���ޣ��\����of�<x6��e����������k����]Ԇ뜻ya?SԣV� +{��/��`KK\?��MW�&�No~�AaK�4aΝ�c[���I}�� }!��Q۹��rt4>򺩵޳��9G������4X��-kQ�r۰^GP�ZJ7��o^�7��K���X�j����F�[�K:��vY-v�d�A���v�4�s�aޝ���î�J9�M.5޷O4~��6wm}y~l ��������O����_5��]mi�:��*�zF�ʳoB�ݳ��.*�GG]�]��m�����н�B� mMJ�����Z?�Ӿ�'�?fM�k[?��3�1��5�h�շ��(Y�[*__��sE����?�U�k0��Q�B������N�|�8n)�n��g���������3�>�(��Y�4wn��[+h��*�����a�E���%�򃡣�I�<�%U�BE������h4� ���Y �ȋ�FO�nބg�#����Y������빺 �6|�z&�ި~i���=r����&޽[�J�����W�/�Z/�X���Kk��)u'u��"�rV�������I�(��k�\�3pÏ/��x���V�ge���u����n�W��&T2��f��ͳx�sG�`�~�U�#��IFܠx5x�WDZ����Ӎ��oR�,\Y��"7P���c��W�����'�i��B:ds���㭙�d�M2t�����z����t~�|��s����JX��&�$�i^�d�ޫ�2�ۅ�/{m4�}]��.�/||٣���^����#������O�|����{���e���=]������}^�S��^��a�(�8KZW����X��z�L���u��g���p�8��+Q�@pvy\ǵ�l������P��\M�����Ss���6����|���s�}W�>�ϩ3�oqF�_fm�r������:� �֩�ڭ����1՛;Div}����Kǁ�m�����dzՙB+F>K#���������� +}-�����x�"�����n?���J5[t��7�Ѳ����R�"ǣZ� ś�v��X�ܩ��x���������r�"^�#�}C��k������RYx����0���-���=�)����w&�q4��V���ﶬ|��t�\�mN�M��Oa3��o�k��>K���� ^h=I���\��6o�#T�D���!|��,��o��l���T����tt�FC%[�_�+�Ժ�q��ל%As���Y>���Z"���#�'iƞ�xQK��O=�V�����|�.8 n���.�JW��^{�B\w�����!��;�=ą��vؙ��l��KD�HV��whj=w����۝�tO��b��X�����s��_�1��v�L�us�x��Z�=D���{����겝F�RW��C�xr�y�S�n-\x�:sD��}��u�!���\R���������.e����4�?��P�*�oc��^�F�w�xm:�zҙ\��e��4}��OU1����Q�a���;ҿ �������}*��->�<��sVs��G�,�]y�͞�3H��<���o��d���ߑk2H\�\u�����[����b��Hc�1?�X��;��عo1�R��;=�-�0�ҳ:�;��fkgV#7��� +�z�C�$�ܷ�[�U�*}n��rĥ5�UW�s�X�u��T؊����σӽ�D�x��@�=n"�����:�����2^�Z��ۙ[����ɣ��d`�:�0L�:=�+�1��Rz|�Y*}z;5Px���o�h�83���o���h�O��Ԙ3����޳&����NgeE� ��ם��;�ِ�U5�q�]=�>7��|<�������>����o���EM�������#��O'���V'�Nt��=�>^���Qu���h2;(jg|�p�����1Yg&�Ox#� +B�{ё���ao�����?E_j�o<6|������dUT���S~ÿ)f3�����S��#!y_�.��(m�KI�E�z�z��܁]5��pzFq6=.��W������o|�a2��X�'1�K�D��ߑ$��lj?я#�ڶt�a��ɏ����9dm�]gH�o��m�'[� j����̿5ݺ[{�+��j�K퍞��ƾ�5{#�q�g���L���N�w��T�͞ϵ7��o��K�Ro5c/���C;y�:�#��ڱ_�?��Y(⯻k�n-Qyi+��N:�B��y]���(}���t@�>��PJWj-�n�j��������Z¼��?����u��I����⻖|��� �f�S� Y�������[��񊧞�W��<�}���ɷ�kɴ���ɴێ��?j��W+f��B��ؕ�[�����$9�+l�ב��y�.�l��7�ɫ��}��s�xzg��^(/� +�3��uj����Z���ȸ�d��}������t-���̷���c��>#�!Zv�TEn���j �J�CF�:I �@O[�U�=���tvi.x�850�ܨ���H���������|ɊB��4��`m�le���K���{=[�o^����lI.^]�o^��O�aI�y9n�u������%���8Y%�/�J��/��S_��re~,t7�%�T��~!� ˕ϯ�W�V��6��뙎��t����Z�t�!��ԯ�o��˟�.V^,4�Ɋk\@�%�j >έ�_�}����̎�Ԛ����O��_?O����6��=��M6���d��ˮ��&���3��(�d-�xu���r��C�f��X/�%,��5x���CJ#�tF|���qtS��;,"R*��ڲ@�t�P�`�H�U�;T4(���*��@ݽ�o��0��^���XWGv�[�_��z�*�Ɓ"�+o* ��� 7�ܢX�u� !P��Z?��Q�����j�b�t�h`���� �uG/]ohwa=�����m� �;uwP��g���;s��wQ���>R��D!������h����\ی�׍~�X]bf�U1ܩ���V��R%�o�� �Bi�]^�6�u�-��Ş�O�V��q�bh敢�| ?�'��r�A�6T�����4�8]�ٍW=�.YsjљEk�.j���7Vzo�NS�G\-�_��pq�Ϳ�3�,������ߨ��9Q=����=��4ro���}t�<;�U��L_���h��7�����0î������z��5z�8��A~f�m~��ar����X͓���:�� �~���qڃ�g�+]�,~u�^�;�-r. �u��ES�-��������@J� BE��"�G�;T4(�����x�M����@�����E�E˱]* g�Y\*��j<(��Eh)ůs���|7m�,P��/�Ԧ��OE�M��x��{jSd�"��|_m�S���[�";^�=�)�@Q[���Y����{kSd�"���M���L�H�\*�z�m��*(�&.}0���MԞ��병�\�D(���]������f{ro�֎ ��;�@_zK��"��j{-*-��ܷ�ZTZ��Axx]L}�{6t,��n�l����� +�F�uW���疡��Pu���,���{,<�xݛvV�/� �:=��׫�a�r���o��!��?�����X�c|��{���}Y�G.w�ώ��_/���Y���9����+�έs����cџ��u1�u�ݘ��Óĵ޴�d`- +B��¨ƵRm׌��xu�?�U_����F�f��������_�n�ۘ��;fgg^��Lj���?�7�>�� +��_(��d#�Kݭ�����\��U����*v}����������K_�WQ�x2��`C^]k�#T4(�R����@Z7 ��Sn* ѧ�$T4(��{M�h0P�V8�'T4(���u��E��7T4(�SW�h0P$��_*���3���@���S�" ����q�" ɓ��iSd��6R��Y�ȭ�p�m��xQk���W��~Wm�,P$;��O�" �g7nS�;��(��wצi��������P�Ѧ�E�T�^�Y�H�0�Ӧ�E���>�Y��r_m��;�O�"?����Y�H�˽�) +W�!��6E(j{��C�" ��K}���4��K�"{#�a�M�����hSd�"Y��~�wܫpm�H[��Ҧ�E���Y��Zwߦ�p I�m���%�E,�<��:���ٽk�n`U�ɟ��Í,�T��ǝ��dhU2�=LX���評wK�OI!+�@uN��~#LA���j��1�P�yf�G��h�����|:�*t��{C�|��Yi��2z�ד�oʕ�b�orfi1ݖn�p-B?�͇����g�����7O�.8s���y�Iƈ�h�c��hTt�����6e+�-~+�+��������"#ϖ���4k��]W����/m������~_ϖ�f���o�>~��m&M�3& ��%V�Tq�Pg�/ǖ���64'�.���}���V�–���UW��“,��vQ|X�;Y��wb� ÿ��/F~N���D�7�ZzV�t�)w�� ���V�� kc��d��l#��~ ,΄ +c<�6s�3�C!uN�D8�r��/S�YG�x!�J��T���ɣ/�_����Uv����ް/V����E�e'+/�oQ��r.�o��� O��,P^� ����1��#��d�Q�I6�=l�MS6Ɇ[����v:���*w��+o��=��M�A�h�q&jb�:��5�J]ߛ'˕��ɨ�? �/���l�ߏ�ύ=m�g���I6��x��$���5ͭOW��G���~*:τ�0<�#�G���m�c�=&w�����S���������E�9��9Y�D��0��rqowu���du&,Pv��y���Mp�>��kMT��U��˃��aq�<3����ed�E�Ie2�(ċt��3ϣ����B�';J]�}��T>.��ީ�v7�'��S����$����*�p¬��FD�x��+=V$i;uiw{�Ӎ"U�\����P����UU7�L Y��[}�f�_>����Ǘf�;�Dinz=J���a��Չ��ѡrg���|Oq.��8���{�Wd�����v���+��\���e�\��9�$twk�tz�'�vr��ړJ�ca4�1�zǮO�B��7J萓J�S ��S��f�jvN��B|�e繅�b��~\-���,F����}h������K������è�g��T�E�8:��x]Ѕ�~X=(��3�K�߿�? -�����/sWG�p�O�&��[t�����oU��cC�V�T�s��]Ϊ�$&�I���}ӊ0����a��Dy�yt ��-���?��G�l}�o��ߑ��޵W�T��8�::n]ܮ"���w-g ��pyR�G��gT�l���ɹ��Z� ��+����li��m1��E�B������� +׋Js�x2%t��E�����ޥ��ץw�0�/��]�N�P�͆��@��;��ÖFj�/�N7���@�"�n_���� Q��x.*/-#������cm��̀��Q�>z;�����z���`��t�m�t����fy:�e��u�n�^�NG��!L)�����%cMһ�\oNH��חm��������/\HV�L��������'ڦ�*qj)�d_���d�خ5@>���� +���7�v��dT":O��>T����ǡ������5j'OG5i���U���Kѫ�����{.ʮ���/V��n�����\�z� ��� �����s9�!x{���p~���������~�y���g ����p���jk&DEc#��?��2Nw5vVW�I �|mٚ�!/�ڰ�i�Y<���+�o�Md�� �~���s���D �ܜ�; ��H���Nb��IK�D�u��٤��f"����g"���w1iipuldzr���틦AG9��k��1��������?��N������e�� Ƿ�ݵʮ;?e�\�6�q-P4�����<����m&Ie3���G���Co\�G�{�3y���W��}�/����&��{6�*jW����������qC�t�Lٝ����6${6����(+�-���Z�o���[����q����v�����hh�� ��F��ˇtp�b�;��J�-��z+�G��Dը��J���I��%�:s�{�9�h�-ٕ��h�h�t�G��ih���e .f����� 5~���9��v>�T�X��}k����o�? ��x=���Áެ��.�4���b�Q�����o +���� �zN�{�YI��I������s?ʯ����<�2J�]qf���U�{�� j]<�<�/Y�o��B\�EYn��o�^��~�[�]t]�����QrCSl�yn ��\��u�5�?]����U .��6KL�먧)v��W-�ݙK�G}��$�HK��c����8 ��n�^��v o�[��I���Kf<�,cܙ���8 �Kry���)4��x5 &�]��v2Y�������r���Y���j���":��?W ����Cl���D\u����ۜ��Q��T����4�NjѲ���~y�:s�t����\�!.�N�,����MK��Ɵ���9��C|�����[gCT5��˻G��ޟ9���������-�C�O�9u��'e��\�?�� �Z�����S⪲�|��z������/7>�N�����������O�+��n�� r�����:{g3H-�՛M7�_�A�}�3)�a��i��()C��4��:�<�����J��G�������C4����&���[E�P#��^��|��D��wHꗫRbw��v9b��V#7�v�:���s̸���a�?��Cg�o=��;�G~U��h>{�n�/���ǻ�o�.�qY����7��r�9E��7�]��[������Q���>�����`�-�>��w�6<��O��olj����n��M#n��I��a��z樂1-�u��r����sU�NW�7NE��7U�l ���W�/���ah�%�����ɣ�3#. +�`��[�?y�p�*��g���Ϲo�鰊�_�e�ڭE�8��<�6Y=v��v��k�翟5=mj � �f��?6��F�y�w��o������o?w��w�Tz����/��偁CO��+>�\�mj�����`���Iǃ���z'�N*?>���s�s��#�W�/W*F��������*nE���{nE_r���`2�����>?��~����G/F?�׃R���>| ��|����Fr籲s|�H{G��sM����������ԃ����Cw���J��>=(�� F�,���{��/���Wٟ�^?� �5����џF������ȃ��r_��`_��������c��_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU�=8�mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU��@���TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ ��FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa��_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU�=8�mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU��@���TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ ��FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU endstream endobj 154 0 obj <>stream +UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa��_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU�=8�mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU��@���TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ ��FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUaw~V��8?���� +��BW ��2��\�A�l�q"}��E�ڵ�>8��9�^��z�˦��.f�u���g˔⮫��.�-�y���UWo�����*no��0 ���zY�����i����h��� ���i�l��l�f�E����0�j��)�i�+��jޔ��;?v���O���.o�:n긫�u�N�?��o�u���rqx�~�oF�(�����9��DdUI�A�TteV\O\2��������t-Y��{�Ϣ?��Ꮥr�פ���^����z��-[����X�ny�� ���g����>J�׶��g/�:m\;6���LOϗ��dmƯ3����=�z�9�{�X=��e��\k����i���j�g�;J��>_�R֪��{�����ܨ�~����{��1�� ��w���m����|����Y���b���x�@[�����#m�� �'�%zx���N�j#�ͤ�>G|ﵝ�/�l�<_YO��j�0�=�ʧG�����c����+��3�/O�X/.���+��U?5<�S��>3_�PNo���9����h@>�J9��{��iƒ��>�'ɥzxނ����V�����M����� ��d�Dǭ��/e�O�c O��|��z������o����RM +�m7(��4*\��N+; T����K�V� +�����4U��.�e��a��C�@�l�I�R[^��\pb����ob�1���tk���]��\ ��m��Z˽™`�����_3~5�S- +�=��&b���f�6���Z����? �]S�B_����L?C�� o���� _6��"�,x���t,��ֽ,��B���)ߡ�v�f'd�4���z��²���W>P�J�㉅ (!�����Rn���6&욵 �� ��_��E�� *�5d[K�ޘ"^����g��p���D�p?3��p����ZԏkY�*�EI�b�y��gR/�w�S��� S m Q +��<(�N�pa��SV|:i {U~�7�b��ޱ�&��`D�O�c�� P���E �W^5�� +ʐ��wj���G0>�/Ԛ�U���sa)�pᣕ V����S�!�����1Fj�O��rA��S�{�I��������J�%8�M�$_� �c�mW'"j0u �����`�Z��;�u���! ���(�Q��Q�$��rrt�R�@X���Ph��X�$�Ll�m)�Z�۲�7'<"�j�-�nw��Bv&�Z3=���[�l ��|"6V��1������P�#�氨ȶ S�}��a@�U�My�`�2�p'ׁ�!��4����B�{��Y��]��_���<) ��K�C�)l��V`��,�\;T-8A��9���u�)��J8h�`��P��HIk��=� Uo����@�ßmp~s��s:���+} +�+��5D������u`���Jv&�(��k���>!��S K����2!S" �2e����� �5a���ñD@{U�:gs�Z�P}�i�'q��5^f`6����A>s�F�r�zQ�$$m3[R֌y�)k��p��� �x 5b�"�w��fg(e�,@�5!F�Z5�Y�o!��� �ڽ�ԇ+,h[��@�ړ׶'q"sJ�����D�����P�7h{�4C�$x���FX�2!��&�T� -e��\�MJQ�>a�f.no�CV�f�� pq�a��?:� � �}���>s�*bTu�I������;MB��� 9Ø��Ow� &>��E�p>�PZ�m�� �|���Ty��vf9�v����_Qo#��� ,���bm���$�K��I�"�D[`��� h���y����6�d!�z�CBj��]� �. +�,�����à/��# [wR�i,�=rVu����J����.6�=<{�+xy �(�Q�.gi�5h�� ���e�#�4�bI����� v���k՛(��6cx�*u�*�a�j��(H\�-�RUuDWC��u���끄~z��IDi�0�nT�����3�ѪP�r��9<(�� +瞠�46x�yR8������tl���� ]����D����m7�0� �K��6�x�@ݗ�kKi��d�~�� hP84��2Kb� �B�4Ĵ�*�����U��VF�H pst��9��S�`Ar��1���d���]vA4���kTG�� 4(�;W��� ;�i�`������_0��K���R�f��<!�Q4=�.�MC]$H{�L$p@Z�G�,$/�ЕyPQ��Ĉ[5/Gԭ�q3@�LS�j����.�Æ^��q$��a�I`u��(��cg�*� ��D�FX������c�f��|���6�<�+��Fm�x���FdӫiE��؊t�5b���4hu�g��ˍ3F]A�4~C�B0kW��n�U��+8���[�J�X� O ¹����.q�ƾ��h�7X Yl��yo�d,nצ=n�:䊥>�zJ1�o���Z�U=65�`#&v9g�k���Cf ���|�+��DS�Z�������E�'>�� ���,���΋&�\���1�RW�q��F �%r��M �s��S)'�,�o�W����uD�US +�f�K��Uv�T~��������#'����[�1��Yc&s3�3c�Uo�h��C����T�<�H�X=<Ӌ�מ��m��C���pTs���R]GvR�r�� ^3T�E��p%v�״�� �m�t�����R����n�<;9�'$�h�j�`�$1Go#��ZC�ܭ�9��F�)H$�ҏ ��E��Uu��1���/��2{b�8��K�-y����sN���n�j��=$ey�w.��qR&�Q��Y-\1� �*�?���p��)�r�f��7w�ޠs����C�'%�έl����L���3_W� ���2x��N� �r��<{� �>Q���RS�|�v���oO�� ��&���z�#�R��}L%VP�6y`�� �7;뭮G�D������ =��� `���k����ɿq���W4��/ѩB�?�g90 ��k��%���HM��X�F *�M"�C Ep�����׻�Ge�䑺����ohv� +�wd��䗥r� �Z8I%��5ݺ�C�����~�zǡGt������%:��69���l;��ʼn�k�� I�s7:kh��k�)x������|pJ�E�p[,`^� ���%Í� @&����7R|�z�QhV��5 +�j���ŀ�+u�[%���Vf���索28HV4�Ůi�����m��r�;#�rތ������5�-G�,��k{0�V���%�5��y��N�{��p��;^������M�ꚃ���H���S�r>�yu�ʊFy�j�a�S���W�x{SQߒ� +ˠ��&u�P9�AR�՞8�X"���fǓ'q�5l$fw��L���O�MKٖ��Ic�tk,�Rm����Z���h����M���!���1���zS+|��P�H4�B}G��4���u�m��ɠެ�Z,޲A��O��G�uv�q�IE΂�)�JS=���:��{B?#C��ن�+��yӅ]S�8G�6����V�u��w8�N�����Grt;l*된eCAƿr��l��)s���\����l�Y�{l��`'��|)�7 +_;hR���>���_���� +HJ��ρG+>��3R8��J<�-Vٻ��ӆn"���u8�G[�"m��~[t�SX +bGp�9^X<:��u�������p��N{ p��a3ʭ���y)t���k������7�]��lW�nl)9:��Ԙ�݊jz�������/m��(��F��eX ?>���7!�a���}._�Ȗ/L`FQ�'���KpcG��Tݵ.���l����z����R��}$'�Q��fƋN���⨳��Q���5�ٵ�÷��.@9h��ťVo*������Ww� O��Xd� eEc��x�%m4"���\����X0l��@��ׯ�͓�h���ppL�*xđ|�q��8Dž4�`�;��q�3��k�~k �KT;�`�G�z�U)kZ_�Yb��Yf����hFx�HK5�Q���|�M��tqT +�QNw��Ivy{AT�U!ir +_�Tw3ou��e��%PY�ЛV������� ��ߊ/(\��Q�η����"N/�I׫پP +*�$�ҿ:g̓Od�R)�p���2vSN�,�)l��|���������*oQo�θ���B��������qv�-Mݿ^/m�7�/��~�v_�_�o����}������&+�JN�?_n��� �n/`�8��.��o�x�>^۵p��_�D���ȫ� �u���O�|�Ӡ�w��v�|�ݿ����||���?^~����������_~{�����/ן>����_>����߿�)X�_����_/��o] endstream endobj 142 0 obj [/ICCBased 147 0 R] endobj 5 0 obj <> endobj 31 0 obj <> endobj 56 0 obj <> endobj 81 0 obj <> endobj 106 0 obj <> endobj 123 0 obj [/View/Design] endobj 124 0 obj <>>> endobj 98 0 obj [/View/Design] endobj 99 0 obj <>>> endobj 73 0 obj [/View/Design] endobj 74 0 obj <>>> endobj 48 0 obj [/View/Design] endobj 49 0 obj <>>> endobj 23 0 obj [/View/Design] endobj 24 0 obj <>>> endobj 132 0 obj [131 0 R] endobj 155 0 obj <> endobj xref 0 156 0000000004 65535 f +0000000016 00000 n +0000000220 00000 n +0000013326 00000 n +0000000006 00000 f +0000150011 00000 n +0000000008 00000 f +0000013377 00000 n +0000000009 00000 f +0000000010 00000 f +0000000011 00000 f +0000000012 00000 f +0000000013 00000 f +0000000014 00000 f +0000000015 00000 f +0000000016 00000 f +0000000017 00000 f +0000000018 00000 f +0000000019 00000 f +0000000020 00000 f +0000000021 00000 f +0000000022 00000 f +0000000025 00000 f +0000150850 00000 n +0000150881 00000 n +0000000026 00000 f +0000000027 00000 f +0000000028 00000 f +0000000029 00000 f +0000000030 00000 f +0000000032 00000 f +0000150086 00000 n +0000000033 00000 f +0000000034 00000 f +0000000035 00000 f +0000000036 00000 f +0000000037 00000 f +0000000038 00000 f +0000000039 00000 f +0000000040 00000 f +0000000041 00000 f +0000000042 00000 f +0000000043 00000 f +0000000044 00000 f +0000000045 00000 f +0000000046 00000 f +0000000047 00000 f +0000000050 00000 f +0000150734 00000 n +0000150765 00000 n +0000000051 00000 f +0000000052 00000 f +0000000053 00000 f +0000000054 00000 f +0000000055 00000 f +0000000057 00000 f +0000150162 00000 n +0000000058 00000 f +0000000059 00000 f +0000000060 00000 f +0000000061 00000 f +0000000062 00000 f +0000000063 00000 f +0000000064 00000 f +0000000065 00000 f +0000000066 00000 f +0000000067 00000 f +0000000068 00000 f +0000000069 00000 f +0000000070 00000 f +0000000071 00000 f +0000000072 00000 f +0000000075 00000 f +0000150618 00000 n +0000150649 00000 n +0000000076 00000 f +0000000077 00000 f +0000000078 00000 f +0000000079 00000 f +0000000080 00000 f +0000000082 00000 f +0000150235 00000 n +0000000083 00000 f +0000000084 00000 f +0000000085 00000 f +0000000086 00000 f +0000000087 00000 f +0000000088 00000 f +0000000089 00000 f +0000000090 00000 f +0000000091 00000 f +0000000092 00000 f +0000000093 00000 f +0000000094 00000 f +0000000095 00000 f +0000000096 00000 f +0000000097 00000 f +0000000100 00000 f +0000150502 00000 n +0000150533 00000 n +0000000101 00000 f +0000000102 00000 f +0000000103 00000 f +0000000104 00000 f +0000000105 00000 f +0000000000 00000 f +0000150308 00000 n +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000150384 00000 n +0000150416 00000 n +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000051021 00000 n +0000150966 00000 n +0000013836 00000 n +0000018135 00000 n +0000051442 00000 n +0000033651 00000 n +0000048282 00000 n +0000051215 00000 n +0000051328 00000 n +0000019095 00000 n +0000018199 00000 n +0000149974 00000 n +0000018531 00000 n +0000018581 00000 n +0000033697 00000 n +0000048319 00000 n +0000048371 00000 n +0000051097 00000 n +0000051129 00000 n +0000051518 00000 n +0000051719 00000 n +0000052746 00000 n +0000056528 00000 n +0000122117 00000 n +0000150993 00000 n +trailer <<527CD3784726476DB3E96B5D9CE51257>]>> startxref 151131 %%EOF \ No newline at end of file diff --git a/src/samples/clock/font/dalifont.png b/src/samples/clock/font/dalifont.png new file mode 100644 index 0000000000000000000000000000000000000000..7bb364424b66bc03115e2401b2c83d091bbefbcf GIT binary patch literal 197077 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

ek&gWR0CS?dN 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%0AVl|%;{7963Ddv|gNVDSsp>94 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 S<(_?M;^`X}sG#nw(s0((Th259nZaW-jgkiWSdk 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 Z_tpf&c4@n4RS?kKyB+mDQtll1(?3!9&(r__ literal 0 HcmV?d00001 diff --git a/src/samples/clock/font/dalifontF.gif b/src/samples/clock/font/dalifontF.gif new file mode 100644 index 0000000000000000000000000000000000000000..cbde49afe49f36f2d9ce1e6d87dd09ddb1391c1d GIT binary patch literal 1960 zcmZ|G|3A}-1Hke3zS&odk9G2;G|Xh29HCM+Q_L{zi!XgKo2BSlzGZ#ghWXA+Uwr9m zMZQlvU%I8_?aOs2MOV_dy9t%_#mPmb^LgAKaL*rKkH>3A;C3%>{tXBTXaj(i{{Z|4 z%D?^J7l5ze2LhYAyk>{5uf&pzh5ODYG`1wo-V{^rr5^Y*qs20K-ZVF7{Ze^O~ka%t%fNft9uBn87!MyPXIl z2l6@loe%MB4;1jR_RzfrI2ajL9&6wPInObyO0P3T;5br8sN|HuA-|x=h0O^kHI@0b zu16Yi(ZROuU5sCeIvToF#mKgYH=B+eY0>V7X>04f8+jUs&;g7?b%9Z*6SVi*)Bx?( zdbpN0WOrje_crG4K2 zls-p^x_~pv$(Ovm!^>-2%_l%4bQJKBx)s7rDgu{4`#Tk;dx46YkHR~Q5NCt& zI`~X};x~p>%#@~|?L-{xXNv9&BceV1we0%@fP-|QZaEbttG`vVvA;ojU;q9$*F!$; z>b;Qq(*_jWo(+!jH^hF2-7=0$wI{~d=OP!6GKPmbk@CLSZP;(+Jdm+3_E&}6B2%Zj z5&&E0OVy)`HMQb%X>=(vOW?{jJvNqk)8ZzI8vK;;1d|e^s9)CiPMC~BlDWyAX@&0? zl?oQHNK0M!oKu56ddV>JVIh?jHbdMcAxur$;_|*RjsT<2YkO9>tD4hSBNIL7)c8sX z5|%Nno_l&y_nCKr(Dsx1957db@0U@Uw@(BuaI~R{wd5U)oqCVgkib>GUePEYALSns z=%#Xc;fv!e@c4JRuW6xgB?$ljS)T#H6jyRBJjIkzCyQeqF;En=tH&XMls5=@{xj;~ zY}Te$(AMyQERsfZ5(|gpcFWRy^PF~3_u@ttbdZBV1M?N~bxNVC&?VW%4)N;rXZ2Y( zIxjwvW9D6i54W2!v$s{~g$Fmj^&_F0oO!v#iXVN$W87$iJzUS-Vb4520gb&CKd@`- z_Qp7?_n>x!#5SN}tUv8THO+`%r@hkwm1U&eb?SnN_8#`d-@N6Wq|7Y+}RaVKeTF$%6+Fzy6A-cCmL zCHp>yVOFyyHSU~JSEM;F0($l(mrp_36rcmc&8sBySpCB!DC8K8PP;}JcuUom_`f)` zKD`qNzG#nQb>M|ZSrIj6NgS<#$CLW@fxuGAEE?JkVEf5gU!)H(*uL_yg&o zQcw(O=-ql)f%nz*F*mISP?X!ADGqKXl;7 z+Heu;&f@N$cR<;=9u1RWjP-<)7d~lgN|DR%*g(q3i&1NjeR2jgi5a0^ANies)zStH z?Jt4F?#EBB(Qeb|7-?@dhc3EH+vcOhP_s7J3|npW^_tD5CvCnXN{ICI=+qj#nHs*4 zYfT7XqAo>n2`u`xVGj`#%-6&G=e*HEt&!y^yxHzD7;!M3!XZx>tIXx%qRDI~6XH&K z4Vh~%@mz~um*%Tb+ z(OL5(-Ais^7>?T31X>daN_swmFRPB@b0?(X!KLvkvtCqWvQ+q2IGjbVB57=bk{{b4 zF5Hj`T9;(0V5@3%jFLc?WJV4rAcR}^bP8VDJ+6gb9t0Z16+tFD`yhr};P%comDRf@ zs$yQ0I5f>&{Tt}>R`&a*j;9p*je!~Csh79LC4=a34LyfU`dOdam%s&Gw_Nv)Z#28p zJzczF;%Jlq_x(}OHXnYZ!+0c7vtz8)B~}OxaS3gR)N@JSNGkbxDJrI9+G2>qLn}MYVkbDQS)#*-?76 zo>bW;DrCSC8{-do02jQ0L_%Dm literal 0 HcmV?d00001 diff --git a/src/samples/clock/font/dalifontF.psd.gz b/src/samples/clock/font/dalifontF.psd.gz new file mode 100644 index 0000000000000000000000000000000000000000..2a37c46254fe731b9f784beaff676fc7ccf1c382 GIT binary patch literal 20137 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 )>71x^y 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 literal 0 HcmV?d00001 diff --git a/src/samples/clock/font/dalifontG.psd.gz b/src/samples/clock/font/dalifontG.psd.gz new file mode 100644 index 0000000000000000000000000000000000000000..f45a6de72a151b65be48d1d3826488c54e388f80 GIT binary patch literal 17497 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 #include "gifenc.h" +#include "xdaliclock.h" /* Font stolen from https://github.com/def-/time.gif */ static const uint8_t font[10][5] = { @@ -99,6 +100,51 @@ LWAN_HANDLER(clock) return HTTP_OK; } +static void destroy_xdaliclock(void *data) +{ + struct xdaliclock *xdc = data; + + xdaliclock_free(xdc); +} + +LWAN_HANDLER(dali) +{ + ge_GIF *gif = ge_new_gif(response->buffer, 314, 64, NULL, 2, -1); + struct xdaliclock *xdc; + + if (!gif) + return HTTP_INTERNAL_ERROR; + + coro_defer(request->conn->coro, destroy_gif, gif); + + xdc = xdaliclock_new(gif); + if (!xdc) + return HTTP_INTERNAL_ERROR; + + coro_defer(request->conn->coro, destroy_xdaliclock, xdc); + + response->mime_type = "image/gif"; + response->headers = (struct lwan_key_value[]){ + {.key = "Content-Transfer-Encoding", .value = "binary"}, + {.key = "Cache-Control", .value = "no-cache"}, + {.key = "Cache-Control", .value = "no-store"}, + {.key = "Cache-Control", .value = "no-transform"}, + {}, + }; + + memset(gif->frame, 0, (size_t)(width * height)); + + while (true) { + xdaliclock_update(xdc); + + ge_add_frame(gif, 0); + lwan_response_send_chunk(request); + lwan_request_sleep(request, xdaliclock_get_frame_time(xdc)); + } + + return HTTP_OK; +} + LWAN_HANDLER(index) { static const char index[] = "\n" @@ -129,6 +175,7 @@ LWAN_HANDLER(index) "

 File nameTypeSize
Parent directory
 File nameTypeSize
{{{file_list.name}}}{{file_list.type}}{{file_list.size}}{{file_list.unit}}{{file_list.size}}{{file_list.unit}}
\n" \ "\n" \ ""; + response->mime_type = "text/html"; lwan_strbuf_set_static(response->buffer, index, sizeof(index) - 1); @@ -139,6 +186,7 @@ int main(void) { const struct lwan_url_map default_map[] = { {.prefix = "/clock.gif", .handler = LWAN_HANDLER_REF(clock)}, + {.prefix = "/dali.gif", .handler = LWAN_HANDLER_REF(dali)}, {.prefix = "/", .handler = LWAN_HANDLER_REF(index)}, {.prefix = NULL}, }; diff --git a/src/samples/clock/numbers.c b/src/samples/clock/numbers.c new file mode 100644 index 000000000..c8da3bdac --- /dev/null +++ b/src/samples/clock/numbers.c @@ -0,0 +1,33 @@ +/* Dali Clock - a melting digital clock for PalmOS. + * Copyright (c) 1991-2010 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "numbers.h" + +#include "font/colonE.xbm" +#include "font/eightE.xbm" +#include "font/fiveE.xbm" +#include "font/fourE.xbm" +#include "font/nineE.xbm" +#include "font/oneE.xbm" +#include "font/sevenE.xbm" +#include "font/sixE.xbm" +#include "font/slashE.xbm" +#include "font/threeE.xbm" +#include "font/twoE.xbm" +#include "font/zeroE.xbm" +FONT(E); + +const struct raw_number *get_raw_numbers(void) { return numbers_E; } diff --git a/src/samples/clock/numbers.h b/src/samples/clock/numbers.h new file mode 100644 index 000000000..4819e54e5 --- /dev/null +++ b/src/samples/clock/numbers.h @@ -0,0 +1,42 @@ +/* Dali Clock - a melting digital clock for PalmOS. + * Copyright (c) 1991-2010 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#ifndef __NUMBERS_H__ +#define __NUMBERS_H__ + +typedef unsigned char POS; + +struct raw_number { + const unsigned char *bits; + POS width, height; +}; + +#define FONT(X) \ + static const struct raw_number numbers_ ## X [] = { \ + { zero ## X ## _bits, zero ## X ## _width, zero ## X ## _height }, \ + { one ## X ## _bits, one ## X ## _width, one ## X ## _height }, \ + { two ## X ## _bits, two ## X ## _width, two ## X ## _height }, \ + { three ## X ## _bits, three ## X ## _width, three ## X ## _height }, \ + { four ## X ## _bits, four ## X ## _width, four ## X ## _height }, \ + { five ## X ## _bits, five ## X ## _width, five ## X ## _height }, \ + { six ## X ## _bits, six ## X ## _width, six ## X ## _height }, \ + { seven ## X ## _bits, seven ## X ## _width, seven ## X ## _height }, \ + { eight ## X ## _bits, eight ## X ## _width, eight ## X ## _height }, \ + { nine ## X ## _bits, nine ## X ## _width, nine ## X ## _height }, \ + { colon ## X ## _bits, colon ## X ## _width, colon ## X ## _height }, \ + { slash ## X ## _bits, slash ## X ## _width, slash ## X ## _height }, \ + { 0, }, \ +} + +const struct raw_number *get_raw_numbers (void); + +#endif /* __NUMBERS_H__ */ diff --git a/src/samples/clock/xdaliclock.c b/src/samples/clock/xdaliclock.c new file mode 100644 index 000000000..0ce35e49f --- /dev/null +++ b/src/samples/clock/xdaliclock.c @@ -0,0 +1,377 @@ +/* + * Lwan port of Dali Clock + * Copyright (c) 2018 Leandro A. F. Pereira + * + * Based on: + * Dali Clock - a melting digital clock for Pebble. + * Copyright (c) 2014 Joshua Wise + * Copyright (c) 1991-2010 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#include +#include + +#include "gifenc.h" +#include "numbers.h" +#include "lwan-private.h" + +#define ANIMATION_TIME_MSEC 1000 + +/**************************************************************************/ +/* Scanline parsing. + * + * (Largely stolen from the PalmOS original). + */ + +#define MAX_SEGS_PER_LINE 3 + +struct scanline { + POS left[MAX_SEGS_PER_LINE], right[MAX_SEGS_PER_LINE]; +}; + +struct frame { + struct scanline scanlines[1]; +}; + +struct point { + int x; + int y; +}; + +struct xdaliclock { + ge_GIF *gif_enc; + + int current_digits[6]; + int target_digits[6]; + + struct frame *temp_frame; + struct frame *clear_frame; + + uint32_t animtime; + uint32_t fps; + + time_t last_time; +}; + +static struct frame *base_frames[12]; +static POS char_height, char_width, colon_width; + +static struct frame *frame_mk(int width, int height) +{ + struct frame *fr = + calloc(1, sizeof(struct frame) + + (sizeof(struct scanline) * ((size_t)height - 1))); + POS half_width = (POS)(width / 2); + int x, y; + + if (!fr) + return NULL; + + for (y = 0; y < height; y++) { + for (x = 0; x < MAX_SEGS_PER_LINE; x++) { + fr->scanlines[y].left[x] = half_width; + fr->scanlines[y].right[x] = half_width; + } + } + + return fr; +} + +static struct frame * +frame_from_pixmap(const unsigned char *bits, int width, int height) +{ + int x, y; + struct frame *frame; + POS *left, *right; + POS half_width = (POS)(width / 2); + + frame = frame_mk(width, height); + if (!frame) + return NULL; + + for (y = 0; y < height; y++) { + int seg, end; + x = 0; + +#define GETBIT(bits, x, y) \ + (!!((bits)[((y) * ((width + 7) >> 3)) + ((x) >> 3)] & (1 << ((x)&7)))) + + left = frame->scanlines[y].left; + right = frame->scanlines[y].right; + + for (seg = 0; seg < MAX_SEGS_PER_LINE; seg++) { + left[seg] = half_width; + right[seg] = half_width; + } + + for (seg = 0; seg < MAX_SEGS_PER_LINE; seg++) { + for (; x < width; x++) { + if (GETBIT(bits, x, y)) + break; + } + if (x == width) + break; + left[seg] = (POS)x; + for (; x < width; x++) { + if (!GETBIT(bits, x, y)) + break; + } + right[seg] = (POS)x; + } + + for (; x < width; x++) { + if (GETBIT(bits, x, y)) { + /* This means the font is too curvy. Increase MAX_SEGS_PER_LINE + and recompile. */ + lwan_status_debug("builtin font is bogus"); + return NULL; + } + } + + /* If there were any segments on this line, then replicate the last + one out to the end of the line. If it's blank, leave it alone, + meaning it will be a 0-pixel-wide line down the middle. + */ + end = seg; + if (end > 0) { + for (; seg < MAX_SEGS_PER_LINE; seg++) { + left[seg] = left[end - 1]; + right[seg] = right[end - 1]; + } + } +#undef GETBIT + } + + return frame; +} + +__attribute__((constructor)) static void initialize_numbers(void) +{ + const struct raw_number *raw = get_raw_numbers(); + + char_width = raw[0].width; + char_height = raw[0].height; + colon_width = raw[10].width; + + for (unsigned int i = 0; i < N_ELEMENTS(base_frames); i++) { + struct frame *frame; + + frame = frame_from_pixmap(raw[i].bits, raw[i].width, raw[i].height); + if (!frame) + lwan_status_critical("Could not allocate frame"); + + /* The base frames leak, but it's only one per program instance */ + base_frames[i] = frame; + } +} + +static inline POS lerp(const struct xdaliclock *xdc, POS a, POS b) +{ + uint32_t part_a = a * (65536 - xdc->animtime); + uint32_t part_b = b * (xdc->animtime + 1); + + return (POS)((part_a + part_b) / 65536); +} + +static void frame_lerp(struct xdaliclock *xdc, int digit) +{ + const int from = xdc->current_digits[digit]; + const int to = xdc->target_digits[digit]; + struct frame *fromf, *tof; + int y, x; + + fromf = from >= 0 ? base_frames[from] : xdc->clear_frame; + tof = to >= 0 ? base_frames[to] : xdc->clear_frame; + + for (y = 0; y < char_height; y++) { + for (x = 0; x < MAX_SEGS_PER_LINE; x++) { + xdc->temp_frame->scanlines[y].left[x] = lerp( + xdc, fromf->scanlines[y].left[x], tof->scanlines[y].left[x]); + xdc->temp_frame->scanlines[y].right[x] = lerp( + xdc, fromf->scanlines[y].right[x], tof->scanlines[y].right[x]); + } + } +} + +static void draw_horizontal_line(struct xdaliclock *xdc, + int x1, + int x2, + int y, + int screen_width, + bool black_p) +{ + uint8_t color = black_p ? 0 : 3; + + if (x1 > screen_width) + x1 = screen_width; + else if (x1 < 0) + x1 = 0; + + if (x2 > screen_width) + x2 = screen_width; + else if (x2 < 0) + x2 = 0; + + if (x1 == x2) + return; + + if (x1 > x2) { + int swap = x1; + x1 = x2; + x2 = swap; + } + + memset(xdc->gif_enc->frame + y * xdc->gif_enc->w + x1, color, + (size_t)(x2 - x1)); +} + +static void frame_render(struct xdaliclock *xdc, struct point ofs) +{ + struct frame *frame = xdc->temp_frame; + int px, py; + + for (py = 0; py < char_height; py++) { + struct scanline *line = &frame->scanlines[py]; + int last_right = 0; + + for (px = 0; px < MAX_SEGS_PER_LINE; px++) { + if (px > 0 && (line->left[px] == line->right[px] || + (line->left[px] == line->left[px - 1] && + line->right[px] == line->right[px - 1]))) { + continue; + } + + /* Erase the line between the last segment and this segment. + */ + draw_horizontal_line(xdc, ofs.x + last_right, + ofs.x + line->left[px], ofs.y + py, + xdc->gif_enc->w, 1 /* Black */); + + /* Draw the line of this segment. + */ + draw_horizontal_line(xdc, ofs.x + line->left[px], + ofs.x + line->right[px], ofs.y + py, + xdc->gif_enc->w, 0 /* White */); + + last_right = line->right[px]; + } + + /* Erase the line between the last segment and the right edge. + */ + draw_horizontal_line(xdc, ofs.x + last_right, ofs.x + char_width, + ofs.y + py, xdc->gif_enc->w, 1 /* Black */); + } +} + +void xdaliclock_update(struct xdaliclock *xdc) +{ + time_t now; + int x; + + now = time(NULL); + if (now != xdc->last_time) { + struct tm *tm; + + for (int i = 0; i < 6; i++) + xdc->current_digits[i] = xdc->target_digits[i]; + + tm = localtime(&now); + + xdc->target_digits[0] = tm->tm_hour / 10; + xdc->target_digits[1] = tm->tm_hour % 10; + xdc->target_digits[2] = tm->tm_min / 10; + xdc->target_digits[3] = tm->tm_min % 10; + xdc->target_digits[4] = tm->tm_sec / 10; + xdc->target_digits[5] = tm->tm_sec % 10; + + xdc->last_time = now; + xdc->animtime = 0; + } + + /* Hours... */ + x = 0; + frame_lerp(xdc, 0); + frame_render(xdc, (struct point){.x = x, .y = 0}); + x += char_width; + + frame_lerp(xdc, 1); + frame_render(xdc, (struct point){.x = x, .y = 0}); + x += char_width; + + /* Minutes... */ + x += char_width / 2; + + frame_lerp(xdc, 2); + frame_render(xdc, (struct point){.x = x, .y = 0}); + x += char_width; + + frame_lerp(xdc, 3); + frame_render(xdc, (struct point){.x = x, .y = 0}); + x += char_width; + + /* Seconds... */ + x += char_width / 2; + + frame_lerp(xdc, 4); + frame_render(xdc, (struct point){.x = x, .y = 0}); + x += char_width; + + frame_lerp(xdc, 5); + frame_render(xdc, (struct point){.x = x, .y = 0}); + x += char_width; + + xdc->animtime += 65535 / (xdc->fps + 1); +} + +struct xdaliclock *xdaliclock_new(ge_GIF *ge) +{ + struct xdaliclock *xdc = malloc(sizeof(*xdc)); + + if (!xdc) + return NULL; + + xdc->animtime = 0; + xdc->fps = 10; + xdc->gif_enc = ge; + xdc->last_time = 0; + + xdc->temp_frame = frame_mk(char_width, char_height); + if (!xdc->temp_frame) { + free(xdc); + return NULL; + } + + xdc->clear_frame = frame_mk(char_width, char_height); + if (!xdc->clear_frame) { + free(xdc); + return NULL; + } + + for (unsigned int i = 0; i < N_ELEMENTS(xdc->target_digits); i++) + xdc->target_digits[i] = xdc->current_digits[i] = -1; + + return xdc; +} + +void xdaliclock_free(struct xdaliclock *xdc) +{ + if (!xdc) + return; + + free(xdc->temp_frame); + free(xdc->clear_frame); + free(xdc); +} + +uint32_t xdaliclock_get_frame_time(const struct xdaliclock *xdc) +{ + return ANIMATION_TIME_MSEC / xdc->fps; +} diff --git a/src/samples/clock/xdaliclock.h b/src/samples/clock/xdaliclock.h new file mode 100644 index 000000000..6e6da0e0b --- /dev/null +++ b/src/samples/clock/xdaliclock.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include "gifenc.h" + +struct xdaliclock; + +struct xdaliclock *xdaliclock_new(ge_GIF *gif); +void xdaliclock_free(struct xdaliclock *xdaliclock); + +void xdaliclock_update(struct xdaliclock *xdaliclock); +uint32_t xdaliclock_get_frame_time(const struct xdaliclock *xdc); + + From 88e7d23f2523deb13497087985c5671a148b35d1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 1 Sep 2018 16:38:35 -0700 Subject: [PATCH 0801/2505] Get rid of `struct point` in xdaliclock The GIF always start at y=0, so there's no need to pass a point around. Also, roll the digit drawing code into a foor loop. --- src/samples/clock/xdaliclock.c | 62 ++++++++-------------------------- 1 file changed, 14 insertions(+), 48 deletions(-) diff --git a/src/samples/clock/xdaliclock.c b/src/samples/clock/xdaliclock.c index 0ce35e49f..8dcb993d5 100644 --- a/src/samples/clock/xdaliclock.c +++ b/src/samples/clock/xdaliclock.c @@ -41,11 +41,6 @@ struct frame { struct scanline scanlines[1]; }; -struct point { - int x; - int y; -}; - struct xdaliclock { ge_GIF *gif_enc; @@ -233,7 +228,7 @@ static void draw_horizontal_line(struct xdaliclock *xdc, (size_t)(x2 - x1)); } -static void frame_render(struct xdaliclock *xdc, struct point ofs) +static void frame_render(struct xdaliclock *xdc, int x) { struct frame *frame = xdc->temp_frame; int px, py; @@ -251,40 +246,38 @@ static void frame_render(struct xdaliclock *xdc, struct point ofs) /* Erase the line between the last segment and this segment. */ - draw_horizontal_line(xdc, ofs.x + last_right, - ofs.x + line->left[px], ofs.y + py, + draw_horizontal_line(xdc, x + last_right, x + line->left[px], py, xdc->gif_enc->w, 1 /* Black */); /* Draw the line of this segment. */ - draw_horizontal_line(xdc, ofs.x + line->left[px], - ofs.x + line->right[px], ofs.y + py, - xdc->gif_enc->w, 0 /* White */); + draw_horizontal_line(xdc, x + line->left[px], x + line->right[px], + py, xdc->gif_enc->w, 0 /* White */); last_right = line->right[px]; } /* Erase the line between the last segment and the right edge. */ - draw_horizontal_line(xdc, ofs.x + last_right, ofs.x + char_width, - ofs.y + py, xdc->gif_enc->w, 1 /* Black */); + draw_horizontal_line(xdc, x + last_right, x + char_width, py, + xdc->gif_enc->w, 1 /* Black */); } } void xdaliclock_update(struct xdaliclock *xdc) { + const int offsets[] = { + 0, 0, char_width / 2, char_width / 2, char_width, char_width, + }; time_t now; - int x; now = time(NULL); if (now != xdc->last_time) { - struct tm *tm; + struct tm *tm = localtime(&now); for (int i = 0; i < 6; i++) xdc->current_digits[i] = xdc->target_digits[i]; - tm = localtime(&now); - xdc->target_digits[0] = tm->tm_hour / 10; xdc->target_digits[1] = tm->tm_hour % 10; xdc->target_digits[2] = tm->tm_min / 10; @@ -296,37 +289,10 @@ void xdaliclock_update(struct xdaliclock *xdc) xdc->animtime = 0; } - /* Hours... */ - x = 0; - frame_lerp(xdc, 0); - frame_render(xdc, (struct point){.x = x, .y = 0}); - x += char_width; - - frame_lerp(xdc, 1); - frame_render(xdc, (struct point){.x = x, .y = 0}); - x += char_width; - - /* Minutes... */ - x += char_width / 2; - - frame_lerp(xdc, 2); - frame_render(xdc, (struct point){.x = x, .y = 0}); - x += char_width; - - frame_lerp(xdc, 3); - frame_render(xdc, (struct point){.x = x, .y = 0}); - x += char_width; - - /* Seconds... */ - x += char_width / 2; - - frame_lerp(xdc, 4); - frame_render(xdc, (struct point){.x = x, .y = 0}); - x += char_width; - - frame_lerp(xdc, 5); - frame_render(xdc, (struct point){.x = x, .y = 0}); - x += char_width; + for (int digit = 0, x = 0; digit < 6; digit++, x += char_width) { + frame_lerp(xdc, digit); + frame_render(xdc, x + offsets[digit]); + } xdc->animtime += 65535 / (xdc->fps + 1); } From 9dbbcd2f7eee7dd0e1b4bfb5fb213ec4ae6f95f1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 1 Sep 2018 16:42:31 -0700 Subject: [PATCH 0802/2505] Use an enum to signal the paint instead of a boolean --- src/samples/clock/xdaliclock.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/samples/clock/xdaliclock.c b/src/samples/clock/xdaliclock.c index 8dcb993d5..271c879d1 100644 --- a/src/samples/clock/xdaliclock.c +++ b/src/samples/clock/xdaliclock.c @@ -56,6 +56,8 @@ struct xdaliclock { time_t last_time; }; +enum paint_color { BACKGROUND, FOREGROUND }; + static struct frame *base_frames[12]; static POS char_height, char_width, colon_width; @@ -201,9 +203,9 @@ static void draw_horizontal_line(struct xdaliclock *xdc, int x2, int y, int screen_width, - bool black_p) + enum paint_color pc) { - uint8_t color = black_p ? 0 : 3; + uint8_t color = (pc == BACKGROUND) ? 0 : 3; if (x1 > screen_width) x1 = screen_width; @@ -247,12 +249,12 @@ static void frame_render(struct xdaliclock *xdc, int x) /* Erase the line between the last segment and this segment. */ draw_horizontal_line(xdc, x + last_right, x + line->left[px], py, - xdc->gif_enc->w, 1 /* Black */); + xdc->gif_enc->w, BACKGROUND); /* Draw the line of this segment. */ draw_horizontal_line(xdc, x + line->left[px], x + line->right[px], - py, xdc->gif_enc->w, 0 /* White */); + py, xdc->gif_enc->w, FOREGROUND); last_right = line->right[px]; } @@ -260,7 +262,7 @@ static void frame_render(struct xdaliclock *xdc, int x) /* Erase the line between the last segment and the right edge. */ draw_horizontal_line(xdc, x + last_right, x + char_width, py, - xdc->gif_enc->w, 1 /* Black */); + xdc->gif_enc->w, BACKGROUND); } } From 8246fba49350b8996727a4b4e44b778cc518d42b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 1 Sep 2018 16:44:34 -0700 Subject: [PATCH 0803/2505] Clean up xdaliclock_new() slightly --- src/samples/clock/xdaliclock.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/samples/clock/xdaliclock.c b/src/samples/clock/xdaliclock.c index 271c879d1..b02fcc4da 100644 --- a/src/samples/clock/xdaliclock.c +++ b/src/samples/clock/xdaliclock.c @@ -312,21 +312,21 @@ struct xdaliclock *xdaliclock_new(ge_GIF *ge) xdc->last_time = 0; xdc->temp_frame = frame_mk(char_width, char_height); - if (!xdc->temp_frame) { - free(xdc); - return NULL; - } + if (!xdc->temp_frame) + goto out; xdc->clear_frame = frame_mk(char_width, char_height); - if (!xdc->clear_frame) { - free(xdc); - return NULL; - } + if (!xdc->clear_frame) + goto out; for (unsigned int i = 0; i < N_ELEMENTS(xdc->target_digits); i++) xdc->target_digits[i] = xdc->current_digits[i] = -1; return xdc; + +out: + free(xdc); + return NULL; } void xdaliclock_free(struct xdaliclock *xdc) From 20d3751349a97f1010a2dccde075c985f5bf7324 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 1 Sep 2018 16:49:42 -0700 Subject: [PATCH 0804/2505] Clean up frame lerping --- src/samples/clock/xdaliclock.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/samples/clock/xdaliclock.c b/src/samples/clock/xdaliclock.c index b02fcc4da..02e4a405d 100644 --- a/src/samples/clock/xdaliclock.c +++ b/src/samples/clock/xdaliclock.c @@ -182,18 +182,18 @@ static void frame_lerp(struct xdaliclock *xdc, int digit) { const int from = xdc->current_digits[digit]; const int to = xdc->target_digits[digit]; - struct frame *fromf, *tof; - int y, x; - - fromf = from >= 0 ? base_frames[from] : xdc->clear_frame; - tof = to >= 0 ? base_frames[to] : xdc->clear_frame; + struct frame *fromf = (from >= 0) ? base_frames[from] : xdc->clear_frame; + struct frame *tof = (to >= 0) ? base_frames[to] : xdc->clear_frame; + int x, y; for (y = 0; y < char_height; y++) { + struct scanline *line = &xdc->temp_frame->scanlines[y]; + struct scanline *to_line = &tof->scanlines[y]; + struct scanline *from_line = &fromf->scanlines[y]; + for (x = 0; x < MAX_SEGS_PER_LINE; x++) { - xdc->temp_frame->scanlines[y].left[x] = lerp( - xdc, fromf->scanlines[y].left[x], tof->scanlines[y].left[x]); - xdc->temp_frame->scanlines[y].right[x] = lerp( - xdc, fromf->scanlines[y].right[x], tof->scanlines[y].right[x]); + line->left[x] = lerp(xdc, from_line->left[x], to_line->left[x]); + line->right[x] = lerp(xdc, from_line->right[x], to_line->right[x]); } } } From 91fdef998f5092ea636a960a16d7da5826fdc0bd Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 1 Sep 2018 16:54:58 -0700 Subject: [PATCH 0805/2505] Use an inline function instead of a macro to implement GETBIT() --- src/samples/clock/xdaliclock.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/samples/clock/xdaliclock.c b/src/samples/clock/xdaliclock.c index 02e4a405d..fe33e4e05 100644 --- a/src/samples/clock/xdaliclock.c +++ b/src/samples/clock/xdaliclock.c @@ -82,6 +82,11 @@ static struct frame *frame_mk(int width, int height) return fr; } +static inline bool get_bit(const unsigned char *bits, int x, int y, int width) +{ + return bits[(y * ((width + 7) >> 3)) + (x >> 3)] & 1 << (x & 7); +} + static struct frame * frame_from_pixmap(const unsigned char *bits, int width, int height) { @@ -98,9 +103,6 @@ frame_from_pixmap(const unsigned char *bits, int width, int height) int seg, end; x = 0; -#define GETBIT(bits, x, y) \ - (!!((bits)[((y) * ((width + 7) >> 3)) + ((x) >> 3)] & (1 << ((x)&7)))) - left = frame->scanlines[y].left; right = frame->scanlines[y].right; @@ -111,21 +113,21 @@ frame_from_pixmap(const unsigned char *bits, int width, int height) for (seg = 0; seg < MAX_SEGS_PER_LINE; seg++) { for (; x < width; x++) { - if (GETBIT(bits, x, y)) + if (get_bit(bits, x, y, width)) break; } if (x == width) break; left[seg] = (POS)x; for (; x < width; x++) { - if (!GETBIT(bits, x, y)) + if (!get_bit(bits, x, y, width)) break; } right[seg] = (POS)x; } for (; x < width; x++) { - if (GETBIT(bits, x, y)) { + if (get_bit(bits, x, y, width)) { /* This means the font is too curvy. Increase MAX_SEGS_PER_LINE and recompile. */ lwan_status_debug("builtin font is bogus"); @@ -144,7 +146,6 @@ frame_from_pixmap(const unsigned char *bits, int width, int height) right[seg] = right[end - 1]; } } -#undef GETBIT } return frame; From f9957d9ab4a7f06a389291e3441762fc8f481101 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 1 Sep 2018 16:56:26 -0700 Subject: [PATCH 0806/2505] Use screen_width instead of derefencing gif_enc again --- src/samples/clock/xdaliclock.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/samples/clock/xdaliclock.c b/src/samples/clock/xdaliclock.c index fe33e4e05..9285adb4d 100644 --- a/src/samples/clock/xdaliclock.c +++ b/src/samples/clock/xdaliclock.c @@ -227,7 +227,7 @@ static void draw_horizontal_line(struct xdaliclock *xdc, x2 = swap; } - memset(xdc->gif_enc->frame + y * xdc->gif_enc->w + x1, color, + memset(xdc->gif_enc->frame + y * screen_width + x1, color, (size_t)(x2 - x1)); } From 3e0cd779451db6d243440b8c2f2b7817a694a928 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 1 Sep 2018 17:10:41 -0700 Subject: [PATCH 0807/2505] Separate hours, minutes, and seconds by colons --- src/samples/clock/main.c | 2 +- src/samples/clock/xdaliclock.c | 28 ++++++++++++++++------------ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/samples/clock/main.c b/src/samples/clock/main.c index 732395d01..0daf602f3 100644 --- a/src/samples/clock/main.c +++ b/src/samples/clock/main.c @@ -109,7 +109,7 @@ static void destroy_xdaliclock(void *data) LWAN_HANDLER(dali) { - ge_GIF *gif = ge_new_gif(response->buffer, 314, 64, NULL, 2, -1); + ge_GIF *gif = ge_new_gif(response->buffer, 320, 64, NULL, 2, -1); struct xdaliclock *xdc; if (!gif) diff --git a/src/samples/clock/xdaliclock.c b/src/samples/clock/xdaliclock.c index 9285adb4d..0dc921e86 100644 --- a/src/samples/clock/xdaliclock.c +++ b/src/samples/clock/xdaliclock.c @@ -44,8 +44,8 @@ struct frame { struct xdaliclock { ge_GIF *gif_enc; - int current_digits[6]; - int target_digits[6]; + int current_digits[8]; + int target_digits[8]; struct frame *temp_frame; struct frame *clear_frame; @@ -269,8 +269,10 @@ static void frame_render(struct xdaliclock *xdc, int x) void xdaliclock_update(struct xdaliclock *xdc) { - const int offsets[] = { - 0, 0, char_width / 2, char_width / 2, char_width, char_width, + const int widths[] = { + [0] = char_width, [1] = char_width, [2] = colon_width, + [3] = char_width, [4] = char_width, [5] = colon_width, + [6] = char_width, [7] = char_width, [8] = 0 /* avoid UB */, }; time_t now; @@ -278,23 +280,25 @@ void xdaliclock_update(struct xdaliclock *xdc) if (now != xdc->last_time) { struct tm *tm = localtime(&now); - for (int i = 0; i < 6; i++) - xdc->current_digits[i] = xdc->target_digits[i]; + memcpy(xdc->current_digits, xdc->target_digits, + sizeof(xdc->current_digits)); xdc->target_digits[0] = tm->tm_hour / 10; xdc->target_digits[1] = tm->tm_hour % 10; - xdc->target_digits[2] = tm->tm_min / 10; - xdc->target_digits[3] = tm->tm_min % 10; - xdc->target_digits[4] = tm->tm_sec / 10; - xdc->target_digits[5] = tm->tm_sec % 10; + xdc->target_digits[2] = 10; + xdc->target_digits[3] = tm->tm_min / 10; + xdc->target_digits[4] = tm->tm_min % 10; + xdc->target_digits[5] = 10; + xdc->target_digits[6] = tm->tm_sec / 10; + xdc->target_digits[7] = tm->tm_sec % 10; xdc->last_time = now; xdc->animtime = 0; } - for (int digit = 0, x = 0; digit < 6; digit++, x += char_width) { + for (int digit = 0, x = 0; digit < 8; x += widths[digit++]) { frame_lerp(xdc, digit); - frame_render(xdc, x + offsets[digit]); + frame_render(xdc, x); } xdc->animtime += 65535 / (xdc->fps + 1); From a752e62e36e60e73753324095dc8aa780035b839 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 1 Sep 2018 17:11:16 -0700 Subject: [PATCH 0808/2505] Rename animtime to frame (animtime was a remnant from the Pebble port; frame makes more sense in the way this thing is implemented right now.) --- src/samples/clock/xdaliclock.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/samples/clock/xdaliclock.c b/src/samples/clock/xdaliclock.c index 0dc921e86..2a01a356d 100644 --- a/src/samples/clock/xdaliclock.c +++ b/src/samples/clock/xdaliclock.c @@ -50,7 +50,7 @@ struct xdaliclock { struct frame *temp_frame; struct frame *clear_frame; - uint32_t animtime; + uint32_t frame; uint32_t fps; time_t last_time; @@ -173,8 +173,8 @@ __attribute__((constructor)) static void initialize_numbers(void) static inline POS lerp(const struct xdaliclock *xdc, POS a, POS b) { - uint32_t part_a = a * (65536 - xdc->animtime); - uint32_t part_b = b * (xdc->animtime + 1); + uint32_t part_a = a * (65536 - xdc->frame); + uint32_t part_b = b * (xdc->frame + 1); return (POS)((part_a + part_b) / 65536); } @@ -293,7 +293,7 @@ void xdaliclock_update(struct xdaliclock *xdc) xdc->target_digits[7] = tm->tm_sec % 10; xdc->last_time = now; - xdc->animtime = 0; + xdc->frame = 0; } for (int digit = 0, x = 0; digit < 8; x += widths[digit++]) { @@ -301,7 +301,7 @@ void xdaliclock_update(struct xdaliclock *xdc) frame_render(xdc, x); } - xdc->animtime += 65535 / (xdc->fps + 1); + xdc->frame += 65535 / (xdc->fps + 1); } struct xdaliclock *xdaliclock_new(ge_GIF *ge) @@ -311,7 +311,7 @@ struct xdaliclock *xdaliclock_new(ge_GIF *ge) if (!xdc) return NULL; - xdc->animtime = 0; + xdc->frame = 0; xdc->fps = 10; xdc->gif_enc = ge; xdc->last_time = 0; From 2365c003852268343ebc74f4510965f9dfb91ba0 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 1 Sep 2018 17:18:50 -0700 Subject: [PATCH 0809/2505] Use a constant for the frame rate This should enable some optimizations (kind of moot in a program like this, though). --- src/samples/clock/xdaliclock.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/samples/clock/xdaliclock.c b/src/samples/clock/xdaliclock.c index 2a01a356d..6f6000fe7 100644 --- a/src/samples/clock/xdaliclock.c +++ b/src/samples/clock/xdaliclock.c @@ -23,6 +23,7 @@ #include "numbers.h" #include "lwan-private.h" +#define FRAMES_PER_SECOND 10 #define ANIMATION_TIME_MSEC 1000 /**************************************************************************/ @@ -50,10 +51,9 @@ struct xdaliclock { struct frame *temp_frame; struct frame *clear_frame; - uint32_t frame; - uint32_t fps; - time_t last_time; + + uint32_t frame; }; enum paint_color { BACKGROUND, FOREGROUND }; @@ -301,7 +301,7 @@ void xdaliclock_update(struct xdaliclock *xdc) frame_render(xdc, x); } - xdc->frame += 65535 / (xdc->fps + 1); + xdc->frame += 65535 / (FRAMES_PER_SECOND + 1); } struct xdaliclock *xdaliclock_new(ge_GIF *ge) @@ -312,7 +312,6 @@ struct xdaliclock *xdaliclock_new(ge_GIF *ge) return NULL; xdc->frame = 0; - xdc->fps = 10; xdc->gif_enc = ge; xdc->last_time = 0; @@ -344,7 +343,8 @@ void xdaliclock_free(struct xdaliclock *xdc) free(xdc); } -uint32_t xdaliclock_get_frame_time(const struct xdaliclock *xdc) +uint32_t xdaliclock_get_frame_time(const struct xdaliclock *xdc + __attribute__((unused))) { - return ANIMATION_TIME_MSEC / xdc->fps; + return ANIMATION_TIME_MSEC / FRAMES_PER_SECOND; } From 23b7ea2b5a9cd4d61299aeb5c043f84a2756d546 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 1 Sep 2018 17:58:42 -0700 Subject: [PATCH 0810/2505] Move initialization of widths array to initialize_numbers() No need to do this every frame. --- src/samples/clock/xdaliclock.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/samples/clock/xdaliclock.c b/src/samples/clock/xdaliclock.c index 6f6000fe7..ea27648c3 100644 --- a/src/samples/clock/xdaliclock.c +++ b/src/samples/clock/xdaliclock.c @@ -60,6 +60,7 @@ enum paint_color { BACKGROUND, FOREGROUND }; static struct frame *base_frames[12]; static POS char_height, char_width, colon_width; +static int digit_widths[8]; static struct frame *frame_mk(int width, int height) { @@ -169,6 +170,13 @@ __attribute__((constructor)) static void initialize_numbers(void) /* The base frames leak, but it's only one per program instance */ base_frames[i] = frame; } + + const int widths[] = { + [0] = char_width, [1] = char_width, [2] = colon_width, + [3] = char_width, [4] = char_width, [5] = colon_width, + [6] = char_width, [7] = char_width, [8] = 0 /* avoid UB */, + }; + memcpy(digit_widths, widths, sizeof(digit_widths)); } static inline POS lerp(const struct xdaliclock *xdc, POS a, POS b) @@ -269,14 +277,8 @@ static void frame_render(struct xdaliclock *xdc, int x) void xdaliclock_update(struct xdaliclock *xdc) { - const int widths[] = { - [0] = char_width, [1] = char_width, [2] = colon_width, - [3] = char_width, [4] = char_width, [5] = colon_width, - [6] = char_width, [7] = char_width, [8] = 0 /* avoid UB */, - }; - time_t now; + time_t now = time(NULL); - now = time(NULL); if (now != xdc->last_time) { struct tm *tm = localtime(&now); @@ -296,7 +298,7 @@ void xdaliclock_update(struct xdaliclock *xdc) xdc->frame = 0; } - for (int digit = 0, x = 0; digit < 8; x += widths[digit++]) { + for (int digit = 0, x = 0; digit < 8; x += digit_widths[digit++]) { frame_lerp(xdc, digit); frame_render(xdc, x); } From d04ca4237e6d56bfdf916b3d393ca6df1c369a84 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 2 Sep 2018 00:35:30 -0700 Subject: [PATCH 0811/2505] Use const qualifiers to signal that digits are shared --- src/samples/clock/xdaliclock.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/samples/clock/xdaliclock.c b/src/samples/clock/xdaliclock.c index ea27648c3..e5cc152a4 100644 --- a/src/samples/clock/xdaliclock.c +++ b/src/samples/clock/xdaliclock.c @@ -191,14 +191,15 @@ static void frame_lerp(struct xdaliclock *xdc, int digit) { const int from = xdc->current_digits[digit]; const int to = xdc->target_digits[digit]; - struct frame *fromf = (from >= 0) ? base_frames[from] : xdc->clear_frame; - struct frame *tof = (to >= 0) ? base_frames[to] : xdc->clear_frame; + const struct frame *fromf = + (from >= 0) ? base_frames[from] : xdc->clear_frame; + const struct frame *tof = (to >= 0) ? base_frames[to] : xdc->clear_frame; int x, y; for (y = 0; y < char_height; y++) { struct scanline *line = &xdc->temp_frame->scanlines[y]; - struct scanline *to_line = &tof->scanlines[y]; - struct scanline *from_line = &fromf->scanlines[y]; + const struct scanline *to_line = &tof->scanlines[y]; + const struct scanline *from_line = &fromf->scanlines[y]; for (x = 0; x < MAX_SEGS_PER_LINE; x++) { line->left[x] = lerp(xdc, from_line->left[x], to_line->left[x]); @@ -241,11 +242,11 @@ static void draw_horizontal_line(struct xdaliclock *xdc, static void frame_render(struct xdaliclock *xdc, int x) { - struct frame *frame = xdc->temp_frame; + const struct frame *frame = xdc->temp_frame; int px, py; for (py = 0; py < char_height; py++) { - struct scanline *line = &frame->scanlines[py]; + const struct scanline *line = &frame->scanlines[py]; int last_right = 0; for (px = 0; px < MAX_SEGS_PER_LINE; px++) { From 9380312382aa68d6cc428efa819e5251d7d6520c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 2 Sep 2018 09:39:12 -0700 Subject: [PATCH 0812/2505] Make a better clock page, with links to both Digital and Dali clocks --- src/samples/clock/main.c | 149 ++++++++++++++++++++++++++++++--------- 1 file changed, 115 insertions(+), 34 deletions(-) diff --git a/src/samples/clock/main.c b/src/samples/clock/main.c index 0daf602f3..3f2059d06 100644 --- a/src/samples/clock/main.c +++ b/src/samples/clock/main.c @@ -18,9 +18,11 @@ * USA. */ -#include "lwan.h" #include +#include "lwan.h" +#include "lwan-template.h" +#include "lwan-mod-redirect.h" #include "gifenc.h" #include "xdaliclock.h" @@ -145,50 +147,129 @@ LWAN_HANDLER(dali) return HTTP_OK; } -LWAN_HANDLER(index) +struct index { + const char *title; + const char *variant; + int width; +}; + +static const struct lwan_var_descriptor index_desc[] = { + TPL_VAR_STR_ESCAPE(struct index, title), + TPL_VAR_STR_ESCAPE(struct index, variant), + TPL_VAR_INT(struct index, width), + TPL_VAR_SENTINEL, +}; + +static struct lwan_tpl *index_tpl; + +__attribute__((constructor)) static void initialize_template(void) { - static const char index[] = "\n" - "\n" \ - "\n" \ - "Lwan Clock Sample\n" - "\n" \ - "\n" \ - " \n" \ - " \n" \ - " \n" \ - "
\n" \ - "
\n" \ - "
\n" \ - "\n" \ + " image-rendering: pixelated;\n" + " image-rendering: -moz-crisp-edges;\n" + " image-rendering: crisp-edges;\n" + "}\n" + "#styles {\n" + " color: #444;\n" + " top: 0;\n" + " position: absolute;\n" + " padding: 16px;\n" + " left: calc(50% - 100px - 16px);\n" + " width: 200px;\n" + "}\n" + "#styles a, #styles a:visited, #lwan a, #lwan a:visited { color: #666; }\n" + "#lwan {\n" + " color: #555;\n" + " top: calc(100% - 40px);\n" + " position: absolute;\n" + " height: 20px;\n" + " font-size: 75%;\n" + " width: 300px;\n" + "}\n" + "\n" + "{{title}}\n" + "\n" + "\n" + "
\n" + " Powered by the Lwan web server.\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + "
\n" + "
\n" + "
\n" + "
\n" + " Styles: Digital · Dali\n" + "
\n" + "\n" ""; + index_tpl = lwan_tpl_compile_string_full(index, index_desc, + LWAN_TPL_FLAG_CONST_TEMPLATE); + if (!index_tpl) + lwan_status_critical("Could not compile template"); +} + +LWAN_HANDLER(templated_index) +{ response->mime_type = "text/html"; - lwan_strbuf_set_static(response->buffer, index, sizeof(index) - 1); - return HTTP_OK; + if (lwan_tpl_apply_with_buffer(index_tpl, response->buffer, data)) + return HTTP_OK; + + return HTTP_INTERNAL_ERROR; } int main(void) { + struct index sample_clock = { + .title = "Lwan Sample Clock", + .variant = "clock", + .width = 200, + }; + struct index dali_clock = { + .title = "Lwan Dali Clock", + .variant = "dali", + .width = 320, + }; const struct lwan_url_map default_map[] = { - {.prefix = "/clock.gif", .handler = LWAN_HANDLER_REF(clock)}, - {.prefix = "/dali.gif", .handler = LWAN_HANDLER_REF(dali)}, - {.prefix = "/", .handler = LWAN_HANDLER_REF(index)}, - {.prefix = NULL}, + { + .prefix = "/clock.gif", + .handler = LWAN_HANDLER_REF(clock), + }, + { + .prefix = "/dali.gif", + .handler = LWAN_HANDLER_REF(dali), + }, + { + .prefix = "/clock", + .handler = LWAN_HANDLER_REF(templated_index), + .data = &sample_clock, + }, + { + .prefix = "/dali", + .handler = LWAN_HANDLER_REF(templated_index), + .data = &dali_clock, + }, + { + .prefix = "/", + REDIRECT("/clock"), + }, + {}, }; struct lwan l; From ff141677bfc208842dbe14fdb10bcda6dee0e1d8 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 2 Sep 2018 09:39:30 -0700 Subject: [PATCH 0813/2505] Issue a critical error if font is too curvy --- src/samples/clock/xdaliclock.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/samples/clock/xdaliclock.c b/src/samples/clock/xdaliclock.c index e5cc152a4..d322ff3fc 100644 --- a/src/samples/clock/xdaliclock.c +++ b/src/samples/clock/xdaliclock.c @@ -129,9 +129,9 @@ frame_from_pixmap(const unsigned char *bits, int width, int height) for (; x < width; x++) { if (get_bit(bits, x, y, width)) { - /* This means the font is too curvy. Increase MAX_SEGS_PER_LINE - and recompile. */ - lwan_status_debug("builtin font is bogus"); + lwan_status_critical( + "Font too curvy. Increase MAX_SEGS_PER_LINE " + "and recompile"); return NULL; } } From 5899ee7608207e78b26f8e3ae9c159519ef304e2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 2 Sep 2018 09:42:33 -0700 Subject: [PATCH 0814/2505] Response headers can be const pointers --- src/lib/lwan.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 305ea34f5..79d6f408c 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -252,7 +252,7 @@ struct lwan_response { struct lwan_strbuf *buffer; const char *mime_type; size_t content_length; - struct lwan_key_value *headers; + const struct lwan_key_value *headers; struct { enum lwan_http_status (*callback)(struct lwan_request *request, void *data); From 2a7fd3a415ac32fefdafe4487407a649eda52c15 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 2 Sep 2018 09:42:45 -0700 Subject: [PATCH 0815/2505] Have only one copy of the "seriously, do not cache" headers --- src/samples/clock/main.c | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/samples/clock/main.c b/src/samples/clock/main.c index 3f2059d06..3d5ff442a 100644 --- a/src/samples/clock/main.c +++ b/src/samples/clock/main.c @@ -34,6 +34,14 @@ static const uint8_t font[10][5] = { [9] = {7, 5, 7, 1, 7}, }; +static const struct lwan_key_value seriously_do_not_cache[] = { + {.key = "Content-Transfer-Encoding", .value = "binary"}, + {.key = "Cache-Control", .value = "no-cache"}, + {.key = "Cache-Control", .value = "no-store"}, + {.key = "Cache-Control", .value = "no-transform"}, + {}, +}; + static const uint16_t width = 3 * 6 /* 6*3px wide digits */ + 3 * 1 /* 3*1px wide decimal digit space */ + 3 * 2 /* 2*3px wide minutes+seconds dots */; @@ -58,13 +66,7 @@ LWAN_HANDLER(clock) coro_defer(request->conn->coro, destroy_gif, gif); response->mime_type = "image/gif"; - response->headers = (struct lwan_key_value[]){ - {.key = "Content-Transfer-Encoding", .value = "binary"}, - {.key = "Cache-Control", .value = "no-cache"}, - {.key = "Cache-Control", .value = "no-store"}, - {.key = "Cache-Control", .value = "no-transform"}, - {}, - }; + response->headers = (struct lwan_key_value *)seriously_do_not_cache; memset(gif->frame, 0, (size_t)(width * height)); @@ -126,13 +128,7 @@ LWAN_HANDLER(dali) coro_defer(request->conn->coro, destroy_xdaliclock, xdc); response->mime_type = "image/gif"; - response->headers = (struct lwan_key_value[]){ - {.key = "Content-Transfer-Encoding", .value = "binary"}, - {.key = "Cache-Control", .value = "no-cache"}, - {.key = "Cache-Control", .value = "no-store"}, - {.key = "Cache-Control", .value = "no-transform"}, - {}, - }; + response->headers = seriously_do_not_cache; memset(gif->frame, 0, (size_t)(width * height)); From 045c07aedced138b56863c9c96aec2ba5bb50632 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 2 Sep 2018 10:12:43 -0700 Subject: [PATCH 0816/2505] Do not set MIME type when template couldn't be applied Let lwan_default_response() do its job. --- src/samples/clock/main.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/samples/clock/main.c b/src/samples/clock/main.c index 3d5ff442a..b93d6b36f 100644 --- a/src/samples/clock/main.c +++ b/src/samples/clock/main.c @@ -222,10 +222,10 @@ __attribute__((constructor)) static void initialize_template(void) LWAN_HANDLER(templated_index) { - response->mime_type = "text/html"; - - if (lwan_tpl_apply_with_buffer(index_tpl, response->buffer, data)) + if (lwan_tpl_apply_with_buffer(index_tpl, response->buffer, data)) { + response->mime_type = "text/html"; return HTTP_OK; + } return HTTP_INTERNAL_ERROR; } From 33f71609543bd0d68895eff72b6c7a5238b7a9ca Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 2 Sep 2018 10:33:58 -0700 Subject: [PATCH 0817/2505] MAX_SEGS_PER_LINE can be reduced This reduces the memory use slightly, and cuts an iteration from the frame lerping routine. --- src/samples/clock/xdaliclock.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/samples/clock/xdaliclock.c b/src/samples/clock/xdaliclock.c index d322ff3fc..f6c8b98d5 100644 --- a/src/samples/clock/xdaliclock.c +++ b/src/samples/clock/xdaliclock.c @@ -32,7 +32,7 @@ * (Largely stolen from the PalmOS original). */ -#define MAX_SEGS_PER_LINE 3 +#define MAX_SEGS_PER_LINE 2 struct scanline { POS left[MAX_SEGS_PER_LINE], right[MAX_SEGS_PER_LINE]; From ec85ae906966124c5d3984b209ec35c0e82ba668 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 3 Sep 2018 09:00:41 -0700 Subject: [PATCH 0818/2505] Lerping is not needed if not melting between digits Digits don't alter all the time, so just use a fast memcpy() instead. --- src/samples/clock/xdaliclock.c | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/samples/clock/xdaliclock.c b/src/samples/clock/xdaliclock.c index f6c8b98d5..a0d357f5c 100644 --- a/src/samples/clock/xdaliclock.c +++ b/src/samples/clock/xdaliclock.c @@ -191,19 +191,27 @@ static void frame_lerp(struct xdaliclock *xdc, int digit) { const int from = xdc->current_digits[digit]; const int to = xdc->target_digits[digit]; - const struct frame *fromf = - (from >= 0) ? base_frames[from] : xdc->clear_frame; const struct frame *tof = (to >= 0) ? base_frames[to] : xdc->clear_frame; int x, y; - for (y = 0; y < char_height; y++) { - struct scanline *line = &xdc->temp_frame->scanlines[y]; - const struct scanline *to_line = &tof->scanlines[y]; - const struct scanline *from_line = &fromf->scanlines[y]; - - for (x = 0; x < MAX_SEGS_PER_LINE; x++) { - line->left[x] = lerp(xdc, from_line->left[x], to_line->left[x]); - line->right[x] = lerp(xdc, from_line->right[x], to_line->right[x]); + if (from == to) { + /* Lerping not necessary: just copy the scanlines. */ + memcpy(&xdc->temp_frame->scanlines, &tof->scanlines, + char_height * sizeof(struct scanline)); + } else { + const struct frame *fromf = + (from >= 0) ? base_frames[from] : xdc->clear_frame; + + for (y = 0; y < char_height; y++) { + struct scanline *line = &xdc->temp_frame->scanlines[y]; + const struct scanline *to_line = &tof->scanlines[y]; + const struct scanline *from_line = &fromf->scanlines[y]; + + for (x = 0; x < MAX_SEGS_PER_LINE; x++) { + line->left[x] = lerp(xdc, from_line->left[x], to_line->left[x]); + line->right[x] = + lerp(xdc, from_line->right[x], to_line->right[x]); + } } } } From a2d3bce1c9cf38aa281d8756d687698398b99b12 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 4 Sep 2018 07:59:21 -0700 Subject: [PATCH 0819/2505] Use an exponential easing function to lerp between digits --- src/samples/clock/xdaliclock.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/samples/clock/xdaliclock.c b/src/samples/clock/xdaliclock.c index a0d357f5c..ae577f7fa 100644 --- a/src/samples/clock/xdaliclock.c +++ b/src/samples/clock/xdaliclock.c @@ -26,6 +26,10 @@ #define FRAMES_PER_SECOND 10 #define ANIMATION_TIME_MSEC 1000 +#if (FRAMES_PER_SECOND + 1) > 15 +#error Animation easing routine needs to be updated for this framerate +#endif + /**************************************************************************/ /* Scanline parsing. * @@ -54,6 +58,7 @@ struct xdaliclock { time_t last_time; uint32_t frame; + uint32_t frame_count; }; enum paint_color { BACKGROUND, FOREGROUND }; @@ -305,6 +310,7 @@ void xdaliclock_update(struct xdaliclock *xdc) xdc->last_time = now; xdc->frame = 0; + xdc->frame_count = 0; } for (int digit = 0, x = 0; digit < 8; x += digit_widths[digit++]) { @@ -312,7 +318,8 @@ void xdaliclock_update(struct xdaliclock *xdc) frame_render(xdc, x); } - xdc->frame += 65535 / (FRAMES_PER_SECOND + 1); + xdc->frame = 65535u - 65535u / (1u << (xdc->frame_count + 1)); + xdc->frame_count++; } struct xdaliclock *xdaliclock_new(ge_GIF *ge) @@ -325,6 +332,7 @@ struct xdaliclock *xdaliclock_new(ge_GIF *ge) xdc->frame = 0; xdc->gif_enc = ge; xdc->last_time = 0; + xdc->frame_count = 0; xdc->temp_frame = frame_mk(char_width, char_height); if (!xdc->temp_frame) From 8ff40171a30a20bad4dfc3ab5b21ad35863ebdbb Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 4 Sep 2018 18:23:10 -0700 Subject: [PATCH 0820/2505] Animation always lasts 1s --- src/samples/clock/xdaliclock.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/samples/clock/xdaliclock.c b/src/samples/clock/xdaliclock.c index ae577f7fa..dcfbb72df 100644 --- a/src/samples/clock/xdaliclock.c +++ b/src/samples/clock/xdaliclock.c @@ -24,7 +24,6 @@ #include "lwan-private.h" #define FRAMES_PER_SECOND 10 -#define ANIMATION_TIME_MSEC 1000 #if (FRAMES_PER_SECOND + 1) > 15 #error Animation easing routine needs to be updated for this framerate @@ -365,5 +364,5 @@ void xdaliclock_free(struct xdaliclock *xdc) uint32_t xdaliclock_get_frame_time(const struct xdaliclock *xdc __attribute__((unused))) { - return ANIMATION_TIME_MSEC / FRAMES_PER_SECOND; + return 1000 / FRAMES_PER_SECOND; } From a7a5db3fea30dfd9808809dd7e74277e47c8e4af Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 4 Sep 2018 18:04:34 -0700 Subject: [PATCH 0821/2505] Ensure epoll_wait() is never called with a 0 timeout --- src/lib/lwan-thread.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index aed55b771..b48d00470 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -375,18 +375,21 @@ turn_timer_wheel(struct death_queue *dq, struct lwan_thread *t, int epoll_fd) wheel_timeout = timeouts_timeout(t->wheel); if (UNLIKELY((int64_t)wheel_timeout < 0)) - return -1; + goto infinite_timeout; if (wheel_timeout == 0) { - if (process_pending_timers(dq, t, epoll_fd)) { - wheel_timeout = timeouts_timeout(t->wheel); + if (!process_pending_timers(dq, t, epoll_fd)) + goto infinite_timeout; - if (!wheel_timeout) - return -1; - } + wheel_timeout = timeouts_timeout(t->wheel); + if (wheel_timeout == 0) + goto infinite_timeout; } return (int)wheel_timeout; + +infinite_timeout: + return -1; } static void *thread_io_loop(void *data) From 9c26ab9c3797c5b7749abc6f469131bbb18d97a1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 4 Sep 2018 18:08:13 -0700 Subject: [PATCH 0822/2505] Pre-compute easing curve --- src/samples/clock/xdaliclock.c | 35 ++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/samples/clock/xdaliclock.c b/src/samples/clock/xdaliclock.c index dcfbb72df..9151b89e6 100644 --- a/src/samples/clock/xdaliclock.c +++ b/src/samples/clock/xdaliclock.c @@ -57,7 +57,6 @@ struct xdaliclock { time_t last_time; uint32_t frame; - uint32_t frame_count; }; enum paint_color { BACKGROUND, FOREGROUND }; @@ -65,6 +64,7 @@ enum paint_color { BACKGROUND, FOREGROUND }; static struct frame *base_frames[12]; static POS char_height, char_width, colon_width; static int digit_widths[8]; +static unsigned int easing[FRAMES_PER_SECOND]; static struct frame *frame_mk(int width, int height) { @@ -181,17 +181,22 @@ __attribute__((constructor)) static void initialize_numbers(void) [6] = char_width, [7] = char_width, [8] = 0 /* avoid UB */, }; memcpy(digit_widths, widths, sizeof(digit_widths)); + + /* Pre-compute easing function. */ + for (unsigned int i = 0; i < FRAMES_PER_SECOND - 1; i++) + easing[i] = 65535u - 65535u / (1u << (i + 1)); + easing[FRAMES_PER_SECOND - 1] = 65535u; } -static inline POS lerp(const struct xdaliclock *xdc, POS a, POS b) +static inline POS lerp(const struct xdaliclock *xdc, POS a, POS b, unsigned int anim) { - uint32_t part_a = a * (65536 - xdc->frame); - uint32_t part_b = b * (xdc->frame + 1); + uint32_t part_a = a * (65536 - anim); + uint32_t part_b = b * (anim + 1); return (POS)((part_a + part_b) / 65536); } -static void frame_lerp(struct xdaliclock *xdc, int digit) +static void frame_lerp(struct xdaliclock *xdc, int digit, unsigned int anim) { const int from = xdc->current_digits[digit]; const int to = xdc->target_digits[digit]; @@ -212,9 +217,9 @@ static void frame_lerp(struct xdaliclock *xdc, int digit) const struct scanline *from_line = &fromf->scanlines[y]; for (x = 0; x < MAX_SEGS_PER_LINE; x++) { - line->left[x] = lerp(xdc, from_line->left[x], to_line->left[x]); + line->left[x] = lerp(xdc, from_line->left[x], to_line->left[x], anim); line->right[x] = - lerp(xdc, from_line->right[x], to_line->right[x]); + lerp(xdc, from_line->right[x], to_line->right[x], anim); } } } @@ -309,16 +314,14 @@ void xdaliclock_update(struct xdaliclock *xdc) xdc->last_time = now; xdc->frame = 0; - xdc->frame_count = 0; } for (int digit = 0, x = 0; digit < 8; x += digit_widths[digit++]) { - frame_lerp(xdc, digit); + frame_lerp(xdc, digit, easing[xdc->frame]); frame_render(xdc, x); } - xdc->frame = 65535u - 65535u / (1u << (xdc->frame_count + 1)); - xdc->frame_count++; + xdc->frame++; } struct xdaliclock *xdaliclock_new(ge_GIF *ge) @@ -328,11 +331,6 @@ struct xdaliclock *xdaliclock_new(ge_GIF *ge) if (!xdc) return NULL; - xdc->frame = 0; - xdc->gif_enc = ge; - xdc->last_time = 0; - xdc->frame_count = 0; - xdc->temp_frame = frame_mk(char_width, char_height); if (!xdc->temp_frame) goto out; @@ -344,6 +342,11 @@ struct xdaliclock *xdaliclock_new(ge_GIF *ge) for (unsigned int i = 0; i < N_ELEMENTS(xdc->target_digits); i++) xdc->target_digits[i] = xdc->current_digits[i] = -1; + /* Ensure time() is called the first time xdaliclock_update() is called */ + xdc->frame = FRAMES_PER_SECOND; + xdc->gif_enc = ge; + xdc->last_time = 0; + return xdc; out: From c3173b9b10aa4a93aa2eeaebc324c9f674eaa805 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 4 Sep 2018 18:08:40 -0700 Subject: [PATCH 0823/2505] No need to store last_time time(NULL) was called, just use frame counter --- src/samples/clock/xdaliclock.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/samples/clock/xdaliclock.c b/src/samples/clock/xdaliclock.c index 9151b89e6..9e57ec043 100644 --- a/src/samples/clock/xdaliclock.c +++ b/src/samples/clock/xdaliclock.c @@ -54,8 +54,6 @@ struct xdaliclock { struct frame *temp_frame; struct frame *clear_frame; - time_t last_time; - uint32_t frame; }; @@ -295,10 +293,9 @@ static void frame_render(struct xdaliclock *xdc, int x) void xdaliclock_update(struct xdaliclock *xdc) { - time_t now = time(NULL); - - if (now != xdc->last_time) { - struct tm *tm = localtime(&now); + if (xdc->frame >= FRAMES_PER_SECOND) { + const time_t now = time(NULL); + const struct tm *tm = localtime(&now); memcpy(xdc->current_digits, xdc->target_digits, sizeof(xdc->current_digits)); @@ -312,7 +309,6 @@ void xdaliclock_update(struct xdaliclock *xdc) xdc->target_digits[6] = tm->tm_sec / 10; xdc->target_digits[7] = tm->tm_sec % 10; - xdc->last_time = now; xdc->frame = 0; } @@ -345,7 +341,6 @@ struct xdaliclock *xdaliclock_new(ge_GIF *ge) /* Ensure time() is called the first time xdaliclock_update() is called */ xdc->frame = FRAMES_PER_SECOND; xdc->gif_enc = ge; - xdc->last_time = 0; return xdc; From 4a6accd58a1d80a40c3306705ab2e089a2fe424d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 4 Sep 2018 18:22:01 -0700 Subject: [PATCH 0824/2505] Stop streaming GIFs after one hour But reload the HTML page after one hour, so if the page is open, things will work as usual. However, if a bot is just downloading the clock, they will be interrupted once one hour passes. --- src/samples/clock/main.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/samples/clock/main.c b/src/samples/clock/main.c index b93d6b36f..21f65e9a0 100644 --- a/src/samples/clock/main.c +++ b/src/samples/clock/main.c @@ -70,7 +70,7 @@ LWAN_HANDLER(clock) memset(gif->frame, 0, (size_t)(width * height)); - while (true) { + for (int frame = 0; frame < 3600 * 2; frame++) { time_t curtime; char digits[8]; int digit, line, base; @@ -115,6 +115,7 @@ LWAN_HANDLER(dali) { ge_GIF *gif = ge_new_gif(response->buffer, 320, 64, NULL, 2, -1); struct xdaliclock *xdc; + uint32_t one_hour; if (!gif) return HTTP_INTERNAL_ERROR; @@ -132,7 +133,8 @@ LWAN_HANDLER(dali) memset(gif->frame, 0, (size_t)(width * height)); - while (true) { + one_hour = 3600 * 1000 / xdaliclock_get_frame_time(xdc); + for (uint32_t frame = 0; frame < one_hour; frame++) { xdaliclock_update(xdc); ge_add_frame(gif, 0); @@ -195,6 +197,7 @@ __attribute__((constructor)) static void initialize_template(void) " width: 300px;\n" "}\n" "\n" + "\n" "{{title}}\n" "\n" "\n" From 4373d775b287f73f8afdf70876930612fc1d181c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 4 Sep 2018 18:58:04 -0700 Subject: [PATCH 0825/2505] No need to zero out new frame after allocating The frame will be completely initialized anyway, so use a plain old malloc() instead of calloc(). --- src/samples/clock/xdaliclock.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/samples/clock/xdaliclock.c b/src/samples/clock/xdaliclock.c index 9e57ec043..336e8f361 100644 --- a/src/samples/clock/xdaliclock.c +++ b/src/samples/clock/xdaliclock.c @@ -66,9 +66,8 @@ static unsigned int easing[FRAMES_PER_SECOND]; static struct frame *frame_mk(int width, int height) { - struct frame *fr = - calloc(1, sizeof(struct frame) + - (sizeof(struct scanline) * ((size_t)height - 1))); + struct frame *fr = malloc(sizeof(struct frame) + + (sizeof(struct scanline) * ((size_t)height - 1))); POS half_width = (POS)(width / 2); int x, y; From acbfd936bdd9b06b606543edf132719a5273fd41 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 4 Sep 2018 21:38:18 -0700 Subject: [PATCH 0826/2505] No need to memcpy() non-lerped frames If there's no need to lerp between two frames, just render the actual base frame instead. This shaves of a memcpy() of 256 bytes per non-lerped digit. Copies 1792 less bytes per rendered frame if just one digit changes; at 10 frames per second, that's over 17kb/s of memory copies that are not being performed. --- src/samples/clock/xdaliclock.c | 45 +++++++++++++++++----------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/src/samples/clock/xdaliclock.c b/src/samples/clock/xdaliclock.c index 336e8f361..1a44a4e9f 100644 --- a/src/samples/clock/xdaliclock.c +++ b/src/samples/clock/xdaliclock.c @@ -193,33 +193,31 @@ static inline POS lerp(const struct xdaliclock *xdc, POS a, POS b, unsigned int return (POS)((part_a + part_b) / 65536); } -static void frame_lerp(struct xdaliclock *xdc, int digit, unsigned int anim) +static const struct frame *frame_lerp(struct xdaliclock *xdc, int digit, unsigned int anim) { const int from = xdc->current_digits[digit]; const int to = xdc->target_digits[digit]; const struct frame *tof = (to >= 0) ? base_frames[to] : xdc->clear_frame; + const struct frame *from; int x, y; - if (from == to) { - /* Lerping not necessary: just copy the scanlines. */ - memcpy(&xdc->temp_frame->scanlines, &tof->scanlines, - char_height * sizeof(struct scanline)); - } else { - const struct frame *fromf = - (from >= 0) ? base_frames[from] : xdc->clear_frame; - - for (y = 0; y < char_height; y++) { - struct scanline *line = &xdc->temp_frame->scanlines[y]; - const struct scanline *to_line = &tof->scanlines[y]; - const struct scanline *from_line = &fromf->scanlines[y]; - - for (x = 0; x < MAX_SEGS_PER_LINE; x++) { - line->left[x] = lerp(xdc, from_line->left[x], to_line->left[x], anim); - line->right[x] = - lerp(xdc, from_line->right[x], to_line->right[x], anim); - } + if (from == to) + return tof; + + fromf = (from >= 0) ? base_frames[from] : xdc->clear_frame; + for (y = 0; y < char_height; y++) { + struct scanline *line = &xdc->temp_frame->scanlines[y]; + const struct scanline *to_line = &tof->scanlines[y]; + const struct scanline *from_line = &fromf->scanlines[y]; + + for (x = 0; x < MAX_SEGS_PER_LINE; x++) { + line->left[x] = lerp(xdc, from_line->left[x], to_line->left[x], anim); + line->right[x] = + lerp(xdc, from_line->right[x], to_line->right[x], anim); } } + + return xdc->temp_frame; } static void draw_horizontal_line(struct xdaliclock *xdc, @@ -254,9 +252,9 @@ static void draw_horizontal_line(struct xdaliclock *xdc, (size_t)(x2 - x1)); } -static void frame_render(struct xdaliclock *xdc, int x) +static void +frame_render(struct xdaliclock *xdc, const struct frame *frame, int x) { - const struct frame *frame = xdc->temp_frame; int px, py; for (py = 0; py < char_height; py++) { @@ -312,8 +310,9 @@ void xdaliclock_update(struct xdaliclock *xdc) } for (int digit = 0, x = 0; digit < 8; x += digit_widths[digit++]) { - frame_lerp(xdc, digit, easing[xdc->frame]); - frame_render(xdc, x); + const struct frame *frame = frame_lerp(xdc, digit, easing[xdc->frame]); + + frame_render(xdc, frame, x); } xdc->frame++; From bc9a586c892cbf6aa41743a334f4cdb72ad3ecdf Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 4 Sep 2018 21:43:25 -0700 Subject: [PATCH 0827/2505] Fix build after acbfd93 --- src/samples/clock/xdaliclock.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/samples/clock/xdaliclock.c b/src/samples/clock/xdaliclock.c index 1a44a4e9f..afd3cc7b6 100644 --- a/src/samples/clock/xdaliclock.c +++ b/src/samples/clock/xdaliclock.c @@ -198,7 +198,7 @@ static const struct frame *frame_lerp(struct xdaliclock *xdc, int digit, unsigne const int from = xdc->current_digits[digit]; const int to = xdc->target_digits[digit]; const struct frame *tof = (to >= 0) ? base_frames[to] : xdc->clear_frame; - const struct frame *from; + const struct frame *fromf; int x, y; if (from == to) From f374fe0cb73e90752890a4e57637f597e1ce7f5f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 4 Sep 2018 22:08:59 -0700 Subject: [PATCH 0828/2505] Remove one branch per drawn line --- src/samples/clock/xdaliclock.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/samples/clock/xdaliclock.c b/src/samples/clock/xdaliclock.c index afd3cc7b6..76ded0751 100644 --- a/src/samples/clock/xdaliclock.c +++ b/src/samples/clock/xdaliclock.c @@ -57,7 +57,7 @@ struct xdaliclock { uint32_t frame; }; -enum paint_color { BACKGROUND, FOREGROUND }; +enum paint_color { BACKGROUND = 0, FOREGROUND = 3 }; static struct frame *base_frames[12]; static POS char_height, char_width, colon_width; @@ -225,10 +225,8 @@ static void draw_horizontal_line(struct xdaliclock *xdc, int x2, int y, int screen_width, - enum paint_color pc) + enum paint_color color) { - uint8_t color = (pc == BACKGROUND) ? 0 : 3; - if (x1 > screen_width) x1 = screen_width; else if (x1 < 0) @@ -248,7 +246,7 @@ static void draw_horizontal_line(struct xdaliclock *xdc, x2 = swap; } - memset(xdc->gif_enc->frame + y * screen_width + x1, color, + memset(xdc->gif_enc->frame + y * screen_width + x1, (uint8_t)color, (size_t)(x2 - x1)); } From 744e85e4562934261615b4c5b9527b5d900a6be5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 4 Sep 2018 22:13:26 -0700 Subject: [PATCH 0829/2505] Mark unlikely comparisons in draw_horizontal_line() as such --- src/samples/clock/xdaliclock.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/samples/clock/xdaliclock.c b/src/samples/clock/xdaliclock.c index 76ded0751..c36309cb2 100644 --- a/src/samples/clock/xdaliclock.c +++ b/src/samples/clock/xdaliclock.c @@ -227,14 +227,16 @@ static void draw_horizontal_line(struct xdaliclock *xdc, int screen_width, enum paint_color color) { - if (x1 > screen_width) + /* These unlikely checks won't happen with the default font. */ + + if (UNLIKELY(x1 > screen_width)) x1 = screen_width; - else if (x1 < 0) + else if (UNLIKELY(x1 < 0)) x1 = 0; - if (x2 > screen_width) + if (UNLIKELY(x2 > screen_width)) x2 = screen_width; - else if (x2 < 0) + else if (UNLIKELY(x2 < 0)) x2 = 0; if (x1 == x2) From 6eb8766f221ed40a9db38421b9d841d5d868a69d Mon Sep 17 00:00:00 2001 From: Oleg Kalachev Date: Sun, 9 Sep 2018 02:05:57 +0300 Subject: [PATCH 0830/2505] Fix build on macOS --- src/lib/missing-pthread.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/missing-pthread.c b/src/lib/missing-pthread.c index 4c96f1210..5cd00aa93 100644 --- a/src/lib/missing-pthread.c +++ b/src/lib/missing-pthread.c @@ -18,6 +18,7 @@ */ #define _GNU_SOURCE +#include #include #include @@ -91,7 +92,7 @@ int pthread_set_name_np(pthread_t thread, const char *name) #elif defined(__APPLE__) int pthread_set_name_np(pthread_t thread, const char *name) { - if (!pthread_equal(thread, pthread_self()) + if (!pthread_equal(thread, pthread_self())) return EPERM; /* macOS takes a char*; I don't know if it's modified or not, so From 20d699ed827e12743af782c60c0a171ccd09187d Mon Sep 17 00:00:00 2001 From: halosghost Date: Mon, 3 Sep 2018 14:43:11 -0500 Subject: [PATCH 0831/2505] break LWAN_HANDLER() into declaration and definition --- src/lib/lwan.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 79d6f408c..b03dda89b 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -62,9 +62,11 @@ extern "C" { #define LWAN_HANDLER_REF(name_) lwan_handler_##name_ -#define LWAN_HANDLER(name_) \ +#define LWAN_HANDLER_DECLARE(name_) \ static enum lwan_http_status lwan_handler_##name_( \ - struct lwan_request *, struct lwan_response *, void *data); \ + struct lwan_request *, struct lwan_response *, void *) + +#define LWAN_HANDLER_DEFINE(name_) \ static const struct lwan_handler_info \ __attribute__((used, section(LWAN_SECTION_NAME(lwan_handler)))) \ lwan_handler_info_##name_ = {.name = #name_, \ @@ -74,6 +76,10 @@ extern "C" { struct lwan_response *response __attribute__((unused)), \ void *data __attribute__((unused))) +#define LWAN_HANDLER(name_) \ + LWAN_HANDLER_DECLARE(name_); \ + LWAN_HANDLER_DEFINE(name_) + #ifdef DISABLE_INLINE_FUNCTIONS # define ALWAYS_INLINE #else From 730629ad2d753f907faa7388187adf7acfcacb81 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 9 Sep 2018 10:39:07 -0700 Subject: [PATCH 0832/2505] Automatically resize hash table buckets array Use a simple (and maybe not optimal) heuristic to either duplicate, or reduce by half, the buckets array, when items are respectively added or removed from the hash table. This heuristic can be tuned later. While new buckets for entries will be found during the rehashing process, there's no need to calculate the hash again during resizing, since the hash value is kept in the hash_entry struct. --- src/lib/hash.c | 118 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 102 insertions(+), 16 deletions(-) diff --git a/src/lib/hash.c b/src/lib/hash.c index 1ad8a5784..0902b50a0 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -19,6 +19,7 @@ */ #define _GNU_SOURCE +#include #include #include #include @@ -30,31 +31,37 @@ #include #include +#include "lwan-private.h" #include "hash.h" #include "murmur3.h" struct hash_entry { const char *key; const void *value; - unsigned hashval; + + unsigned int hashval; }; struct hash_bucket { struct hash_entry *entries; - unsigned used; - unsigned total; + + unsigned int used; + unsigned int total; }; struct hash { - unsigned count; + unsigned int count; + unsigned int n_buckets; + unsigned (*hash_value)(const void *key); int (*key_compare)(const void *k1, const void *k2); void (*free_value)(void *value); void (*free_key)(void *value); - struct hash_bucket buckets[]; + + struct hash_bucket *buckets; }; -#define N_BUCKETS 512 +#define MIN_BUCKETS 512 #define STEPS 64 #define DEFAULT_ODD_CONSTANT 0x27d4eb2d @@ -177,16 +184,26 @@ hash_internal_new(unsigned int (*hash_value)(const void *key), void (*free_key)(void *value), void (*free_value)(void *value)) { - struct hash *hash = - calloc(1, sizeof(struct hash) + N_BUCKETS * sizeof(struct hash_bucket)); + struct hash *hash = malloc(sizeof(*hash)); if (hash == NULL) return NULL; + hash->buckets = calloc(MIN_BUCKETS, sizeof(struct hash_bucket)); + if (hash->buckets == NULL) { + free(hash); + return NULL; + } + hash->hash_value = hash_value; hash->key_compare = key_compare; + hash->free_value = free_value; hash->free_key = free_key; + + hash->n_buckets = MIN_BUCKETS; + hash->count = 0; + return hash; } @@ -214,7 +231,7 @@ void hash_free(struct hash *hash) return; bucket = hash->buckets; - bucket_end = bucket + N_BUCKETS; + bucket_end = hash->buckets + hash->n_buckets; for (; bucket < bucket_end; bucket++) { struct hash_entry *entry, *entry_end; entry = bucket->entries; @@ -225,13 +242,14 @@ void hash_free(struct hash *hash) } free(bucket->entries); } + free(hash->buckets); free(hash); } -static struct hash_entry *hash_add_entry(struct hash *hash, const void *key) +static struct hash_entry *hash_add_entry_hashed(struct hash *hash, const void *key, + unsigned int hashval) { - unsigned int hashval = hash->hash_value(key); - unsigned int pos = hashval & (N_BUCKETS - 1); + unsigned int pos = hashval & (hash->n_buckets - 1); struct hash_bucket *bucket = hash->buckets + pos; struct hash_entry *entry, *entry_end; @@ -270,6 +288,59 @@ static struct hash_entry *hash_add_entry(struct hash *hash, const void *key) return entry; } +static int rehash(struct hash *hash, unsigned int new_bucket_size) +{ + struct hash_bucket *buckets = calloc(new_bucket_size, sizeof(*buckets)); + const struct hash_bucket *bucket_end = hash->buckets + hash->n_buckets; + const struct hash_bucket *bucket; + struct hash hash_copy = *hash; + + assert(hash->n_buckets != new_bucket_size); + + if (buckets == NULL) + return -errno; + + hash_copy.count = 0; + hash_copy.n_buckets = new_bucket_size; + hash_copy.buckets = buckets; + + for (bucket = hash->buckets; bucket < bucket_end; bucket++) { + const struct hash_entry *old = bucket->entries; + const struct hash_entry *old_end = old + bucket->used; + + for (; old < old_end; old++) { + struct hash_entry *new; + + new = hash_add_entry_hashed(&hash_copy, old->key, old->hashval); + if (UNLIKELY(!new)) { + free(buckets); + return -ENOMEM; + } + + new->key = old->key; + new->value = old->value; + } + + free(bucket->entries); + } + + free(hash->buckets); + + hash->buckets = buckets; + hash->n_buckets = new_bucket_size; + + assert(hash_copy.count == hash->count); + + return 0; +} + +static struct hash_entry *hash_add_entry(struct hash *hash, const void *key) +{ + unsigned int hashval = hash->hash_value(key); + + return hash_add_entry_hashed(hash, key, hashval); +} + /* * add or replace key in hash map. * @@ -289,6 +360,11 @@ int hash_add(struct hash *hash, const void *key, const void *value) entry->key = key; entry->value = value; + if (hash->count > hash->n_buckets) { + /* Not being able to resize the bucket array is not an error */ + rehash(hash, hash->n_buckets * 2); + } + return 0; } @@ -306,13 +382,18 @@ int hash_add_unique(struct hash *hash, const void *key, const void *value) entry->key = key; entry->value = value; + if (hash->count > hash->n_buckets) { + /* Not being able to resize the bucket array is not an error */ + rehash(hash, hash->n_buckets * 2); + } + return 0; } static inline struct hash_entry * hash_find_entry(const struct hash *hash, const char *key, unsigned int hashval) { - unsigned int pos = hashval & (N_BUCKETS - 1); + unsigned int pos = hashval & (hash->n_buckets - 1); const struct hash_bucket *bucket = hash->buckets + pos; struct hash_entry *entry, *entry_end; @@ -341,7 +422,7 @@ void *hash_find(const struct hash *hash, const void *key) int hash_del(struct hash *hash, const void *key) { unsigned int hashval = hash->hash_value(key); - unsigned int pos = hashval & (N_BUCKETS - 1); + unsigned int pos = hashval & (hash->n_buckets - 1); unsigned int steps_used, steps_total; struct hash_bucket *bucket = hash->buckets + pos; struct hash_entry *entry, *entry_end; @@ -371,6 +452,11 @@ int hash_del(struct hash *hash, const void *key) } } + if (hash->n_buckets > MIN_BUCKETS && hash->count < hash->n_buckets / 2) { + /* Not being able to trim the bucket array size isn't an error. */ + rehash(hash, hash->n_buckets / 2); + } + return 0; } @@ -395,14 +481,14 @@ bool hash_iter_next(struct hash_iter *iter, if ((unsigned int)iter->entry >= b->used) { iter->entry = 0; - for (iter->bucket++; iter->bucket < N_BUCKETS; iter->bucket++) { + for (iter->bucket++; iter->bucket < iter->hash->n_buckets; iter->bucket++) { b = iter->hash->buckets + iter->bucket; if (b->used > 0) break; } - if (iter->bucket >= N_BUCKETS) + if (iter->bucket >= iter->hash->n_buckets) return false; } From d4c304a41c92252d03dd84fd460ea763bb37f96e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 9 Sep 2018 19:38:10 -0700 Subject: [PATCH 0833/2505] Reduce hash table entry array allocation steps from 64 to 16 Since the table can now be resized on the fly, this value can be decreased to reduce memory usage. --- src/lib/hash.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/hash.c b/src/lib/hash.c index 0902b50a0..7fd0505a4 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -62,7 +62,7 @@ struct hash { }; #define MIN_BUCKETS 512 -#define STEPS 64 +#define STEPS 16 #define DEFAULT_ODD_CONSTANT 0x27d4eb2d static inline unsigned int hash_int_shift_mult(const void *keyptr); From cd80cb94ccbdf257fcec06612b805682fa02b84c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 9 Sep 2018 19:46:20 -0700 Subject: [PATCH 0834/2505] Use getentropy() to initialize fuzzing constant in hash table Even though glibc implements getentropy(3) on top of getrandom(2), which is used directly by Lwan with a call to syscall(2), other systems, such as OpenBSD, implement only the getentropy(2) interface. --- CMakeLists.txt | 1 + src/cmake/lwan-build-config.h.cmake | 1 + src/lib/hash.c | 6 +++++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ca1ac3e4a..b7b7d09d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -121,6 +121,7 @@ check_function_exists(pthread_barrier_init HAVE_PTHREADBARRIER) check_function_exists(pthread_set_name_np HAVE_PTHREAD_SET_NAME_NP) check_function_exists(eventfd HAVE_EVENTFD) check_function_exists(posix_fadvise HAVE_POSIX_FADVISE) +check_function_exists(getentropy HAVE_GETENTROPY) # This is available on -ldl in glibc, but some systems (such as OpenBSD) # will bundle these in the C library. This isn't required for glibc anyway, diff --git a/src/cmake/lwan-build-config.h.cmake b/src/cmake/lwan-build-config.h.cmake index e555c46da..4cd1ab79d 100644 --- a/src/cmake/lwan-build-config.h.cmake +++ b/src/cmake/lwan-build-config.h.cmake @@ -40,6 +40,7 @@ #cmakedefine HAVE_POSIX_FADVISE #cmakedefine HAVE_LINUX_CAPABILITY #cmakedefine HAVE_PTHREAD_SET_NAME_NP +#cmakedefine HAVE_GETENTROPY /* Compiler builtins for specific CPU instruction support */ #cmakedefine HAVE_BUILTIN_CLZLL diff --git a/src/lib/hash.c b/src/lib/hash.c index 7fd0505a4..0399b74e0 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -75,10 +75,14 @@ static unsigned int get_random_unsigned(void) { unsigned int value; -#ifdef SYS_getrandom +#if defined(SYS_getrandom) long int ret = syscall(SYS_getrandom, &value, sizeof(value), 0); if (ret == sizeof(value)) return value; +#elif defined(HAVE_GETENTROPY) + int ret = getentropy(value, sizeof(value)); + if (ret == 0) + return value; #endif int fd = open("/dev/urandom", O_CLOEXEC | O_RDONLY); From 4ffc520bd27fd942b2e3cd7568f08f01055503ad Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 9 Sep 2018 22:48:34 -0700 Subject: [PATCH 0835/2505] Reduce minimum amount of hash table buckets to 64 Now that the bucket array can be dynamically resized, reduce its minimum size to decrease memory usage. --- src/lib/hash.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/hash.c b/src/lib/hash.c index 0399b74e0..e2e570535 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -61,7 +61,7 @@ struct hash { struct hash_bucket *buckets; }; -#define MIN_BUCKETS 512 +#define MIN_BUCKETS 64 #define STEPS 16 #define DEFAULT_ODD_CONSTANT 0x27d4eb2d From f0249f1dbe13b4e6baee1b04496f48c15f7ab86a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 9 Sep 2018 23:04:38 -0700 Subject: [PATCH 0836/2505] Do not yield coroutine after last call to sendfile() There's no need to yield the coroutine, or queue a readahead, if there's nothing left to be sent. --- src/lib/lwan-io-wrappers.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index 769d7b68a..0ede7f3ad 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -122,7 +122,7 @@ lwan_sendfile(struct lwan_request *request, int in_fd, off_t offset, size_t coun lwan_send(request, header, header_len, MSG_MORE); - do { + while (true) { ssize_t written = sendfile(request->fd, in_fd, &offset, chunk_size); if (written < 0) { switch (errno) { @@ -138,11 +138,13 @@ lwan_sendfile(struct lwan_request *request, int in_fd, off_t offset, size_t coun } to_be_written -= (size_t)written; - chunk_size = min_size(to_be_written, 1<<19); + if (!to_be_written) + break; + chunk_size = min_size(to_be_written, 1<<19); lwan_readahead_queue(in_fd, offset, chunk_size); coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); - } while (to_be_written > 0); + } } #elif defined(__FreeBSD__) || defined(__APPLE__) void From 96635bdf69c7b75e665557d81c60c041849635e5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 10 Sep 2018 00:07:36 -0700 Subject: [PATCH 0837/2505] If rehashing when removing item from hash table, don't trim buckets Reallocating the bucket array just to throw the malloc() work out of the window then rehashing is just wasted effort. --- src/lib/hash.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/lib/hash.c b/src/lib/hash.c index e2e570535..12055cdcf 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -427,7 +427,6 @@ int hash_del(struct hash *hash, const void *key) { unsigned int hashval = hash->hash_value(key); unsigned int pos = hashval & (hash->n_buckets - 1); - unsigned int steps_used, steps_total; struct hash_bucket *bucket = hash->buckets + pos; struct hash_entry *entry, *entry_end; @@ -445,20 +444,21 @@ int hash_del(struct hash *hash, const void *key) bucket->used--; hash->count--; - steps_used = bucket->used / STEPS; - steps_total = bucket->total / STEPS; - if (steps_used + 1 < steps_total) { - struct hash_entry *tmp = - reallocarray(bucket->entries, steps_used + 1, STEPS * sizeof(*tmp)); - if (tmp) { - bucket->entries = tmp; - bucket->total = (steps_used + 1) * STEPS; - } - } - if (hash->n_buckets > MIN_BUCKETS && hash->count < hash->n_buckets / 2) { /* Not being able to trim the bucket array size isn't an error. */ rehash(hash, hash->n_buckets / 2); + } else { + unsigned int steps_used = bucket->used / STEPS; + unsigned int steps_total = bucket->total / STEPS; + + if (steps_used + 1 < steps_total) { + struct hash_entry *tmp = reallocarray( + bucket->entries, steps_used + 1, STEPS * sizeof(*tmp)); + if (tmp) { + bucket->entries = tmp; + bucket->total = (steps_used + 1) * STEPS; + } + } } return 0; From 05ddd670c6c69ff27f72d2eb671b84a798c82cca Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 10 Sep 2018 08:51:45 -0700 Subject: [PATCH 0838/2505] Use a sans-serif font for the clock sample --- src/samples/clock/main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/samples/clock/main.c b/src/samples/clock/main.c index 21f65e9a0..784a353f4 100644 --- a/src/samples/clock/main.c +++ b/src/samples/clock/main.c @@ -173,6 +173,7 @@ __attribute__((constructor)) static void initialize_template(void) " border:0;\n" " margin:0;\n" " padding:0;\n" + " font-family: sans-serif;\n" "}\n" "img {\n" " image-rendering: pixelated;\n" From e87c64b2a9bf06ca27f40b2da5bc30a5be96cca7 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 10 Sep 2018 18:59:25 -0700 Subject: [PATCH 0839/2505] Fix build on macOS --- src/lib/hash.c | 2 +- src/lib/missing/unistd.h | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/lib/hash.c b/src/lib/hash.c index 12055cdcf..9faf01e33 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -80,7 +80,7 @@ static unsigned int get_random_unsigned(void) if (ret == sizeof(value)) return value; #elif defined(HAVE_GETENTROPY) - int ret = getentropy(value, sizeof(value)); + int ret = getentropy(&value, sizeof(value)); if (ret == 0) return value; #endif diff --git a/src/lib/missing/unistd.h b/src/lib/missing/unistd.h index cfc920cbb..c7e4e0419 100644 --- a/src/lib/missing/unistd.h +++ b/src/lib/missing/unistd.h @@ -41,4 +41,9 @@ int getresgid(gid_t *rgid, gid_t *egid, gid_t *sgid) char *get_current_dir_name(void); #endif +#if defined(__APPLE__) +/* getrandom() is defined in on macOS */ +#include +#endif + #endif /* MISSING_UNISTD_H */ From 1e28e238a2754f935d4e1859336742cfecfb630e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 10 Sep 2018 19:00:49 -0700 Subject: [PATCH 0840/2505] Hash resizing should not modify original table until 100% done --- src/lib/hash.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib/hash.c b/src/lib/hash.c index 9faf01e33..8f7827ef2 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -324,10 +324,13 @@ static int rehash(struct hash *hash, unsigned int new_bucket_size) new->key = old->key; new->value = old->value; } - - free(bucket->entries); } + /* Original table must remain untouched in the event resizing fails: + * previous loop may return early on allocation failure, so can't free + * bucket entry arrays there. */ + for (bucket = hash->buckets; bucket < bucket_end; bucket++) + free(bucket->entries); free(hash->buckets); hash->buckets = buckets; From 3632c48ce08a97c44b690754186044064c2b6d78 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 10 Sep 2018 19:32:30 -0700 Subject: [PATCH 0841/2505] Store mask, rather than number of buckets When the number of buckets was constant, "hash & (constant - 1)" would be compiled down to two operations, with "- 1" being calculated by the compiler. When this was moved to a member in a struct, the "- 1" was being calculated during runtime. Store only the mask (size_as_power_of_two - 1), as masking the least significant bits of a hash is a common operation. Also, put some asserts in place to ensure that the bucket sizes will remain a power of two. --- src/lib/hash.c | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/src/lib/hash.c b/src/lib/hash.c index 8f7827ef2..6c73f2c1e 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -51,7 +51,7 @@ struct hash_bucket { struct hash { unsigned int count; - unsigned int n_buckets; + unsigned int n_buckets_mask; unsigned (*hash_value)(const void *key); int (*key_compare)(const void *k1, const void *k2); @@ -65,6 +65,9 @@ struct hash { #define STEPS 16 #define DEFAULT_ODD_CONSTANT 0x27d4eb2d +static_assert((MIN_BUCKETS & (MIN_BUCKETS - 1)) == 0, + "Bucket size is power of 2"); + static inline unsigned int hash_int_shift_mult(const void *keyptr); static unsigned int odd_constant = DEFAULT_ODD_CONSTANT; @@ -205,7 +208,7 @@ hash_internal_new(unsigned int (*hash_value)(const void *key), hash->free_value = free_value; hash->free_key = free_key; - hash->n_buckets = MIN_BUCKETS; + hash->n_buckets_mask = MIN_BUCKETS - 1; hash->count = 0; return hash; @@ -235,7 +238,7 @@ void hash_free(struct hash *hash) return; bucket = hash->buckets; - bucket_end = hash->buckets + hash->n_buckets; + bucket_end = hash->buckets + hash->n_buckets_mask + 1; for (; bucket < bucket_end; bucket++) { struct hash_entry *entry, *entry_end; entry = bucket->entries; @@ -253,7 +256,7 @@ void hash_free(struct hash *hash) static struct hash_entry *hash_add_entry_hashed(struct hash *hash, const void *key, unsigned int hashval) { - unsigned int pos = hashval & (hash->n_buckets - 1); + unsigned int pos = hashval & hash->n_buckets_mask; struct hash_bucket *bucket = hash->buckets + pos; struct hash_entry *entry, *entry_end; @@ -295,17 +298,18 @@ static struct hash_entry *hash_add_entry_hashed(struct hash *hash, const void *k static int rehash(struct hash *hash, unsigned int new_bucket_size) { struct hash_bucket *buckets = calloc(new_bucket_size, sizeof(*buckets)); - const struct hash_bucket *bucket_end = hash->buckets + hash->n_buckets; + const struct hash_bucket *bucket_end = hash->buckets + hash->n_buckets_mask + 1; const struct hash_bucket *bucket; struct hash hash_copy = *hash; - assert(hash->n_buckets != new_bucket_size); + assert((new_bucket_size & (new_bucket_size - 1)) == 0); + assert((hash->n_buckets_mask + 1) != new_bucket_size); if (buckets == NULL) return -errno; hash_copy.count = 0; - hash_copy.n_buckets = new_bucket_size; + hash_copy.n_buckets_mask = new_bucket_size - 1; hash_copy.buckets = buckets; for (bucket = hash->buckets; bucket < bucket_end; bucket++) { @@ -334,7 +338,7 @@ static int rehash(struct hash *hash, unsigned int new_bucket_size) free(hash->buckets); hash->buckets = buckets; - hash->n_buckets = new_bucket_size; + hash->n_buckets_mask = new_bucket_size - 1; assert(hash_copy.count == hash->count); @@ -367,9 +371,9 @@ int hash_add(struct hash *hash, const void *key, const void *value) entry->key = key; entry->value = value; - if (hash->count > hash->n_buckets) { + if (hash->count > hash->n_buckets_mask) { /* Not being able to resize the bucket array is not an error */ - rehash(hash, hash->n_buckets * 2); + rehash(hash, (hash->n_buckets_mask + 1) * 2); } return 0; @@ -389,9 +393,9 @@ int hash_add_unique(struct hash *hash, const void *key, const void *value) entry->key = key; entry->value = value; - if (hash->count > hash->n_buckets) { + if (hash->count > hash->n_buckets_mask) { /* Not being able to resize the bucket array is not an error */ - rehash(hash, hash->n_buckets * 2); + rehash(hash, (hash->n_buckets_mask + 1) * 2); } return 0; @@ -400,7 +404,7 @@ int hash_add_unique(struct hash *hash, const void *key, const void *value) static inline struct hash_entry * hash_find_entry(const struct hash *hash, const char *key, unsigned int hashval) { - unsigned int pos = hashval & (hash->n_buckets - 1); + unsigned int pos = hashval & hash->n_buckets_mask; const struct hash_bucket *bucket = hash->buckets + pos; struct hash_entry *entry, *entry_end; @@ -429,7 +433,7 @@ void *hash_find(const struct hash *hash, const void *key) int hash_del(struct hash *hash, const void *key) { unsigned int hashval = hash->hash_value(key); - unsigned int pos = hashval & (hash->n_buckets - 1); + unsigned int pos = hashval & hash->n_buckets_mask; struct hash_bucket *bucket = hash->buckets + pos; struct hash_entry *entry, *entry_end; @@ -447,9 +451,9 @@ int hash_del(struct hash *hash, const void *key) bucket->used--; hash->count--; - if (hash->n_buckets > MIN_BUCKETS && hash->count < hash->n_buckets / 2) { + if (hash->n_buckets_mask > (MIN_BUCKETS - 1) && hash->count < hash->n_buckets_mask / 2) { /* Not being able to trim the bucket array size isn't an error. */ - rehash(hash, hash->n_buckets / 2); + rehash(hash, (hash->n_buckets_mask + 1) / 2); } else { unsigned int steps_used = bucket->used / STEPS; unsigned int steps_total = bucket->total / STEPS; @@ -486,16 +490,18 @@ bool hash_iter_next(struct hash_iter *iter, iter->entry++; if ((unsigned int)iter->entry >= b->used) { + unsigned int n_buckets = iter->hash->n_buckets_mask + 1; + iter->entry = 0; - for (iter->bucket++; iter->bucket < iter->hash->n_buckets; iter->bucket++) { + for (iter->bucket++; iter->bucket < n_buckets; iter->bucket++) { b = iter->hash->buckets + iter->bucket; if (b->used > 0) break; } - if (iter->bucket >= iter->hash->n_buckets) + if (iter->bucket >= n_buckets) return false; } From fe53937ad7fe0abd3a5aabef201e2fe4a56c52d0 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 12 Sep 2018 08:36:59 -0700 Subject: [PATCH 0842/2505] Clean up n_buckets_mask -> n_buckets conversion --- src/lib/hash.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/lib/hash.c b/src/lib/hash.c index 6c73f2c1e..c048822dc 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -230,6 +230,12 @@ struct hash *hash_str_new(void (*free_key)(void *value), free_key ? free_key : no_op, free_value ? free_value : no_op); } +static __attribute__((pure)) inline unsigned int +hash_n_buckets(const struct hash *hash) +{ + return hash->n_buckets_mask + 1; +} + void hash_free(struct hash *hash) { struct hash_bucket *bucket, *bucket_end; @@ -238,7 +244,7 @@ void hash_free(struct hash *hash) return; bucket = hash->buckets; - bucket_end = hash->buckets + hash->n_buckets_mask + 1; + bucket_end = hash->buckets + hash_n_buckets(hash); for (; bucket < bucket_end; bucket++) { struct hash_entry *entry, *entry_end; entry = bucket->entries; @@ -298,12 +304,12 @@ static struct hash_entry *hash_add_entry_hashed(struct hash *hash, const void *k static int rehash(struct hash *hash, unsigned int new_bucket_size) { struct hash_bucket *buckets = calloc(new_bucket_size, sizeof(*buckets)); - const struct hash_bucket *bucket_end = hash->buckets + hash->n_buckets_mask + 1; + const struct hash_bucket *bucket_end = hash->buckets + hash_n_buckets(hash); const struct hash_bucket *bucket; struct hash hash_copy = *hash; assert((new_bucket_size & (new_bucket_size - 1)) == 0); - assert((hash->n_buckets_mask + 1) != new_bucket_size); + assert(hash_n_buckets(hash) != new_bucket_size); if (buckets == NULL) return -errno; @@ -373,7 +379,7 @@ int hash_add(struct hash *hash, const void *key, const void *value) if (hash->count > hash->n_buckets_mask) { /* Not being able to resize the bucket array is not an error */ - rehash(hash, (hash->n_buckets_mask + 1) * 2); + rehash(hash, hash_n_buckets(hash) * 2); } return 0; @@ -395,7 +401,7 @@ int hash_add_unique(struct hash *hash, const void *key, const void *value) if (hash->count > hash->n_buckets_mask) { /* Not being able to resize the bucket array is not an error */ - rehash(hash, (hash->n_buckets_mask + 1) * 2); + rehash(hash, hash_n_buckets(hash) * 2); } return 0; @@ -453,7 +459,7 @@ int hash_del(struct hash *hash, const void *key) if (hash->n_buckets_mask > (MIN_BUCKETS - 1) && hash->count < hash->n_buckets_mask / 2) { /* Not being able to trim the bucket array size isn't an error. */ - rehash(hash, (hash->n_buckets_mask + 1) / 2); + rehash(hash, hash_n_buckets(hash) / 2); } else { unsigned int steps_used = bucket->used / STEPS; unsigned int steps_total = bucket->total / STEPS; @@ -490,7 +496,7 @@ bool hash_iter_next(struct hash_iter *iter, iter->entry++; if ((unsigned int)iter->entry >= b->used) { - unsigned int n_buckets = iter->hash->n_buckets_mask + 1; + unsigned int n_buckets = hash_n_buckets(iter->hash); iter->entry = 0; From 75cfa448a4ba5c34d271c2d2cba03332a73d27dd Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 12 Sep 2018 19:39:42 -0700 Subject: [PATCH 0843/2505] Reduce number of allocation steps to 4 After some testing, I realized that most buckets won't have more than 4 entries in each bucket, so we can reduce the allocation step to 4, reducing even further the hash table size. --- src/lib/hash.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/lib/hash.c b/src/lib/hash.c index c048822dc..131229cd1 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -62,7 +62,12 @@ struct hash { }; #define MIN_BUCKETS 64 -#define STEPS 16 + +/* Due to rehashing heuristics, most hash tables won't have more than 4 + * entries in each bucket. Use a conservative allocation threshold to + * curb wasteful allocations */ +#define STEPS 4 + #define DEFAULT_ODD_CONSTANT 0x27d4eb2d static_assert((MIN_BUCKETS & (MIN_BUCKETS - 1)) == 0, From 8d7f476e868501fd21471506e5e6f8030d24e397 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 12 Sep 2018 22:33:33 -0700 Subject: [PATCH 0844/2505] When rehashing fails, ensure temporary bucket entries are freed --- src/lib/hash.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/lib/hash.c b/src/lib/hash.c index 131229cd1..abc01c030 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -331,10 +331,8 @@ static int rehash(struct hash *hash, unsigned int new_bucket_size) struct hash_entry *new; new = hash_add_entry_hashed(&hash_copy, old->key, old->hashval); - if (UNLIKELY(!new)) { - free(buckets); - return -ENOMEM; - } + if (UNLIKELY(!new)) + goto fail; new->key = old->key; new->value = old->value; @@ -354,6 +352,14 @@ static int rehash(struct hash *hash, unsigned int new_bucket_size) assert(hash_copy.count == hash->count); return 0; + +fail: + for (bucket_end = bucket, bucket = hash->buckets; bucket < bucket_end; + bucket++) + free(bucket->entries); + + free(buckets); + return -ENOMEM; } static struct hash_entry *hash_add_entry(struct hash *hash, const void *key) From 0f614659f031bde1f12d54a8c7765866e450c28f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 16 Sep 2018 17:40:02 -0700 Subject: [PATCH 0845/2505] Cleanup lwan_response_send_event() --- src/lib/lwan-response.c | 55 +++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 0f90afcca..31304c45f 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -442,46 +442,47 @@ lwan_response_set_event_stream(struct lwan_request *request, return true; } -void -lwan_response_send_event(struct lwan_request *request, const char *event) +void lwan_response_send_event(struct lwan_request *request, const char *event) { + struct iovec vec[6]; + int last = 0; + if (!(request->flags & RESPONSE_SENT_HEADERS)) { if (UNLIKELY(!lwan_response_set_event_stream(request, HTTP_OK))) return; } - struct iovec vec[6]; - int last = 0; - if (event) { - vec[last].iov_base = "event: "; - vec[last].iov_len = sizeof("event: ") - 1; - last++; - - vec[last].iov_base = (char *)event; - vec[last].iov_len = strlen(event); - last++; - - vec[last].iov_base = "\r\n"; - vec[last].iov_len = 2; - last++; + vec[last++] = (struct iovec){ + .iov_base = "event: ", + .iov_len = sizeof("event: ") - 1, + }; + vec[last++] = (struct iovec){ + .iov_base = (char *)event, + .iov_len = strlen(event), + }; + vec[last++] = (struct iovec){ + .iov_base = "\r\n", + .iov_len = 2, + }; } size_t buffer_len = lwan_strbuf_get_length(request->response.buffer); if (buffer_len) { - vec[last].iov_base = "data: "; - vec[last].iov_len = sizeof("data: ") - 1; - last++; - - vec[last].iov_base = lwan_strbuf_get_buffer(request->response.buffer); - vec[last].iov_len = buffer_len; - last++; - + vec[last++] = (struct iovec){ + .iov_base = "data: ", + .iov_len = sizeof("data: ") - 1, + }; + vec[last++] = (struct iovec){ + .iov_base = lwan_strbuf_get_buffer(request->response.buffer), + .iov_len = buffer_len, + }; } - vec[last].iov_base = "\r\n\r\n"; - vec[last].iov_len = 4; - last++; + vec[last++] = (struct iovec){ + .iov_base = "\r\n\r\n", + .iov_len = 4, + }; lwan_writev(request, vec, last); From c3fafc562d846641d9ff0399f386016a707fec3c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 26 Sep 2018 18:56:42 -0700 Subject: [PATCH 0846/2505] Put NUL terminator in the correct place in coro_strndup() The NUL terminator was being written always at the last byte in a chunk of memory of max_len bytes. This might not correspond to the size of the string, if the string to be copied is smaller than that buffer. Use strnlen() to calculate the minimum between the max desired length and the actual size for the string being copied. To avoid strlen() being called in addition to strnlen() when coro_strdup() (which is implemented on top of coro_strndup()) is called, always pass `SIZE_MAX - 1` as the maximum length instead. This is as safe as calling strlen() anyway. --- src/lib/lwan-coro.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index d87c967f8..eede35286 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -364,8 +364,9 @@ coro_malloc(struct coro *coro, size_t size) char * coro_strndup(struct coro *coro, const char *str, size_t max_len) { - const size_t len = max_len + 1; + const size_t len = strnlen(str, max_len) + 1; char *dup = coro_malloc(coro, len); + if (LIKELY(dup)) { memcpy(dup, str, len); dup[len - 1] = '\0'; @@ -376,7 +377,7 @@ coro_strndup(struct coro *coro, const char *str, size_t max_len) char * coro_strdup(struct coro *coro, const char *str) { - return coro_strndup(coro, str, strlen(str)); + return coro_strndup(coro, str, SIZE_MAX - 1); } char * From 85b56c5927becfeedbc7ab83a7f0aaaa9f8b7618 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 28 Sep 2018 07:16:38 -0700 Subject: [PATCH 0847/2505] No need to pass xdc to lerp() --- src/samples/clock/xdaliclock.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/samples/clock/xdaliclock.c b/src/samples/clock/xdaliclock.c index c36309cb2..4bf682f38 100644 --- a/src/samples/clock/xdaliclock.c +++ b/src/samples/clock/xdaliclock.c @@ -185,7 +185,7 @@ __attribute__((constructor)) static void initialize_numbers(void) easing[FRAMES_PER_SECOND - 1] = 65535u; } -static inline POS lerp(const struct xdaliclock *xdc, POS a, POS b, unsigned int anim) +static inline POS lerp(POS a, POS b, unsigned int anim) { uint32_t part_a = a * (65536 - anim); uint32_t part_b = b * (anim + 1); @@ -211,9 +211,8 @@ static const struct frame *frame_lerp(struct xdaliclock *xdc, int digit, unsigne const struct scanline *from_line = &fromf->scanlines[y]; for (x = 0; x < MAX_SEGS_PER_LINE; x++) { - line->left[x] = lerp(xdc, from_line->left[x], to_line->left[x], anim); - line->right[x] = - lerp(xdc, from_line->right[x], to_line->right[x], anim); + line->left[x] = lerp(from_line->left[x], to_line->left[x], anim); + line->right[x] = lerp(from_line->right[x], to_line->right[x], anim); } } From 311d0452c5849c3fc199f395852f6022899dd7d4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 28 Sep 2018 07:17:18 -0700 Subject: [PATCH 0848/2505] Update documentation about some configuration options --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 0005eb57b..210ad2101 100644 --- a/README.md +++ b/README.md @@ -255,13 +255,13 @@ can be decided automatically, so some configuration options are provided. | Option | Type | Default | Description | |--------|------|---------|-------------| -| `keep alive timeout` | `time` | `15` | Timeout to keep a connection alive | +| `keep_alive_timeout` | `time` | `15` | Timeout to keep a connection alive | | `quiet` | `bool` | `false` | Set to true to not print any debugging messages. Only effective in release builds. | -| `reuse port` | `bool` | `false` | Sets `SO_REUSEPORT` to `1` in the master socket | +| `reuse_port` | `bool` | `false` | Sets `SO_REUSEPORT` to `1` in the master socket | | `expires` | `time` | `1M 1w` | Value of the "Expires" header. Default is 1 month and 1 week | | `threads` | `int` | `0` | Number of I/O threads. Default (0) is the number of online CPUs | -| `proxy protocol` | `bool` | `false` | Enables the [PROXY protocol](https://www.haproxy.com/blog/haproxy/proxy-protocol/). Versions 1 and 2 are supported. Only enable this setting if using Lwan behind a proxy, and the proxy supports this protocol; otherwise, this allows anybody to spoof origin IP addresses | -| `max post data size` | `int` | `40960` | Sets the maximum number of data size for POST requests, in bytes | +| `proxy_protocol` | `bool` | `false` | Enables the [PROXY protocol](https://www.haproxy.com/blog/haproxy/proxy-protocol/). Versions 1 and 2 are supported. Only enable this setting if using Lwan behind a proxy, and the proxy supports this protocol; otherwise, this allows anybody to spoof origin IP addresses | +| `max_post_data_size` | `int` | `40960` | Sets the maximum number of data size for POST requests, in bytes | ### Straitjacket @@ -282,7 +282,7 @@ malconfiguration.) |--------|------|---------|-------------| | `user` | `str` | `NULL` | Drop privileges to this user name | | `chroot` | `str` | `NULL` | Path to `chroot()` | -| `drop_capabilities` | `bool` | `true` | Drop all capabilities with capset(2). Only effective under Linux. | +| `drop_capabilities` | `bool` | `true` | Drop all capabilities with capset(2) (under Linux), or pledge(2) (under OpenBSD). | ### Listeners @@ -335,7 +335,7 @@ best to serve files in the fastest way possible according to some heuristics. | `serve_precompressed_path` | `bool` | `true` | If $FILE.gz exists, is smaller and newer than $FILE, and the client accepts `gzip` encoding, transfer it | | `auto_index` | `bool` | `true` | Generate a directory list automatically if no `index_path` file present. Otherwise, yields 404 | | `directory_list_template` | `str` | `NULL` | Path to a Mustache template for the directory list; by default, use an internal template | -| `read_ahead` | `int` | `131702` | Maximum amount of bytes to read ahead when caching open files. A value of `0` disables readahead. Larger values can block until all the filesystem metadata is loaded from the disk. | +| `read_ahead` | `int` | `131702` | Maximum amount of bytes to read ahead when caching open files. A value of `0` disables readahead. Readahead is performed by a low priority thread to not block the I/O threads while file extents are being read from the filesystem. | #### Lua @@ -483,7 +483,7 @@ section with a `basic` parameter, and set one of its options. | Option | Type | Default | Description | |--------|------|---------|-------------| | `realm` | `str` | `Lwan` | Realm for authorization. This is usually shown in the user/password UI in browsers | -| `password file` | `str` | `NULL` | Path for a file containing username and passwords (in clear text). The file format is the same as the configuration file format used by Lwan | +| `password_file` | `str` | `NULL` | Path for a file containing username and passwords (in clear text). The file format is the same as the configuration file format used by Lwan | Hacking ------- From 7ab1dd5df0dd70cd392c99ad41fe5985e7e2ee8a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 28 Sep 2018 07:18:22 -0700 Subject: [PATCH 0849/2505] Formatted timestamps are 29 characters long, so reserve 30 bytes for them --- src/lib/lwan-mod-serve-files.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index bdacbab71..98bf26317 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -102,7 +102,7 @@ struct file_cache_entry { struct cache_entry base; struct { - char string[31]; + char string[30]; time_t integer; } last_modified; From df39a3b1fba2572c179bc6c6cc127fdc191b1954 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 29 Sep 2018 10:17:46 -0700 Subject: [PATCH 0850/2505] Port Tobias Blum's Tetris Time to Lwan Port @toblum's Tetris Time[1] project to Lwan. The code has been cleaned up quite a bit, in order to use more tables instead of functions, but it's essentially still the same logic. [1] https://github.com/toblum/esp_p10_tetris_clock --- src/samples/clock/CMakeLists.txt | 1 + src/samples/clock/blocks.c | 336 +++++++++++++++++++++++++++++++ src/samples/clock/blocks.h | 14 ++ src/samples/clock/main.c | 70 ++++++- 4 files changed, 419 insertions(+), 2 deletions(-) create mode 100644 src/samples/clock/blocks.c create mode 100644 src/samples/clock/blocks.h diff --git a/src/samples/clock/CMakeLists.txt b/src/samples/clock/CMakeLists.txt index bd3751113..69d335258 100644 --- a/src/samples/clock/CMakeLists.txt +++ b/src/samples/clock/CMakeLists.txt @@ -3,6 +3,7 @@ add_executable(clock gifenc.c xdaliclock.c numbers.c + blocks.c ) target_link_libraries(clock diff --git a/src/samples/clock/blocks.c b/src/samples/clock/blocks.c new file mode 100644 index 000000000..140d62fb1 --- /dev/null +++ b/src/samples/clock/blocks.c @@ -0,0 +1,336 @@ +/* + * Falling block clock + * Copyright (c) 2018 Leandro A. F. Pereira + * + * Inspired by code written by Tobias Blum + * https://github.com/toblum/esp_p10_tetris_clock + * + * Licensed under the terms of the MIT License. + */ + +#include +#include +#include + +#include "blocks.h" + +enum shape { + SHAPE_SQUARE = 0, + SHAPE_L = 1, + SHAPE_L_REVERSE = 2, + SHAPE_I = 3, + SHAPE_S = 4, + SHAPE_S_REVERSE = 5, + SHAPE_T = 6, + SHAPE_MAX, +}; + +enum color { + COLOR_BLACK = 0, + COLOR_RED = 1, + COLOR_GREEN = 2, + COLOR_ORANGE = 3, + COLOR_BLUE = 4, + COLOR_MAGENTA = 5, + COLOR_CYAN = 6, + COLOR_YELLOW = 11, + COLOR_WHITE = 15, + COLOR_MAX, +}; + +struct fall { + enum shape shape; + enum color color; + int x_pos; + int y_stop; + int n_rot; +}; + +static const int offs[SHAPE_MAX][4][8] = { + [SHAPE_SQUARE][0] = {0, 0, 1, 0, 0, -1, 1, -1}, + [SHAPE_SQUARE][1] = {0, 0, 1, 0, 0, -1, 1, -1}, + [SHAPE_SQUARE][2] = {0, 0, 1, 0, 0, -1, 1, -1}, + [SHAPE_SQUARE][3] = {0, 0, 1, 0, 0, -1, 1, -1}, + [SHAPE_L][0] = {0, 0, 1, 0, 0, -1, 0, -2}, + [SHAPE_L][1] = {0, 0, 0, -1, 1, -1, 2, -1}, + [SHAPE_L][2] = {1, 0, 1, -1, 1, -2, 0, -2}, + [SHAPE_L][3] = {0, 0, 1, 0, 2, 0, 2, -1}, + [SHAPE_L_REVERSE][0] = {0, 0, 1, 0, 1, -1, 1, -2}, + [SHAPE_L_REVERSE][1] = {0, 0, 1, 0, 2, 0, 0, -1}, + [SHAPE_L_REVERSE][2] = {0, 0, 0, -1, 0, -2, 1, -2}, + [SHAPE_L_REVERSE][3] = {0, -1, 1, -1, 2, -1, 2, 0}, + [SHAPE_I][0] = {0, 0, 1, 0, 2, 0, 3, 0}, + [SHAPE_I][1] = {0, 0, 0, -1, 0, -2, 0, -3}, + [SHAPE_I][2] = {0, 0, 1, 0, 2, 0, 3, 0}, + [SHAPE_I][3] = {0, 0, 0, -1, 0, -2, 0, -3}, + [SHAPE_S][0] = {1, 0, 0, -1, 1, -1, 0, -2}, + [SHAPE_S][1] = {0, 0, 1, 0, 1, -1, 2, -1}, + [SHAPE_S][2] = {1, 0, 0, -1, 1, -1, 0, -2}, + [SHAPE_S][3] = {0, 0, 1, 0, 1, -1, 2, -1}, + [SHAPE_S_REVERSE][0] = {0, 0, 0, -1, 1, -1, 1, -2}, + [SHAPE_S_REVERSE][1] = {1, 0, 2, 0, 0, -1, 1, -1}, + [SHAPE_S_REVERSE][2] = {0, 0, 0, -1, 1, -1, 1, -2}, + [SHAPE_S_REVERSE][3] = {1, 0, 2, 0, 0, -1, 1, -1}, + [SHAPE_T][0] = {0, 0, 1, 0, 2, 0, 1, -1}, + [SHAPE_T][1] = {0, 0, 0, -1, 0, -2, 1, -1}, + [SHAPE_T][2] = {1, 0, 0, -1, 1, -1, 2, -1}, + [SHAPE_T][3] = {1, 0, 0, -1, 1, -1, 1, -2}, +}; + +static const struct fall fall0[] = { + {SHAPE_L_REVERSE, COLOR_CYAN, 4, 16, 0}, + {SHAPE_S, COLOR_ORANGE, 2, 16, 1}, + {SHAPE_I, COLOR_YELLOW, 0, 16, 1}, + {SHAPE_T, COLOR_MAGENTA, 1, 16, 1}, + {SHAPE_S_REVERSE, COLOR_GREEN, 4, 14, 0}, + {SHAPE_T, COLOR_MAGENTA, 0, 13, 3}, + {SHAPE_S_REVERSE, COLOR_GREEN, 4, 12, 0}, + {SHAPE_S_REVERSE, COLOR_GREEN, 0, 11, 0}, + {SHAPE_T, COLOR_MAGENTA, 4, 10, 1}, + {SHAPE_T, COLOR_MAGENTA, 0, 9, 1}, + {SHAPE_S_REVERSE, COLOR_GREEN, 1, 8, 1}, + {SHAPE_L_REVERSE, COLOR_CYAN, 3, 8, 3}, + {SHAPE_MAX, COLOR_MAX, 0, 0, 0}, +}; + +static const struct fall fall1[] = { + {SHAPE_L_REVERSE, COLOR_CYAN, 4, 16, 0}, + {SHAPE_I, COLOR_YELLOW, 4, 15, 1}, + {SHAPE_I, COLOR_YELLOW, 5, 13, 3}, + {SHAPE_L_REVERSE, COLOR_CYAN, 4, 11, 2}, + {SHAPE_SQUARE, COLOR_RED, 4, 8, 0}, + {SHAPE_MAX, COLOR_MAX, 0, 0, 0}, +}; + +static const struct fall fall2[] = { + {SHAPE_SQUARE, COLOR_RED, 4, 16, 0}, + {SHAPE_I, COLOR_YELLOW, 0, 16, 1}, + {SHAPE_L, COLOR_BLUE, 1, 16, 3}, + {SHAPE_L, COLOR_BLUE, 1, 15, 0}, + {SHAPE_I, COLOR_YELLOW, 1, 12, 2}, + {SHAPE_L, COLOR_BLUE, 0, 12, 1}, + {SHAPE_L_REVERSE, COLOR_CYAN, 3, 12, 3}, + {SHAPE_SQUARE, COLOR_RED, 4, 10, 0}, + {SHAPE_I, COLOR_YELLOW, 1, 8, 0}, + {SHAPE_L_REVERSE, COLOR_CYAN, 3, 8, 3}, + {SHAPE_L, COLOR_BLUE, 0, 8, 1}, + {SHAPE_MAX, COLOR_MAX, 0, 0, 0}, +}; + +static const struct fall fall3[] = { + {SHAPE_L, COLOR_BLUE, 3, 16, 3}, + {SHAPE_L_REVERSE, COLOR_CYAN, 0, 16, 1}, + {SHAPE_I, COLOR_YELLOW, 1, 15, 2}, + {SHAPE_SQUARE, COLOR_RED, 4, 14, 0}, + {SHAPE_I, COLOR_YELLOW, 1, 12, 2}, + {SHAPE_L, COLOR_BLUE, 0, 12, 1}, + {SHAPE_I, COLOR_YELLOW, 5, 12, 3}, + {SHAPE_L_REVERSE, COLOR_CYAN, 3, 11, 0}, + {SHAPE_I, COLOR_YELLOW, 1, 8, 0}, + {SHAPE_L, COLOR_BLUE, 0, 8, 1}, + {SHAPE_L_REVERSE, COLOR_CYAN, 3, 8, 3}, + {SHAPE_MAX, COLOR_MAX, 0, 0, 0}, +}; + +static const struct fall fall4[] = { + {SHAPE_SQUARE, COLOR_RED, 4, 16, 0}, + {SHAPE_SQUARE, COLOR_RED, 4, 14, 0}, + {SHAPE_I, COLOR_YELLOW, 1, 12, 0}, + {SHAPE_L, COLOR_BLUE, 0, 12, 1}, + {SHAPE_L_REVERSE, COLOR_CYAN, 0, 10, 0}, + {SHAPE_L_REVERSE, COLOR_CYAN, 3, 12, 3}, + {SHAPE_I, COLOR_YELLOW, 4, 10, 3}, + {SHAPE_L_REVERSE, COLOR_CYAN, 0, 9, 2}, + {SHAPE_I, COLOR_YELLOW, 5, 10, 1}, + {SHAPE_MAX, COLOR_MAX, 0, 0, 0}, +}; + +static const struct fall fall5[] = { + {SHAPE_SQUARE, COLOR_RED, 0, 16, 0}, + {SHAPE_L_REVERSE, COLOR_CYAN, 2, 16, 1}, + {SHAPE_L_REVERSE, COLOR_CYAN, 3, 15, 0}, + {SHAPE_I, COLOR_YELLOW, 5, 16, 1}, + {SHAPE_I, COLOR_YELLOW, 1, 12, 0}, + {SHAPE_L, COLOR_BLUE, 0, 12, 1}, + {SHAPE_L_REVERSE, COLOR_CYAN, 3, 12, 3}, + {SHAPE_SQUARE, COLOR_RED, 0, 10, 0}, + {SHAPE_I, COLOR_YELLOW, 1, 8, 2}, + {SHAPE_L, COLOR_BLUE, 0, 8, 1}, + {SHAPE_L_REVERSE, COLOR_CYAN, 3, 8, 3}, + {SHAPE_MAX, COLOR_MAX, 0, 0, 0}, +}; + +static const struct fall fall6[] = { + {SHAPE_L_REVERSE, COLOR_CYAN, 0, 16, 1}, + {SHAPE_S_REVERSE, COLOR_GREEN, 2, 16, 1}, + {SHAPE_T, COLOR_MAGENTA, 0, 15, 3}, + {SHAPE_T, COLOR_MAGENTA, 4, 16, 3}, + {SHAPE_S_REVERSE, COLOR_GREEN, 4, 14, 0}, + {SHAPE_I, COLOR_YELLOW, 1, 12, 2}, + {SHAPE_L_REVERSE, COLOR_CYAN, 0, 13, 2}, + {SHAPE_I, COLOR_YELLOW, 2, 11, 0}, + {SHAPE_SQUARE, COLOR_RED, 0, 10, 0}, + {SHAPE_I, COLOR_YELLOW, 1, 8, 0}, + {SHAPE_L, COLOR_BLUE, 0, 8, 1}, + {SHAPE_L_REVERSE, COLOR_CYAN, 3, 8, 3}, + {SHAPE_MAX, COLOR_MAX, 0, 0, 0}, +}; + +static const struct fall fall7[] = { + {SHAPE_SQUARE, COLOR_RED, 4, 16, 0}, + {SHAPE_L, COLOR_BLUE, 4, 14, 0}, + {SHAPE_I, COLOR_YELLOW, 5, 13, 1}, + {SHAPE_L_REVERSE, COLOR_CYAN, 4, 11, 2}, + {SHAPE_I, COLOR_YELLOW, 1, 8, 2}, + {SHAPE_L_REVERSE, COLOR_CYAN, 3, 8, 3}, + {SHAPE_L, COLOR_BLUE, 0, 8, 1}, + {SHAPE_MAX, COLOR_MAX, 0, 0, 0}, +}; + +static const struct fall fall8[] = { + {SHAPE_I, COLOR_YELLOW, 1, 16, 0}, + {SHAPE_T, COLOR_MAGENTA, 0, 16, 1}, + {SHAPE_I, COLOR_YELLOW, 5, 16, 1}, + {SHAPE_L, COLOR_BLUE, 2, 15, 3}, + {SHAPE_S, COLOR_ORANGE, 0, 14, 0}, + {SHAPE_L, COLOR_BLUE, 1, 12, 3}, + {SHAPE_T, COLOR_MAGENTA, 4, 13, 1}, + {SHAPE_L_REVERSE, COLOR_CYAN, 0, 11, 1}, + {SHAPE_S, COLOR_ORANGE, 0, 10, 0}, + {SHAPE_S, COLOR_ORANGE, 4, 11, 0}, + {SHAPE_S_REVERSE, COLOR_GREEN, 0, 8, 1}, + {SHAPE_S_REVERSE, COLOR_GREEN, 2, 8, 1}, + {SHAPE_L, COLOR_BLUE, 4, 9, 2}, + {SHAPE_MAX, COLOR_MAX, 0, 0, 0}, +}; + +static const struct fall fall9[] = { + {SHAPE_SQUARE, COLOR_RED, 0, 16, 0}, + {SHAPE_I, COLOR_YELLOW, 2, 16, 0}, + {SHAPE_L, COLOR_BLUE, 2, 15, 3}, + {SHAPE_L, COLOR_BLUE, 4, 15, 2}, + {SHAPE_I, COLOR_YELLOW, 1, 12, 2}, + {SHAPE_I, COLOR_YELLOW, 5, 12, 3}, + {SHAPE_S_REVERSE, COLOR_GREEN, 0, 12, 0}, + {SHAPE_L, COLOR_BLUE, 2, 11, 3}, + {SHAPE_S_REVERSE, COLOR_GREEN, 4, 9, 0}, + {SHAPE_T, COLOR_MAGENTA, 0, 10, 1}, + {SHAPE_S_REVERSE, COLOR_GREEN, 0, 8, 1}, + {SHAPE_T, COLOR_MAGENTA, 2, 8, 2}, + {SHAPE_MAX, COLOR_MAX, 0, 0, 0}, +}; + +static const struct fall *fall[] = { + fall0, fall1, fall2, fall3, fall4, fall5, fall6, fall7, fall8, fall9, +}; + +static int block_sizes[10]; + +__attribute__((constructor)) void calculate_block_sizes(void) +{ + for (int i = 0; i < 10; i++) { + const struct fall *instr = fall[i]; + + while (instr->shape != SHAPE_MAX) + instr++; + + block_sizes[i] = (int)(instr - fall[i]) + 1; + } +} + +static void draw_shape(enum shape shape, + enum color color, + int x, + int y, + int rot, + unsigned char *buffer) +{ + assert(rot >= 0 && rot <= 3); + + if (y < 0) + return; + + for (int i = 0; i < 8; i += 2) { + int x_off = offs[shape][rot][i + 0]; + int y_off = offs[shape][rot][i + 1]; + int dx = x + x_off; + + if (dx < 32) + buffer[(y + y_off) * 32 + dx] = color; + } +} + +void blocks_init(struct block_state *states) +{ + states[0] = (struct block_state){1, 0, 0, 1}; + states[1] = (struct block_state){2, 0, 0, 8}; + states[2] = (struct block_state){3, 0, 0, 18}; + states[3] = (struct block_state){4, 0, 0, 25}; +} + +uint64_t blocks_draw(struct block_state *states, unsigned char *buffer, bool odd_second) +{ + int digits_fallen = 0; + int i; + + memset(buffer, COLOR_BLACK, 32 * 16); + + for (i = 0; i < 4; i++) { + struct block_state *state = &states[i]; + + if (state->block_index < block_sizes[state->num_to_draw]) { + const struct fall *curr = + &fall[state->num_to_draw][state->block_index]; + int rotations = curr->n_rot; + + switch (rotations) { + case 1: + if (state->fall_index < curr->y_stop / 2) + rotations = 0; + break; + case 2: + if (state->fall_index < curr->y_stop / 3) + rotations = 0; + else if (state->fall_index < curr->y_stop / 3 * 2) + rotations = 1; + break; + case 3: + if (state->fall_index < curr->y_stop / 4) + rotations = 0; + else if (state->fall_index < curr->y_stop / 4 * 2) + rotations = 1; + else if (state->fall_index < curr->y_stop / 4 * 3) + rotations = 2; + break; + } + + draw_shape(curr->shape, curr->color, curr->x_pos + state->x_shift, + state->fall_index - 1, rotations, buffer); + state->fall_index++; + + if (state->fall_index > curr->y_stop) { + state->fall_index = 0; + state->block_index++; + } + + digits_fallen++; + } + + if (state->block_index > 0) { + for (int j = 0; j < state->block_index; j++) { + const struct fall *fallen = &fall[state->num_to_draw][j]; + + draw_shape(fallen->shape, fallen->color, + fallen->x_pos + state->x_shift, fallen->y_stop - 1, + fallen->n_rot, buffer); + } + } + } + + if (odd_second & 1) { + draw_shape(SHAPE_SQUARE, COLOR_WHITE, 15, 13, 0, buffer); + draw_shape(SHAPE_SQUARE, COLOR_WHITE, 15, 9, 0, buffer); + } + + return digits_fallen ? 100 : 500; +} diff --git a/src/samples/clock/blocks.h b/src/samples/clock/blocks.h new file mode 100644 index 000000000..97c506eaa --- /dev/null +++ b/src/samples/clock/blocks.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +struct block_state { + int num_to_draw; + int block_index; + int fall_index; + int x_shift; +}; + +void blocks_init(struct block_state *states); +uint64_t blocks_draw(struct block_state *states, unsigned char *buffer, bool odd_second); diff --git a/src/samples/clock/main.c b/src/samples/clock/main.c index 784a353f4..db71cc069 100644 --- a/src/samples/clock/main.c +++ b/src/samples/clock/main.c @@ -25,6 +25,7 @@ #include "lwan-mod-redirect.h" #include "gifenc.h" #include "xdaliclock.h" +#include "blocks.h" /* Font stolen from https://github.com/def-/time.gif */ static const uint8_t font[10][5] = { @@ -145,6 +146,54 @@ LWAN_HANDLER(dali) return HTTP_OK; } +LWAN_HANDLER(blocks) +{ + ge_GIF *gif = ge_new_gif(response->buffer, 32, 16, NULL, 4, -1); + struct block_state blocks[4]; + int last_digits[4] = {0, 0, 0, 0}; + uint64_t total_waited = 0; + + if (!gif) + return HTTP_INTERNAL_ERROR; + + coro_defer(request->conn->coro, destroy_gif, gif); + + blocks_init(blocks); + + response->mime_type = "image/gif"; + response->headers = seriously_do_not_cache; + + memset(gif->frame, 0, (size_t)(32 * 16)); + + while (total_waited <= 3600000) { + uint64_t timeout; + time_t curtime; + char digits[5]; + + curtime = time(NULL); + strftime(digits, sizeof(digits), "%H%M", localtime(&curtime)); + + for (int i = 0; i < 4; i++) { + blocks[i].num_to_draw = digits[i] - '0'; + + if (blocks[i].num_to_draw != last_digits[i]) { + blocks[i].fall_index = 0; + blocks[i].block_index = 0; + last_digits[i] = blocks[i].num_to_draw; + } + } + + timeout = blocks_draw(blocks, gif->frame, curtime & 1); + total_waited += timeout; + + ge_add_frame(gif, 0); + lwan_response_send_chunk(request); + lwan_request_sleep(request, timeout); + } + + return HTTP_OK; +} + struct index { const char *title; const char *variant; @@ -186,7 +235,7 @@ __attribute__((constructor)) static void initialize_template(void) " position: absolute;\n" " padding: 16px;\n" " left: calc(50% - 100px - 16px);\n" - " width: 200px;\n" + " width: 250px;\n" "}\n" "#styles a, #styles a:visited, #lwan a, #lwan a:visited { color: #666; }\n" "#lwan {\n" @@ -213,7 +262,10 @@ __attribute__((constructor)) static void initialize_template(void) " \n" " \n" "
\n" - " Styles: Digital · Dali\n" + " Styles: " + "Digital · " + "Dali · " + "Blocks\n" "
\n" "\n" ""; @@ -246,6 +298,11 @@ int main(void) .variant = "dali", .width = 320, }; + struct index blocks_clock = { + .title = "Lwan Blocks Clock", + .variant = "blocks", + .width = 320, + }; const struct lwan_url_map default_map[] = { { .prefix = "/clock.gif", @@ -255,6 +312,10 @@ int main(void) .prefix = "/dali.gif", .handler = LWAN_HANDLER_REF(dali), }, + { + .prefix = "/blocks.gif", + .handler = LWAN_HANDLER_REF(blocks), + }, { .prefix = "/clock", .handler = LWAN_HANDLER_REF(templated_index), @@ -265,6 +326,11 @@ int main(void) .handler = LWAN_HANDLER_REF(templated_index), .data = &dali_clock, }, + { + .prefix = "/blocks", + .handler = LWAN_HANDLER_REF(templated_index), + .data = &blocks_clock, + }, { .prefix = "/", REDIRECT("/clock"), From 098faf563334757cd3071c4b3d4c2ab55e2342fb Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 29 Sep 2018 12:34:12 -0700 Subject: [PATCH 0851/2505] Reduce memory used to store falling block color information --- src/samples/clock/blocks.c | 200 +++++++++++++------------------------ 1 file changed, 71 insertions(+), 129 deletions(-) diff --git a/src/samples/clock/blocks.c b/src/samples/clock/blocks.c index 140d62fb1..e01f31880 100644 --- a/src/samples/clock/blocks.c +++ b/src/samples/clock/blocks.c @@ -40,12 +40,18 @@ enum color { struct fall { enum shape shape; - enum color color; int x_pos; int y_stop; int n_rot; }; +static const enum color colors[] = { + [SHAPE_SQUARE] = COLOR_RED, [SHAPE_L] = COLOR_CYAN, + [SHAPE_L_REVERSE] = COLOR_BLUE, [SHAPE_I] = COLOR_YELLOW, + [SHAPE_S] = COLOR_GREEN, [SHAPE_S_REVERSE] = COLOR_ORANGE, + [SHAPE_T] = COLOR_MAGENTA, +}; + static const int offs[SHAPE_MAX][4][8] = { [SHAPE_SQUARE][0] = {0, 0, 1, 0, 0, -1, 1, -1}, [SHAPE_SQUARE][1] = {0, 0, 1, 0, 0, -1, 1, -1}, @@ -78,146 +84,86 @@ static const int offs[SHAPE_MAX][4][8] = { }; static const struct fall fall0[] = { - {SHAPE_L_REVERSE, COLOR_CYAN, 4, 16, 0}, - {SHAPE_S, COLOR_ORANGE, 2, 16, 1}, - {SHAPE_I, COLOR_YELLOW, 0, 16, 1}, - {SHAPE_T, COLOR_MAGENTA, 1, 16, 1}, - {SHAPE_S_REVERSE, COLOR_GREEN, 4, 14, 0}, - {SHAPE_T, COLOR_MAGENTA, 0, 13, 3}, - {SHAPE_S_REVERSE, COLOR_GREEN, 4, 12, 0}, - {SHAPE_S_REVERSE, COLOR_GREEN, 0, 11, 0}, - {SHAPE_T, COLOR_MAGENTA, 4, 10, 1}, - {SHAPE_T, COLOR_MAGENTA, 0, 9, 1}, - {SHAPE_S_REVERSE, COLOR_GREEN, 1, 8, 1}, - {SHAPE_L_REVERSE, COLOR_CYAN, 3, 8, 3}, - {SHAPE_MAX, COLOR_MAX, 0, 0, 0}, + {SHAPE_L_REVERSE, 4, 16, 0}, {SHAPE_S, 2, 16, 1}, + {SHAPE_I, 0, 16, 1}, {SHAPE_T, 1, 16, 1}, + {SHAPE_S_REVERSE, 4, 14, 0}, {SHAPE_T, 0, 13, 3}, + {SHAPE_S_REVERSE, 4, 12, 0}, {SHAPE_S_REVERSE, 0, 11, 0}, + {SHAPE_T, 4, 10, 1}, {SHAPE_T, 0, 9, 1}, + {SHAPE_S_REVERSE, 1, 8, 1}, {SHAPE_L_REVERSE, 3, 8, 3}, + {SHAPE_MAX, 0, 0, 0}, }; static const struct fall fall1[] = { - {SHAPE_L_REVERSE, COLOR_CYAN, 4, 16, 0}, - {SHAPE_I, COLOR_YELLOW, 4, 15, 1}, - {SHAPE_I, COLOR_YELLOW, 5, 13, 3}, - {SHAPE_L_REVERSE, COLOR_CYAN, 4, 11, 2}, - {SHAPE_SQUARE, COLOR_RED, 4, 8, 0}, - {SHAPE_MAX, COLOR_MAX, 0, 0, 0}, + {SHAPE_L_REVERSE, 4, 16, 0}, {SHAPE_I, 4, 15, 1}, {SHAPE_I, 5, 13, 3}, + {SHAPE_L_REVERSE, 4, 11, 2}, {SHAPE_SQUARE, 4, 8, 0}, {SHAPE_MAX, 0, 0, 0}, }; static const struct fall fall2[] = { - {SHAPE_SQUARE, COLOR_RED, 4, 16, 0}, - {SHAPE_I, COLOR_YELLOW, 0, 16, 1}, - {SHAPE_L, COLOR_BLUE, 1, 16, 3}, - {SHAPE_L, COLOR_BLUE, 1, 15, 0}, - {SHAPE_I, COLOR_YELLOW, 1, 12, 2}, - {SHAPE_L, COLOR_BLUE, 0, 12, 1}, - {SHAPE_L_REVERSE, COLOR_CYAN, 3, 12, 3}, - {SHAPE_SQUARE, COLOR_RED, 4, 10, 0}, - {SHAPE_I, COLOR_YELLOW, 1, 8, 0}, - {SHAPE_L_REVERSE, COLOR_CYAN, 3, 8, 3}, - {SHAPE_L, COLOR_BLUE, 0, 8, 1}, - {SHAPE_MAX, COLOR_MAX, 0, 0, 0}, + {SHAPE_SQUARE, 4, 16, 0}, {SHAPE_I, 0, 16, 1}, {SHAPE_L, 1, 16, 3}, + {SHAPE_L, 1, 15, 0}, {SHAPE_I, 1, 12, 2}, {SHAPE_L, 0, 12, 1}, + {SHAPE_L_REVERSE, 3, 12, 3}, {SHAPE_SQUARE, 4, 10, 0}, {SHAPE_I, 1, 8, 0}, + {SHAPE_L_REVERSE, 3, 8, 3}, {SHAPE_L, 0, 8, 1}, {SHAPE_MAX, 0, 0, 0}, }; static const struct fall fall3[] = { - {SHAPE_L, COLOR_BLUE, 3, 16, 3}, - {SHAPE_L_REVERSE, COLOR_CYAN, 0, 16, 1}, - {SHAPE_I, COLOR_YELLOW, 1, 15, 2}, - {SHAPE_SQUARE, COLOR_RED, 4, 14, 0}, - {SHAPE_I, COLOR_YELLOW, 1, 12, 2}, - {SHAPE_L, COLOR_BLUE, 0, 12, 1}, - {SHAPE_I, COLOR_YELLOW, 5, 12, 3}, - {SHAPE_L_REVERSE, COLOR_CYAN, 3, 11, 0}, - {SHAPE_I, COLOR_YELLOW, 1, 8, 0}, - {SHAPE_L, COLOR_BLUE, 0, 8, 1}, - {SHAPE_L_REVERSE, COLOR_CYAN, 3, 8, 3}, - {SHAPE_MAX, COLOR_MAX, 0, 0, 0}, + {SHAPE_L, 3, 16, 3}, {SHAPE_L_REVERSE, 0, 16, 1}, {SHAPE_I, 1, 15, 2}, + {SHAPE_SQUARE, 4, 14, 0}, {SHAPE_I, 1, 12, 2}, {SHAPE_L, 0, 12, 1}, + {SHAPE_I, 5, 12, 3}, {SHAPE_L_REVERSE, 3, 11, 0}, {SHAPE_I, 1, 8, 0}, + {SHAPE_L, 0, 8, 1}, {SHAPE_L_REVERSE, 3, 8, 3}, {SHAPE_MAX, 0, 0, 0}, }; static const struct fall fall4[] = { - {SHAPE_SQUARE, COLOR_RED, 4, 16, 0}, - {SHAPE_SQUARE, COLOR_RED, 4, 14, 0}, - {SHAPE_I, COLOR_YELLOW, 1, 12, 0}, - {SHAPE_L, COLOR_BLUE, 0, 12, 1}, - {SHAPE_L_REVERSE, COLOR_CYAN, 0, 10, 0}, - {SHAPE_L_REVERSE, COLOR_CYAN, 3, 12, 3}, - {SHAPE_I, COLOR_YELLOW, 4, 10, 3}, - {SHAPE_L_REVERSE, COLOR_CYAN, 0, 9, 2}, - {SHAPE_I, COLOR_YELLOW, 5, 10, 1}, - {SHAPE_MAX, COLOR_MAX, 0, 0, 0}, + {SHAPE_SQUARE, 4, 16, 0}, {SHAPE_SQUARE, 4, 14, 0}, + {SHAPE_I, 1, 12, 0}, {SHAPE_L, 0, 12, 1}, + {SHAPE_L_REVERSE, 0, 10, 0}, {SHAPE_L_REVERSE, 3, 12, 3}, + {SHAPE_I, 4, 10, 3}, {SHAPE_L_REVERSE, 0, 9, 2}, + {SHAPE_I, 5, 10, 1}, {SHAPE_MAX, 0, 0, 0}, }; static const struct fall fall5[] = { - {SHAPE_SQUARE, COLOR_RED, 0, 16, 0}, - {SHAPE_L_REVERSE, COLOR_CYAN, 2, 16, 1}, - {SHAPE_L_REVERSE, COLOR_CYAN, 3, 15, 0}, - {SHAPE_I, COLOR_YELLOW, 5, 16, 1}, - {SHAPE_I, COLOR_YELLOW, 1, 12, 0}, - {SHAPE_L, COLOR_BLUE, 0, 12, 1}, - {SHAPE_L_REVERSE, COLOR_CYAN, 3, 12, 3}, - {SHAPE_SQUARE, COLOR_RED, 0, 10, 0}, - {SHAPE_I, COLOR_YELLOW, 1, 8, 2}, - {SHAPE_L, COLOR_BLUE, 0, 8, 1}, - {SHAPE_L_REVERSE, COLOR_CYAN, 3, 8, 3}, - {SHAPE_MAX, COLOR_MAX, 0, 0, 0}, + {SHAPE_SQUARE, 0, 16, 0}, {SHAPE_L_REVERSE, 2, 16, 1}, + {SHAPE_L_REVERSE, 3, 15, 0}, {SHAPE_I, 5, 16, 1}, + {SHAPE_I, 1, 12, 0}, {SHAPE_L, 0, 12, 1}, + {SHAPE_L_REVERSE, 3, 12, 3}, {SHAPE_SQUARE, 0, 10, 0}, + {SHAPE_I, 1, 8, 2}, {SHAPE_L, 0, 8, 1}, + {SHAPE_L_REVERSE, 3, 8, 3}, {SHAPE_MAX, 0, 0, 0}, }; static const struct fall fall6[] = { - {SHAPE_L_REVERSE, COLOR_CYAN, 0, 16, 1}, - {SHAPE_S_REVERSE, COLOR_GREEN, 2, 16, 1}, - {SHAPE_T, COLOR_MAGENTA, 0, 15, 3}, - {SHAPE_T, COLOR_MAGENTA, 4, 16, 3}, - {SHAPE_S_REVERSE, COLOR_GREEN, 4, 14, 0}, - {SHAPE_I, COLOR_YELLOW, 1, 12, 2}, - {SHAPE_L_REVERSE, COLOR_CYAN, 0, 13, 2}, - {SHAPE_I, COLOR_YELLOW, 2, 11, 0}, - {SHAPE_SQUARE, COLOR_RED, 0, 10, 0}, - {SHAPE_I, COLOR_YELLOW, 1, 8, 0}, - {SHAPE_L, COLOR_BLUE, 0, 8, 1}, - {SHAPE_L_REVERSE, COLOR_CYAN, 3, 8, 3}, - {SHAPE_MAX, COLOR_MAX, 0, 0, 0}, + {SHAPE_L_REVERSE, 0, 16, 1}, {SHAPE_S_REVERSE, 2, 16, 1}, + {SHAPE_T, 0, 15, 3}, {SHAPE_T, 4, 16, 3}, + {SHAPE_S_REVERSE, 4, 14, 0}, {SHAPE_I, 1, 12, 2}, + {SHAPE_L_REVERSE, 0, 13, 2}, {SHAPE_I, 2, 11, 0}, + {SHAPE_SQUARE, 0, 10, 0}, {SHAPE_I, 1, 8, 0}, + {SHAPE_L, 0, 8, 1}, {SHAPE_L_REVERSE, 3, 8, 3}, + {SHAPE_MAX, 0, 0, 0}, }; static const struct fall fall7[] = { - {SHAPE_SQUARE, COLOR_RED, 4, 16, 0}, - {SHAPE_L, COLOR_BLUE, 4, 14, 0}, - {SHAPE_I, COLOR_YELLOW, 5, 13, 1}, - {SHAPE_L_REVERSE, COLOR_CYAN, 4, 11, 2}, - {SHAPE_I, COLOR_YELLOW, 1, 8, 2}, - {SHAPE_L_REVERSE, COLOR_CYAN, 3, 8, 3}, - {SHAPE_L, COLOR_BLUE, 0, 8, 1}, - {SHAPE_MAX, COLOR_MAX, 0, 0, 0}, + {SHAPE_SQUARE, 4, 16, 0}, {SHAPE_L, 4, 14, 0}, + {SHAPE_I, 5, 13, 1}, {SHAPE_L_REVERSE, 4, 11, 2}, + {SHAPE_I, 1, 8, 2}, {SHAPE_L_REVERSE, 3, 8, 3}, + {SHAPE_L, 0, 8, 1}, {SHAPE_MAX, 0, 0, 0}, }; static const struct fall fall8[] = { - {SHAPE_I, COLOR_YELLOW, 1, 16, 0}, - {SHAPE_T, COLOR_MAGENTA, 0, 16, 1}, - {SHAPE_I, COLOR_YELLOW, 5, 16, 1}, - {SHAPE_L, COLOR_BLUE, 2, 15, 3}, - {SHAPE_S, COLOR_ORANGE, 0, 14, 0}, - {SHAPE_L, COLOR_BLUE, 1, 12, 3}, - {SHAPE_T, COLOR_MAGENTA, 4, 13, 1}, - {SHAPE_L_REVERSE, COLOR_CYAN, 0, 11, 1}, - {SHAPE_S, COLOR_ORANGE, 0, 10, 0}, - {SHAPE_S, COLOR_ORANGE, 4, 11, 0}, - {SHAPE_S_REVERSE, COLOR_GREEN, 0, 8, 1}, - {SHAPE_S_REVERSE, COLOR_GREEN, 2, 8, 1}, - {SHAPE_L, COLOR_BLUE, 4, 9, 2}, - {SHAPE_MAX, COLOR_MAX, 0, 0, 0}, + {SHAPE_I, 1, 16, 0}, {SHAPE_T, 0, 16, 1}, + {SHAPE_I, 5, 16, 1}, {SHAPE_L, 2, 15, 3}, + {SHAPE_S, 0, 14, 0}, {SHAPE_L, 1, 12, 3}, + {SHAPE_T, 4, 13, 1}, {SHAPE_L_REVERSE, 0, 11, 1}, + {SHAPE_S, 0, 10, 0}, {SHAPE_S, 4, 11, 0}, + {SHAPE_S_REVERSE, 0, 8, 1}, {SHAPE_S_REVERSE, 2, 8, 1}, + {SHAPE_L, 4, 9, 2}, {SHAPE_MAX, 0, 0, 0}, }; static const struct fall fall9[] = { - {SHAPE_SQUARE, COLOR_RED, 0, 16, 0}, - {SHAPE_I, COLOR_YELLOW, 2, 16, 0}, - {SHAPE_L, COLOR_BLUE, 2, 15, 3}, - {SHAPE_L, COLOR_BLUE, 4, 15, 2}, - {SHAPE_I, COLOR_YELLOW, 1, 12, 2}, - {SHAPE_I, COLOR_YELLOW, 5, 12, 3}, - {SHAPE_S_REVERSE, COLOR_GREEN, 0, 12, 0}, - {SHAPE_L, COLOR_BLUE, 2, 11, 3}, - {SHAPE_S_REVERSE, COLOR_GREEN, 4, 9, 0}, - {SHAPE_T, COLOR_MAGENTA, 0, 10, 1}, - {SHAPE_S_REVERSE, COLOR_GREEN, 0, 8, 1}, - {SHAPE_T, COLOR_MAGENTA, 2, 8, 2}, - {SHAPE_MAX, COLOR_MAX, 0, 0, 0}, + {SHAPE_SQUARE, 0, 16, 0}, {SHAPE_I, 2, 16, 0}, + {SHAPE_L, 2, 15, 3}, {SHAPE_L, 4, 15, 2}, + {SHAPE_I, 1, 12, 2}, {SHAPE_I, 5, 12, 3}, + {SHAPE_S_REVERSE, 0, 12, 0}, {SHAPE_L, 2, 11, 3}, + {SHAPE_S_REVERSE, 4, 9, 0}, {SHAPE_T, 0, 10, 1}, + {SHAPE_S_REVERSE, 0, 8, 1}, {SHAPE_T, 2, 8, 2}, + {SHAPE_MAX, 0, 0, 0}, }; static const struct fall *fall[] = { @@ -238,12 +184,8 @@ __attribute__((constructor)) void calculate_block_sizes(void) } } -static void draw_shape(enum shape shape, - enum color color, - int x, - int y, - int rot, - unsigned char *buffer) +static void +draw_shape(enum shape shape, int x, int y, int rot, unsigned char *buffer) { assert(rot >= 0 && rot <= 3); @@ -256,7 +198,7 @@ static void draw_shape(enum shape shape, int dx = x + x_off; if (dx < 32) - buffer[(y + y_off) * 32 + dx] = color; + buffer[(y + y_off) * 32 + dx] = colors[shape]; } } @@ -268,7 +210,8 @@ void blocks_init(struct block_state *states) states[3] = (struct block_state){4, 0, 0, 25}; } -uint64_t blocks_draw(struct block_state *states, unsigned char *buffer, bool odd_second) +uint64_t +blocks_draw(struct block_state *states, unsigned char *buffer, bool odd_second) { int digits_fallen = 0; int i; @@ -304,7 +247,7 @@ uint64_t blocks_draw(struct block_state *states, unsigned char *buffer, bool odd break; } - draw_shape(curr->shape, curr->color, curr->x_pos + state->x_shift, + draw_shape(curr->shape, curr->x_pos + state->x_shift, state->fall_index - 1, rotations, buffer); state->fall_index++; @@ -320,16 +263,15 @@ uint64_t blocks_draw(struct block_state *states, unsigned char *buffer, bool odd for (int j = 0; j < state->block_index; j++) { const struct fall *fallen = &fall[state->num_to_draw][j]; - draw_shape(fallen->shape, fallen->color, - fallen->x_pos + state->x_shift, fallen->y_stop - 1, - fallen->n_rot, buffer); + draw_shape(fallen->shape, fallen->x_pos + state->x_shift, + fallen->y_stop - 1, fallen->n_rot, buffer); } } } - if (odd_second & 1) { - draw_shape(SHAPE_SQUARE, COLOR_WHITE, 15, 13, 0, buffer); - draw_shape(SHAPE_SQUARE, COLOR_WHITE, 15, 9, 0, buffer); + if (odd_second) { + draw_shape(SHAPE_SQUARE, 15, 13, 0, buffer); + draw_shape(SHAPE_SQUARE, 15, 9, 0, buffer); } return digits_fallen ? 100 : 500; From 58519bca80d473294f38fd44d3a2e0260e62afd7 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 30 Sep 2018 08:44:55 -0700 Subject: [PATCH 0852/2505] Draw second dots in white After 098faf5, the dorts were being painted in red; move them back to white. --- src/samples/clock/blocks.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/samples/clock/blocks.c b/src/samples/clock/blocks.c index e01f31880..954d21109 100644 --- a/src/samples/clock/blocks.c +++ b/src/samples/clock/blocks.c @@ -184,8 +184,12 @@ __attribute__((constructor)) void calculate_block_sizes(void) } } -static void -draw_shape(enum shape shape, int x, int y, int rot, unsigned char *buffer) +static void draw_shape_full(enum shape shape, + enum color color, + int x, + int y, + int rot, + unsigned char *buffer) { assert(rot >= 0 && rot <= 3); @@ -198,10 +202,16 @@ draw_shape(enum shape shape, int x, int y, int rot, unsigned char *buffer) int dx = x + x_off; if (dx < 32) - buffer[(y + y_off) * 32 + dx] = colors[shape]; + buffer[(y + y_off) * 32 + dx] = color; } } +static void +draw_shape(enum shape shape, int x, int y, int rot, unsigned char *buffer) +{ + draw_shape_full(shape, colors[shape], x, y, rot, buffer); +} + void blocks_init(struct block_state *states) { states[0] = (struct block_state){1, 0, 0, 1}; @@ -270,8 +280,8 @@ blocks_draw(struct block_state *states, unsigned char *buffer, bool odd_second) } if (odd_second) { - draw_shape(SHAPE_SQUARE, 15, 13, 0, buffer); - draw_shape(SHAPE_SQUARE, 15, 9, 0, buffer); + draw_shape_full(SHAPE_SQUARE, COLOR_WHITE, 15, 13, 0, buffer); + draw_shape_full(SHAPE_SQUARE, COLOR_WHITE, 15, 9, 0, buffer); } return digits_fallen ? 100 : 500; From 3245e0e18d16cad516158acdffdb985804d5f066 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 30 Sep 2018 09:12:22 -0700 Subject: [PATCH 0853/2505] Better document the parameters for blocks_{init,draw}() --- src/samples/clock/blocks.c | 15 ++++++++------- src/samples/clock/blocks.h | 8 +++++--- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/samples/clock/blocks.c b/src/samples/clock/blocks.c index 954d21109..453260173 100644 --- a/src/samples/clock/blocks.c +++ b/src/samples/clock/blocks.c @@ -212,16 +212,17 @@ draw_shape(enum shape shape, int x, int y, int rot, unsigned char *buffer) draw_shape_full(shape, colors[shape], x, y, rot, buffer); } -void blocks_init(struct block_state *states) +void blocks_init(struct block_state states[static 4]) { - states[0] = (struct block_state){1, 0, 0, 1}; - states[1] = (struct block_state){2, 0, 0, 8}; - states[2] = (struct block_state){3, 0, 0, 18}; - states[3] = (struct block_state){4, 0, 0, 25}; + states[0] = (struct block_state){0, 0, 0, 1}; + states[1] = (struct block_state){0, 0, 0, 8}; + states[2] = (struct block_state){0, 0, 0, 18}; + states[3] = (struct block_state){0, 0, 0, 25}; } -uint64_t -blocks_draw(struct block_state *states, unsigned char *buffer, bool odd_second) +uint64_t blocks_draw(struct block_state states[static 4], + unsigned char *buffer, + bool odd_second) { int digits_fallen = 0; int i; diff --git a/src/samples/clock/blocks.h b/src/samples/clock/blocks.h index 97c506eaa..3b9cd7d98 100644 --- a/src/samples/clock/blocks.h +++ b/src/samples/clock/blocks.h @@ -1,7 +1,7 @@ #pragma once -#include #include +#include struct block_state { int num_to_draw; @@ -10,5 +10,7 @@ struct block_state { int x_shift; }; -void blocks_init(struct block_state *states); -uint64_t blocks_draw(struct block_state *states, unsigned char *buffer, bool odd_second); +void blocks_init(struct block_state states[static 4]); +uint64_t blocks_draw(struct block_state states[static 4], + unsigned char *buffer, + bool odd_second); From a987334c2c99e77857a6ac33285a09af2e9e3bcd Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 30 Sep 2018 09:54:10 -0700 Subject: [PATCH 0854/2505] Remove unused Dali Clock digits These digits were either the original Photoshop, Illustrator, or sizes that the sample application isn't going to use. Keep only the "E" size. --- src/samples/clock/font/colonA.xbm | 3153 ------------ src/samples/clock/font/colonB.xbm | 790 --- src/samples/clock/font/colonB2.xbm | 287 -- src/samples/clock/font/colonC.xbm | 208 - src/samples/clock/font/colonC2.xbm | 171 - src/samples/clock/font/colonC3.xbm | 56 - src/samples/clock/font/colonD.xbm | 55 - src/samples/clock/font/colonD2.xbm | 49 - src/samples/clock/font/colonD3.xbm | 34 - src/samples/clock/font/colonD4.xbm | 25 - src/samples/clock/font/colonF.xbm | 8 - src/samples/clock/font/colonG.xbm | 6 - src/samples/clock/font/dalifont.ai | 473 -- src/samples/clock/font/dalifont.png | Bin 197077 -> 0 bytes src/samples/clock/font/dalifontF.gif | Bin 1960 -> 0 bytes src/samples/clock/font/dalifontF.psd.gz | Bin 20137 -> 0 bytes src/samples/clock/font/dalifontG.gif | Bin 827 -> 0 bytes src/samples/clock/font/dalifontG.psd.gz | Bin 17497 -> 0 bytes src/samples/clock/font/eightA.xbm | 6302 ----------------------- src/samples/clock/font/eightB.xbm | 1577 ------ src/samples/clock/font/eightB2.xbm | 551 -- src/samples/clock/font/eightC.xbm | 396 -- src/samples/clock/font/eightC2.xbm | 324 -- src/samples/clock/font/eightC3.xbm | 108 - src/samples/clock/font/eightD.xbm | 106 - src/samples/clock/font/eightD2.xbm | 80 - src/samples/clock/font/eightD3.xbm | 59 - src/samples/clock/font/eightD4.xbm | 41 - src/samples/clock/font/eightF.xbm | 10 - src/samples/clock/font/eightG.xbm | 6 - src/samples/clock/font/fiveA.xbm | 6302 ----------------------- src/samples/clock/font/fiveB.xbm | 1577 ------ src/samples/clock/font/fiveB2.xbm | 551 -- src/samples/clock/font/fiveC.xbm | 396 -- src/samples/clock/font/fiveC2.xbm | 324 -- src/samples/clock/font/fiveC3.xbm | 108 - src/samples/clock/font/fiveD.xbm | 106 - src/samples/clock/font/fiveD2.xbm | 80 - src/samples/clock/font/fiveD3.xbm | 59 - src/samples/clock/font/fiveD4.xbm | 41 - src/samples/clock/font/fiveF.xbm | 10 - src/samples/clock/font/fiveG.xbm | 6 - src/samples/clock/font/fourA.xbm | 6302 ----------------------- src/samples/clock/font/fourB.xbm | 1577 ------ src/samples/clock/font/fourB2.xbm | 551 -- src/samples/clock/font/fourC.xbm | 396 -- src/samples/clock/font/fourC2.xbm | 324 -- src/samples/clock/font/fourC3.xbm | 108 - src/samples/clock/font/fourD.xbm | 106 - src/samples/clock/font/fourD2.xbm | 80 - src/samples/clock/font/fourD3.xbm | 59 - src/samples/clock/font/fourD4.xbm | 41 - src/samples/clock/font/fourF.xbm | 10 - src/samples/clock/font/fourG.xbm | 6 - src/samples/clock/font/nineA.xbm | 6302 ----------------------- src/samples/clock/font/nineB.xbm | 1577 ------ src/samples/clock/font/nineB2.xbm | 551 -- src/samples/clock/font/nineC.xbm | 396 -- src/samples/clock/font/nineC2.xbm | 324 -- src/samples/clock/font/nineC3.xbm | 108 - src/samples/clock/font/nineD.xbm | 106 - src/samples/clock/font/nineD2.xbm | 80 - src/samples/clock/font/nineD3.xbm | 59 - src/samples/clock/font/nineD4.xbm | 41 - src/samples/clock/font/nineF.xbm | 10 - src/samples/clock/font/nineG.xbm | 6 - src/samples/clock/font/oneA.xbm | 6302 ----------------------- src/samples/clock/font/oneB.xbm | 1577 ------ src/samples/clock/font/oneB2.xbm | 551 -- src/samples/clock/font/oneC.xbm | 396 -- src/samples/clock/font/oneC2.xbm | 324 -- src/samples/clock/font/oneC3.xbm | 108 - src/samples/clock/font/oneD.xbm | 106 - src/samples/clock/font/oneD2.xbm | 80 - src/samples/clock/font/oneD3.xbm | 59 - src/samples/clock/font/oneD4.xbm | 41 - src/samples/clock/font/oneF.xbm | 10 - src/samples/clock/font/oneG.xbm | 6 - src/samples/clock/font/sevenA.xbm | 6302 ----------------------- src/samples/clock/font/sevenB.xbm | 1577 ------ src/samples/clock/font/sevenB2.xbm | 551 -- src/samples/clock/font/sevenC.xbm | 396 -- src/samples/clock/font/sevenC2.xbm | 324 -- src/samples/clock/font/sevenC3.xbm | 108 - src/samples/clock/font/sevenD.xbm | 106 - src/samples/clock/font/sevenD2.xbm | 80 - src/samples/clock/font/sevenD3.xbm | 59 - src/samples/clock/font/sevenD4.xbm | 41 - src/samples/clock/font/sevenF.xbm | 10 - src/samples/clock/font/sevenG.xbm | 6 - src/samples/clock/font/sixA.xbm | 6302 ----------------------- src/samples/clock/font/sixB.xbm | 1577 ------ src/samples/clock/font/sixB2.xbm | 551 -- src/samples/clock/font/sixC.xbm | 396 -- src/samples/clock/font/sixC2.xbm | 324 -- src/samples/clock/font/sixC3.xbm | 108 - src/samples/clock/font/sixD.xbm | 106 - src/samples/clock/font/sixD2.xbm | 80 - src/samples/clock/font/sixD3.xbm | 59 - src/samples/clock/font/sixD4.xbm | 41 - src/samples/clock/font/sixF.xbm | 10 - src/samples/clock/font/sixG.xbm | 6 - src/samples/clock/font/slashA.xbm | 3153 ------------ src/samples/clock/font/slashB.xbm | 790 --- src/samples/clock/font/slashB2.xbm | 287 -- src/samples/clock/font/slashC.xbm | 208 - src/samples/clock/font/slashC2.xbm | 171 - src/samples/clock/font/slashC3.xbm | 56 - src/samples/clock/font/slashD.xbm | 55 - src/samples/clock/font/slashD2.xbm | 49 - src/samples/clock/font/slashD3.xbm | 34 - src/samples/clock/font/slashD4.xbm | 25 - src/samples/clock/font/slashF.xbm | 8 - src/samples/clock/font/slashG.xbm | 6 - src/samples/clock/font/threeA.xbm | 6302 ----------------------- src/samples/clock/font/threeB.xbm | 1577 ------ src/samples/clock/font/threeB2.xbm | 551 -- src/samples/clock/font/threeC.xbm | 396 -- src/samples/clock/font/threeC2.xbm | 324 -- src/samples/clock/font/threeC3.xbm | 108 - src/samples/clock/font/threeD.xbm | 106 - src/samples/clock/font/threeD2.xbm | 80 - src/samples/clock/font/threeD3.xbm | 59 - src/samples/clock/font/threeD4.xbm | 41 - src/samples/clock/font/threeF.xbm | 10 - src/samples/clock/font/threeG.xbm | 6 - src/samples/clock/font/twoA.xbm | 6302 ----------------------- src/samples/clock/font/twoB.xbm | 1577 ------ src/samples/clock/font/twoB2.xbm | 551 -- src/samples/clock/font/twoC.xbm | 396 -- src/samples/clock/font/twoC2.xbm | 324 -- src/samples/clock/font/twoC3.xbm | 108 - src/samples/clock/font/twoD.xbm | 106 - src/samples/clock/font/twoD2.xbm | 80 - src/samples/clock/font/twoD3.xbm | 59 - src/samples/clock/font/twoD4.xbm | 41 - src/samples/clock/font/twoF.xbm | 10 - src/samples/clock/font/twoG.xbm | 6 - src/samples/clock/font/zeroA.xbm | 6302 ----------------------- src/samples/clock/font/zeroB.xbm | 1577 ------ src/samples/clock/font/zeroB2.xbm | 551 -- src/samples/clock/font/zeroC.xbm | 396 -- src/samples/clock/font/zeroC2.xbm | 324 -- src/samples/clock/font/zeroC3.xbm | 108 - src/samples/clock/font/zeroD.xbm | 106 - src/samples/clock/font/zeroD2.xbm | 80 - src/samples/clock/font/zeroD3.xbm | 59 - src/samples/clock/font/zeroD4.xbm | 41 - src/samples/clock/font/zeroF.xbm | 10 - src/samples/clock/font/zeroG.xbm | 6 - 150 files changed, 105757 deletions(-) delete mode 100644 src/samples/clock/font/colonA.xbm delete mode 100644 src/samples/clock/font/colonB.xbm delete mode 100644 src/samples/clock/font/colonB2.xbm delete mode 100644 src/samples/clock/font/colonC.xbm delete mode 100644 src/samples/clock/font/colonC2.xbm delete mode 100644 src/samples/clock/font/colonC3.xbm delete mode 100644 src/samples/clock/font/colonD.xbm delete mode 100644 src/samples/clock/font/colonD2.xbm delete mode 100644 src/samples/clock/font/colonD3.xbm delete mode 100644 src/samples/clock/font/colonD4.xbm delete mode 100644 src/samples/clock/font/colonF.xbm delete mode 100644 src/samples/clock/font/colonG.xbm delete mode 100644 src/samples/clock/font/dalifont.ai delete mode 100644 src/samples/clock/font/dalifont.png delete mode 100644 src/samples/clock/font/dalifontF.gif delete mode 100644 src/samples/clock/font/dalifontF.psd.gz delete mode 100644 src/samples/clock/font/dalifontG.gif delete mode 100644 src/samples/clock/font/dalifontG.psd.gz delete mode 100644 src/samples/clock/font/eightA.xbm delete mode 100644 src/samples/clock/font/eightB.xbm delete mode 100644 src/samples/clock/font/eightB2.xbm delete mode 100644 src/samples/clock/font/eightC.xbm delete mode 100644 src/samples/clock/font/eightC2.xbm delete mode 100644 src/samples/clock/font/eightC3.xbm delete mode 100644 src/samples/clock/font/eightD.xbm delete mode 100644 src/samples/clock/font/eightD2.xbm delete mode 100644 src/samples/clock/font/eightD3.xbm delete mode 100644 src/samples/clock/font/eightD4.xbm delete mode 100644 src/samples/clock/font/eightF.xbm delete mode 100644 src/samples/clock/font/eightG.xbm delete mode 100644 src/samples/clock/font/fiveA.xbm delete mode 100644 src/samples/clock/font/fiveB.xbm delete mode 100644 src/samples/clock/font/fiveB2.xbm delete mode 100644 src/samples/clock/font/fiveC.xbm delete mode 100644 src/samples/clock/font/fiveC2.xbm delete mode 100644 src/samples/clock/font/fiveC3.xbm delete mode 100644 src/samples/clock/font/fiveD.xbm delete mode 100644 src/samples/clock/font/fiveD2.xbm delete mode 100644 src/samples/clock/font/fiveD3.xbm delete mode 100644 src/samples/clock/font/fiveD4.xbm delete mode 100644 src/samples/clock/font/fiveF.xbm delete mode 100644 src/samples/clock/font/fiveG.xbm delete mode 100644 src/samples/clock/font/fourA.xbm delete mode 100644 src/samples/clock/font/fourB.xbm delete mode 100644 src/samples/clock/font/fourB2.xbm delete mode 100644 src/samples/clock/font/fourC.xbm delete mode 100644 src/samples/clock/font/fourC2.xbm delete mode 100644 src/samples/clock/font/fourC3.xbm delete mode 100644 src/samples/clock/font/fourD.xbm delete mode 100644 src/samples/clock/font/fourD2.xbm delete mode 100644 src/samples/clock/font/fourD3.xbm delete mode 100644 src/samples/clock/font/fourD4.xbm delete mode 100644 src/samples/clock/font/fourF.xbm delete mode 100644 src/samples/clock/font/fourG.xbm delete mode 100644 src/samples/clock/font/nineA.xbm delete mode 100644 src/samples/clock/font/nineB.xbm delete mode 100644 src/samples/clock/font/nineB2.xbm delete mode 100644 src/samples/clock/font/nineC.xbm delete mode 100644 src/samples/clock/font/nineC2.xbm delete mode 100644 src/samples/clock/font/nineC3.xbm delete mode 100644 src/samples/clock/font/nineD.xbm delete mode 100644 src/samples/clock/font/nineD2.xbm delete mode 100644 src/samples/clock/font/nineD3.xbm delete mode 100644 src/samples/clock/font/nineD4.xbm delete mode 100644 src/samples/clock/font/nineF.xbm delete mode 100644 src/samples/clock/font/nineG.xbm delete mode 100644 src/samples/clock/font/oneA.xbm delete mode 100644 src/samples/clock/font/oneB.xbm delete mode 100644 src/samples/clock/font/oneB2.xbm delete mode 100644 src/samples/clock/font/oneC.xbm delete mode 100644 src/samples/clock/font/oneC2.xbm delete mode 100644 src/samples/clock/font/oneC3.xbm delete mode 100644 src/samples/clock/font/oneD.xbm delete mode 100644 src/samples/clock/font/oneD2.xbm delete mode 100644 src/samples/clock/font/oneD3.xbm delete mode 100644 src/samples/clock/font/oneD4.xbm delete mode 100644 src/samples/clock/font/oneF.xbm delete mode 100644 src/samples/clock/font/oneG.xbm delete mode 100644 src/samples/clock/font/sevenA.xbm delete mode 100644 src/samples/clock/font/sevenB.xbm delete mode 100644 src/samples/clock/font/sevenB2.xbm delete mode 100644 src/samples/clock/font/sevenC.xbm delete mode 100644 src/samples/clock/font/sevenC2.xbm delete mode 100644 src/samples/clock/font/sevenC3.xbm delete mode 100644 src/samples/clock/font/sevenD.xbm delete mode 100644 src/samples/clock/font/sevenD2.xbm delete mode 100644 src/samples/clock/font/sevenD3.xbm delete mode 100644 src/samples/clock/font/sevenD4.xbm delete mode 100644 src/samples/clock/font/sevenF.xbm delete mode 100644 src/samples/clock/font/sevenG.xbm delete mode 100644 src/samples/clock/font/sixA.xbm delete mode 100644 src/samples/clock/font/sixB.xbm delete mode 100644 src/samples/clock/font/sixB2.xbm delete mode 100644 src/samples/clock/font/sixC.xbm delete mode 100644 src/samples/clock/font/sixC2.xbm delete mode 100644 src/samples/clock/font/sixC3.xbm delete mode 100644 src/samples/clock/font/sixD.xbm delete mode 100644 src/samples/clock/font/sixD2.xbm delete mode 100644 src/samples/clock/font/sixD3.xbm delete mode 100644 src/samples/clock/font/sixD4.xbm delete mode 100644 src/samples/clock/font/sixF.xbm delete mode 100644 src/samples/clock/font/sixG.xbm delete mode 100644 src/samples/clock/font/slashA.xbm delete mode 100644 src/samples/clock/font/slashB.xbm delete mode 100644 src/samples/clock/font/slashB2.xbm delete mode 100644 src/samples/clock/font/slashC.xbm delete mode 100644 src/samples/clock/font/slashC2.xbm delete mode 100644 src/samples/clock/font/slashC3.xbm delete mode 100644 src/samples/clock/font/slashD.xbm delete mode 100644 src/samples/clock/font/slashD2.xbm delete mode 100644 src/samples/clock/font/slashD3.xbm delete mode 100644 src/samples/clock/font/slashD4.xbm delete mode 100644 src/samples/clock/font/slashF.xbm delete mode 100644 src/samples/clock/font/slashG.xbm delete mode 100644 src/samples/clock/font/threeA.xbm delete mode 100644 src/samples/clock/font/threeB.xbm delete mode 100644 src/samples/clock/font/threeB2.xbm delete mode 100644 src/samples/clock/font/threeC.xbm delete mode 100644 src/samples/clock/font/threeC2.xbm delete mode 100644 src/samples/clock/font/threeC3.xbm delete mode 100644 src/samples/clock/font/threeD.xbm delete mode 100644 src/samples/clock/font/threeD2.xbm delete mode 100644 src/samples/clock/font/threeD3.xbm delete mode 100644 src/samples/clock/font/threeD4.xbm delete mode 100644 src/samples/clock/font/threeF.xbm delete mode 100644 src/samples/clock/font/threeG.xbm delete mode 100644 src/samples/clock/font/twoA.xbm delete mode 100644 src/samples/clock/font/twoB.xbm delete mode 100644 src/samples/clock/font/twoB2.xbm delete mode 100644 src/samples/clock/font/twoC.xbm delete mode 100644 src/samples/clock/font/twoC2.xbm delete mode 100644 src/samples/clock/font/twoC3.xbm delete mode 100644 src/samples/clock/font/twoD.xbm delete mode 100644 src/samples/clock/font/twoD2.xbm delete mode 100644 src/samples/clock/font/twoD3.xbm delete mode 100644 src/samples/clock/font/twoD4.xbm delete mode 100644 src/samples/clock/font/twoF.xbm delete mode 100644 src/samples/clock/font/twoG.xbm delete mode 100644 src/samples/clock/font/zeroA.xbm delete mode 100644 src/samples/clock/font/zeroB.xbm delete mode 100644 src/samples/clock/font/zeroB2.xbm delete mode 100644 src/samples/clock/font/zeroC.xbm delete mode 100644 src/samples/clock/font/zeroC2.xbm delete mode 100644 src/samples/clock/font/zeroC3.xbm delete mode 100644 src/samples/clock/font/zeroD.xbm delete mode 100644 src/samples/clock/font/zeroD2.xbm delete mode 100644 src/samples/clock/font/zeroD3.xbm delete mode 100644 src/samples/clock/font/zeroD4.xbm delete mode 100644 src/samples/clock/font/zeroF.xbm delete mode 100644 src/samples/clock/font/zeroG.xbm diff --git a/src/samples/clock/font/colonA.xbm b/src/samples/clock/font/colonA.xbm deleted file mode 100644 index 7d12500f4..000000000 --- a/src/samples/clock/font/colonA.xbm +++ /dev/null @@ -1,3153 +0,0 @@ -#define colonA_width 368 -#define colonA_height 1027 -static unsigned char colonA_bits[] = { - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0x3f,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0x1f, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff, - 0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff, - 0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xfc,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xfc,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0x7f, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff, - 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff, - 0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff, - 0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00}; diff --git a/src/samples/clock/font/colonB.xbm b/src/samples/clock/font/colonB.xbm deleted file mode 100644 index 8c66c5b4d..000000000 --- a/src/samples/clock/font/colonB.xbm +++ /dev/null @@ -1,790 +0,0 @@ -#define colonB_width 184 -#define colonB_height 513 -static unsigned char colonB_bits[] = { - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x3f,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff, - 0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff, - 0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00, - 0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00, - 0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00, - 0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00, - 0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01, - 0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00, - 0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00, - 0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00, - 0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff, - 0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff, - 0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, - 0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x7f,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xf8,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x7f,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff, - 0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00, - 0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00, - 0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00, - 0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00, - 0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00, - 0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00, - 0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01, - 0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00, - 0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe, - 0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0x0f,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xe0,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x7f,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xf8,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x7f,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; diff --git a/src/samples/clock/font/colonB2.xbm b/src/samples/clock/font/colonB2.xbm deleted file mode 100644 index 98a7632e4..000000000 --- a/src/samples/clock/font/colonB2.xbm +++ /dev/null @@ -1,287 +0,0 @@ -#define colonB2_width 107 -#define colonB2_height 304 -static unsigned char colonB2_bits[] = { - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0x03,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x80,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xe0,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xf8,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc, - 0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, - 0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff, - 0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff, - 0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff, - 0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0x01, - 0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00, - 0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00, - 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00, - 0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00, - 0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00, - 0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0xc0, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0xe0,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x01,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x01,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01, - 0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00, - 0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00, - 0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00, - 0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00, - 0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfe, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfe,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfe,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x03,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x03,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03, - 0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00, - 0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00, - 0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00, - 0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00, - 0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0xf8, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0xf8,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x1f,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f, - 0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00, - 0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, - 0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00, - 0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00, - 0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00, - 0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xf0,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0, - 0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff, - 0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, - 0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff, - 0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff, - 0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0x7f, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0x1f,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0x07,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0x07,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xe0,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe, - 0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff, - 0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff, - 0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff, - 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0x03, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x07,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00, - 0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00, - 0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x7f,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f, - 0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00, - 0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00, - 0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00, - 0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00, - 0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00, - 0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0xfc, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfc,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfc,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x03,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x03,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03, - 0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00, - 0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00, - 0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00, - 0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00, - 0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfc, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfc,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfc,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f, - 0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00, - 0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00, - 0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00, - 0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00, - 0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00, - 0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0xfe, - 0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff, - 0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff, - 0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff, - 0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff, - 0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff, - 0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0x1f, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x0f,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0x03,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xf8,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00}; diff --git a/src/samples/clock/font/colonC.xbm b/src/samples/clock/font/colonC.xbm deleted file mode 100644 index 72188ed64..000000000 --- a/src/samples/clock/font/colonC.xbm +++ /dev/null @@ -1,208 +0,0 @@ -#define colonC_width 91 -#define colonC_height 256 -static unsigned char colonC_bits[] = { - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xc0,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff, - 0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0x0f,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xf0,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff, - 0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0x07, - 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00, - 0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x80, - 0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff, - 0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0x7f, - 0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00, - 0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0xf8, - 0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff, - 0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff, - 0x03,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, - 0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0xfe, - 0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff, - 0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x0f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00, - 0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x1f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00, - 0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x80,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x1f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00, - 0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x0f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00, - 0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0xfe, - 0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff, - 0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff, - 0x07,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00, - 0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xf8, - 0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff, - 0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff, - 0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00, - 0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0xc0, - 0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff, - 0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x0f, - 0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00, - 0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00, - 0xf8,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff, - 0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0x3f,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xf0,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff, - 0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0x01,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0x7f,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0xfe,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff, - 0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0x7f,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00, - 0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00, - 0xfe,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff, - 0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0x1f, - 0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00, - 0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0xe0, - 0xff,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff, - 0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0xff,0xff, - 0x01,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00, - 0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfc, - 0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff, - 0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff, - 0x07,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, - 0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x0f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00, - 0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x1f,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00, - 0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0x1f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00, - 0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff, - 0x0f,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, - 0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0xfc, - 0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff, - 0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0xff,0xff, - 0x03,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00, - 0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0xf0, - 0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff, - 0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0x7f, - 0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00, - 0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00, - 0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff, - 0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0x03, - 0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00, - 0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xc0,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, - 0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0x07,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xff,0x0f,0x00,0x00,0x00,0x00,0x00}; diff --git a/src/samples/clock/font/colonC2.xbm b/src/samples/clock/font/colonC2.xbm deleted file mode 100644 index 1705eda7e..000000000 --- a/src/samples/clock/font/colonC2.xbm +++ /dev/null @@ -1,171 +0,0 @@ -#define colonC2_width 81 -#define colonC2_height 229 -static unsigned char colonC2_bits[] = { - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xfe,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff, - 0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0x1f,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xfe,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff, - 0x03,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00, - 0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0xf0,0xff, - 0xff,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0x3f, - 0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00, - 0x00,0xfc,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff, - 0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0x00, - 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00, - 0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff, - 0xff,0xff,0x03,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00, - 0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0xc0,0xff, - 0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff, - 0xff,0x07,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, - 0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0xe0,0xff,0xff, - 0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff, - 0x0f,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00, - 0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0xe0,0xff,0xff,0xff, - 0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0x0f, - 0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0xe0, - 0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff, - 0xff,0xff,0x0f,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00, - 0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0xc0,0xff, - 0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff, - 0xff,0x07,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, - 0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x80,0xff,0xff, - 0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff, - 0x03,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00, - 0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0xfe,0xff,0xff, - 0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0x7f,0x00, - 0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00, - 0xf8,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff, - 0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0x0f,0x00,0x00, - 0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x80, - 0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff, - 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0x7f,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xf0,0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0, - 0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x01,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0x07,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0xf0,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, - 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0xff,0x0f,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xfc,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff, - 0x01,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00, - 0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0xe0,0xff, - 0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0x1f, - 0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00, - 0x00,0xfc,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff, - 0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0x00, - 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00, - 0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff, - 0xff,0xff,0x03,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00, - 0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0xc0,0xff, - 0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff, - 0xff,0x07,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, - 0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0xe0,0xff,0xff, - 0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff, - 0x0f,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00, - 0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0xe0,0xff,0xff,0xff, - 0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0x0f, - 0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0xe0, - 0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff, - 0xff,0xff,0x0f,0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00, - 0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0xc0,0xff, - 0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff, - 0xff,0x07,0x00,0x00,0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00, - 0x00,0xc0,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x80,0xff,0xff, - 0xff,0xff,0xff,0xff,0x03,0x00,0x00,0x00,0x80,0xff,0xff,0xff,0xff,0xff,0xff, - 0x03,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00, - 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x00,0x00,0x00,0x00,0xfe,0xff,0xff, - 0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0xff,0xff,0x00, - 0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00, - 0xf8,0xff,0xff,0xff,0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff, - 0xff,0x3f,0x00,0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0xff,0xff,0x1f,0x00,0x00, - 0x00,0x00,0x00,0xe0,0xff,0xff,0xff,0xff,0x0f,0x00,0x00,0x00,0x00,0x00,0xc0, - 0xff,0xff,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff, - 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0xfc,0xff,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0, - 0xff,0xff,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xff,0xff,0x07,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0x7f,0x00,0x00,0x00,0x00,0x00}; diff --git a/src/samples/clock/font/colonC3.xbm b/src/samples/clock/font/colonC3.xbm deleted file mode 100644 index 56ef8f80a..000000000 --- a/src/samples/clock/font/colonC3.xbm +++ /dev/null @@ -1,56 +0,0 @@ -#define colonC3_width 47 -#define colonC3_height 131 -static unsigned char colonC3_bits[] = { - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0x1f,0x00,0x00, - 0x00,0x00,0xfe,0x7f,0x00,0x00,0x00,0x80,0xff,0xff,0x01,0x00,0x00,0xc0,0xff, - 0xff,0x03,0x00,0x00,0xe0,0xff,0xff,0x07,0x00,0x00,0xf0,0xff,0xff,0x0f,0x00, - 0x00,0xf8,0xff,0xff,0x1f,0x00,0x00,0xf8,0xff,0xff,0x1f,0x00,0x00,0xfc,0xff, - 0xff,0x3f,0x00,0x00,0xfc,0xff,0xff,0x3f,0x00,0x00,0xfe,0xff,0xff,0x7f,0x00, - 0x00,0xfe,0xff,0xff,0x7f,0x00,0x00,0xfe,0xff,0xff,0x7f,0x00,0x00,0xfe,0xff, - 0xff,0x7f,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0x00, - 0x00,0xff,0xff,0xff,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0xfe,0xff, - 0xff,0x7f,0x00,0x00,0xfe,0xff,0xff,0x7f,0x00,0x00,0xfe,0xff,0xff,0x7f,0x00, - 0x00,0xfe,0xff,0xff,0x7f,0x00,0x00,0xfc,0xff,0xff,0x3f,0x00,0x00,0xfc,0xff, - 0xff,0x3f,0x00,0x00,0xf8,0xff,0xff,0x1f,0x00,0x00,0xf0,0xff,0xff,0x0f,0x00, - 0x00,0xe0,0xff,0xff,0x07,0x00,0x00,0xc0,0xff,0xff,0x03,0x00,0x00,0x80,0xff, - 0xff,0x01,0x00,0x00,0x00,0xff,0xff,0x00,0x00,0x00,0x00,0xfc,0x3f,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0x0f,0x00,0x00, - 0x00,0x00,0xfe,0x7f,0x00,0x00,0x00,0x80,0xff,0xff,0x01,0x00,0x00,0xc0,0xff, - 0xff,0x03,0x00,0x00,0xe0,0xff,0xff,0x07,0x00,0x00,0xf0,0xff,0xff,0x0f,0x00, - 0x00,0xf8,0xff,0xff,0x1f,0x00,0x00,0xf8,0xff,0xff,0x1f,0x00,0x00,0xfc,0xff, - 0xff,0x3f,0x00,0x00,0xfc,0xff,0xff,0x3f,0x00,0x00,0xfe,0xff,0xff,0x7f,0x00, - 0x00,0xfe,0xff,0xff,0x7f,0x00,0x00,0xfe,0xff,0xff,0x7f,0x00,0x00,0xfe,0xff, - 0xff,0x7f,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0x00, - 0x00,0xff,0xff,0xff,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0xfe,0xff, - 0xff,0x7f,0x00,0x00,0xfe,0xff,0xff,0x7f,0x00,0x00,0xfe,0xff,0xff,0x7f,0x00, - 0x00,0xfe,0xff,0xff,0x7f,0x00,0x00,0xfc,0xff,0xff,0x3f,0x00,0x00,0xfc,0xff, - 0xff,0x3f,0x00,0x00,0xf8,0xff,0xff,0x1f,0x00,0x00,0xf8,0xff,0xff,0x1f,0x00, - 0x00,0xf0,0xff,0xff,0x0f,0x00,0x00,0xe0,0xff,0xff,0x07,0x00,0x00,0xc0,0xff, - 0xff,0x03,0x00,0x00,0x00,0xff,0xff,0x00,0x00,0x00,0x00,0xfc,0x3f,0x00,0x00, - 0x00,0x00,0xe0,0x07,0x00,0x00}; diff --git a/src/samples/clock/font/colonD.xbm b/src/samples/clock/font/colonD.xbm deleted file mode 100644 index 782194def..000000000 --- a/src/samples/clock/font/colonD.xbm +++ /dev/null @@ -1,55 +0,0 @@ -#define colonD_width 46 -#define colonD_height 128 -static unsigned char colonD_bits[] = { - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xfc,0x07,0x00,0x00,0x00,0x00,0xff,0x3f,0x00,0x00, - 0x00,0xc0,0xff,0x7f,0x00,0x00,0x00,0xe0,0xff,0xff,0x00,0x00,0x00,0xf0,0xff, - 0xff,0x03,0x00,0x00,0xf8,0xff,0xff,0x03,0x00,0x00,0xfc,0xff,0xff,0x07,0x00, - 0x00,0xfe,0xff,0xff,0x0f,0x00,0x00,0xfe,0xff,0xff,0x0f,0x00,0x00,0xff,0xff, - 0xff,0x1f,0x00,0x00,0xff,0xff,0xff,0x1f,0x00,0x00,0xff,0xff,0xff,0x1f,0x00, - 0x00,0xff,0xff,0xff,0x3f,0x00,0x80,0xff,0xff,0xff,0x3f,0x00,0x80,0xff,0xff, - 0xff,0x3f,0x00,0x80,0xff,0xff,0xff,0x3f,0x00,0x80,0xff,0xff,0xff,0x3f,0x00, - 0x80,0xff,0xff,0xff,0x3f,0x00,0x00,0xff,0xff,0xff,0x3f,0x00,0x00,0xff,0xff, - 0xff,0x1f,0x00,0x00,0xff,0xff,0xff,0x1f,0x00,0x00,0xff,0xff,0xff,0x1f,0x00, - 0x00,0xfe,0xff,0xff,0x0f,0x00,0x00,0xfe,0xff,0xff,0x0f,0x00,0x00,0xfc,0xff, - 0xff,0x07,0x00,0x00,0xf8,0xff,0xff,0x03,0x00,0x00,0xf0,0xff,0xff,0x01,0x00, - 0x00,0xe0,0xff,0xff,0x00,0x00,0x00,0xc0,0xff,0x7f,0x00,0x00,0x00,0x00,0xff, - 0x1f,0x00,0x00,0x00,0x00,0xf8,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc, - 0x07,0x00,0x00,0x00,0x00,0xff,0x3f,0x00,0x00,0x00,0xc0,0xff,0x7f,0x00,0x00, - 0x00,0xe0,0xff,0xff,0x01,0x00,0x00,0xf0,0xff,0xff,0x03,0x00,0x00,0xf8,0xff, - 0xff,0x03,0x00,0x00,0xfc,0xff,0xff,0x07,0x00,0x00,0xfe,0xff,0xff,0x0f,0x00, - 0x00,0xfe,0xff,0xff,0x0f,0x00,0x00,0xff,0xff,0xff,0x1f,0x00,0x00,0xff,0xff, - 0xff,0x1f,0x00,0x00,0xff,0xff,0xff,0x1f,0x00,0x00,0xff,0xff,0xff,0x3f,0x00, - 0x80,0xff,0xff,0xff,0x3f,0x00,0x80,0xff,0xff,0xff,0x3f,0x00,0x80,0xff,0xff, - 0xff,0x3f,0x00,0x80,0xff,0xff,0xff,0x3f,0x00,0x80,0xff,0xff,0xff,0x3f,0x00, - 0x00,0xff,0xff,0xff,0x3f,0x00,0x00,0xff,0xff,0xff,0x1f,0x00,0x00,0xff,0xff, - 0xff,0x1f,0x00,0x00,0xff,0xff,0xff,0x1f,0x00,0x00,0xfe,0xff,0xff,0x0f,0x00, - 0x00,0xfe,0xff,0xff,0x0f,0x00,0x00,0xfc,0xff,0xff,0x07,0x00,0x00,0xf8,0xff, - 0xff,0x03,0x00,0x00,0xf0,0xff,0xff,0x01,0x00,0x00,0xe0,0xff,0xff,0x00,0x00, - 0x00,0xc0,0xff,0x7f,0x00,0x00,0x00,0x00,0xff,0x1f,0x00,0x00,0x00,0x00,0xf8, - 0x03,0x00,0x00}; diff --git a/src/samples/clock/font/colonD2.xbm b/src/samples/clock/font/colonD2.xbm deleted file mode 100644 index 03e032d5e..000000000 --- a/src/samples/clock/font/colonD2.xbm +++ /dev/null @@ -1,49 +0,0 @@ -#define colonD2_width 41 -#define colonD2_height 115 -static unsigned char colonD2_bits[] = { - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0x01,0x00,0x00, - 0x00,0xc0,0xff,0x0f,0x00,0x00,0x00,0xe0,0xff,0x1f,0x00,0x00,0x00,0xf0,0xff, - 0x3f,0x00,0x00,0x00,0xf8,0xff,0x7f,0x00,0x00,0x00,0xfc,0xff,0xff,0x00,0x00, - 0x00,0xfe,0xff,0xff,0x01,0x00,0x00,0xfe,0xff,0xff,0x01,0x00,0x00,0xff,0xff, - 0xff,0x03,0x00,0x00,0xff,0xff,0xff,0x03,0x00,0x00,0xff,0xff,0xff,0x03,0x00, - 0x80,0xff,0xff,0xff,0x03,0x00,0x80,0xff,0xff,0xff,0x07,0x00,0x80,0xff,0xff, - 0xff,0x07,0x00,0x80,0xff,0xff,0xff,0x07,0x00,0x80,0xff,0xff,0xff,0x07,0x00, - 0x80,0xff,0xff,0xff,0x03,0x00,0x00,0xff,0xff,0xff,0x03,0x00,0x00,0xff,0xff, - 0xff,0x03,0x00,0x00,0xff,0xff,0xff,0x03,0x00,0x00,0xfe,0xff,0xff,0x01,0x00, - 0x00,0xfe,0xff,0xff,0x01,0x00,0x00,0xfc,0xff,0xff,0x00,0x00,0x00,0xf8,0xff, - 0x7f,0x00,0x00,0x00,0xf0,0xff,0x3f,0x00,0x00,0x00,0xe0,0xff,0x1f,0x00,0x00, - 0x00,0x80,0xff,0x07,0x00,0x00,0x00,0x00,0xfe,0x01,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x78, - 0x00,0x00,0x00,0x00,0x80,0xff,0x07,0x00,0x00,0x00,0xe0,0xff,0x0f,0x00,0x00, - 0x00,0xf0,0xff,0x3f,0x00,0x00,0x00,0xf8,0xff,0x7f,0x00,0x00,0x00,0xfc,0xff, - 0xff,0x00,0x00,0x00,0xfe,0xff,0xff,0x00,0x00,0x00,0xfe,0xff,0xff,0x01,0x00, - 0x00,0xff,0xff,0xff,0x01,0x00,0x00,0xff,0xff,0xff,0x03,0x00,0x00,0xff,0xff, - 0xff,0x03,0x00,0x80,0xff,0xff,0xff,0x03,0x00,0x80,0xff,0xff,0xff,0x07,0x00, - 0x80,0xff,0xff,0xff,0x07,0x00,0x80,0xff,0xff,0xff,0x07,0x00,0x80,0xff,0xff, - 0xff,0x07,0x00,0x80,0xff,0xff,0xff,0x07,0x00,0x80,0xff,0xff,0xff,0x03,0x00, - 0x00,0xff,0xff,0xff,0x03,0x00,0x00,0xff,0xff,0xff,0x03,0x00,0x00,0xff,0xff, - 0xff,0x01,0x00,0x00,0xfe,0xff,0xff,0x01,0x00,0x00,0xfc,0xff,0xff,0x00,0x00, - 0x00,0xfc,0xff,0x7f,0x00,0x00,0x00,0xf8,0xff,0x7f,0x00,0x00,0x00,0xf0,0xff, - 0x1f,0x00,0x00,0x00,0xc0,0xff,0x0f,0x00,0x00,0x00,0x00,0xff,0x03,0x00,0x00}; diff --git a/src/samples/clock/font/colonD3.xbm b/src/samples/clock/font/colonD3.xbm deleted file mode 100644 index 12ff2de1e..000000000 --- a/src/samples/clock/font/colonD3.xbm +++ /dev/null @@ -1,34 +0,0 @@ -#define colonD3_width 34 -#define colonD3_height 93 -static unsigned char colonD3_bits[] = { - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0x3f,0x00,0x00,0x00,0xf8,0x7f,0x00,0x00, - 0x00,0xfe,0xff,0x01,0x00,0x00,0xff,0xff,0x03,0x00,0x00,0xff,0xff,0x03,0x00, - 0x80,0xff,0xff,0x07,0x00,0x80,0xff,0xff,0x07,0x00,0xc0,0xff,0xff,0x0f,0x00, - 0xc0,0xff,0xff,0x0f,0x00,0xc0,0xff,0xff,0x0f,0x00,0xc0,0xff,0xff,0x0f,0x00, - 0xc0,0xff,0xff,0x0f,0x00,0xc0,0xff,0xff,0x0f,0x00,0xc0,0xff,0xff,0x0f,0x00, - 0xc0,0xff,0xff,0x0f,0x00,0x80,0xff,0xff,0x07,0x00,0x80,0xff,0xff,0x07,0x00, - 0x00,0xff,0xff,0x03,0x00,0x00,0xfe,0xff,0x01,0x00,0x00,0xfc,0xff,0x00,0x00, - 0x00,0xf8,0x7f,0x00,0x00,0x00,0xe0,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0x1f,0x00,0x00, - 0x00,0xf8,0x7f,0x00,0x00,0x00,0xfc,0xff,0x00,0x00,0x00,0xfe,0xff,0x01,0x00, - 0x00,0xff,0xff,0x03,0x00,0x80,0xff,0xff,0x07,0x00,0x80,0xff,0xff,0x07,0x00, - 0xc0,0xff,0xff,0x0f,0x00,0xc0,0xff,0xff,0x0f,0x00,0xc0,0xff,0xff,0x0f,0x00, - 0xc0,0xff,0xff,0x0f,0x00,0xc0,0xff,0xff,0x0f,0x00,0xc0,0xff,0xff,0x0f,0x00, - 0xc0,0xff,0xff,0x0f,0x00,0xc0,0xff,0xff,0x0f,0x00,0x80,0xff,0xff,0x07,0x00, - 0x80,0xff,0xff,0x07,0x00,0x00,0xff,0xff,0x03,0x00,0x00,0xff,0xff,0x03,0x00, - 0x00,0xfe,0xff,0x01,0x00,0x00,0xf8,0x7f,0x00,0x00,0x00,0xf0,0x3f,0x00,0x00}; diff --git a/src/samples/clock/font/colonD4.xbm b/src/samples/clock/font/colonD4.xbm deleted file mode 100644 index 031a05997..000000000 --- a/src/samples/clock/font/colonD4.xbm +++ /dev/null @@ -1,25 +0,0 @@ -#define colonD4_width 30 -#define colonD4_height 80 -static unsigned char colonD4_bits[] = { - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0x03,0x00,0x00,0xfc,0x0f,0x00,0x00, - 0xff,0x1f,0x00,0x00,0xff,0x3f,0x00,0x80,0xff,0x7f,0x00,0xc0,0xff,0xff,0x00, - 0xc0,0xff,0xff,0x00,0xc0,0xff,0xff,0x00,0xc0,0xff,0xff,0x00,0xc0,0xff,0xff, - 0x00,0xc0,0xff,0xff,0x00,0xc0,0xff,0xff,0x00,0xc0,0xff,0xff,0x00,0xc0,0xff, - 0xff,0x00,0x80,0xff,0x7f,0x00,0x80,0xff,0x3f,0x00,0x00,0xff,0x3f,0x00,0x00, - 0xfc,0x0f,0x00,0x00,0xf8,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0xf8,0x07,0x00,0x00,0xfc,0x0f,0x00,0x00,0xff,0x3f, - 0x00,0x80,0xff,0x3f,0x00,0x80,0xff,0x7f,0x00,0xc0,0xff,0xff,0x00,0xc0,0xff, - 0xff,0x00,0xc0,0xff,0xff,0x00,0xc0,0xff,0xff,0x00,0xc0,0xff,0xff,0x00,0xc0, - 0xff,0xff,0x00,0xc0,0xff,0xff,0x00,0xc0,0xff,0xff,0x00,0xc0,0xff,0xff,0x00, - 0x80,0xff,0x7f,0x00,0x00,0xff,0x3f,0x00,0x00,0xff,0x1f,0x00,0x00,0xfc,0x0f, - 0x00,0x00,0xf0,0x03,0x00}; diff --git a/src/samples/clock/font/colonF.xbm b/src/samples/clock/font/colonF.xbm deleted file mode 100644 index 04f3e3d8a..000000000 --- a/src/samples/clock/font/colonF.xbm +++ /dev/null @@ -1,8 +0,0 @@ -#define colonF_width 11 -#define colonF_height 35 -static unsigned char colonF_bits[] = { - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x70,0x00,0xf8,0x00,0xfc,0x01, - 0xfc,0x01,0xfc,0x01,0xfc,0x01,0xfc,0x01,0xf8,0x00,0x70,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x70,0x00,0xf8,0x00,0xfc,0x01,0xfc,0x01, - 0xfc,0x01,0xfc,0x01,0xfc,0x01,0xf8,0x00,0x70,0x00}; diff --git a/src/samples/clock/font/colonG.xbm b/src/samples/clock/font/colonG.xbm deleted file mode 100644 index 4a10cfaf2..000000000 --- a/src/samples/clock/font/colonG.xbm +++ /dev/null @@ -1,6 +0,0 @@ -#define colonG_width 10 -#define colonG_height 16 -static unsigned char colonG_bits[] = { - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x70,0x00,0x70,0x00,0x70, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x70,0x00,0x70,0x00, - 0x70,0x00}; diff --git a/src/samples/clock/font/dalifont.ai b/src/samples/clock/font/dalifont.ai deleted file mode 100644 index a4a9b4143..000000000 --- a/src/samples/clock/font/dalifont.ai +++ /dev/null @@ -1,473 +0,0 @@ -%PDF-1.5 %���� -1 0 obj <>/OCGs[5 0 R 31 0 R 56 0 R 81 0 R 106 0 R 131 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <>stream - - - - - application/pdf - - - Adobe Illustrator CS6 (Macintosh) - 2013-07-15T11:48:25-07:00 - 2013-07-15T13:11:36-07:00 - 2013-07-15T13:11:36-07:00 - - - - 256 - 40 - JPEG - /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgAKAEAAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9Ma95i0Hy/YNqGuahb6b ZJsZ7mRYlJpXiORHJvADfFUk8g/mf5S8+x6jN5ankubXTZlt5bh4miR2ZeQMYejkU8VGKssxV5v+ YX/OQX5a+RNT/ROr3ktxqigNPZWUfrSQhhyX1SWRFJBqF5VpvSmKsl8ifmF5T89aN+lvLd6Lq2Vv TnjZTHLDJSvCSNqFT+B7E4qyGSSOKNpJGCRoCzuxAVVAqSSegGKvHr//AJyy/Jyz1b9H/Xbu4iDc W1GC2Z7YbkFgaiRlFOqoa9q4q9b03UbDU7C31DT50urG7jWa2uIjyR43FVZSOxGKojFWFeY/zf8A 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 dirsVdirsVdirsVdirsVdirsVdirsVf/2Q== - - - - - - xmp.did:F97F117407206811822AB33B9AB6B0AC - uuid:9b02df42-3b96-bb4c-a8f3-4f96e0b6335f - uuid:B4FB81C46090DF11AB3682B45BA6D570 - proof:pdf - - uuid:B5FB81C46090DF11AB3682B45BA6D570 - uuid:B4FB81C46090DF11AB3682B45BA6D570 - uuid:B4FB81C46090DF11AB3682B45BA6D570 - - - - - saved - xmp.iid:F97F117407206811822AB33B9AB6B0AC - 2013-07-15T11:48:27-07:00 - Adobe Illustrator CS6 (Macintosh) - / - - - - - - 3 - - - - 1 - 720000/10000 - 720000/10000 - 2 - - - 1 - 12960 - 1112 - - - 1 - True - False - - 180.000000 - 15.444444 - Inches - - - - Cyan - Magenta - Yellow - Black - - - - - - Default Swatch Group - 0 - - - - - - Document - - - - - - - - - - - - - - - - - - - - - - - - - endstream endobj 3 0 obj <> endobj 7 0 obj <>/Resources<>/ExtGState<>/ProcSet[/PDF/ImageC/ImageI]/Properties<>/XObject<>>>/Thumb 141 0 R/TrimBox[0.0 0.0 12960.0 1112.0]/Type/Page>> endobj 133 0 obj <>stream -H�dWK�%� ��)��-꯭{� �0f�<��j�q�pD��W]�^�S� C�����������/���#�鴼ںך���X��y���߿���t��ǁ9�s�d���q|��G:���w��^��S/��|����o?N�����;76n��i�-�%+wN��=������q�z�b'~�����F>K�Ǵs����Gop��ak��{���ڨ�J�����8/~����V���k<�YX�X Kl"b{ �S+�g��Ƽ+������)k����ִq\��q��x�H�����ą��:^n>��&�%����P��t��� -O����YW�I� ?i~���Ұ-; -\_�=����^�S:jힱv�q�װ�G<�:�I�����|�ȫܭ�O��}����~��>y؆��X�>m�����=���m��F��g�bE��|��� f�&V$�@�J�Tc�}.��K��e*�y�Ȑ;�VM'�����9�]�k�p������(� ��'!1*V8�ϻã�n���#� 8��g��1懥��@�?��-��t"Xc���Eh&C�T\% �F�Z���9�]��Q�=|;p�J. �H�U�36������D$�'Ƀzc.�5������[�=�aiJ�e��4,��Y�R��L�0Q0 �>� �6�Ql��,�?��y��W�{[rͷU���)Jj��X�oGA��>}ۚ�g��;ѻW[��Ѕ%W�N�y x�+��,T�k� ���wuG�8Y*�Lc\�*N��R� �mXwU� ���&R�o@� -��2^hh�,&NtT�pr弔��[��!�)�U����� ���Jc0a��y=#\���zk X�ڗ|{�� -y1��6��]E�O�#^@q6dB ?��7����Ɛ���c�x�&��!h.̛�b{b A�qㆌcY�X�����,� z�)շ�Y���A�Y��nM�����݌��A��@L���)k�p^`�u!yDb�~� ͅ^�1W����)d�%���<�F7�.2C\�R���t<���+: ��"��� ëaL<6v��V� -�Qa��`Y��I/��0��=�pyȂN��2�*b�i�R�@��J�b�����3x��r�QP��(?�Ą*��{2�,-��y���� 2���Jy컒�'��S�8���x��� :�8��u���,T��F{��1�ra` щ������ -  B��}����#p���3Z�x�@�;_>�5�=P�p���8��(U��@C��_��Pm$O��;B{����V��>-{�f����^P�����*/Q��^bluiV�wR�lXlz�����um�;M]*KI!� -)�% ^��;/���j3wV���(�i���j&���l#ԏQ<>riA頡U��4�0QIg�p�T]j� �Q��eWw2�cD���%�@@a��`yMR�E��O��Jwr^�/��F"[��xz[}��� *�*-��G�����]ݪ�t���g��iQ���B�ѐ�;]"{Q���?H��E �u�($ ���,�xU ���„�����=��ĎȲ�h��t�,y�F�q��,QL���ư.��۱ l:Bo -�j��p���ͩA� -:�K�G~�� "� �/�l��1��:4��S,���A5m.��M��$��,�y,��ৗ �%���-����-���}:M �"���zH���h<=�w�%XnZ&�����U�3h�J�M'W�5���LJ�B��� -�+W��@��t ���a�%=��"���\3����kl­��h�!�J��B*����劼%a���r�j��>���5kg ��8Y�%�"@+��{�v$���&$��n�P3K!�?9O�����N�|�&UA�����.���tqj|;�riE -��B�ݓ.~+L -��L��ޏQ� �����-'�=�B�z�a�����-@Tʈˆ���#��%�(*�6��i͹�/�1�ig@��<���� -��j",b����"���{<@"�(u7��� �S���� ��B�ޗ��B*�a��`��e��u��RZ��Ө�� g����>4��1U�ސ1�[C"�j:nr�fH�k��ؗz?Zz��Dݡ@�����!��S�^=j��c�V�Č= ���p�w.�*1�����r�M>$j)g5�Jg�� �>C��暷��WS�Bc�r�8�cD���Ζ�⠞�� ]!��Ӡ��V�)�+�������3��.�� -�C��V��Jq;/�)�cY�Y���ڜ�)�ŀ����ț�����F���@�����)g"��6Y��`����)�\�5���'�� �;l�+���dI����V4����l,?+�qxp�ۜ!��Ϥ�J����ƪ�]}g�+��I<"�]��?1x����O��M p��Vx�ma0�%�*Ř����j���(�pU�=�,U!��J�6�T"��T��-~��6ؙ7<�.H��k�<:+g{TW��mX����U�/�c�̱$�a ��)���}����)���*>Y]ӒUI�R$�|(�i9k�@ ���e�pS/�k��PŕH����-˱�𔎠bS��&t��Z��m�8\�C�Z�Z�%�C�NȮ|�3���_���_?���mf�:W?9SE�D�mFBut9ӆ�kV��}8��0G2�)p������5T�Q�zV[��g\���gf:G�\-��9���Ș;��YRc�:�(���g�h�3a��?S� '��8Y�R�T]5%F�h��W�!��+ 1��D�&��fgv��X ދ��w!v DQ?pЛ�"���@��oX�k�e��uBZ��D vQ�.�%[���Z8L�IR*��4�C�V��D;�ـ"��O.g���;�#�x�*Q]��WF)�>7o��ey��z�f�?�Z���,��/����QoSY��<�,{ܯȏ��ˇr#_�!�# M?��p��F�&wr��H\X�VD�ɫVd���aM����̑[4�k��:=���J5g��ƒ��^v���ݕ�םAΌ,�:��<� d��INE��=N�zU�F��Y}��DƆ���3^�8Z�H����c1�bT=��w e� ��{�ku/+*.W�Õ������q� juB�:�f�m�-��#����p[� U�yBqGji�l���k+��S�K�Z�B R���B� �t*�-LTOz����<��������iZ*�r>��~�M��1�h�� ��Յ��Sj�a�W��ہ���V�t���� uÕ�7��L0n�Ɲٺ�6��SDP����q��x=uc��P)�Qg�׏�� ��k#ɦ�R�7�?��ڏ6_���Ko� `��w�5|�XB8f7 PS������zý� -�@Ow���Б*i�l�=sl�?�H?X�d� }BD]O��'ذ�=�LZbdd9�w��bЖ�>�C�$�b(ii?��G�m���&��?��'^�M8�>���d|�W�*__m)-�J�� ��0�m��Z�H ���P�I�B���B<�|��>Z���%:�s�m�MU6�֦�U@����S�d;�֩)�fR����$C�3FkF�g�&b#m�jb��1a���z����5���;?��s �Tp��np�YNO������t���-���t� -)�[ϘP�X�33�e�.��t ����y�dg�W�8j׻��'Fk�q��şij����}��\�����D e{ȍ��������-TYx=��kf� ���l�f R����כּ.[�_i'��]DS��IY���X��H<����gVy���U|Y,��F�,T��WR�)���-i3�!���_�(�5\���0ҸZ���m��:m�X��MH���p8������r[�#e�gHOg�� �/ z����*(��b#J���V�DJB�y��O�������-��JK�+� &)'s�LB�0�@I(��m����A��E��<��:���������: endstream endobj 134 0 obj <> endobj 141 0 obj <>stream -8;V^l9,<17%-%7&j>t"Pg[[)J'ug:u%1.RZ_*T+=i3(]s5_5+/gQ"k@^ucd$+@c9J -S=II;8s1^m=q=_q%l!r)o0\!/O.o\[UR(@bZVaE(U]!n4UKs,:J_g%Yqc9Xm4d?kt -2p+Yq,t[J>Lg5/~> endstream endobj 143 0 obj [/Indexed/DeviceRGB 255 144 0 R] endobj 144 0 obj <>stream -8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 -b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` -E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn -6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( -l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~> endstream endobj 140 0 obj <>stream -H�������� -�ك ��FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa��_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU�=8�mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU��@���TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ ��FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa��_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU�=8�mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU��@���TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ ��FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa��_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU�=8�mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU��@���TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ ��FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa��_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU�=8�mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU��@���TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ ��FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa��_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU�=8�mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU��@���TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ ��FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa��_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU�=8�mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU��@���TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ ��FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa��_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU�=8$���7 ��� endstream endobj 136 0 obj [/Indexed 137 0 R 0 146 0 R] endobj 145 0 obj <>/Filter/FlateDecode/Height 1112/Intent/RelativeColorimetric/Length 14328/Name/X/Subtype/Image/Type/XObject/Width 12960>>stream -H�������� -�ك ��FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa��_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU�=8�mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU��@���TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ ��FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa��_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU�=8�mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU��@���TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ ��FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa��_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU�=8�mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU��@���TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ ��FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa��_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU�=8�mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU��@���TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ ��FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa��_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU�=8�mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU��@���TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ ��FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa��_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU�=8�mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU��@���TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ ��FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa��_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU�=8$���7 �i��Z endstream endobj 137 0 obj [/ICCBased 147 0 R] endobj 146 0 obj <>stream -��� endstream endobj 147 0 obj <>stream -H���yTSw�oɞ����c [���5, �� �BHBK!aP�V�X��=u����:X��K�è���Z\;v^�����N�����߽��~��w��.M�h�aUZ�>31[�_��& (�D���Ԭ�l�K/�jq'�VO��V����?�:�O��s�RU�����z��d���WRab5�? Ζ&Vϳ���S�7���T�rc�3�MQ]Ym�c�:�B� :Ŀ9��ᝩ*U�UZ<"�2�V��[��4�ZL��OM��a?��\�⎽�"����?.�KH�6|zӷJ.Hǟy���~N�ϳ�}��V����dfc -��n~��Y�&�+`��;�A4�I d�|�(@�zPZ@;�=`=���v0v��� ����<�\��$�� x -^AD��� W� ��P$�@�P>T �!-d�Z�P� C;������ �t � -��@�A/a��<�v�}a1'���X ��Mp'��G�}�a�|�O��Y 4��8"BD�H�4�)E�H+ҍ "��~�r��L"��(�*D�Q)��*���E��]�a�4z�Bg�����E#��jB=��0H�I��p�p�0MxJ$�D1�(%�ˉ��^�V��q�%�]�,�D�"y�"Hi$9�@�"m!�#}F�L�&='��dr���%w��{ȟ�/��_QXWJ%���4R�(c�c���i�+*�*�FP����v�u?� �6� �Fs���2h�r����iS�tݓ.�ҍ�u����_џ0 7F4��a`�c�f�b|�xn�5�1��)���F��]6{̤0]�1̥�&� ��"���rcIXrV+k�u�u�5��E�4v����}�}�C�q�9JN'��)�].�u�J� -� -�� w�G� x2^9���{�oƜch�k�`>b���$��e�J~� �:����E���b��~���,m,�-U�ݖ,�Y��¬�*�6X�[ݱF�=�3�뭷Y��~dó �Q�t���i �z�f�6�~`{�v���.�Ng����#{�}�}��������������c1X%6���fm��F�����N9NN��8S��Υ��'�g\\R]Z\���t���]�\7��u}�&p�s[�6�v_`)� �{���Q�5��W=�b� -��_zžA�e�#��`�`/��V�K��Po���� !]#��N��}R|:|�}����n�=���/ȯ�o�#Ju�������W���_ `$� �6�+P�-�AܠԠUA'����� �%�8佐b�8]�+�<���q苰�0C���� �+����_ X�Z0��n�S�PE��U�J#J�K�#��ʢ��i$�aͷ������*�*>���2��@���ꨖ��О���n�����u�&k�j6����;k��%�G <�g��ݸ�8UY7R��>��P�A�p�ѳqM㽦���5�͊�-�-�-S�b��h�ZKZO�9�u�M/O\����^��������W�8�i׹����ĕ{�̺�]7V��ھ]�Y=�&`͖5����_��� ��Ы��b�h���ו��� �۶��^����� ����M�w7�n<<� t|��hӹ���훩���'�� �Z�L���$�����h�՛B��������d�Ҟ@��������i�ءG���&����v��V�ǥ8��������n��R�ĩ7�������u��\�ЭD���-�������u��`�ֲK�³8���%�������y��h��Y�ѹJ�º;���.���!������ -�����z���p���g���_���X���Q���K���F���Aǿ�=ȼ�:ɹ�8ʷ�6˶�5̵�5͵�6ζ�7ϸ�9к�<Ѿ�?���D���I���N���U���\���d���l���v��ۀ�܊�ݖ�ޢ�)߯�6��D���S���c���s���� ����2��F���[���p������(��@���X���r������4���P���m��������8���W���w����)���K���m�� ��� endstream endobj 131 0 obj <> endobj 148 0 obj [/View/Design] endobj 149 0 obj <>>> endobj 138 0 obj <> endobj 139 0 obj <> endobj 135 0 obj <> endobj 150 0 obj <> endobj 151 0 obj <>stream -%!PS-Adobe-3.0 %%Creator: Adobe Illustrator(R) 16.0 %%AI8_CreatorVersion: 16.0.0 %%For: (Jamie Zawinski) () %%Title: (dalifont.ai) %%CreationDate: 7/15/13 1:11 PM %%Canvassize: 16383 %%BoundingBox: -6084 -516 6876 1399 %%HiResBoundingBox: -6084 -515.9189 6876 1398.6221 %%DocumentProcessColors: Cyan Magenta Yellow Black %AI5_FileFormat 12.0 %AI12_BuildNumber: 682 %AI3_ColorUsage: Color %AI7_ImageSettings: 0 %%RGBProcessColor: 0 0 0 ([Registration]) %AI3_Cropmarks: -6084 56 6876 1168 %AI3_TemplateBox: 395.5 612.5 395.5 612.5 %AI3_TileBox: 12.2402 318.2402 779.7598 905.7598 %AI3_DocumentPreview: None %AI5_ArtSize: 14400 14400 %AI5_RulerUnits: 0 %AI9_ColorModel: 1 %AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 %AI5_TargetResolution: 800 %AI5_NumLayers: 1 %AI9_OpenToView: -6104 3579 0.15 1970 896 18 1 0 117 134 0 0 0 1 1 1 1 1 0 1 %AI5_OpenViewLayers: 7 %%PageOrigin:0 0 %AI7_GridSettings: 72 8 72 8 1 0 0.8 0.8 0.8 0.9 0.9 0.9 %AI9_Flatten: 1 %AI12_CMSettings: 00.MS %%EndComments endstream endobj 152 0 obj <>stream -%%BoundingBox: -6084 -516 6876 1399 %%HiResBoundingBox: -6084 -515.9189 6876 1398.6221 %AI7_Thumbnail: 128 20 8 %%BeginData: 3580 Hex Bytes %0000330000660000990000CC0033000033330033660033990033CC0033FF %0066000066330066660066990066CC0066FF009900009933009966009999 %0099CC0099FF00CC0000CC3300CC6600CC9900CCCC00CCFF00FF3300FF66 %00FF9900FFCC3300003300333300663300993300CC3300FF333300333333 %3333663333993333CC3333FF3366003366333366663366993366CC3366FF %3399003399333399663399993399CC3399FF33CC0033CC3333CC6633CC99 %33CCCC33CCFF33FF0033FF3333FF6633FF9933FFCC33FFFF660000660033 %6600666600996600CC6600FF6633006633336633666633996633CC6633FF %6666006666336666666666996666CC6666FF669900669933669966669999 %6699CC6699FF66CC0066CC3366CC6666CC9966CCCC66CCFF66FF0066FF33 %66FF6666FF9966FFCC66FFFF9900009900339900669900999900CC9900FF %9933009933339933669933999933CC9933FF996600996633996666996699 %9966CC9966FF9999009999339999669999999999CC9999FF99CC0099CC33 %99CC6699CC9999CCCC99CCFF99FF0099FF3399FF6699FF9999FFCC99FFFF %CC0000CC0033CC0066CC0099CC00CCCC00FFCC3300CC3333CC3366CC3399 %CC33CCCC33FFCC6600CC6633CC6666CC6699CC66CCCC66FFCC9900CC9933 %CC9966CC9999CC99CCCC99FFCCCC00CCCC33CCCC66CCCC99CCCCCCCCCCFF %CCFF00CCFF33CCFF66CCFF99CCFFCCCCFFFFFF0033FF0066FF0099FF00CC %FF3300FF3333FF3366FF3399FF33CCFF33FFFF6600FF6633FF6666FF6699 %FF66CCFF66FFFF9900FF9933FF9966FF9999FF99CCFF99FFFFCC00FFCC33 %FFCC66FFCC99FFCCCCFFCCFFFFFF33FFFF66FFFF99FFFFCC110000001100 %000011111111220000002200000022222222440000004400000044444444 %550000005500000055555555770000007700000077777777880000008800 %000088888888AA000000AA000000AAAAAAAABB000000BB000000BBBBBBBB %DD000000DD000000DDDDDDDDEE000000EE000000EEEEEEEE0000000000FF %00FF0000FFFFFF0000FF00FFFFFF00FFFFFF %524C45FDFCFFFD09FF7DFD08FFA87DA8FD08FF7D7DA8FD0AFF7DA8FD06FF %A8FD047DFD09FFA87DFD05FFFD067DFD06FF7D7D7DFD07FFA87D7DA8FD08 %FF52A8FD1BFF7D52F827FD07FF27F8F8F87DFD06FF27F8F8F87DFD08FF52 %F87DFD06FFFD05F8FD07FF522752A8FD04FF52FD05F827FD05FFF8277D27 %F8A8FD04FFA8F82752F87DFD05FFA8F852F827FD19FFA87DF8F827FD06FF %7D27A827F8F8FD05FF52A8FF52F827FD07FF7DF8F87DFD05FF7DFD04527D %FD06FF27F87DFD06FF52F8522752F87DFD04FF52F87DFF7DF852FD04FF27 %F8FFFFF8F8FD05FF27F8FFA8F87DFD1AFF52F827FD06FF52FFFFA8F8F8A8 %FFFFFFA8FFFFFF52F852FD06FFA82727F87DFD05FF5252FD09FF52F852FD %07FF52FFFFFF7DF8A8FD04FF52F827A87DF87DFD04FFF8F8A8FFF8F87DFF %FFFF7DF8F8FF7DF827FD0DFFA87DA8FD0AFF52F827FD09FFA8F8F8FD07FF %7D27F8A8FD06FF27A827F87DFD05FF27F8F8277DFD05FFA8F8F85252A8FD %09FF2727FD05FFA8F8F827277DFD04FFA8F8F8A8FF27F87DFFFFFF7DF827 %FFA8F8F8A8FD0CFF52F852FD0AFF52F852FD09FF7DF87DFD06FFA827F8F8 %F8FD05FF527DFF27F87DFD04FFA827FD04F852FD04FF7DF82752F8F87DFD %07FFA8F87DFD04FFA8FF7DF8F8F852FD05FF27F852FFF8F852FFFFFF52F8 %F8FF7DF8F8A8FD0CFF52F852FD0AFF52F827FD09FFF852FD09FF7DF8F87D %FFFFFF7D52FFFF27F87DFD07FFA852F827FD04FF52F827FF7DF8F8FD07FF %52F8A8FD06FF522752F8F852FD05FF272727F8F8A8FFFFFF7DF827FFA8F8 %F8A8FFFFFF7D52527DFD06FFA8FD0BFF52F852FD08FF2752FFFFA8FD08FF %52F87DFFFFFF52FD06F87DFD08FF27F8FD04FF7DF827FFA8F8F8A8FD06FF %2727FD06FF52F8A8FF27F827FD07FF7DF827FD04FF7DF8F8FF7DF8F8FD04 %FF52F8F8F8FD12FF52F827FD07FF5227A8A85252FD04FF7DFFFFFF7DF8A8 %FFFFFF7D525252F8F827A8FD04FF52FFFFFF5227FD04FF7DF8F8FFA8F8F8 %FD06FFA8F8A8FD06FF27F8A8FF7DF827FD07FFF8F8A8FD05FFF827FFA8F8 %52FD04FFA8FFA8FD06FFA827A8FD0AFF52F827FD06FF7DFD05F87DFFFFFF %7DF8F87DA82752FD08FF27F87DFD04FF52F8F87D5227A8FD05FF27F8A87D %F87DFD06FF27F8A8FD06FF7DF87DFF52F87DFD05FFA8F827A8FD06FF7DF8 %A852F8A8FD0DFF27F827FD08FFA87D2727277DA8FFFFFFA827F8FD0427FD %05FF5227F852A8FD09FF27277DFD05FF52272752A8FD07FF5252277DFD07 %FF2752FD08FF7D275227A8FD05FF7D527DFD09FF7D2752A8FD0EFFA827A8 %FD09FFA8FFFFFFA8FD33FFA8FD15FFA8FD07FFA8FDFCFFFDFCFFFDFCFFFD %32FFFF %%EndData endstream endobj 153 0 obj <>stream -%AI12_CompressedDatax�ܽ��H(������3M�96� ��`�af��q���� ��6�a��sv���,U�T*I%U�F۞���q�j�^�&q�H�a5����ٜ�# 2��j�߁�d18f{8I��]�~D?���M%l����߫�^��&3x�Y78x8�6�9�;:���L �`G�4�t��.��v�ԍ*|����(j�/�x��[�8�f��"E�V��hЫ��\~�?�]�P�V-��9B�`�t��nx%CLO[|wl����4�!H*�N��v�*�O0��l��թ 6]s^Ɂ��j�S���q��p|��D �E�8����i[�v�! �E�{�:�6��i;�;�A7��3�ԥ@K@��g�s`\܂_���: -(�ah�S�N���L�-|���p|hfђ�~��k��"ߙ�.����� �GO������>�g�?���[�q��@������?0/\�������k�gx�$�mfܽ^e�2OZ� Nvw�#��d1�0�J�� �����`4O��\׿ @#�� �9iA�)�W�?8C���w�G���w�^���Q�˧v��:, ��]��,���?��4� �:��Ak�a����jf[����:��:���;�r�:��i-�������JW9"�:�m@1����-�>g��� �`�]>�O�맽�S� 9'�S�W�������:��N�'�3I������6̙Ŧ�#��\���L��^�����Ħ+�|���Į0���|zF��o��8�*�y���bЯ�fu����UN��%X�K\` �)'�Ā���*��;� �t�@q�9�vRG���^P���sr�l�s��������v��p���-�6sN�� $ˣL�'��iÅ�cGg�ܙ�:������O{�ѷ��� -t���snOŸU7p� r6������5=m��3~g�N8;9#��9��lKҵ�b���9�ū�m3@#n�-֐������ɫ=�9�Ǧ�3� F�6�����2���&9�-�-Ȁ�4��'���;��l�� - -ȅi�.��$ƌh�DtK�ri7��F�r�W�ìx��=�C̠��c}�g��/ ����8Q@DT�$GD���IF2���e��ߋ\q-^�� P��s��]g � D����<���˶��63[lJBy��æ�`�43�U��i����v���`�%a�b�=U'KkP_iܡ��q]/'��p��elj�� ����|���� -+�d����0����l'�f�d����ǰq ��cW Um��;���I�/����؉=���������X|�Q��n�_`�ٷ7ˢ��s`����U����/|C����_�P�1ruc��0RM�~�\Y�ի���^q_��,�$�Y��� -���=\�:�4O��H!�v+P��7��m����Sha��y���Al.h��.�ٟ�_!����®�ctK�R���-M<�_ƞ��*N�>����J̳G�V|+j{�'�/ �����][Ń��,Pr�X��7v�.+� l[\v�� � �~��:�W�=�w��%���n�9�}A� W����Z��B����P�,GQ�U�O�߱��84��5l��m*g{�K� ���*��;�c��� -~#��������v��߃��g��=�:{{���>����V�PuU�9D�3��{KE�&�DTj�����tO@ׯ��ˌ,x��@����� -�r�����n���T�H]�q���`T:�����ͨ�P�3� ���� �(��m�9 ����J?v�����V�q������CQ���c����x�"t��-x��KnCdۭ�%�JÛ����A�x�����vhE�i�td�.6�4�)�FoE�&� ��G �[��+�VB^�r�b�m:D�>� E�Ė��� -�b �|�Xn�c���&,R��6>��c��V$u� -��$�ڥ;� ��/������.eC Q����Z�3\t�EԦ����v���� p)��$Ib �� �s��K঒���q�~ -����6v��/��"`1k��� ۜΰ�wJnB�m ���)wv�5W�� 1]�3%�9C�H0ۯɊ�Up)��6~�ѝ��?.��IN�d9���+l�����Q��i���R<�!�Y�a��' -��]M����0�AZ�`�)����m�j�� �����?�����YM�o���|ڤ���%��翭{�@��+�(���vW�t�B��=���)���;��N�\^���7dK:�Jzx��/� &����߫�q��O,����O��6q"�8��__����n�?b3` =ێГ���������<8��Ԧu���H{�I�}�P8��Ղ��2Ϊx���!�˝6�s��MgO�<�? �˓����ԥ�~���m��[ߥw�p�=6]ٞy}��9l�'a��=h�d��A�v��%��̊:b�)7�$d�d�ٜH���{i��&�p��rP��4[��GX�.�� x墈��� �*wAH Ǔ ���j,����`(�� � ���ҫ9��w�4�S�YJ$�F��@ó�I�&�,�nff,�g}vKt�s�z��J]~�����I5�;���f�Q�p(�8a��:��W|���aVg�ı$��['E`Z�`�8����S�K��~���T1r���$y�9S����S��Ϫ�ӺN�d��'�l��Rn��b�2���Vu�f g:Y&�*~�%�S�Թ��X�v�;Cм�,=4�Ab0K���좞`��b2�s����l&�-F�M���Q����Y,g���US�/SbC^��dQ�< u�n�e -p�˥>/����������.� �"`~�z�G�pH����C�j{�К�o�@�,?S��˟o����v����h�j��.]��$.���t�Q��`ɳ���"�.N�rp���~g!�6 �>�v.��5H��ɿpu�:5c6�n��b�?�F���{���ʡ鳊�V����S=��4�˶.�W��]��m:�9 -㪠!�8��0-Ijg��{�~:�˿Ù�7��`�� �5��y"H�f�Y����X�?j��K��GgQ�Y���������X�n1��r5] -!/���629� :*:��� ��<0ym]�v� O��r��L=��ΐ`�"�b�q��-�����@\ʠ"8�� 2؍�P�A.�z�ƈ� �1p�d�>���;|�]��@S��+ ���<��kJ��7���f��RXwa8a�va�(6>T@L���w�T�(�"IA<����A.��p�*x�L���� w�C�eM�$ �c<T��Oe� - @Ɍ8 0;=��.of߉��F�P��n*#% �ȟ%���@���v�x���JC�s�[��d�:�u����'g�/S�_o|����8j[`�qL>1�@W���( -s]N�dd q\�s�SdZ�[���]�U�<�' -���*�V'�/rkn|�w��6V���N��鑣�V&̔�U���2AZ��,�R�n`� |�B _�Sb�4��b޺O˙Z�c -�v�X �28�I�A��)�(��� �d��B(���d,՞W�O�� dE �B+�'��U���o�>8-s�|e���A�wA,F��TҨ�A_%[���?�N�͢ |Б�k'.6��� Q����dX��Dox0�F#�5�=-����5� F���:������)�5lz���IqG����|2��׎J�7:�6���4��~]�O�|;�^Q'�J7ԗq���`w�Osi�<��]�՞��L���|)�rB�_龄@�����y.=������줃jr��)��T�,����Hv�>�MY܀ߦav� f�Ƃ>S>yF4�hfzTzz�I-�tg���;�� ��=W�� {���K���c|���=�\n���x���/=���;gϲ�`~�<Ց�5Z �uL��?�@7���y�:�j�pt�c�غ�6@KL��~ z�jW>5�h%[�7C*��za{L��ad��ױ��x��S_���:<�%�U�#JW�u g/��3�����S.�+����^� -ak�Zq%oӀ5[����i���u��X�G2�rhW -'۶�.�Z̎*}`�wdp�-����R�p�g�,ϭ�������)R�zv��N��@;#�cM�C��LqV4���Mb�Im|�I ��$9-����NM�(W�d��,���{�O�H�5�6N�A�C��ۿ<�T�)3���2�/�5��h{D3+� H��2K��t�'+;Vf�P��k��Yh��@�3�4�ߴ9D����k�2/���6b@4�a��25D�N}L]C���o �y����D��6�X�bmjR��7�!�����͈s��!��D\TG<ڣ�~�g���M;B�1�U�y H�����t -F��$/Ց�^l�$v.+� -[2H�S;F�R���R�￐b}�E��S�`�1R{�����_D��� -i�^���w���G���,�1�-���n57_�w�gC&�V���u�|� K��#kZ@���!� �UҤ���^�P}_� �-f�h�' ]y ��i�e���n� T�n���w��7t#�ȗ1U&��b�=36��o��ʴ7�N˪q�X��Jql<�o@��1�䘇�M��?l����l.�n��h�����65�J��i�B��c��1S���L[�@�7��(�9�P�c=3���}�7��ͫxP16A�E�9X-ç��f���wm)xt��i 0ˀ�G�����W�OˑZ�����ck���X3���Z�e6�~���bJo��֣�P�m��6es�5[�N|ڲTne�O)�m0lxl�oOֶ���vͮ����q�=�'��ڡo/o愽���ؿ��U���Z�O��aM}V~�d�H,�����3��JT���݂cS�m���'괷Zgp��9ӧ��Y=Ώni���s� j2%����]�o>,��� -�[d����;@ץ/�**��U3;]��y�*bh��%n�ĕ&\�B���VI�;f���Dv�.�G��őw�?�>T�F�9��y�^�'���z�T�3 ����"����J�u�Vo,0xK���n���@Gy)���Yז�/�h|�tZ��:�f�Ծ@}�%�XRo����ϥ�m���'�� ˗��g9���G��@/�^f$a P3��T��`�P���7��Ԃ�r�'��'�!7^n�R��>��.��o\�{0j [V�f8�� @g���|��:��Jѕ7Dě��"����t2�Jd�/#G �GQ��M�V��rZ�b�/s�0������X�23�j�r�t�ؾ���m�2�e&�x�l)��U0���jox����x��������e��=J8�yg"�m�I̶�} �I��Jfg�E���ϓ?Fl�2F�?�P�M��(����Ԟ�ZU���f��J<�>K��$����O�L�^�2��Ñ��������: �U6����/$�]Y�����C�"�e(W���ܤ~��H�����Q�h�*��~Z��f�����B�A� ��l���lE[�/&��b�s�.���d�|r�Jq\k-�;�ai���eS��*�>F�2�B@_��S�TbcwJ����IKe�����]H�_���Nw���"H�X,kI����ik�m�+SwTs�zf��럚��~Ȯ� ��6�o_���8:6���� �M�2�U����7�L��z��Z��󭵭uKm'n���֤=���D�e�;o�N#l�w���k�����g9��h'�]�Y�|���W]ƫ��!��7��^���\̻���^�'��컿0�y�[M��ź:X�u�Af؝ ��������e���c ۬���S�ϲg1���&�/sl�|�������>>D��J?��“a�ٮ ��&0�b~���|�G���;vt��q�� -���K��[<}�w�����8�t��������D������$�j4&�Y�1!-��4X;�T�i�r}N�r��m��Ys���m�ӌ{K�o�>R�&i�=��t^￟��y³�=�E�m/�+�,=�]d�pn{�͚$W�/sd��?W�ZX�?�ƴ����n��bl2lf�^&XS?{l�6���Ʌo5���6mo�#w��3�B�]����M�h��G�T���$ݻѺv�h����)���w�w�1 �x8�p {���Sf�<������ę0�:C'�+� ������X�z֍L�9z3u���:K����P��W���j�=~R��ߩ)�/H�qO�U�}�(�3{jN&wI2t�����#�C��� x�ة�� ->�I3��j�58�lj@����:�W���L�����Gxd�j��Q��6����ۚW4 ��Q�@�����R�vkx�C��p�!7�)��� $�yO�w:r[)g�F��f�+�ۺ�������`S�H�D�i^tH��K�� `���f)Iӿ$��e���ˤ9�v��fB�D�Ϡ���eWK��95/I�7�K� �߀��4U���P���2�+��L��y3C|gጩJ��$�x��[}��P�`-'L(x̜� '&Tm L�?��Q܁$S�I-PSt����Z�csv ;��i;�a�����u���L�}�ׄC�vK������� q�g��{��j�i�o5O�uܾ��aԼ�g�! �?�s���c���/��R����o*g��D���+�N��4K����2FV3zP#rjx$!�꾏��ſ�:t:U�(� r]��З|/uo�H���o�z�.\�*�ZwI�]*0H���Tu�vGU��`E�,_�W�=y���m(�� - -$f��� �Ֆ)�'Uf��=�}}�����;2���_��5���Ã.޺.�r� L(x� <�|�֣^_��O.�vMi����8@�g�t�߽[��v��+��g,�9�xW��$L_�I�� �8�6n�߭�����.2��˅�q���r۶޷����nj��V���T��o���)��ɘ^aJ���~m8��ǫ޷p��XM��i �uW�z�-�R���†���Ò���T�*��*}"RA�3P�Z��Dԋ�ӫX|�q8��R��� dr�p�#�� S��-�%*T��>.�q����xu�����W�3s�4E� ��^��+�I;z�D�p�a"�� aʱ�fl�SΔ��Uz�Y�o ����;q��yjyL/.;� ���rHDߌ}h2�-F�d8��<$e��m�vǴ���T�Џ�:�N}MSz�G�I��`M�l�p��K�'K�W�.#�)���V�켲d(�P' W���jT��6�7���~������oNDJK�J���nj�ZcՈ3����x�a��4S�z�m#�{�Yx�vb`|]՘�n�S�@c��`P�'�Ũ����|��_���5ڛ}���F��A��~�ɧ��o� �⩵�ogE�7������qbQC�~���>�Xh��h�^��5������l�O۲� 2��]���Z��͝y[�X<�X42���葼4૧|,�*]0�6�e�� ��!�̘f5����?�?�d'�c�̝�CÌ���mF �q<�YN�ɆnZMv�0���p���>���o� ����h}E�8��y�Df���h~����F��A�*}v��R��R.A�ˮ7\���#��H?<�͚"B#!+�����'f`!*�>b��L��ƕBK�0��GW ��B$"��&�)O���$���'�[豃c���� -�9�aoN/�����Z28I�P��\°�ŒJ>9.�hi�ڿsp����Oc�>3���a[N�G�j.=/QWy2˥g��k#j��~c~ -e���L�� �Q̆Oc�ǘ1���-\�&V��� ��G�_�V����扬�3�.6�`b����C?e�AV��A:�r�`�E�>3��ea�1��jB�ڔP���� �Vo����Ǯ��M�,�3K�e�,�|* b!��=~���\�Ж�wXH�}�lZ��/��O�P�~������Ǵ������a@ 0���g�̓@\<4�mF�������,ö�il�BS��Bw����Sm�7�+� -|[�"�ɷD+Q�7� -�����y�t�GP� ��LrY��g.F�0��0��XF�K_x%3� �`�\��� e�?�g>����]�2�BK��kݗ�X���dH�0�νb�r�*R�qO,>� -�WWQ��YHE�Y �iN����3e*-�>�sgI�&�K��tw?�<� J�˅ ��2���6N�M���58�B��A���V&/4���$�α�\��3 �_�!-�����i����'�C�LO��.�&P�?v� %w��|Qfey/X0��^y�K�\k�>�Ϗ�Y�3�9�ɩ����ǎ%A�5�{/��2��cNp���ąS�iǀ1_�l�f�pX�)��~�OIo*��䒞��D��YiA�%4�����L����RI��dk��&���6k�8M���\"�ߟ�����|1���C��N5� �ZW[c� -�0~O9�F 2�����R+S"-�b��°�,���Nf[�կ�T'Yӯ�O�e��q�򾃼�������!^�̛T۹j|��Ҍ��qk>�!IWH��:CW �]c<��d=���ӫ�32������O�߁@kF��ٯ�kƶ7�+* �,���< ���gv�C��C�nL/���s��ٱ��独��;rWGh�4 -4�Q�*�̳��i��Sa�3r~���xW��o�sZ��4��̚��C�� 0�x֍��ުm1 �FƲg���̼kͣ�$�q�gYfҞ��fJ��5��R�*� -�\vw�v�C� ;�Q��� L+�T��}lk����Z��˘g�Wa' h`}�4�Jy����˜��c �%��B��x~<>K��F�QN��V`�7��9���Qj�!��@�m7(RW r�����WS��K�T�&���f��áy���µ��+S.G����n����knʅ�-�y@�&��;N,������/S�6���i�o�-�K�rQ�ڒ?���Sc�K��1��$�Xl.��v�;!7�9�o0 ߂����8��N`t޻J���o,"9Sq� -�j�\�Qm��{/P.}|��>�׹ȚA������𒁶6��/$�v�Y� -2�a�� e=�sL�NzM�n�DM�P����B)�=`���Tڕ��<pK3�2��IJn�zNr3����'+����'� �ʝ�����>? yY�8��JnF?��Mz����D5�/����mD�b�����W"��7#xh�c����.���ě;�i����q���l�3��!� Ӳ�����d{��3ޢۯY�����RJ��󬼮/4��'�"�z#�>uݷ ����+ -,[�-h�Ɵ���C�@=m�Ug��p�m�`�pN��`��1�Y컨Im��}�1���V{#���� �q��MO�2�A,X��.��+2�>H�{�p��)�)<�is|rdk - ��nR??�D�8k3%�)�A�̾Qr�mi�({4�3�� X��kÊe�XR;�rgK6-c�BN�.�:.��h�3��/�@�vµ�����P�<����(��G��y��A��?���n�� {��X>�r��ɣ��`4(K <���~�U8.�� /���D��/;������nP�6|7��B!7>�����7���Da4��1h��9a_=[>��|e'�d?��V�uQ\g�uY�=�I� ��ނL/?��\��؇�ԉ�����+��L��:cY9���'�g?���%o@E�)�I�註i:By������Ԯ-�w����l�2%d�r{��]��a������ p_�O�����.3'�V:t$�fU}*�Ý�ɱ%���u�a -�1;=iF�z�� yl�Hnl�}���d �����^,sF���5��`��'�����p5�rw�� - \�z�k�yntؿ՜1����8H}�#S�j}\ʛ�.;��f0���~c�j p���^��"��@"2����t` ���v,I$�ܵq �)ID��T`W�2��� ���&�<#��1�o晢Y���k:�1�z7�J�f�.�蓠�Sj��\��i��j�2�ξ��w����O��s�;��ȩ��ᕄq���`�k��|Ox#0�I�\ �̽���e����a�'sg3B�m��'+����z -�a_2���Qgl`���uSU��t�<��'T���������O��O�d<6I���R���� Ѿ#G|x{Y���|��\,���v�u�7.� b�:ڰWA�k��g>ļZo�1��]3s�浵�h�W��F�\�_T�����u;O�!F�ͧI��w��TX�34�'� ���tg��%���a�h�h����9����7@$'Y��~��L�f��vT�C��ѵ��J��$�d�54��":%GA׻8�Z֢=�OU1��v�ʩ�R}-��w�q�^���tk�D59�' ��sG��LI�k����M��`��NG�h^S� |��+�o{�=5�ǒ!��Z�x�Fc�� �Zh�tz��la���* � �V��!Kt`7 z�9�D�0���m�d�D��S�2�E��7xA�h�q�ۮDaX�C�ӊ�+��8ѲgT���Q���(IT�oW��o��~f�: -��ص���%�Ũ+Q0.|�=>�K��}�[J�h��OɴQ0.��H��?�N%%A�ˏ�o�8�Zw��k6��J/��h�>�$J�o���*���dĈ��`�v]��c�(�W��ٵ,щ�,�4֐%�a�b�c��҄ɓ'�B�6:���k=f:��� @4F -W�01��D�!���~����Dݩ��x%��C�B���<$k�U�5�y�I���f �7S��#�[gVV'��]8]�B� �>!Q�-Q�^ -�h Eܩ��%n:�D�N�}�!b�5M��֭T� ��Y�� �F���}�"��n"��d@<��]�o�{�`��}��SCu%�X�h=�<�˲@�|��J�㦟�*��7��z�J=M��Y�/����4ZE�A���h}ZlI=����(��ӆ�{Z���s�ITMRO}h98ğF�khߟMK��@?�����t��a�x�@�?sۙc��C��H=M�c�8$�tL�����T�1�� -}��oO���a��z�v����$Ǧ���v�$ޞ�]���A��o��D���n��ͥ�1�˘ -��I�QHę�|�g��zK?�O��/kԽh^� -8fZ��m��v�ni]����4�XSUc�W<��s\���s�Xς$�\���hL�:o��\f>�Q� ͥS�i:�r��������7�5����cp�-��t��O��wF�C��hw(�y��j���� f�& ��p���I��k���N��Q֐(m��d}K�>��D��wi����)I�uRM޺��+0��*&E�[�h��� ���%������}_V"g�� �������%O�.o݇d9 ��&Ax��w�'�| -���<oݒDi�A�(�fRDq�K�+X���D�="�^h���DA_xd�7�� �X��'V�� -�"4�u�KAz�*�0z� ����:{|���;O���f�k�h��Q.g�^g|�e�l�?�\W������uQ�8q�x�@��A[���F� ��O������s h��%�ړ��c��E���w�A����9�� F�?3���s�{�]�&dzz���J0�al~A��H`���K� 48�\�=�� -�Mb9&֨ �Q�&ٲ6��O�X`�f� P��t��?�'�_��J�!��%9���+�zo��'˹�?Ԅ��M�� K0K��E�4=:֧�Y2�l�|W)wV���C�u�+�LK:wi�s^��W!=k��sU �*If�y��Aܨ��m�#p����9G%F# ��砸4��z�Z*�y7���=�n�����2���~�)�-5+�G�ko�r��qV1�9�V�Q��0����^����V�+�*v�y�+q5:ʢ8��?�! d��Fx��<_6lu�0e�W��2�+ƌ�Wi?v���p$��oD�M(��DP��1�o��f�M���Nv�AO<�e�r|2[�j�h%�m9����S�,6�*=w@���^�=E����'���m�]V�uˡ��S�����&6$���{��u��׵JU��;��K-L -l����Z��,�ޫ���]��Xx�z���r�Y��IA�ܩ�PIj���G�I�i%�t:�^��u���V~�8:9Ϥ{`�Pw��hI~��S�.�+@�_Pn�eO"��/�ߪ{�c���eh���嵵�q�c� p\ݩ��K���YĜAq�V|�.�ݙ���آ���Z�YY�{(�Ge��H. -]�\��o�)��n_�b[ -�3v(#H�E�D拒a+�Z� a$��)w�������xrg�����j��sE�,t"lQ�: [r����]�x�,� w�3�Z#g+|J��L�p�C.�U���/؟�7�b�WTi� -"����E[Uz�x+@����T8i\�t����Q3�䱨��<�fO��c}I�lbXX�����'6.@�xb��������|�l���� o�G|*Xn`�d6}���$��@��A�h4�*&�h�u��.�z\��EH޻��h�����,x!��M��ir�G<��γ�֞��>܈�D����E��.h��h��N�\ �G�{�,D��}?wi|�X�0M�%����x�ߚd�x�.ޫ8���P~-�ƥ�?�r�°�&X !���A/B") �y�����%O]�0�l�.�d�x�d�S��7�,<���d����}m��ܖ͵�����T�%="0�=垪�b~�-y�ȕ�t:A��%z|w��`ar��v��t�9q?�,�0 �4��5q�l)�~tr��We�Bd�K�Y�<�{�=&IJh ��V���7�Jb� 2��>��oY�����Q_�&ԝ�Ŭ�{�X³nrcQqfǀ9����oz�8������.1�u⍬X�J��:� -:~���k謲tP'���N�([A'U-�h �|ݵZ�:I�t��{��N���[-�L �|�L��C5t�tl���5t�pt�� j�n&.����Y<[C� S�T�AI�[C�OH�){�Ju�́��:<�$�H/hԽ��*��^�b��&�2�3÷~E� -#� �C���{|�~����$���׊��5��r��)������ �������r�rNq�$E��ק��c�2Q�&�W>ދ\�4I�dNP�u�hN��e�C���;TuQ�[2��1�d���acɮ�Sh��W��}i�m��b7~&�ÁA(ɰ��و1,v����{�n�6�� �����IẦw���W���V������ A�J,T�;�ҳ�uwv/ - -����w��;�ݝ�g�Y -����-wQ�)���(t��C�r�����G����t�`+�$١8� � hJ��^%�!g��y�;��Wr��� )��R���ߕd�t>�u�hK�^��P�HWDI���c?E�3��% { ���4��2;A���t��O���"p�2N�I���hDF�I|i�����IyDF.;]IQ�L��gwĎ��/]��̶DGd��R���&��� ��'�6����\��m�� ��$�ݡ� �wA�`��tD�� Ӄ�+R���=��������9��so� w_�eI8o��}�j,K�+1��J��ek��e2����n����NY2s��"+*C3�2I�����N{4I��ݦ.�U ޭ�{8i�&C��I��F,� �eZ}�^ ކ�wK'釟2���c�|_��(F��M>G���n&�0���Qj8Q.�yug�QGp/O���&��Z����%Wa�ɍ�D�k�%������� -��\��& -/վ�b�Ug�(��� ʔ�5-QA��jZ@�w_�o�K�� ���$Q47,�U�����D�(&C4��D���-JZI�ȗ�ߕb�M��E��^R'��n��\�w�)��kq18:�����c���i��s�N��C� ��(/�����g/�.���+!T�0�J�Qw�h���A{�/�IN�I�g�܏.��$�H�aԼ��^���{��L^��t�9�����v������{�߽g��?K����)�/���R�'n�S�t���_U��&�x5���D�^YM��|�'���b����j:�Z�_V>�TӉ��E3ퟪ���2g��{��N �� !����݊�h5��> �V���N,����_TM'VK�(��j:�X;{�����Nlt�_ZM'�+��j:�Z:��柩�6��d�TӉ�b����������g�����u�U�t�嫫�s�j:>*�+9�y�c�WӉm�H�\��j:1LF�+���j�<�WVӉ��-�WT���^_SM'�3�j�{��k��Ęq��_VM'� �������j���{� SW�{��k -��.��͡��I��8Gs$��'�6Iֺ��}u�օ2>ݽ�V�'�:�!����+A��hճt��M�[��ϴ��]��1��&��4��,�$�D -) #�'��*�r_ҹQ]h��{D%�Gt�f}'2w�-K��;�5k���s���俻�N�v9�Ew�����4�J.E�t/8=�$� ��5w -n��-SRv�ݽ�"}��/+��;�)�B�p[�;����Ew�6�������s��/��ؗ_�����Z���\��|JUև�����;� F�s/���NQ�J��P&�Cy}���4eId�;E����-ӭ� +��:���$�{`h���]�l(�P���l(��U6]����~?2�$ҍ`�� �B�����W�G�ݮbX��]ϳ3��r[�.R)|���A������d kp�Ma�~g/I�9"Sd ���2��[)3��%���N��w�{�͡��n���D2��>3 /��6���x�)�)��z��MyOOC���uI -�C~uÝ�}���;aa��D �C-^����.��� k]%��*e��.�S��_+q�÷�U>J���9W4��D��gD�_RX�$:I�b��H� ',bbeL���&�����.�]�2���X,Օ_��ĵ�3���1�/�y+b��b�ʘx��� �]Q��"]�d�-� �q��Y ���J�p�����+��CEL�x��D���1Ð�H}���a8�n � EW ���5�>;I lz��H�8���JI{lB��h*ȱ�ոN(ɣ��a���dpR�n���$����ݯz�_� -�tG"��pr5�W�(��D�cP�H�}Ա%z����u`��'�X��,�K��}O%�� �A�<��4��\z�;?y�i@�h���Ew�d=y�vH�Y��E�����I߮�|�]s��$ -�~n^kK�կ5�J�љ�=l�6/v�����K�����n{驠�,]���DŽw�q��B�����S]k�I�q� {�_Gy���m�=I�F�X����E�\^�(I���qD�(��;��.@�@bœ��my�/7:mw�p��J��0ʾ��b�-Y�c�IX"��爌iK/����܉�VH��Na�$g7�{����IEj5�9A��N�D�V<��*�&��2�z(�J��)G��f���%(���H�gF>�Jr�7�dL��)���'J�34�\R)�'����;׌���IWZuw��ş/ʙ�P6�E�#�(�8�cMr�ܚ���67���D�e��o�L�>t���?��-X��-����C��ٗ�� ��)5��c�W k%��8ך��]@��; -���y?{%�H��=�%+���(>�Oq���L܇{r�L�`�W���F�i� -��1MU��+��q��:>��q~%!;>�Q�up/�VF`Tf��z�y1��z��Lx��ײE�9�e���s�M�/��9 -�q^���Nf�����|}/U���R�)�w���켯���B��@*�+M9�����T��!��<±��<�;)ع|�cw��y�c2i7�¢�!ñ�V*�Tx�d��2�S:;]I��@a<��*@.�\ ��_T*�����*@���z��<�G����qy� -Pi �J;#�G��ȿ�o�m g��+ޫW\ʧ�F��.���?y)�0�🹔O�F�W]�kF�(/��O��� -_)�0 -����O�~�^ʧ��ʧ.��F`�p���`Xt�4X�V��φb��{��Kٽ~JΆz�^�k�^p6��~�)��^?����l(�{��B���/��!���~s��}I~Ž~�!��ճ���Vr��Ŕ�:�����t��������������+n��O�C������v�������R��RgA��^?���X���{����6��{����@%w����+���{�n�9x��=u�I��s��{��o�{�}|6y,*�x�_� ��"�H���~��&�V?�� O��'��̮��S: -��^�]����O�V��$��y��|���"��{�.�[�}�s��{��e������������+���S<���=_� }��c�����o�g�>~����-t�� ���X��[���J�SM3��I�W~t�^?��f�]�O������˿����$ ����>L�{�����)���p{��CW��T�>y�7-��V?:��{�ΥR��=\��+3GT�~q��t��W��M�{���{Uv��������O�V����e._[����D1<�V� �'����y���'u�٣��ɧw�W����'��ď���^?�Z���.{.���'�^�_�-��s��r��{�.�K�V��f -����� O��'o��� ��S�"?���7�� �����'�|��?{��2�={��u$�6p��k��O~q���|aA,���7;��o�6�.�d|Ε�w�N:��_+�a��,�x/([�IӞ���� �F�:�y!/뎻B�e�(L�K��<�b_�iX/o��c��t�m�1���@���`�40�Y  !�Qa�8b?Ik$~Y�����Y� 4��q��{͕�I4G�*h����bm 4Le;_�� �D�UO�W=�K-�h=n��(vB��� ��u��p��������y�D-�JK�d�{k���0eo�\��Gu� �� hmy�C�q��K) -��V{j��\��#NI0+��8���� �R�[%� -+߭Q�1��R���OKh�^�Rڑ+W�^���Ƃ�Oԙ#�&��f{�1}����M0�����4��o�t���p�p4 �hͫ�#v��kn3�.t"6s"+!֦��nk��bOzf���^@,-�)v�2�0lah� -���eI�"��t�^�� -B��A��j|j�]��">��[�:���G�Q��p�_�R4��~p x�`f��V���~ ��4������eQ-p�Nׇ���䖨P�r�?Z�b���=�Ǭ��.S�Ϗ���#w��v~Pt^�k�=�j{��v��Q-|j[�9�\��}�� ��a�8��!�������;�j���~��;��,aI�*�laSDDpaS�P��y�~�|����N��C��9��y�v��U����et��g.d&un�u�Ƶz�Z�}�TC��5�6x�jT����|����ى�����/�֔�Y�������r����o�}~a���k�Q��6v�k����^���CuE�:t��ڛf���biwq�#z����y{w7ˡu�P����N_�՗����_���W�я����P����r�nT.>K�����������澭٭�/��/[�s��=Ǖ����{�_��ξ��5�h8�U�l�߯��MN(�O�����(����Ծ��4��lKs�����N���K9����^����g��o3p�n����ӡw�_FW�e듸V�{�uW��է��̻�����G�gRU�i�bq$�d�T�Tt.��Ý�f��jd=l1<ŵ�接��i��7�3<����?~=k�>x�1��'-\�^���$���ɓ�q�K�Т�����a�6��T������uR ��ma�a���4Z�蜋�z‘�¨Ή�O� Ǯx��st����IOқ��k�7��86����ݾ�wK/�\���j�)CaM���5.Q\�~�]q���p./z φ��Oo�O~9U�Ł��o�E)+��������۫譥��v$̓����wa��$��q3�?�9����li�{��?���J�r�;ϭ��@dU볱��Y���edl�v]u�����S�[���mj�{<3}����Zbn���LBoܪ�.�@���(?��o�϶��:�ڼ������驞byfmjci��oqb�����xa��dן��ٷ�C���KS�/צ�� �9��|�~���ܯw�'�>'z��~�s�}xCV �Z�����f� -�_fez��������������{O��'��k�>t8��8����3�����q�����$���4}�w��Y\����1����zf~�Gi�p�w������l�Wi*m.�W~��_B�|� -��³����/Z*+ssc��o+}���6��q�ሽ٭w����D�����-�D���T�(��3��O�}�vt���\.Om��������^+WzzF�f�N>�&�>�_�:��&���g��݉�w��v�z�Ib�i|�-Y�#]�#�U�N|��e�X<�L�Z��4[؞|��g{e������N�����J�az_��g�[�3����vS��}���??�;����7x��u�ɩ_p��d�do��O�'��ə���^�vsy����0�Z������щ�o�>�9���j���fK���ޣ��\����of�<x6��e����������k����]Ԇ뜻ya?SԣV� -{��/��`KK\?��MW�&�No~�AaK�4aΝ�c[���I}�� }!��Q۹��rt4>򺩵޳��9G������4X��-kQ�r۰^GP�ZJ7��o^�7��K���X�j����F�[�K:��vY-v�d�A���v�4�s�aޝ���î�J9�M.5޷O4~��6wm}y~l ��������O����_5��]mi�:��*�zF�ʳoB�ݳ��.*�GG]�]��m�����н�B� mMJ�����Z?�Ӿ�'�?fM�k[?��3�1��5�h�շ��(Y�[*__��sE����?�U�k0��Q�B������N�|�8n)�n��g���������3�>�(��Y�4wn��[+h��*�����a�E���%�򃡣�I�<�%U�BE������h4� ���Y �ȋ�FO�nބg�#����Y������빺 �6|�z&�ި~i���=r����&޽[�J�����W�/�Z/�X���Kk��)u'u��"�rV�������I�(��k�\�3pÏ/��x���V�ge���u����n�W��&T2��f��ͳx�sG�`�~�U�#��IFܠx5x�WDZ����Ӎ��oR�,\Y��"7P���c��W�����'�i��B:ds���㭙�d�M2t�����z����t~�|��s����JX��&�$�i^�d�ޫ�2�ۅ�/{m4�}]��.�/||٣���^����#������O�|����{���e���=]������}^�S��^��a�(�8KZW����X��z�L���u��g���p�8��+Q�@pvy\ǵ�l������P��\M�����Ss���6����|���s�}W�>�ϩ3�oqF�_fm�r������:� �֩�ڭ����1՛;Div}����Kǁ�m�����dzՙB+F>K#���������� -}-�����x�"�����n?���J5[t��7�Ѳ����R�"ǣZ� ś�v��X�ܩ��x���������r�"^�#�}C��k������RYx����0���-���=�)����w&�q4��V���ﶬ|��t�\�mN�M��Oa3��o�k��>K���� ^h=I���\��6o�#T�D���!|��,��o��l���T����tt�FC%[�_�+�Ժ�q��ל%As���Y>���Z"���#�'iƞ�xQK��O=�V�����|�.8 n���.�JW��^{�B\w�����!��;�=ą��vؙ��l��KD�HV��whj=w����۝�tO��b��X�����s��_�1��v�L�us�x��Z�=D���{����겝F�RW��C�xr�y�S�n-\x�:sD��}��u�!���\R���������.e����4�?��P�*�oc��^�F�w�xm:�zҙ\��e��4}��OU1����Q�a���;ҿ �������}*��->�<��sVs��G�,�]y�͞�3H��<���o��d���ߑk2H\�\u�����[����b��Hc�1?�X��;��عo1�R��;=�-�0�ҳ:�;��fkgV#7��� -�z�C�$�ܷ�[�U�*}n��rĥ5�UW�s�X�u��T؊����σӽ�D�x��@�=n"�����:�����2^�Z��ۙ[����ɣ��d`�:�0L�:=�+�1��Rz|�Y*}z;5Px���o�h�83���o���h�O��Ԙ3����޳&����NgeE� ��ם��;�ِ�U5�q�]=�>7��|<�������>����o���EM�������#��O'���V'�Nt��=�>^���Qu���h2;(jg|�p�����1Yg&�Ox#� -B�{ё���ao�����?E_j�o<6|������dUT���S~ÿ)f3�����S��#!y_�.��(m�KI�E�z�z��܁]5��pzFq6=.��W������o|�a2��X�'1�K�D��ߑ$��lj?я#�ڶt�a��ɏ����9dm�]gH�o��m�'[� j����̿5ݺ[{�+��j�K퍞��ƾ�5{#�q�g���L���N�w��T�͞ϵ7��o��K�Ro5c/���C;y�:�#��ڱ_�?��Y(⯻k�n-Qyi+��N:�B��y]���(}���t@�>��PJWj-�n�j��������Z¼��?����u��I����⻖|��� �f�S� Y�������[��񊧞�W��<�}���ɷ�kɴ���ɴێ��?j��W+f��B��ؕ�[�����$9�+l�ב��y�.�l��7�ɫ��}��s�xzg��^(/� -�3��uj����Z���ȸ�d��}������t-���̷���c��>#�!Zv�TEn���j �J�CF�:I �@O[�U�=���tvi.x�850�ܨ���H���������|ɊB��4��`m�le���K���{=[�o^����lI.^]�o^��O�aI�y9n�u������%���8Y%�/�J��/��S_��re~,t7�%�T��~!� ˕ϯ�W�V��6��뙎��t����Z�t�!��ԯ�o��˟�.V^,4�Ɋk\@�%�j >έ�_�}����̎�Ԛ����O��_?O����6��=��M6���d��ˮ��&���3��(�d-�xu���r��C�f��X/�%,��5x���CJ#�tF|���qtS��;,"R*��ڲ@�t�P�`�H�U�;T4(���*��@ݽ�o��0��^���XWGv�[�_��z�*�Ɓ"�+o* ��� 7�ܢX�u� !P��Z?��Q�����j�b�t�h`���� �uG/]ohwa=�����m� �;uwP��g���;s��wQ���>R��D!������h����\ی�׍~�X]bf�U1ܩ���V��R%�o�� �Bi�]^�6�u�-��Ş�O�V��q�bh敢�| ?�'��r�A�6T�����4�8]�ٍW=�.YsjљEk�.j���7Vzo�NS�G\-�_��pq�Ϳ�3�,������ߨ��9Q=����=��4ro���}t�<;�U��L_���h��7�����0î������z��5z�8��A~f�m~��ar����X͓���:�� �~���qڃ�g�+]�,~u�^�;�-r. �u��ES�-��������@J� BE��"�G�;T4(�����x�M����@�����E�E˱]* g�Y\*��j<(��Eh)ůs���|7m�,P��/�Ԧ��OE�M��x��{jSd�"��|_m�S���[�";^�=�)�@Q[���Y����{kSd�"���M���L�H�\*�z�m��*(�&.}0���MԞ��병�\�D(���]������f{ro�֎ ��;�@_zK��"��j{-*-��ܷ�ZTZ��Axx]L}�{6t,��n�l����� -�F�uW���疡��Pu���,���{,<�xݛvV�/� �:=��׫�a�r���o��!��?�����X�c|��{���}Y�G.w�ώ��_/���Y���9����+�έs����cџ��u1�u�ݘ��Óĵ޴�d`- -B��¨ƵRm׌��xu�?�U_����F�f��������_�n�ۘ��;fgg^��Lj���?�7�>�� -��_(��d#�Kݭ�����\��U����*v}����������K_�WQ�x2��`C^]k�#T4(�R����@Z7 ��Sn* ѧ�$T4(��{M�h0P�V8�'T4(���u��E��7T4(�SW�h0P$��_*���3���@���S�" ����q�" ɓ��iSd��6R��Y�ȭ�p�m��xQk���W��~Wm�,P$;��O�" �g7nS�;��(��wצi��������P�Ѧ�E�T�^�Y�H�0�Ӧ�E���>�Y��r_m��;�O�"?����Y�H�˽�) -W�!��6E(j{��C�" ��K}���4��K�"{#�a�M�����hSd�"Y��~�wܫpm�H[��Ҧ�E���Y��Zwߦ�p I�m���%�E,�<��:���ٽk�n`U�ɟ��Í,�T��ǝ��dhU2�=LX���評wK�OI!+�@uN��~#LA���j��1�P�yf�G��h�����|:�*t��{C�|��Yi��2z�ד�oʕ�b�orfi1ݖn�p-B?�͇����g�����7O�.8s���y�Iƈ�h�c��hTt�����6e+�-~+�+��������"#ϖ���4k��]W����/m������~_ϖ�f���o�>~��m&M�3& ��%V�Tq�Pg�/ǖ���64'�.���}���V�–���UW��“,��vQ|X�;Y��wb� ÿ��/F~N���D�7�ZzV�t�)w�� ���V�� kc��d��l#��~ ,΄ -c<�6s�3�C!uN�D8�r��/S�YG�x!�J��T���ɣ/�_����Uv����ް/V����E�e'+/�oQ��r.�o��� O��,P^� ����1��#��d�Q�I6�=l�MS6Ɇ[����v:���*w��+o��=��M�A�h�q&jb�:��5�J]ߛ'˕��ɨ�? �/���l�ߏ�ύ=m�g���I6��x��$���5ͭOW��G���~*:τ�0<�#�G���m�c�=&w�����S���������E�9��9Y�D��0��rqowu���du&,Pv��y���Mp�>��kMT��U��˃��aq�<3����ed�E�Ie2�(ċt��3ϣ����B�';J]�}��T>.��ީ�v7�'��S����$����*�p¬��FD�x��+=V$i;uiw{�Ӎ"U�\����P����UU7�L Y��[}�f�_>����Ǘf�;�Dinz=J���a��Չ��ѡrg���|Oq.��8���{�Wd�����v���+��\���e�\��9�$twk�tz�'�vr��ړJ�ca4�1�zǮO�B��7J萓J�S ��S��f�jvN��B|�e繅�b��~\-���,F����}h������K������è�g��T�E�8:��x]Ѕ�~X=(��3�K�߿�? -�����/sWG�p�O�&��[t�����oU��cC�V�T�s��]Ϊ�$&�I���}ӊ0����a��Dy�yt ��-���?��G�l}�o��ߑ��޵W�T��8�::n]ܮ"���w-g ��pyR�G��gT�l���ɹ��Z� ��+����li��m1��E�B������� -׋Js�x2%t��E�����ޥ��ץw�0�/��]�N�P�͆��@��;��ÖFj�/�N7���@�"�n_���� Q��x.*/-#������cm��̀��Q�>z;�����z���`��t�m�t����fy:�e��u�n�^�NG��!L)�����%cMһ�\oNH��חm��������/\HV�L��������'ڦ�*qj)�d_���d�خ5@>���� -���7�v��dT":O��>T����ǡ������5j'OG5i���U���Kѫ�����{.ʮ���/V��n�����\�z� ��� �����s9�!x{���p~���������~�y���g ����p���jk&DEc#��?��2Nw5vVW�I �|mٚ�!/�ڰ�i�Y<���+�o�Md�� �~���s���D �ܜ�; ��H���Nb��IK�D�u��٤��f"����g"���w1iipuldzr���틦AG9��k��1��������?��N������e�� Ƿ�ݵʮ;?e�\�6�q-P4�����<����m&Ie3���G���Co\�G�{�3y���W��}�/����&��{6�*jW����������qC�t�Lٝ����6${6����(+�-���Z�o���[����q����v�����hh�� ��F��ˇtp�b�;��J�-��z+�G��Dը��J���I��%�:s�{�9�h�-ٕ��h�h�t�G��ih���e .f����� 5~���9��v>�T�X��}k����o�? ��x=���Áެ��.�4���b�Q�����o -���� �zN�{�YI��I������s?ʯ����<�2J�]qf���U�{�� j]<�<�/Y�o��B\�EYn��o�^��~�[�]t]�����QrCSl�yn ��\��u�5�?]����U .��6KL�먧)v��W-�ݙK�G}��$�HK��c����8 ��n�^��v o�[��I���Kf<�,cܙ���8 �Kry���)4��x5 &�]��v2Y�������r���Y���j���":��?W ����Cl���D\u����ۜ��Q��T����4�NjѲ���~y�:s�t����\�!.�N�,����MK��Ɵ���9��C|�����[gCT5��˻G��ޟ9���������-�C�O�9u��'e��\�?�� �Z�����S⪲�|��z������/7>�N�����������O�+��n�� r�����:{g3H-�՛M7�_�A�}�3)�a��i��()C��4��:�<�����J��G�������C4����&���[E�P#��^��|��D��wHꗫRbw��v9b��V#7�v�:���s̸���a�?��Cg�o=��;�G~U��h>{�n�/���ǻ�o�.�qY����7��r�9E��7�]��[������Q���>�����`�-�>��w�6<��O��olj����n��M#n��I��a��z樂1-�u��r����sU�NW�7NE��7U�l ���W�/���ah�%�����ɣ�3#. -�`��[�?y�p�*��g���Ϲo�鰊�_�e�ڭE�8��<�6Y=v��v��k�翟5=mj � �f��?6��F�y�w��o������o?w��w�Tz����/��偁CO��+>�\�mj�����`���Iǃ���z'�N*?>���s�s��#�W�/W*F��������*nE���{nE_r���`2�����>?��~����G/F?�׃R���>| ��|����Fr籲s|�H{G��sM����������ԃ����Cw���J��>=(�� F�,���{��/���Wٟ�^?� �5����џF������ȃ��r_��`_��������c��_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU�=8�mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU��@���TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ ��FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa��_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU�=8�mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU��@���TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ ��FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU endstream endobj 154 0 obj <>stream -UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa��_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU�=8�mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU��@���TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ ��FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUaw~V��8?���� -��BW ��2��\�A�l�q"}��E�ڵ�>8��9�^��z�˦��.f�u���g˔⮫��.�-�y���UWo�����*no��0 ���zY�����i����h��� ���i�l��l�f�E����0�j��)�i�+��jޔ��;?v���O���.o�:n긫�u�N�?��o�u���rqx�~�oF�(�����9��DdUI�A�TteV\O\2��������t-Y��{�Ϣ?��Ꮥr�פ���^����z��-[����X�ny�� ���g����>J�׶��g/�:m\;6���LOϗ��dmƯ3����=�z�9�{�X=��e��\k����i���j�g�;J��>_�R֪��{�����ܨ�~����{��1�� ��w���m����|����Y���b���x�@[�����#m�� �'�%zx���N�j#�ͤ�>G|ﵝ�/�l�<_YO��j�0�=�ʧG�����c����+��3�/O�X/.���+��U?5<�S��>3_�PNo���9����h@>�J9��{��iƒ��>�'ɥzxނ����V�����M����� ��d�Dǭ��/e�O�c O��|��z������o����RM -�m7(��4*\��N+; T����K�V� -�����4U��.�e��a��C�@�l�I�R[^��\pb����ob�1���tk���]��\ ��m��Z˽™`�����_3~5�S- +�=��&b���f�6���Z����? �]S�B_����L?C�� o���� _6��"�,x���t,��ֽ,��B���)ߡ�v�f'd�4���z��²���W>P�J�㉅ (!�����Rn���6&욵 �� ��_��E�� *�5d[K�ޘ"^����g��p���D�p?3��p����ZԏkY�*�EI�b�y��gR/�w�S��� S m Q -��<(�N�pa��SV|:i {U~�7�b��ޱ�&��`D�O�c�� P���E �W^5�� -ʐ��wj���G0>�/Ԛ�U���sa)�pᣕ V����S�!�����1Fj�O��rA��S�{�I��������J�%8�M�$_� �c�mW'"j0u �����`�Z��;�u���! ���(�Q��Q�$��rrt�R�@X���Ph��X�$�Ll�m)�Z�۲�7'<"�j�-�nw��Bv&�Z3=���[�l ��|"6V��1������P�#�氨ȶ S�}��a@�U�My�`�2�p'ׁ�!��4����B�{��Y��]��_���<) ��K�C�)l��V`��,�\;T-8A��9���u�)��J8h�`��P��HIk��=� Uo����@�ßmp~s��s:���+} -�+��5D������u`���Jv&�(��k���>!��S K����2!S" �2e����� �5a���ñD@{U�:gs�Z�P}�i�'q��5^f`6����A>s�F�r�zQ�$$m3[R֌y�)k��p��� �x 5b�"�w��fg(e�,@�5!F�Z5�Y�o!��� �ڽ�ԇ+,h[��@�ړ׶'q"sJ�����D�����P�7h{�4C�$x���FX�2!��&�T� -e��\�MJQ�>a�f.no�CV�f�� pq�a��?:� � �}���>s�*bTu�I������;MB��� 9Ø��Ow� &>��E�p>�PZ�m�� �|���Ty��vf9�v����_Qo#��� ,���bm���$�K��I�"�D[`��� h���y����6�d!�z�CBj��]� �. -�,�����à/��# [wR�i,�=rVu����J����.6�=<{�+xy �(�Q�.gi�5h�� ���e�#�4�bI����� v���k՛(��6cx�*u�*�a�j��(H\�-�RUuDWC��u���끄~z��IDi�0�nT�����3�ѪP�r��9<(�� -瞠�46x�yR8������tl���� ]����D����m7�0� �K��6�x�@ݗ�kKi��d�~�� hP84��2Kb� �B�4Ĵ�*�����U��VF�H pst��9��S�`Ar��1���d���]vA4���kTG�� 4(�;W��� ;�i�`������_0��K���R�f��<!�Q4=�.�MC]$H{�L$p@Z�G�,$/�ЕyPQ��Ĉ[5/Gԭ�q3@�LS�j����.�Æ^��q$��a�I`u��(��cg�*� ��D�FX������c�f��|���6�<�+��Fm�x���FdӫiE��؊t�5b���4hu�g��ˍ3F]A�4~C�B0kW��n�U��+8���[�J�X� O ¹����.q�ƾ��h�7X Yl��yo�d,nצ=n�:䊥>�zJ1�o���Z�U=65�`#&v9g�k���Cf ���|�+��DS�Z�������E�'>�� ���,���΋&�\���1�RW�q��F �%r��M �s��S)'�,�o�W����uD�US -�f�K��Uv�T~��������#'����[�1��Yc&s3�3c�Uo�h��C����T�<�H�X=<Ӌ�מ��m��C���pTs���R]GvR�r�� ^3T�E��p%v�״�� �m�t�����R����n�<;9�'$�h�j�`�$1Go#��ZC�ܭ�9��F�)H$�ҏ ��E��Uu��1���/��2{b�8��K�-y����sN���n�j��=$ey�w.��qR&�Q��Y-\1� �*�?���p��)�r�f��7w�ޠs����C�'%�έl����L���3_W� ���2x��N� �r��<{� �>Q���RS�|�v���oO�� ��&���z�#�R��}L%VP�6y`�� �7;뭮G�D������ =��� `���k����ɿq���W4��/ѩB�?�g90 ��k��%���HM��X�F *�M"�C Ep�����׻�Ge�䑺����ohv� -�wd��䗥r� �Z8I%��5ݺ�C�����~�zǡGt������%:��69���l;��ʼn�k�� I�s7:kh��k�)x������|pJ�E�p[,`^� ���%Í� @&����7R|�z�QhV��5 -�j���ŀ�+u�[%���Vf���索28HV4�Ůi�����m��r�;#�rތ������5�-G�,��k{0�V���%�5��y��N�{��p��;^������M�ꚃ���H���S�r>�yu�ʊFy�j�a�S���W�x{SQߒ� -ˠ��&u�P9�AR�՞8�X"���fǓ'q�5l$fw��L���O�MKٖ��Ic�tk,�Rm����Z���h����M���!���1���zS+|��P�H4�B}G��4���u�m��ɠެ�Z,޲A��O��G�uv�q�IE΂�)�JS=���:��{B?#C��ن�+��yӅ]S�8G�6����V�u��w8�N�����Grt;l*된eCAƿr��l��)s���\����l�Y�{l��`'��|)�7 -_;hR���>���_���� -HJ��ρG+>��3R8��J<�-Vٻ��ӆn"���u8�G[�"m��~[t�SX -bGp�9^X<:��u�������p��N{ p��a3ʭ���y)t���k������7�]��lW�nl)9:��Ԙ�݊jz�������/m��(��F��eX ?>���7!�a���}._�Ȗ/L`FQ�'���KpcG��Tݵ.���l����z����R��}$'�Q��fƋN���⨳��Q���5�ٵ�÷��.@9h��ťVo*������Ww� O��Xd� eEc��x�%m4"���\����X0l��@��ׯ�͓�h���ppL�*xđ|�q��8Dž4�`�;��q�3��k�~k �KT;�`�G�z�U)kZ_�Yb��Yf����hFx�HK5�Q���|�M��tqT -�QNw��Ivy{AT�U!ir -_�Tw3ou��e��%PY�ЛV������� ��ߊ/(\��Q�η����"N/�I׫پP -*�$�ҿ:g̓Od�R)�p���2vSN�,�)l��|���������*oQo�θ���B��������qv�-Mݿ^/m�7�/��~�v_�_�o����}������&+�JN�?_n��� �n/`�8��.��o�x�>^۵p��_�D���ȫ� �u���O�|�Ӡ�w��v�|�ݿ����||���?^~����������_~{�����/ן>����_>����߿�)X�_����_/��o] endstream endobj 142 0 obj [/ICCBased 147 0 R] endobj 5 0 obj <> endobj 31 0 obj <> endobj 56 0 obj <> endobj 81 0 obj <> endobj 106 0 obj <> endobj 123 0 obj [/View/Design] endobj 124 0 obj <>>> endobj 98 0 obj [/View/Design] endobj 99 0 obj <>>> endobj 73 0 obj [/View/Design] endobj 74 0 obj <>>> endobj 48 0 obj [/View/Design] endobj 49 0 obj <>>> endobj 23 0 obj [/View/Design] endobj 24 0 obj <>>> endobj 132 0 obj [131 0 R] endobj 155 0 obj <> endobj xref 0 156 0000000004 65535 f -0000000016 00000 n -0000000220 00000 n -0000013326 00000 n -0000000006 00000 f -0000150011 00000 n -0000000008 00000 f -0000013377 00000 n -0000000009 00000 f -0000000010 00000 f -0000000011 00000 f -0000000012 00000 f -0000000013 00000 f -0000000014 00000 f -0000000015 00000 f -0000000016 00000 f -0000000017 00000 f -0000000018 00000 f -0000000019 00000 f -0000000020 00000 f -0000000021 00000 f -0000000022 00000 f -0000000025 00000 f -0000150850 00000 n -0000150881 00000 n -0000000026 00000 f -0000000027 00000 f -0000000028 00000 f -0000000029 00000 f -0000000030 00000 f -0000000032 00000 f -0000150086 00000 n -0000000033 00000 f -0000000034 00000 f -0000000035 00000 f -0000000036 00000 f -0000000037 00000 f -0000000038 00000 f -0000000039 00000 f -0000000040 00000 f -0000000041 00000 f -0000000042 00000 f -0000000043 00000 f -0000000044 00000 f -0000000045 00000 f -0000000046 00000 f -0000000047 00000 f -0000000050 00000 f -0000150734 00000 n -0000150765 00000 n -0000000051 00000 f -0000000052 00000 f -0000000053 00000 f -0000000054 00000 f -0000000055 00000 f -0000000057 00000 f -0000150162 00000 n -0000000058 00000 f -0000000059 00000 f -0000000060 00000 f -0000000061 00000 f -0000000062 00000 f -0000000063 00000 f -0000000064 00000 f -0000000065 00000 f -0000000066 00000 f -0000000067 00000 f -0000000068 00000 f -0000000069 00000 f -0000000070 00000 f -0000000071 00000 f -0000000072 00000 f -0000000075 00000 f -0000150618 00000 n -0000150649 00000 n -0000000076 00000 f -0000000077 00000 f -0000000078 00000 f -0000000079 00000 f -0000000080 00000 f -0000000082 00000 f -0000150235 00000 n -0000000083 00000 f -0000000084 00000 f -0000000085 00000 f -0000000086 00000 f -0000000087 00000 f -0000000088 00000 f -0000000089 00000 f -0000000090 00000 f -0000000091 00000 f -0000000092 00000 f -0000000093 00000 f -0000000094 00000 f -0000000095 00000 f -0000000096 00000 f -0000000097 00000 f -0000000100 00000 f -0000150502 00000 n -0000150533 00000 n -0000000101 00000 f -0000000102 00000 f -0000000103 00000 f -0000000104 00000 f -0000000105 00000 f -0000000000 00000 f -0000150308 00000 n -0000000000 00000 f -0000000000 00000 f -0000000000 00000 f -0000000000 00000 f -0000000000 00000 f -0000000000 00000 f -0000000000 00000 f -0000000000 00000 f -0000000000 00000 f -0000000000 00000 f -0000000000 00000 f -0000000000 00000 f -0000000000 00000 f -0000000000 00000 f -0000000000 00000 f -0000000000 00000 f -0000150384 00000 n -0000150416 00000 n -0000000000 00000 f -0000000000 00000 f -0000000000 00000 f -0000000000 00000 f -0000000000 00000 f -0000000000 00000 f -0000051021 00000 n -0000150966 00000 n -0000013836 00000 n -0000018135 00000 n -0000051442 00000 n -0000033651 00000 n -0000048282 00000 n -0000051215 00000 n -0000051328 00000 n -0000019095 00000 n -0000018199 00000 n -0000149974 00000 n -0000018531 00000 n -0000018581 00000 n -0000033697 00000 n -0000048319 00000 n -0000048371 00000 n -0000051097 00000 n -0000051129 00000 n -0000051518 00000 n -0000051719 00000 n -0000052746 00000 n -0000056528 00000 n -0000122117 00000 n -0000150993 00000 n -trailer <<527CD3784726476DB3E96B5D9CE51257>]>> startxref 151131 %%EOF \ No newline at end of file diff --git a/src/samples/clock/font/dalifont.png b/src/samples/clock/font/dalifont.png deleted file mode 100644 index 7bb364424b66bc03115e2401b2c83d091bbefbcf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 197077 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

ek&gWR0CS?dN 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%0AVl|%;{7963Ddv|gNVDSsp>94 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 S<(_?M;^`X}sG#nw(s0((Th259nZaW-jgkiWSdk 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 Z_tpf&c4@n4RS?kKyB+mDQtll1(?3!9&(r__ diff --git a/src/samples/clock/font/dalifontF.gif b/src/samples/clock/font/dalifontF.gif deleted file mode 100644 index cbde49afe49f36f2d9ce1e6d87dd09ddb1391c1d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1960 zcmZ|G|3A}-1Hke3zS&odk9G2;G|Xh29HCM+Q_L{zi!XgKo2BSlzGZ#ghWXA+Uwr9m zMZQlvU%I8_?aOs2MOV_dy9t%_#mPmb^LgAKaL*rKkH>3A;C3%>{tXBTXaj(i{{Z|4 z%D?^J7l5ze2LhYAyk>{5uf&pzh5ODYG`1wo-V{^rr5^Y*qs20K-ZVF7{Ze^O~ka%t%fNft9uBn87!MyPXIl z2l6@loe%MB4;1jR_RzfrI2ajL9&6wPInObyO0P3T;5br8sN|HuA-|x=h0O^kHI@0b zu16Yi(ZROuU5sCeIvToF#mKgYH=B+eY0>V7X>04f8+jUs&;g7?b%9Z*6SVi*)Bx?( zdbpN0WOrje_crG4K2 zls-p^x_~pv$(Ovm!^>-2%_l%4bQJKBx)s7rDgu{4`#Tk;dx46YkHR~Q5NCt& zI`~X};x~p>%#@~|?L-{xXNv9&BceV1we0%@fP-|QZaEbttG`vVvA;ojU;q9$*F!$; z>b;Qq(*_jWo(+!jH^hF2-7=0$wI{~d=OP!6GKPmbk@CLSZP;(+Jdm+3_E&}6B2%Zj z5&&E0OVy)`HMQb%X>=(vOW?{jJvNqk)8ZzI8vK;;1d|e^s9)CiPMC~BlDWyAX@&0? zl?oQHNK0M!oKu56ddV>JVIh?jHbdMcAxur$;_|*RjsT<2YkO9>tD4hSBNIL7)c8sX z5|%Nno_l&y_nCKr(Dsx1957db@0U@Uw@(BuaI~R{wd5U)oqCVgkib>GUePEYALSns z=%#Xc;fv!e@c4JRuW6xgB?$ljS)T#H6jyRBJjIkzCyQeqF;En=tH&XMls5=@{xj;~ zY}Te$(AMyQERsfZ5(|gpcFWRy^PF~3_u@ttbdZBV1M?N~bxNVC&?VW%4)N;rXZ2Y( zIxjwvW9D6i54W2!v$s{~g$Fmj^&_F0oO!v#iXVN$W87$iJzUS-Vb4520gb&CKd@`- z_Qp7?_n>x!#5SN}tUv8THO+`%r@hkwm1U&eb?SnN_8#`d-@N6Wq|7Y+}RaVKeTF$%6+Fzy6A-cCmL zCHp>yVOFyyHSU~JSEM;F0($l(mrp_36rcmc&8sBySpCB!DC8K8PP;}JcuUom_`f)` zKD`qNzG#nQb>M|ZSrIj6NgS<#$CLW@fxuGAEE?JkVEf5gU!)H(*uL_yg&o zQcw(O=-ql)f%nz*F*mISP?X!ADGqKXl;7 z+Heu;&f@N$cR<;=9u1RWjP-<)7d~lgN|DR%*g(q3i&1NjeR2jgi5a0^ANies)zStH z?Jt4F?#EBB(Qeb|7-?@dhc3EH+vcOhP_s7J3|npW^_tD5CvCnXN{ICI=+qj#nHs*4 zYfT7XqAo>n2`u`xVGj`#%-6&G=e*HEt&!y^yxHzD7;!M3!XZx>tIXx%qRDI~6XH&K z4Vh~%@mz~um*%Tb+ z(OL5(-Ais^7>?T31X>daN_swmFRPB@b0?(X!KLvkvtCqWvQ+q2IGjbVB57=bk{{b4 zF5Hj`T9;(0V5@3%jFLc?WJV4rAcR}^bP8VDJ+6gb9t0Z16+tFD`yhr};P%comDRf@ zs$yQ0I5f>&{Tt}>R`&a*j;9p*je!~Csh79LC4=a34LyfU`dOdam%s&Gw_Nv)Z#28p zJzczF;%Jlq_x(}OHXnYZ!+0c7vtz8)B~}OxaS3gR)N@JSNGkbxDJrI9+G2>qLn}MYVkbDQS)#*-?76 zo>bW;DrCSC8{-do02jQ0L_%Dm diff --git a/src/samples/clock/font/dalifontF.psd.gz b/src/samples/clock/font/dalifontF.psd.gz deleted file mode 100644 index 2a37c46254fe731b9f784beaff676fc7ccf1c382..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20137 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 )>71x^y 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 diff --git a/src/samples/clock/font/dalifontG.psd.gz b/src/samples/clock/font/dalifontG.psd.gz deleted file mode 100644 index f45a6de72a151b65be48d1d3826488c54e388f80..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17497 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 Date: Sun, 30 Sep 2018 11:04:52 -0700 Subject: [PATCH 0855/2505] Avoid calling localtime() every frame when drawing the Blocks clock localtime() in Glibc will stat("/etc/localtime") every time it's called. --- src/samples/clock/main.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/samples/clock/main.c b/src/samples/clock/main.c index db71cc069..a7cf090c9 100644 --- a/src/samples/clock/main.c +++ b/src/samples/clock/main.c @@ -152,6 +152,8 @@ LWAN_HANDLER(blocks) struct block_state blocks[4]; int last_digits[4] = {0, 0, 0, 0}; uint64_t total_waited = 0; + time_t last = 0; + bool odd_second; if (!gif) return HTTP_INTERNAL_ERROR; @@ -171,7 +173,11 @@ LWAN_HANDLER(blocks) char digits[5]; curtime = time(NULL); - strftime(digits, sizeof(digits), "%H%M", localtime(&curtime)); + if (curtime != last) { + strftime(digits, sizeof(digits), "%H%M", localtime(&curtime)); + last = curtime; + odd_second = last & 1; + } for (int i = 0; i < 4; i++) { blocks[i].num_to_draw = digits[i] - '0'; @@ -183,7 +189,7 @@ LWAN_HANDLER(blocks) } } - timeout = blocks_draw(blocks, gif->frame, curtime & 1); + timeout = blocks_draw(blocks, gif->frame, odd_second); total_waited += timeout; ge_add_frame(gif, 0); From d34d1ddb052b51b37b7d2f5244df80d7e904bac2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 1 Oct 2018 19:09:12 -0700 Subject: [PATCH 0856/2505] Reduce blocks clock usage It's possible to reduce the memory even further, but it's not really practical as far as legibility goes. --- src/samples/clock/blocks.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/samples/clock/blocks.c b/src/samples/clock/blocks.c index 453260173..663767465 100644 --- a/src/samples/clock/blocks.c +++ b/src/samples/clock/blocks.c @@ -39,10 +39,10 @@ enum color { }; struct fall { - enum shape shape; - int x_pos; - int y_stop; - int n_rot; + unsigned short shape : 3; + unsigned short int x_pos : 3; + unsigned short int y_stop : 6; + unsigned short int n_rot : 2; }; static const enum color colors[] = { @@ -52,7 +52,7 @@ static const enum color colors[] = { [SHAPE_T] = COLOR_MAGENTA, }; -static const int offs[SHAPE_MAX][4][8] = { +static const signed char offs[SHAPE_MAX][4][8] = { [SHAPE_SQUARE][0] = {0, 0, 1, 0, 0, -1, 1, -1}, [SHAPE_SQUARE][1] = {0, 0, 1, 0, 0, -1, 1, -1}, [SHAPE_SQUARE][2] = {0, 0, 1, 0, 0, -1, 1, -1}, From 363e53cb2faae62126b518f3e8a22794350058b9 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 1 Oct 2018 19:10:00 -0700 Subject: [PATCH 0857/2505] Ensure that blocks clock will never write outside the GIF buffer --- src/samples/clock/blocks.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/samples/clock/blocks.c b/src/samples/clock/blocks.c index 663767465..da3d4fd15 100644 --- a/src/samples/clock/blocks.c +++ b/src/samples/clock/blocks.c @@ -193,16 +193,15 @@ static void draw_shape_full(enum shape shape, { assert(rot >= 0 && rot <= 3); - if (y < 0) - return; - for (int i = 0; i < 8; i += 2) { int x_off = offs[shape][rot][i + 0]; int y_off = offs[shape][rot][i + 1]; int dx = x + x_off; + int dy = y + y_off; + int index = dy * 32 + dx; - if (dx < 32) - buffer[(y + y_off) * 32 + dx] = color; + if (index >= 0 && index < 32 * 16 && dx < 32) + buffer[index] = color; } } From 534070cc6e57f5f26716190617cb5f106c24269c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 3 Oct 2018 23:16:05 -0700 Subject: [PATCH 0858/2505] Clean up a lot of the blocks clock interface between the sample app --- src/samples/clock/blocks.c | 35 +++++++++++++++++++++-------------- src/samples/clock/blocks.h | 14 ++++++++++---- src/samples/clock/main.c | 22 ++++++---------------- 3 files changed, 37 insertions(+), 34 deletions(-) diff --git a/src/samples/clock/blocks.c b/src/samples/clock/blocks.c index da3d4fd15..4ce7a7908 100644 --- a/src/samples/clock/blocks.c +++ b/src/samples/clock/blocks.c @@ -211,25 +211,32 @@ draw_shape(enum shape shape, int x, int y, int rot, unsigned char *buffer) draw_shape_full(shape, colors[shape], x, y, rot, buffer); } -void blocks_init(struct block_state states[static 4]) +void blocks_init(struct blocks *blocks, ge_GIF *gif) { - states[0] = (struct block_state){0, 0, 0, 1}; - states[1] = (struct block_state){0, 0, 0, 8}; - states[2] = (struct block_state){0, 0, 0, 18}; - states[3] = (struct block_state){0, 0, 0, 25}; + blocks->states[0] = (struct block_state){0, 0, 0, 1}; + blocks->states[1] = (struct block_state){0, 0, 0, 8}; + blocks->states[2] = (struct block_state){0, 0, 0, 18}; + blocks->states[3] = (struct block_state){0, 0, 0, 25}; + blocks->gif = gif; + memset(blocks->last_digits, 0, sizeof(blocks->last_digits)); } -uint64_t blocks_draw(struct block_state states[static 4], - unsigned char *buffer, - bool odd_second) +uint64_t blocks_draw(struct blocks *blocks, bool odd_second) { + unsigned char *frame = blocks->gif->frame; int digits_fallen = 0; int i; - memset(buffer, COLOR_BLACK, 32 * 16); + memset(frame, COLOR_BLACK, (size_t)(blocks->gif->w * blocks->gif->h)); for (i = 0; i < 4; i++) { - struct block_state *state = &states[i]; + struct block_state *state = &blocks->states[i]; + + if (state->num_to_draw != blocks->last_digits[i]) { + state->fall_index = 0; + state->block_index = 0; + blocks->last_digits[i] = state->num_to_draw; + } if (state->block_index < block_sizes[state->num_to_draw]) { const struct fall *curr = @@ -258,7 +265,7 @@ uint64_t blocks_draw(struct block_state states[static 4], } draw_shape(curr->shape, curr->x_pos + state->x_shift, - state->fall_index - 1, rotations, buffer); + state->fall_index - 1, rotations, frame); state->fall_index++; if (state->fall_index > curr->y_stop) { @@ -274,14 +281,14 @@ uint64_t blocks_draw(struct block_state states[static 4], const struct fall *fallen = &fall[state->num_to_draw][j]; draw_shape(fallen->shape, fallen->x_pos + state->x_shift, - fallen->y_stop - 1, fallen->n_rot, buffer); + fallen->y_stop - 1, fallen->n_rot, frame); } } } if (odd_second) { - draw_shape_full(SHAPE_SQUARE, COLOR_WHITE, 15, 13, 0, buffer); - draw_shape_full(SHAPE_SQUARE, COLOR_WHITE, 15, 9, 0, buffer); + draw_shape_full(SHAPE_SQUARE, COLOR_WHITE, 15, 13, 0, frame); + draw_shape_full(SHAPE_SQUARE, COLOR_WHITE, 15, 9, 0, frame); } return digits_fallen ? 100 : 500; diff --git a/src/samples/clock/blocks.h b/src/samples/clock/blocks.h index 3b9cd7d98..3281c1857 100644 --- a/src/samples/clock/blocks.h +++ b/src/samples/clock/blocks.h @@ -3,6 +3,8 @@ #include #include +#include "gifenc.h" + struct block_state { int num_to_draw; int block_index; @@ -10,7 +12,11 @@ struct block_state { int x_shift; }; -void blocks_init(struct block_state states[static 4]); -uint64_t blocks_draw(struct block_state states[static 4], - unsigned char *buffer, - bool odd_second); +struct blocks { + struct block_state states[4]; + int last_digits[4]; + ge_GIF *gif; +}; + +void blocks_init(struct blocks *blocks, ge_GIF *gif); +uint64_t blocks_draw(struct blocks *blocks, bool odd_second); diff --git a/src/samples/clock/main.c b/src/samples/clock/main.c index a7cf090c9..20c57c47d 100644 --- a/src/samples/clock/main.c +++ b/src/samples/clock/main.c @@ -149,24 +149,21 @@ LWAN_HANDLER(dali) LWAN_HANDLER(blocks) { ge_GIF *gif = ge_new_gif(response->buffer, 32, 16, NULL, 4, -1); - struct block_state blocks[4]; - int last_digits[4] = {0, 0, 0, 0}; + struct blocks blocks; uint64_t total_waited = 0; time_t last = 0; - bool odd_second; + bool odd_second = false; if (!gif) return HTTP_INTERNAL_ERROR; coro_defer(request->conn->coro, destroy_gif, gif); - blocks_init(blocks); + blocks_init(&blocks, gif); response->mime_type = "image/gif"; response->headers = seriously_do_not_cache; - memset(gif->frame, 0, (size_t)(32 * 16)); - while (total_waited <= 3600000) { uint64_t timeout; time_t curtime; @@ -179,17 +176,10 @@ LWAN_HANDLER(blocks) odd_second = last & 1; } - for (int i = 0; i < 4; i++) { - blocks[i].num_to_draw = digits[i] - '0'; - - if (blocks[i].num_to_draw != last_digits[i]) { - blocks[i].fall_index = 0; - blocks[i].block_index = 0; - last_digits[i] = blocks[i].num_to_draw; - } - } + for (int i = 0; i < 4; i++) + blocks.states[i].num_to_draw = digits[i] - '0'; - timeout = blocks_draw(blocks, gif->frame, odd_second); + timeout = blocks_draw(&blocks, odd_second); total_waited += timeout; ge_add_frame(gif, 0); From f7bdf54dfd47b17e498d71d61223bf1444770f82 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 3 Oct 2018 23:22:15 -0700 Subject: [PATCH 0859/2505] Reduce block offset usage by 8x by packing bits more efficiently --- src/samples/clock/blocks.c | 87 ++++++++++++++++++++++---------------- 1 file changed, 51 insertions(+), 36 deletions(-) diff --git a/src/samples/clock/blocks.c b/src/samples/clock/blocks.c index 4ce7a7908..b168cd637 100644 --- a/src/samples/clock/blocks.c +++ b/src/samples/clock/blocks.c @@ -52,37 +52,44 @@ static const enum color colors[] = { [SHAPE_T] = COLOR_MAGENTA, }; -static const signed char offs[SHAPE_MAX][4][8] = { - [SHAPE_SQUARE][0] = {0, 0, 1, 0, 0, -1, 1, -1}, - [SHAPE_SQUARE][1] = {0, 0, 1, 0, 0, -1, 1, -1}, - [SHAPE_SQUARE][2] = {0, 0, 1, 0, 0, -1, 1, -1}, - [SHAPE_SQUARE][3] = {0, 0, 1, 0, 0, -1, 1, -1}, - [SHAPE_L][0] = {0, 0, 1, 0, 0, -1, 0, -2}, - [SHAPE_L][1] = {0, 0, 0, -1, 1, -1, 2, -1}, - [SHAPE_L][2] = {1, 0, 1, -1, 1, -2, 0, -2}, - [SHAPE_L][3] = {0, 0, 1, 0, 2, 0, 2, -1}, - [SHAPE_L_REVERSE][0] = {0, 0, 1, 0, 1, -1, 1, -2}, - [SHAPE_L_REVERSE][1] = {0, 0, 1, 0, 2, 0, 0, -1}, - [SHAPE_L_REVERSE][2] = {0, 0, 0, -1, 0, -2, 1, -2}, - [SHAPE_L_REVERSE][3] = {0, -1, 1, -1, 2, -1, 2, 0}, - [SHAPE_I][0] = {0, 0, 1, 0, 2, 0, 3, 0}, - [SHAPE_I][1] = {0, 0, 0, -1, 0, -2, 0, -3}, - [SHAPE_I][2] = {0, 0, 1, 0, 2, 0, 3, 0}, - [SHAPE_I][3] = {0, 0, 0, -1, 0, -2, 0, -3}, - [SHAPE_S][0] = {1, 0, 0, -1, 1, -1, 0, -2}, - [SHAPE_S][1] = {0, 0, 1, 0, 1, -1, 2, -1}, - [SHAPE_S][2] = {1, 0, 0, -1, 1, -1, 0, -2}, - [SHAPE_S][3] = {0, 0, 1, 0, 1, -1, 2, -1}, - [SHAPE_S_REVERSE][0] = {0, 0, 0, -1, 1, -1, 1, -2}, - [SHAPE_S_REVERSE][1] = {1, 0, 2, 0, 0, -1, 1, -1}, - [SHAPE_S_REVERSE][2] = {0, 0, 0, -1, 1, -1, 1, -2}, - [SHAPE_S_REVERSE][3] = {1, 0, 2, 0, 0, -1, 1, -1}, - [SHAPE_T][0] = {0, 0, 1, 0, 2, 0, 1, -1}, - [SHAPE_T][1] = {0, 0, 0, -1, 0, -2, 1, -1}, - [SHAPE_T][2] = {1, 0, 0, -1, 1, -1, 2, -1}, - [SHAPE_T][3] = {1, 0, 0, -1, 1, -1, 1, -2}, +#define PAIR(x, y) (((x) + 3) << 3 | ((y) + 3)) +#define OFF(x1, y1, x2, y2, x3, y3, x4, y4) \ + PAIR(x1, y1) << 24 | PAIR(x2, y2) << 16 | PAIR(x3, y3) << 8 | PAIR(x4, y4) + +static const unsigned int offs[SHAPE_MAX][4] = { + [SHAPE_SQUARE][0] = OFF(0, 0, 1, 0, 0, -1, 1, -1), + [SHAPE_SQUARE][1] = OFF(0, 0, 1, 0, 0, -1, 1, -1), + [SHAPE_SQUARE][2] = OFF(0, 0, 1, 0, 0, -1, 1, -1), + [SHAPE_SQUARE][3] = OFF(0, 0, 1, 0, 0, -1, 1, -1), + [SHAPE_L][0] = OFF(0, 0, 1, 0, 0, -1, 0, -2), + [SHAPE_L][1] = OFF(0, 0, 0, -1, 1, -1, 2, -1), + [SHAPE_L][2] = OFF(1, 0, 1, -1, 1, -2, 0, -2), + [SHAPE_L][3] = OFF(0, 0, 1, 0, 2, 0, 2, -1), + [SHAPE_L_REVERSE][0] = OFF(0, 0, 1, 0, 1, -1, 1, -2), + [SHAPE_L_REVERSE][1] = OFF(0, 0, 1, 0, 2, 0, 0, -1), + [SHAPE_L_REVERSE][2] = OFF(0, 0, 0, -1, 0, -2, 1, -2), + [SHAPE_L_REVERSE][3] = OFF(0, -1, 1, -1, 2, -1, 2, 0), + [SHAPE_I][0] = OFF(0, 0, 1, 0, 2, 0, 3, 0), + [SHAPE_I][1] = OFF(0, 0, 0, -1, 0, -2, 0, -3), + [SHAPE_I][2] = OFF(0, 0, 1, 0, 2, 0, 3, 0), + [SHAPE_I][3] = OFF(0, 0, 0, -1, 0, -2, 0, -3), + [SHAPE_S][0] = OFF(1, 0, 0, -1, 1, -1, 0, -2), + [SHAPE_S][1] = OFF(0, 0, 1, 0, 1, -1, 2, -1), + [SHAPE_S][2] = OFF(1, 0, 0, -1, 1, -1, 0, -2), + [SHAPE_S][3] = OFF(0, 0, 1, 0, 1, -1, 2, -1), + [SHAPE_S_REVERSE][0] = OFF(0, 0, 0, -1, 1, -1, 1, -2), + [SHAPE_S_REVERSE][1] = OFF(1, 0, 2, 0, 0, -1, 1, -1), + [SHAPE_S_REVERSE][2] = OFF(0, 0, 0, -1, 1, -1, 1, -2), + [SHAPE_S_REVERSE][3] = OFF(1, 0, 2, 0, 0, -1, 1, -1), + [SHAPE_T][0] = OFF(0, 0, 1, 0, 2, 0, 1, -1), + [SHAPE_T][1] = OFF(0, 0, 0, -1, 0, -2, 1, -1), + [SHAPE_T][2] = OFF(1, 0, 0, -1, 1, -1, 2, -1), + [SHAPE_T][3] = OFF(1, 0, 0, -1, 1, -1, 1, -2), }; +#undef PAIR +#undef OFF + static const struct fall fall0[] = { {SHAPE_L_REVERSE, 4, 16, 0}, {SHAPE_S, 2, 16, 1}, {SHAPE_I, 0, 16, 1}, {SHAPE_T, 1, 16, 1}, @@ -191,17 +198,25 @@ static void draw_shape_full(enum shape shape, int rot, unsigned char *buffer) { + unsigned int offset; + assert(rot >= 0 && rot <= 3); - for (int i = 0; i < 8; i += 2) { - int x_off = offs[shape][rot][i + 0]; - int y_off = offs[shape][rot][i + 1]; - int dx = x + x_off; - int dy = y + y_off; - int index = dy * 32 + dx; + offset = offs[shape][rot]; + for (int i = 0; i < 4; i++) { + int y_off, x_off, dx, dy, index; + + y_off = ((int)(offset & 7)) - 3; + offset >>= 3; + x_off = ((int)(offset & 7)) - 3; + offset >>= 5; + + dx = x + x_off; + dy = y + y_off; + index = dy * 32 + dx; if (index >= 0 && index < 32 * 16 && dx < 32) - buffer[index] = color; + buffer[index] = (unsigned char *)color; } } From 54afc6686fca38ae8169b5fd026193df949718bc Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 4 Oct 2018 07:12:23 -0700 Subject: [PATCH 0860/2505] Pass GIF encoder width/height to block shape drawing routine --- src/samples/clock/blocks.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/samples/clock/blocks.c b/src/samples/clock/blocks.c index b168cd637..7cc9ace42 100644 --- a/src/samples/clock/blocks.c +++ b/src/samples/clock/blocks.c @@ -195,10 +195,13 @@ static void draw_shape_full(enum shape shape, enum color color, int x, int y, + int w, + int h, int rot, unsigned char *buffer) { unsigned int offset; + int area = w * h; assert(rot >= 0 && rot <= 3); @@ -215,15 +218,15 @@ static void draw_shape_full(enum shape shape, dy = y + y_off; index = dy * 32 + dx; - if (index >= 0 && index < 32 * 16 && dx < 32) - buffer[index] = (unsigned char *)color; + if (index >= 0 && index < area && dx < w) + buffer[index] = (unsigned char)color; } } static void -draw_shape(enum shape shape, int x, int y, int rot, unsigned char *buffer) +draw_shape(enum shape shape, int x, int y, int w, int h, int rot, unsigned char *buffer) { - draw_shape_full(shape, colors[shape], x, y, rot, buffer); + draw_shape_full(shape, colors[shape], x, y, w, h, rot, buffer); } void blocks_init(struct blocks *blocks, ge_GIF *gif) @@ -241,8 +244,10 @@ uint64_t blocks_draw(struct blocks *blocks, bool odd_second) unsigned char *frame = blocks->gif->frame; int digits_fallen = 0; int i; + int w = blocks->gif->w; + int h = blocks->gif->h; - memset(frame, COLOR_BLACK, (size_t)(blocks->gif->w * blocks->gif->h)); + memset(frame, COLOR_BLACK, (size_t)(w * h)); for (i = 0; i < 4; i++) { struct block_state *state = &blocks->states[i]; @@ -280,7 +285,7 @@ uint64_t blocks_draw(struct blocks *blocks, bool odd_second) } draw_shape(curr->shape, curr->x_pos + state->x_shift, - state->fall_index - 1, rotations, frame); + state->fall_index - 1, w, h, rotations, frame); state->fall_index++; if (state->fall_index > curr->y_stop) { @@ -296,14 +301,14 @@ uint64_t blocks_draw(struct blocks *blocks, bool odd_second) const struct fall *fallen = &fall[state->num_to_draw][j]; draw_shape(fallen->shape, fallen->x_pos + state->x_shift, - fallen->y_stop - 1, fallen->n_rot, frame); + fallen->y_stop - 1, w, h, fallen->n_rot, frame); } } } if (odd_second) { - draw_shape_full(SHAPE_SQUARE, COLOR_WHITE, 15, 13, 0, frame); - draw_shape_full(SHAPE_SQUARE, COLOR_WHITE, 15, 9, 0, frame); + draw_shape_full(SHAPE_SQUARE, COLOR_WHITE, 15, 13, w, h, 0, frame); + draw_shape_full(SHAPE_SQUARE, COLOR_WHITE, 15, 9, w, h, 0, frame); } return digits_fallen ? 100 : 500; From 1c474090ee22b19a4d1b6337f3a409d3abe23dfc Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 8 Oct 2018 19:34:49 -0700 Subject: [PATCH 0861/2505] More clean ups in the falling blocks clock sample --- src/samples/clock/blocks.c | 207 +++++++++++++++++++------------------ 1 file changed, 104 insertions(+), 103 deletions(-) diff --git a/src/samples/clock/blocks.c b/src/samples/clock/blocks.c index 7cc9ace42..6c81529ba 100644 --- a/src/samples/clock/blocks.c +++ b/src/samples/clock/blocks.c @@ -17,10 +17,10 @@ enum shape { SHAPE_SQUARE = 0, SHAPE_L = 1, - SHAPE_L_REVERSE = 2, + SHAPE_L_REV = 2, SHAPE_I = 3, SHAPE_S = 4, - SHAPE_S_REVERSE = 5, + SHAPE_S_REV = 5, SHAPE_T = 6, SHAPE_MAX, }; @@ -38,17 +38,25 @@ enum color { COLOR_MAX, }; +enum rotation { + ROT_0, + ROT_90, + ROT_180, + ROT_270, + ROT_MAX, +}; + struct fall { - unsigned short shape : 3; - unsigned short int x_pos : 3; + unsigned short shape : 3; + unsigned short int x_pos : 3; unsigned short int y_stop : 6; - unsigned short int n_rot : 2; + unsigned short int n_rot : 2; }; static const enum color colors[] = { - [SHAPE_SQUARE] = COLOR_RED, [SHAPE_L] = COLOR_CYAN, - [SHAPE_L_REVERSE] = COLOR_BLUE, [SHAPE_I] = COLOR_YELLOW, - [SHAPE_S] = COLOR_GREEN, [SHAPE_S_REVERSE] = COLOR_ORANGE, + [SHAPE_SQUARE] = COLOR_RED, [SHAPE_L] = COLOR_CYAN, + [SHAPE_L_REV] = COLOR_BLUE, [SHAPE_I] = COLOR_YELLOW, + [SHAPE_S] = COLOR_GREEN, [SHAPE_S_REV] = COLOR_ORANGE, [SHAPE_T] = COLOR_MAGENTA, }; @@ -56,120 +64,108 @@ static const enum color colors[] = { #define OFF(x1, y1, x2, y2, x3, y3, x4, y4) \ PAIR(x1, y1) << 24 | PAIR(x2, y2) << 16 | PAIR(x3, y3) << 8 | PAIR(x4, y4) -static const unsigned int offs[SHAPE_MAX][4] = { - [SHAPE_SQUARE][0] = OFF(0, 0, 1, 0, 0, -1, 1, -1), - [SHAPE_SQUARE][1] = OFF(0, 0, 1, 0, 0, -1, 1, -1), - [SHAPE_SQUARE][2] = OFF(0, 0, 1, 0, 0, -1, 1, -1), - [SHAPE_SQUARE][3] = OFF(0, 0, 1, 0, 0, -1, 1, -1), - [SHAPE_L][0] = OFF(0, 0, 1, 0, 0, -1, 0, -2), - [SHAPE_L][1] = OFF(0, 0, 0, -1, 1, -1, 2, -1), - [SHAPE_L][2] = OFF(1, 0, 1, -1, 1, -2, 0, -2), - [SHAPE_L][3] = OFF(0, 0, 1, 0, 2, 0, 2, -1), - [SHAPE_L_REVERSE][0] = OFF(0, 0, 1, 0, 1, -1, 1, -2), - [SHAPE_L_REVERSE][1] = OFF(0, 0, 1, 0, 2, 0, 0, -1), - [SHAPE_L_REVERSE][2] = OFF(0, 0, 0, -1, 0, -2, 1, -2), - [SHAPE_L_REVERSE][3] = OFF(0, -1, 1, -1, 2, -1, 2, 0), - [SHAPE_I][0] = OFF(0, 0, 1, 0, 2, 0, 3, 0), - [SHAPE_I][1] = OFF(0, 0, 0, -1, 0, -2, 0, -3), - [SHAPE_I][2] = OFF(0, 0, 1, 0, 2, 0, 3, 0), - [SHAPE_I][3] = OFF(0, 0, 0, -1, 0, -2, 0, -3), - [SHAPE_S][0] = OFF(1, 0, 0, -1, 1, -1, 0, -2), - [SHAPE_S][1] = OFF(0, 0, 1, 0, 1, -1, 2, -1), - [SHAPE_S][2] = OFF(1, 0, 0, -1, 1, -1, 0, -2), - [SHAPE_S][3] = OFF(0, 0, 1, 0, 1, -1, 2, -1), - [SHAPE_S_REVERSE][0] = OFF(0, 0, 0, -1, 1, -1, 1, -2), - [SHAPE_S_REVERSE][1] = OFF(1, 0, 2, 0, 0, -1, 1, -1), - [SHAPE_S_REVERSE][2] = OFF(0, 0, 0, -1, 1, -1, 1, -2), - [SHAPE_S_REVERSE][3] = OFF(1, 0, 2, 0, 0, -1, 1, -1), - [SHAPE_T][0] = OFF(0, 0, 1, 0, 2, 0, 1, -1), - [SHAPE_T][1] = OFF(0, 0, 0, -1, 0, -2, 1, -1), - [SHAPE_T][2] = OFF(1, 0, 0, -1, 1, -1, 2, -1), - [SHAPE_T][3] = OFF(1, 0, 0, -1, 1, -1, 1, -2), +static const unsigned int offs[SHAPE_MAX][ROT_MAX] = { + [SHAPE_SQUARE][ROT_0] = OFF(0, 0, 1, 0, 0, -1, 1, -1), + [SHAPE_SQUARE][ROT_90] = OFF(0, 0, 1, 0, 0, -1, 1, -1), + [SHAPE_SQUARE][ROT_180] = OFF(0, 0, 1, 0, 0, -1, 1, -1), + [SHAPE_SQUARE][ROT_270] = OFF(0, 0, 1, 0, 0, -1, 1, -1), + [SHAPE_L][ROT_0] = OFF(0, 0, 1, 0, 0, -1, 0, -2), + [SHAPE_L][ROT_90] = OFF(0, 0, 0, -1, 1, -1, 2, -1), + [SHAPE_L][ROT_180] = OFF(1, 0, 1, -1, 1, -2, 0, -2), + [SHAPE_L][ROT_270] = OFF(0, 0, 1, 0, 2, 0, 2, -1), + [SHAPE_L_REV][ROT_0] = OFF(0, 0, 1, 0, 1, -1, 1, -2), + [SHAPE_L_REV][ROT_90] = OFF(0, 0, 1, 0, 2, 0, 0, -1), + [SHAPE_L_REV][ROT_180] = OFF(0, 0, 0, -1, 0, -2, 1, -2), + [SHAPE_L_REV][ROT_270] = OFF(0, -1, 1, -1, 2, -1, 2, 0), + [SHAPE_I][ROT_0] = OFF(0, 0, 1, 0, 2, 0, 3, 0), + [SHAPE_I][ROT_90] = OFF(0, 0, 0, -1, 0, -2, 0, -3), + [SHAPE_I][ROT_180] = OFF(0, 0, 1, 0, 2, 0, 3, 0), + [SHAPE_I][ROT_270] = OFF(0, 0, 0, -1, 0, -2, 0, -3), + [SHAPE_S][ROT_0] = OFF(1, 0, 0, -1, 1, -1, 0, -2), + [SHAPE_S][ROT_90] = OFF(0, 0, 1, 0, 1, -1, 2, -1), + [SHAPE_S][ROT_180] = OFF(1, 0, 0, -1, 1, -1, 0, -2), + [SHAPE_S][ROT_270] = OFF(0, 0, 1, 0, 1, -1, 2, -1), + [SHAPE_S_REV][ROT_0] = OFF(0, 0, 0, -1, 1, -1, 1, -2), + [SHAPE_S_REV][ROT_90] = OFF(1, 0, 2, 0, 0, -1, 1, -1), + [SHAPE_S_REV][ROT_180] = OFF(0, 0, 0, -1, 1, -1, 1, -2), + [SHAPE_S_REV][ROT_270] = OFF(1, 0, 2, 0, 0, -1, 1, -1), + [SHAPE_T][ROT_0] = OFF(0, 0, 1, 0, 2, 0, 1, -1), + [SHAPE_T][ROT_90] = OFF(0, 0, 0, -1, 0, -2, 1, -1), + [SHAPE_T][ROT_180] = OFF(1, 0, 0, -1, 1, -1, 2, -1), + [SHAPE_T][ROT_270] = OFF(1, 0, 0, -1, 1, -1, 1, -2), }; #undef PAIR #undef OFF static const struct fall fall0[] = { - {SHAPE_L_REVERSE, 4, 16, 0}, {SHAPE_S, 2, 16, 1}, - {SHAPE_I, 0, 16, 1}, {SHAPE_T, 1, 16, 1}, - {SHAPE_S_REVERSE, 4, 14, 0}, {SHAPE_T, 0, 13, 3}, - {SHAPE_S_REVERSE, 4, 12, 0}, {SHAPE_S_REVERSE, 0, 11, 0}, - {SHAPE_T, 4, 10, 1}, {SHAPE_T, 0, 9, 1}, - {SHAPE_S_REVERSE, 1, 8, 1}, {SHAPE_L_REVERSE, 3, 8, 3}, + {SHAPE_L_REV, 4, 16, 0}, {SHAPE_S, 2, 16, 1}, {SHAPE_I, 0, 16, 1}, + {SHAPE_T, 1, 16, 1}, {SHAPE_S_REV, 4, 14, 0}, {SHAPE_T, 0, 13, 3}, + {SHAPE_S_REV, 4, 12, 0}, {SHAPE_S_REV, 0, 11, 0}, {SHAPE_T, 4, 10, 1}, + {SHAPE_T, 0, 9, 1}, {SHAPE_S_REV, 1, 8, 1}, {SHAPE_L_REV, 3, 8, 3}, {SHAPE_MAX, 0, 0, 0}, }; static const struct fall fall1[] = { - {SHAPE_L_REVERSE, 4, 16, 0}, {SHAPE_I, 4, 15, 1}, {SHAPE_I, 5, 13, 3}, - {SHAPE_L_REVERSE, 4, 11, 2}, {SHAPE_SQUARE, 4, 8, 0}, {SHAPE_MAX, 0, 0, 0}, + {SHAPE_L_REV, 4, 16, 0}, {SHAPE_I, 4, 15, 1}, {SHAPE_I, 5, 13, 3}, + {SHAPE_L_REV, 4, 11, 2}, {SHAPE_SQUARE, 4, 8, 0}, {SHAPE_MAX, 0, 0, 0}, }; static const struct fall fall2[] = { - {SHAPE_SQUARE, 4, 16, 0}, {SHAPE_I, 0, 16, 1}, {SHAPE_L, 1, 16, 3}, - {SHAPE_L, 1, 15, 0}, {SHAPE_I, 1, 12, 2}, {SHAPE_L, 0, 12, 1}, - {SHAPE_L_REVERSE, 3, 12, 3}, {SHAPE_SQUARE, 4, 10, 0}, {SHAPE_I, 1, 8, 0}, - {SHAPE_L_REVERSE, 3, 8, 3}, {SHAPE_L, 0, 8, 1}, {SHAPE_MAX, 0, 0, 0}, + {SHAPE_SQUARE, 4, 16, 0}, {SHAPE_I, 0, 16, 1}, {SHAPE_L, 1, 16, 3}, + {SHAPE_L, 1, 15, 0}, {SHAPE_I, 1, 12, 2}, {SHAPE_L, 0, 12, 1}, + {SHAPE_L_REV, 3, 12, 3}, {SHAPE_SQUARE, 4, 10, 0}, {SHAPE_I, 1, 8, 0}, + {SHAPE_L_REV, 3, 8, 3}, {SHAPE_L, 0, 8, 1}, {SHAPE_MAX, 0, 0, 0}, }; static const struct fall fall3[] = { - {SHAPE_L, 3, 16, 3}, {SHAPE_L_REVERSE, 0, 16, 1}, {SHAPE_I, 1, 15, 2}, - {SHAPE_SQUARE, 4, 14, 0}, {SHAPE_I, 1, 12, 2}, {SHAPE_L, 0, 12, 1}, - {SHAPE_I, 5, 12, 3}, {SHAPE_L_REVERSE, 3, 11, 0}, {SHAPE_I, 1, 8, 0}, - {SHAPE_L, 0, 8, 1}, {SHAPE_L_REVERSE, 3, 8, 3}, {SHAPE_MAX, 0, 0, 0}, + {SHAPE_L, 3, 16, 3}, {SHAPE_L_REV, 0, 16, 1}, {SHAPE_I, 1, 15, 2}, + {SHAPE_SQUARE, 4, 14, 0}, {SHAPE_I, 1, 12, 2}, {SHAPE_L, 0, 12, 1}, + {SHAPE_I, 5, 12, 3}, {SHAPE_L_REV, 3, 11, 0}, {SHAPE_I, 1, 8, 0}, + {SHAPE_L, 0, 8, 1}, {SHAPE_L_REV, 3, 8, 3}, {SHAPE_MAX, 0, 0, 0}, }; static const struct fall fall4[] = { - {SHAPE_SQUARE, 4, 16, 0}, {SHAPE_SQUARE, 4, 14, 0}, - {SHAPE_I, 1, 12, 0}, {SHAPE_L, 0, 12, 1}, - {SHAPE_L_REVERSE, 0, 10, 0}, {SHAPE_L_REVERSE, 3, 12, 3}, - {SHAPE_I, 4, 10, 3}, {SHAPE_L_REVERSE, 0, 9, 2}, - {SHAPE_I, 5, 10, 1}, {SHAPE_MAX, 0, 0, 0}, + {SHAPE_SQUARE, 4, 16, 0}, {SHAPE_SQUARE, 4, 14, 0}, {SHAPE_I, 1, 12, 0}, + {SHAPE_L, 0, 12, 1}, {SHAPE_L_REV, 0, 10, 0}, {SHAPE_L_REV, 3, 12, 3}, + {SHAPE_I, 4, 10, 3}, {SHAPE_L_REV, 0, 9, 2}, {SHAPE_I, 5, 10, 1}, + {SHAPE_MAX, 0, 0, 0}, }; static const struct fall fall5[] = { - {SHAPE_SQUARE, 0, 16, 0}, {SHAPE_L_REVERSE, 2, 16, 1}, - {SHAPE_L_REVERSE, 3, 15, 0}, {SHAPE_I, 5, 16, 1}, - {SHAPE_I, 1, 12, 0}, {SHAPE_L, 0, 12, 1}, - {SHAPE_L_REVERSE, 3, 12, 3}, {SHAPE_SQUARE, 0, 10, 0}, - {SHAPE_I, 1, 8, 2}, {SHAPE_L, 0, 8, 1}, - {SHAPE_L_REVERSE, 3, 8, 3}, {SHAPE_MAX, 0, 0, 0}, + {SHAPE_SQUARE, 0, 16, 0}, {SHAPE_L_REV, 2, 16, 1}, {SHAPE_L_REV, 3, 15, 0}, + {SHAPE_I, 5, 16, 1}, {SHAPE_I, 1, 12, 0}, {SHAPE_L, 0, 12, 1}, + {SHAPE_L_REV, 3, 12, 3}, {SHAPE_SQUARE, 0, 10, 0}, {SHAPE_I, 1, 8, 2}, + {SHAPE_L, 0, 8, 1}, {SHAPE_L_REV, 3, 8, 3}, {SHAPE_MAX, 0, 0, 0}, }; static const struct fall fall6[] = { - {SHAPE_L_REVERSE, 0, 16, 1}, {SHAPE_S_REVERSE, 2, 16, 1}, - {SHAPE_T, 0, 15, 3}, {SHAPE_T, 4, 16, 3}, - {SHAPE_S_REVERSE, 4, 14, 0}, {SHAPE_I, 1, 12, 2}, - {SHAPE_L_REVERSE, 0, 13, 2}, {SHAPE_I, 2, 11, 0}, - {SHAPE_SQUARE, 0, 10, 0}, {SHAPE_I, 1, 8, 0}, - {SHAPE_L, 0, 8, 1}, {SHAPE_L_REVERSE, 3, 8, 3}, + {SHAPE_L_REV, 0, 16, 1}, {SHAPE_S_REV, 2, 16, 1}, {SHAPE_T, 0, 15, 3}, + {SHAPE_T, 4, 16, 3}, {SHAPE_S_REV, 4, 14, 0}, {SHAPE_I, 1, 12, 2}, + {SHAPE_L_REV, 0, 13, 2}, {SHAPE_I, 2, 11, 0}, {SHAPE_SQUARE, 0, 10, 0}, + {SHAPE_I, 1, 8, 0}, {SHAPE_L, 0, 8, 1}, {SHAPE_L_REV, 3, 8, 3}, {SHAPE_MAX, 0, 0, 0}, }; static const struct fall fall7[] = { - {SHAPE_SQUARE, 4, 16, 0}, {SHAPE_L, 4, 14, 0}, - {SHAPE_I, 5, 13, 1}, {SHAPE_L_REVERSE, 4, 11, 2}, - {SHAPE_I, 1, 8, 2}, {SHAPE_L_REVERSE, 3, 8, 3}, + {SHAPE_SQUARE, 4, 16, 0}, {SHAPE_L, 4, 14, 0}, {SHAPE_I, 5, 13, 1}, + {SHAPE_L_REV, 4, 11, 2}, {SHAPE_I, 1, 8, 2}, {SHAPE_L_REV, 3, 8, 3}, {SHAPE_L, 0, 8, 1}, {SHAPE_MAX, 0, 0, 0}, }; static const struct fall fall8[] = { - {SHAPE_I, 1, 16, 0}, {SHAPE_T, 0, 16, 1}, - {SHAPE_I, 5, 16, 1}, {SHAPE_L, 2, 15, 3}, - {SHAPE_S, 0, 14, 0}, {SHAPE_L, 1, 12, 3}, - {SHAPE_T, 4, 13, 1}, {SHAPE_L_REVERSE, 0, 11, 1}, - {SHAPE_S, 0, 10, 0}, {SHAPE_S, 4, 11, 0}, - {SHAPE_S_REVERSE, 0, 8, 1}, {SHAPE_S_REVERSE, 2, 8, 1}, - {SHAPE_L, 4, 9, 2}, {SHAPE_MAX, 0, 0, 0}, + {SHAPE_I, 1, 16, 0}, {SHAPE_T, 0, 16, 1}, {SHAPE_I, 5, 16, 1}, + {SHAPE_L, 2, 15, 3}, {SHAPE_S, 0, 14, 0}, {SHAPE_L, 1, 12, 3}, + {SHAPE_T, 4, 13, 1}, {SHAPE_L_REV, 0, 11, 1}, {SHAPE_S, 0, 10, 0}, + {SHAPE_S, 4, 11, 0}, {SHAPE_S_REV, 0, 8, 1}, {SHAPE_S_REV, 2, 8, 1}, + {SHAPE_L, 4, 9, 2}, {SHAPE_MAX, 0, 0, 0}, }; static const struct fall fall9[] = { - {SHAPE_SQUARE, 0, 16, 0}, {SHAPE_I, 2, 16, 0}, - {SHAPE_L, 2, 15, 3}, {SHAPE_L, 4, 15, 2}, - {SHAPE_I, 1, 12, 2}, {SHAPE_I, 5, 12, 3}, - {SHAPE_S_REVERSE, 0, 12, 0}, {SHAPE_L, 2, 11, 3}, - {SHAPE_S_REVERSE, 4, 9, 0}, {SHAPE_T, 0, 10, 1}, - {SHAPE_S_REVERSE, 0, 8, 1}, {SHAPE_T, 2, 8, 2}, + {SHAPE_SQUARE, 0, 16, 0}, {SHAPE_I, 2, 16, 0}, {SHAPE_L, 2, 15, 3}, + {SHAPE_L, 4, 15, 2}, {SHAPE_I, 1, 12, 2}, {SHAPE_I, 5, 12, 3}, + {SHAPE_S_REV, 0, 12, 0}, {SHAPE_L, 2, 11, 3}, {SHAPE_S_REV, 4, 9, 0}, + {SHAPE_T, 0, 10, 1}, {SHAPE_S_REV, 0, 8, 1}, {SHAPE_T, 2, 8, 2}, {SHAPE_MAX, 0, 0, 0}, }; @@ -197,15 +193,12 @@ static void draw_shape_full(enum shape shape, int y, int w, int h, - int rot, + enum rotation rot, unsigned char *buffer) { - unsigned int offset; - int area = w * h; + unsigned int offset = offs[shape][rot]; + const int area = w * h; - assert(rot >= 0 && rot <= 3); - - offset = offs[shape][rot]; for (int i = 0; i < 4; i++) { int y_off, x_off, dx, dy, index; @@ -223,8 +216,13 @@ static void draw_shape_full(enum shape shape, } } -static void -draw_shape(enum shape shape, int x, int y, int w, int h, int rot, unsigned char *buffer) +static void draw_shape(enum shape shape, + int x, + int y, + int w, + int h, + enum rotation rot, + unsigned char *buffer) { draw_shape_full(shape, colors[shape], x, y, w, h, rot, buffer); } @@ -266,26 +264,29 @@ uint64_t blocks_draw(struct blocks *blocks, bool odd_second) switch (rotations) { case 1: if (state->fall_index < curr->y_stop / 2) - rotations = 0; + rotations = ROT_0; break; case 2: if (state->fall_index < curr->y_stop / 3) - rotations = 0; + rotations = ROT_0; else if (state->fall_index < curr->y_stop / 3 * 2) - rotations = 1; + rotations = ROT_90; break; case 3: if (state->fall_index < curr->y_stop / 4) - rotations = 0; + rotations = ROT_0; else if (state->fall_index < curr->y_stop / 4 * 2) - rotations = 1; + rotations = ROT_90; else if (state->fall_index < curr->y_stop / 4 * 3) - rotations = 2; + rotations = ROT_180; break; } + assert(rotations >= 0 && rotations < ROT_MAX); + draw_shape(curr->shape, curr->x_pos + state->x_shift, - state->fall_index - 1, w, h, rotations, frame); + state->fall_index - 1, w, h, + (enum rotation)rotations, frame); state->fall_index++; if (state->fall_index > curr->y_stop) { @@ -307,8 +308,8 @@ uint64_t blocks_draw(struct blocks *blocks, bool odd_second) } if (odd_second) { - draw_shape_full(SHAPE_SQUARE, COLOR_WHITE, 15, 13, w, h, 0, frame); - draw_shape_full(SHAPE_SQUARE, COLOR_WHITE, 15, 9, w, h, 0, frame); + draw_shape_full(SHAPE_SQUARE, COLOR_WHITE, 15, 13, w, h, ROT_0, frame); + draw_shape_full(SHAPE_SQUARE, COLOR_WHITE, 15, 9, w, h, ROT_0, frame); } return digits_fallen ? 100 : 500; From 2f7bad67bc6feff8af5ce3fe6705746f717fcf58 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 8 Oct 2018 20:28:48 -0700 Subject: [PATCH 0862/2505] Refactor header parsing code This cleans up a lot of the code for parsing headers, making it more idiomatic and reducing amount of repeated code due to extensive use of macros. By splitting the loop in two, this not only opens the opportunity to use SIMD to find the start and end pointers for every header, this also makes it possible to reduce calls to memchr() and the likes, making the parser also faster in the end. This also removes calls to unbounded string search functions, and gets rid of some undefined behavior. Also, by using compound statements, the implicit assignment of value and length is removed, making the code a lot easier to read and maintain. All tests are passing, even those for pipelined requests. The only drawback is that the current version limits each request to 32 headers. --- src/lib/lwan-request.c | 115 +++++++++++++++++++---------------------- 1 file changed, 53 insertions(+), 62 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 6648e7dce..c257a7218 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -487,105 +487,96 @@ identify_http_path(struct lwan_request *request, char *buffer, return end_of_line + 1; } -#define MATCH_HEADER(hdr) \ - do { \ - p += sizeof(hdr) - 1; \ - if (UNLIKELY(p >= buffer_end)) /* reached the end of header blocks */ \ - return NULL; \ - \ - if (UNLIKELY(string_as_int16(p) != MULTICHAR_CONSTANT_SMALL(':', ' '))) \ - goto did_not_match; \ - p += 2; \ - \ - char *end = strchr(p, '\r'); \ - if (UNLIKELY(!end)) \ - goto did_not_match; \ - \ - *end = '\0'; \ - value = p; \ - length = (size_t)(end - value); \ - \ - p = end + 1; \ - if (UNLIKELY(*p != '\n')) \ - goto did_not_match; \ - } while (0) - -#define CASE_HEADER(hdr_const,hdr_name) \ - case hdr_const: MATCH_HEADER(hdr_name); - -static char * -parse_headers(struct request_parser_helper *helper, char *buffer, char *buffer_end) +#define HEADER(hdr) \ + ({ \ + p += sizeof(hdr) - 1; \ + if (UNLIKELY(string_as_int16(p) != \ + MULTICHAR_CONSTANT_SMALL(':', ' '))) \ + continue; \ + char *value = p + sizeof(": ") - 1; \ + (struct lwan_value){.value = value, .len = (size_t)(end - value)}; \ + }) + +static char *parse_headers(struct request_parser_helper *helper, + char *buffer, + char *buffer_end) { - for (char *p = buffer; *p; buffer = ++p) { - char *value; - size_t length; + char *header_start[64]; + size_t n_headers = 0; + + for (char *p = buffer; n_headers < N_ELEMENTS(header_start);) { + char *next_chr = p + 1; + char *next_hdr = memchr(next_chr, '\r', (size_t)(buffer_end - p)); + + if (!next_hdr) + break; + + header_start[n_headers++] = next_chr; + header_start[n_headers++] = next_hdr; - if ((p + sizeof(int32_t)) >= buffer_end) + if (next_hdr == next_chr) break; + *next_hdr = '\0'; + p = next_hdr + 1; + } + + for (size_t i = 0; i < n_headers; i += 2) { + char *p = header_start[i]; + char *end = header_start[i + 1]; + STRING_SWITCH_L(p) { case MULTICHAR_CONSTANT_L('A','c','c','e'): p += sizeof("Accept") - 1; STRING_SWITCH_L(p) { - CASE_HEADER(MULTICHAR_CONSTANT_L('-','E','n','c'), "-Encoding") - helper->accept_encoding.value = value; - helper->accept_encoding.len = length; + case MULTICHAR_CONSTANT_L('-','E','n','c'): + helper->accept_encoding = HEADER("-Encoding"); break; } break; - CASE_HEADER(MULTICHAR_CONSTANT_L('A','u','t','h'), "Authorization") - helper->authorization.value = value; - helper->authorization.len = length; + case MULTICHAR_CONSTANT_L('A','u','t','h'): + helper->authorization = HEADER("Authorization"); break; - CASE_HEADER(MULTICHAR_CONSTANT_L('C','o','n','n'), "Connection") - helper->connection = (*value | 0x20); + case MULTICHAR_CONSTANT_L('C','o','n','n'): { + struct lwan_value conn = HEADER("Connection"); + helper->connection = (*conn.value | 0x20); break; + } case MULTICHAR_CONSTANT_L('C','o','n','t'): p += sizeof("Content") - 1; STRING_SWITCH_L(p) { - CASE_HEADER(MULTICHAR_CONSTANT_L('-','T','y','p'), "-Type") - helper->content_type.value = value; - helper->content_type.len = length; + case MULTICHAR_CONSTANT_L('-','T','y','p'): + helper->content_type = HEADER("-Type"); break; - CASE_HEADER(MULTICHAR_CONSTANT_L('-','L','e','n'), "-Length") - helper->content_length.value = value; - helper->content_length.len = length; + case MULTICHAR_CONSTANT_L('-','L','e','n'): + helper->content_length = HEADER("-Length"); break; } break; - CASE_HEADER(MULTICHAR_CONSTANT_L('C','o','o','k'), "Cookie") - helper->cookie.value = value; - helper->cookie.len = length; + case MULTICHAR_CONSTANT_L('C','o','o','k'): + helper->cookie = HEADER("Cookie"); break; - CASE_HEADER(MULTICHAR_CONSTANT_L('I','f','-','M'), "If-Modified-Since") - helper->if_modified_since.value = value; - helper->if_modified_since.len = length; + case MULTICHAR_CONSTANT_L('I','f','-','M'): + helper->if_modified_since = HEADER("If-Modified-Since"); break; - CASE_HEADER(MULTICHAR_CONSTANT_L('R','a','n','g'), "Range") - helper->range.value = value; - helper->range.len = length; + case MULTICHAR_CONSTANT_L('R','a','n','g'): + helper->range = HEADER("Range"); break; default: STRING_SWITCH_SMALL(p) { case MULTICHAR_CONSTANT_SMALL('\r','\n'): - *p = '\0'; helper->next_request = p + sizeof("\r\n") - 1; return p; } } -did_not_match: - p = memchr(p, '\n', (size_t)(buffer_end - p)); - if (!p) - break; } return buffer; } -#undef CASE_HEADER -#undef MATCH_HEADER +#undef HEADER static void parse_if_modified_since(struct lwan_request *request, struct request_parser_helper *helper) From 94dcd818b5e8fd96e119eb6ee50d88ad0b77b44c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 9 Oct 2018 07:18:38 -0700 Subject: [PATCH 0863/2505] Comment struct request_parser_helper --- src/lib/lwan-request.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index c257a7218..9e7751cb8 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -49,25 +49,25 @@ enum lwan_read_finalizer { }; struct request_parser_helper { - struct lwan_value *buffer; + struct lwan_value *buffer; /* The whole request buffer */ char *next_request; /* For pipelined requests */ - struct lwan_value accept_encoding; - struct lwan_value if_modified_since; - struct lwan_value range; - struct lwan_value cookie; - - struct lwan_value query_string; - struct lwan_value fragment; - struct lwan_value content_length; - struct lwan_value authorization; - - struct lwan_value post_data; - struct lwan_value content_type; - - time_t error_when_time; - int error_when_n_packets; - int urls_rewritten; - char connection; + struct lwan_value accept_encoding; /* Accept-Encoding: */ + struct lwan_value if_modified_since;/* If-Modified-Since: */ + struct lwan_value range; /* Range: */ + struct lwan_value cookie; /* Cookie: */ + + struct lwan_value query_string; /* Stuff after ? and before # */ + struct lwan_value fragment; /* Stuff after # */ + struct lwan_value content_length; /* Content-Length: */ + struct lwan_value authorization; /* Authorization: */ + + struct lwan_value post_data; /* Request body for POST */ + struct lwan_value content_type; /* Content-Type: for POST */ + + time_t error_when_time; /* Time to abort request read */ + int error_when_n_packets; /* Max. number of packets */ + int urls_rewritten; /* Times URLs have been rewritten */ + char connection; /* k=keep-alive, c=close, u=upgrade */ }; struct proxy_header_v2 { From 87126f32ca28bbc1715e5801fed47a3f3c2ad5a3 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 9 Oct 2018 08:28:25 -0700 Subject: [PATCH 0864/2505] Simplify parsing of Connection header Only one character is saved for this header: no need to allocate a struct lwan_value temporarily to obtain the first character from the header just to throw it away immediately after. Provide a HEADER_RAW macro that does most of the processing of HEADER, but returns the pointer to the header value rather than a struct lwan_value. Then define HEADER in terms of HEADER_RAW. --- src/lib/lwan-request.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 9e7751cb8..f0804e2bb 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -487,13 +487,18 @@ identify_http_path(struct lwan_request *request, char *buffer, return end_of_line + 1; } -#define HEADER(hdr) \ +#define HEADER_RAW(hdr) \ ({ \ p += sizeof(hdr) - 1; \ if (UNLIKELY(string_as_int16(p) != \ MULTICHAR_CONSTANT_SMALL(':', ' '))) \ continue; \ - char *value = p + sizeof(": ") - 1; \ + p + sizeof(": ") - 1; \ + }) + +#define HEADER(hdr) \ + ({ \ + char *value = HEADER_RAW(hdr); \ (struct lwan_value){.value = value, .len = (size_t)(end - value)}; \ }) @@ -538,11 +543,9 @@ static char *parse_headers(struct request_parser_helper *helper, case MULTICHAR_CONSTANT_L('A','u','t','h'): helper->authorization = HEADER("Authorization"); break; - case MULTICHAR_CONSTANT_L('C','o','n','n'): { - struct lwan_value conn = HEADER("Connection"); - helper->connection = (*conn.value | 0x20); + case MULTICHAR_CONSTANT_L('C','o','n','n'): + helper->connection = *HEADER_RAW("Connection") | 0x20; break; - } case MULTICHAR_CONSTANT_L('C','o','n','t'): p += sizeof("Content") - 1; @@ -576,6 +579,7 @@ static char *parse_headers(struct request_parser_helper *helper, return buffer; } +#undef HEADER_RAW #undef HEADER static void From 32e9fa188dfb551ca649185cfaff73552682dcca Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 9 Oct 2018 20:39:09 -0700 Subject: [PATCH 0865/2505] No need to mention glibc/Linux in README.md anymore Lwan is quite portable now, running on Linux, FreeBSD, OpenBSD, and macOS. --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 210ad2101..8e21c2192 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ Lwan Web Server =============== -Lwan is a **high-performance** & **scalable** web server for glibc/Linux -platforms. +Lwan is a **high-performance** & **scalable** web server. The [project web site](https://lwan.ws/) contains more details. From c87880552a7324d0ab6f60c089aa57539f58b6d4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 12 Oct 2018 20:23:09 -0700 Subject: [PATCH 0866/2505] Only write NUL terminator when header is actually matched Reduces memory writes in the fast path. --- src/lib/lwan-request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index f0804e2bb..deca7f77f 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -493,6 +493,7 @@ identify_http_path(struct lwan_request *request, char *buffer, if (UNLIKELY(string_as_int16(p) != \ MULTICHAR_CONSTANT_SMALL(':', ' '))) \ continue; \ + *end = '\0'; \ p + sizeof(": ") - 1; \ }) @@ -522,7 +523,6 @@ static char *parse_headers(struct request_parser_helper *helper, if (next_hdr == next_chr) break; - *next_hdr = '\0'; p = next_hdr + 1; } From 3d81bd8ac2c9b442283581d54bca1200ef0690a9 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 12 Oct 2018 20:23:39 -0700 Subject: [PATCH 0867/2505] Reduce number of pointer increments while looking for headers --- src/lib/lwan-request.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index deca7f77f..a6d82c838 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -510,8 +510,8 @@ static char *parse_headers(struct request_parser_helper *helper, char *header_start[64]; size_t n_headers = 0; - for (char *p = buffer; n_headers < N_ELEMENTS(header_start);) { - char *next_chr = p + 1; + for (char *p = buffer + 1; n_headers < N_ELEMENTS(header_start);) { + char *next_chr = p; char *next_hdr = memchr(next_chr, '\r', (size_t)(buffer_end - p)); if (!next_hdr) @@ -523,7 +523,7 @@ static char *parse_headers(struct request_parser_helper *helper, if (next_hdr == next_chr) break; - p = next_hdr + 1; + p = next_hdr + 2; } for (size_t i = 0; i < n_headers; i += 2) { From 3fa60538bcc801076b76bc9ac611dac97a363a58 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 13 Oct 2018 09:14:03 -0700 Subject: [PATCH 0868/2505] Avoid ring buffer getting full while parsing template action Return to the lexer main loop as soon as a token has ben emitted. --- src/lib/lwan-template.c | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index 4eaea2e7c..736d591cc 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -427,32 +427,42 @@ static void *lex_inside_action(struct lexer *lexer) return lex_right_meta; r = next(lexer); - if (r == EOF) + switch (r) { + case EOF: return lex_error(lexer, "unexpected EOF while scanning action"); - if (r == '\n') + case '\n': return lex_error(lexer, "actions cannot span multiple lines"); - - if (isspace(r)) { - ignore(lexer); - } else if (r == '#') { + case '#': emit(lexer, LEXEME_HASH); - } else if (r == '?') { + break; + case '?': emit(lexer, LEXEME_QUESTION_MARK); - } else if (r == '^') { + break; + case '^': emit(lexer, LEXEME_HAT); - } else if (r == '>') { + break; + case '>': emit(lexer, LEXEME_GREATER_THAN); return lex_partial; - } else if (r == '{') { + case '{': return lex_quoted_identifier; - } else if (r == '/') { + case '/': emit(lexer, LEXEME_SLASH); - } else if (isident(r)) { - backup(lexer); - return lex_identifier; - } else { + break; + default: + if (isspace(r)) { + ignore(lexer); + continue; + } + if (isident(r)) { + backup(lexer); + return lex_identifier; + } + return lex_error(lexer, "unexpected character: %c", r); } + + return lex_inside_action; } } From 4e6b18ef107fa48d04b2eec2eb7d8c14943cb2f2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 13 Oct 2018 13:28:13 -0700 Subject: [PATCH 0869/2505] Fix compile time warning on OpenBSD --- src/lib/missing/pthread.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/missing/pthread.h b/src/lib/missing/pthread.h index 5b3e5e6ec..4a900713f 100644 --- a/src/lib/missing/pthread.h +++ b/src/lib/missing/pthread.h @@ -36,12 +36,12 @@ int pthread_barrier_destroy(pthread_barrier_t *barrier); int pthread_barrier_wait(pthread_barrier_t *barrier); #endif -#ifndef HAVE_PTHREAD_SET_NAME_NP -int pthread_set_name_np(pthread_t thread, const char *name); +#if defined(__FreeBSD__) || defined(__OpenBSD__) +#include #endif -#ifdef __FreeBSD__ -#include +#ifndef HAVE_PTHREAD_SET_NAME_NP +int pthread_set_name_np(pthread_t thread, const char *name); #endif #endif /* MISSING_PTHREAD_H */ From c22a60fcf5358e3f613d87d99e82cf3401dda11d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 14 Oct 2018 07:57:48 -0700 Subject: [PATCH 0870/2505] Reduce number of config_line copies per call to config_read_line() --- src/lib/lwan-config.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 8b90b2b5b..c0cf70466 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -708,17 +708,10 @@ void config_close(struct config *config) bool config_read_line(struct config *conf, struct config_line *cl) { - struct config_line *ptr; - bool ret; - if (conf->error_message) return false; - ret = parser_next(&conf->parser, &ptr); - if (ret) - *cl = *ptr; - - return ret; + return parser_next(&conf->parser, &cl); } static bool find_section_end(struct config *config) From c2251b5eede23794c1d08ff5966a95744de67458 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 14 Oct 2018 08:03:43 -0700 Subject: [PATCH 0871/2505] Remove `stream.priv` pointer from lwan_response This was not being used any longer. Saves 8 bytes per request struct. --- src/lib/lwan-mod-serve-files.c | 3 +-- src/lib/lwan.h | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 98bf26317..3e43729b1 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -1100,8 +1100,7 @@ serve_files_handle_request(struct lwan_request *request, response->mime_type = fce->mime_type; response->stream.callback = fce->funcs->serve; - response->stream.data = ce; - response->stream.priv = priv; + response->stream.data = fce; return HTTP_OK; diff --git a/src/lib/lwan.h b/src/lib/lwan.h index b03dda89b..4e7d59105 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -263,7 +263,6 @@ struct lwan_response { struct { enum lwan_http_status (*callback)(struct lwan_request *request, void *data); void *data; - void *priv; } stream; }; From 1bd8d1243f03f9e4cf304b2df9df82cbd7b4e054 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 14 Oct 2018 08:09:47 -0700 Subject: [PATCH 0872/2505] Reindent lwan.h --- src/lib/lwan.h | 212 +++++++++++++++++++++++++++---------------------- 1 file changed, 115 insertions(+), 97 deletions(-) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 4e7d59105..3f165aebb 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -14,30 +14,31 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #pragma once -#if defined (__cplusplus) +#if defined(__cplusplus) extern "C" { #endif +#include #include #include #include #include -#include #include "hash.h" +#include "queue.h" +#include "timeout.h" #include "lwan-array.h" #include "lwan-config.h" #include "lwan-coro.h" #include "lwan-status.h" -#include "lwan-trie.h" #include "lwan-strbuf.h" -#include "queue.h" -#include "timeout.h" +#include "lwan-trie.h" #define DEFAULT_BUFFER_SIZE 4096 #define DEFAULT_HEADERS_SIZE 512 @@ -50,15 +51,15 @@ extern "C" { extern const struct lwan_module_info lwan_module_info_##name_; #ifdef __APPLE__ -# define LWAN_SECTION_NAME(name_) "__DATA," # name_ +#define LWAN_SECTION_NAME(name_) "__DATA," #name_ #else -# define LWAN_SECTION_NAME(name_) #name_ +#define LWAN_SECTION_NAME(name_) #name_ #endif #define LWAN_REGISTER_MODULE(name_, module_) \ - const struct lwan_module_info \ - __attribute__((used, section(LWAN_SECTION_NAME(lwan_module)))) \ - lwan_module_info_##name_ = {.name = #name_, .module = module_} + const struct lwan_module_info \ + __attribute__((used, section(LWAN_SECTION_NAME(lwan_module)))) \ + lwan_module_info_##name_ = {.name = #name_, .module = module_} #define LWAN_HANDLER_REF(name_) lwan_handler_##name_ @@ -81,41 +82,41 @@ extern "C" { LWAN_HANDLER_DEFINE(name_) #ifdef DISABLE_INLINE_FUNCTIONS -# define ALWAYS_INLINE +#define ALWAYS_INLINE #else -# define ALWAYS_INLINE inline __attribute__((always_inline)) +#define ALWAYS_INLINE inline __attribute__((always_inline)) #endif #ifdef DISABLE_BRANCH_PREDICTION -# define LIKELY_IS(x,y) (x) +#define LIKELY_IS(x, y) (x) #else -# define LIKELY_IS(x,y) __builtin_expect((x), (y)) +#define LIKELY_IS(x, y) __builtin_expect((x), (y)) #endif #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ -# define MULTICHAR_CONSTANT(a,b,c,d) \ +#define MULTICHAR_CONSTANT(a, b, c, d) \ ((int32_t)((a) | (b) << 8 | (c) << 16 | (d) << 24)) -# define MULTICHAR_CONSTANT_SMALL(a,b) \ - ((int16_t)((a) | (b) << 8)) -# define MULTICHAR_CONSTANT_LARGE(a,b,c,d,e,f,g,h) \ - ((int64_t)MULTICHAR_CONSTANT(a,b,c,d) | (int64_t)MULTICHAR_CONSTANT(e,f,g,h)<<32) +#define MULTICHAR_CONSTANT_SMALL(a, b) ((int16_t)((a) | (b) << 8)) +#define MULTICHAR_CONSTANT_LARGE(a, b, c, d, e, f, g, h) \ + ((int64_t)MULTICHAR_CONSTANT(a, b, c, d) | \ + (int64_t)MULTICHAR_CONSTANT(e, f, g, h) << 32) #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ -# define MULTICHAR_CONSTANT(d,c,b,a) \ +#define MULTICHAR_CONSTANT(d, c, b, a) \ ((int32_t)((a) | (b) << 8 | (c) << 16 | (d) << 24)) -# define MULTICHAR_CONSTANT_SMALL(b,a) \ - ((int16_t)((a) | (b) << 8)) -# define MULTICHAR_CONSTANT_LARGE(a,b,c,d,e,f,g,h) \ - ((int64_t)MULTICHAR_CONSTANT(a,b,c,d)<<32 | (int64_t)MULTICHAR_CONSTANT(e,f,g,h)) +#define MULTICHAR_CONSTANT_SMALL(b, a) ((int16_t)((a) | (b) << 8)) +#define MULTICHAR_CONSTANT_LARGE(a, b, c, d, e, f, g, h) \ + ((int64_t)MULTICHAR_CONSTANT(a, b, c, d) << 32 | \ + (int64_t)MULTICHAR_CONSTANT(e, f, g, h)) #elif __BYTE_ORDER__ == __ORDER_PDP_ENDIAN__ -# error A PDP? Seriously? +#error A PDP? Seriously? #endif -#define MULTICHAR_CONSTANT_L(a,b,c,d) \ - (MULTICHAR_CONSTANT(a,b,c,d) | 0x20202020) -#define MULTICHAR_CONSTANT_SMALL_L(a,b) \ - (MULTICHAR_CONSTANT_SMALL(a,b) | 0x2020) -#define MULTICHAR_CONSTANT_LARGE_L(a,b,c,d,e,f,g,h) \ - (MULTICHAR_CONSTANT_LARGE(a,b,c,d,e,f,g,h) | 0x2020202020202020) +#define MULTICHAR_CONSTANT_L(a, b, c, d) \ + (MULTICHAR_CONSTANT(a, b, c, d) | 0x20202020) +#define MULTICHAR_CONSTANT_SMALL_L(a, b) \ + (MULTICHAR_CONSTANT_SMALL(a, b) | 0x2020) +#define MULTICHAR_CONSTANT_LARGE_L(a, b, c, d, e, f, g, h) \ + (MULTICHAR_CONSTANT_LARGE(a, b, c, d, e, f, g, h) | 0x2020202020202020) static ALWAYS_INLINE int64_t string_as_int64(const char *s) { @@ -145,21 +146,22 @@ static ALWAYS_INLINE int16_t string_as_int16(const char *s) #define STRING_SWITCH_SMALL_L(s) switch (string_as_int16(s) | 0x2020) #define STRING_SWITCH_LARGE(s) switch (string_as_int64(s)) -#define STRING_SWITCH_LARGE_L(s) switch (string_as_int64(s) | 0x2020202020202020) +#define STRING_SWITCH_LARGE_L(s) \ + switch (string_as_int64(s) | 0x2020202020202020) -#define LIKELY(x) LIKELY_IS(!!(x), 1) -#define UNLIKELY(x) LIKELY_IS((x), 0) +#define LIKELY(x) LIKELY_IS(!!(x), 1) +#define UNLIKELY(x) LIKELY_IS((x), 0) -#define ATOMIC_READ(V) (*(volatile typeof(V) *)&(V)) -#define ATOMIC_AAF(P, V) (__sync_add_and_fetch((P), (V))) -#define ATOMIC_INC(V) ATOMIC_AAF(&(V), 1) -#define ATOMIC_DEC(V) ATOMIC_AAF(&(V), -1) +#define ATOMIC_READ(V) (*(volatile typeof(V) *)&(V)) +#define ATOMIC_AAF(P, V) (__sync_add_and_fetch((P), (V))) +#define ATOMIC_INC(V) ATOMIC_AAF(&(V), 1) +#define ATOMIC_DEC(V) ATOMIC_AAF(&(V), -1) #define ATOMIC_BITWISE(P, O, V) (__sync_##O##_and_fetch((P), (V))) -#if defined (__cplusplus) +#if defined(__cplusplus) #define ENFORCE_STATIC_BUFFER_LENGTH #else -#define ENFORCE_STATIC_BUFFER_LENGTH static +#define ENFORCE_STATIC_BUFFER_LENGTH static #endif enum lwan_http_status { @@ -185,22 +187,22 @@ enum lwan_http_status { }; enum lwan_handler_flags { - HANDLER_PARSE_QUERY_STRING = 1<<0, - HANDLER_PARSE_IF_MODIFIED_SINCE = 1<<1, - HANDLER_PARSE_RANGE = 1<<2, - HANDLER_PARSE_ACCEPT_ENCODING = 1<<3, - HANDLER_PARSE_POST_DATA = 1<<4, - HANDLER_MUST_AUTHORIZE = 1<<5, - HANDLER_REMOVE_LEADING_SLASH = 1<<6, - HANDLER_CAN_REWRITE_URL = 1<<7, - HANDLER_PARSE_COOKIES = 1<<8, - HANDLER_DATA_IS_HASH_TABLE = 1<<9, - - HANDLER_PARSE_MASK = 1<<0 | 1<<1 | 1<<2 | 1<<3 | 1<<4 | 1<<8 + HANDLER_PARSE_QUERY_STRING = 1 << 0, + HANDLER_PARSE_IF_MODIFIED_SINCE = 1 << 1, + HANDLER_PARSE_RANGE = 1 << 2, + HANDLER_PARSE_ACCEPT_ENCODING = 1 << 3, + HANDLER_PARSE_POST_DATA = 1 << 4, + HANDLER_MUST_AUTHORIZE = 1 << 5, + HANDLER_REMOVE_LEADING_SLASH = 1 << 6, + HANDLER_CAN_REWRITE_URL = 1 << 7, + HANDLER_PARSE_COOKIES = 1 << 8, + HANDLER_DATA_IS_HASH_TABLE = 1 << 9, + + HANDLER_PARSE_MASK = 1 << 0 | 1 << 1 | 1 << 2 | 1 << 3 | 1 << 4 | 1 << 8 }; enum lwan_request_flags { - REQUEST_ALL_FLAGS = -1, + REQUEST_ALL_FLAGS = -1, /* Shift values to make easier to build flags while booting a * request-processing coroutine. @@ -211,35 +213,35 @@ enum lwan_request_flags { REQUEST_ALLOW_PROXY_REQS_SHIFT = 6, REQUEST_ALLOW_CORS_SHIFT = 8, - REQUEST_METHOD_MASK = 1<<0 | 1<<1 | 1<<2, - REQUEST_METHOD_GET = 1<<0, - REQUEST_METHOD_POST = 1<<1, - REQUEST_METHOD_HEAD = 1<<0 | 1<<1, - REQUEST_METHOD_OPTIONS = 1<<2, - REQUEST_METHOD_DELETE = 1<<2 | 1<<0, - - REQUEST_ACCEPT_DEFLATE = 1<<3, - REQUEST_ACCEPT_GZIP = 1<<4, - REQUEST_IS_HTTP_1_0 = 1<<5, - REQUEST_ALLOW_PROXY_REQS = 1<flags & REQUEST_METHOD_MASK); } -#if defined (__cplusplus) +#if defined(__cplusplus) } #endif From f87d9be37c6617686fd52740c15723862321dd3c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 14 Oct 2018 08:10:55 -0700 Subject: [PATCH 0873/2505] Reindent lwan-mod-serve-files.c --- src/lib/lwan-mod-serve-files.c | 73 +++++++++++++++++++++------------- 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 3e43729b1..d042f6f2d 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -32,12 +32,12 @@ #include "lwan-private.h" #include "hash.h" +#include "realpathat.h" #include "lwan-cache.h" #include "lwan-config.h" #include "lwan-io-wrappers.h" #include "lwan-mod-serve-files.h" #include "lwan-template.h" -#include "realpathat.h" #include "auto-index-icons.h" @@ -69,8 +69,10 @@ struct serve_files_priv { struct cache_funcs { enum lwan_http_status (*serve)(struct lwan_request *request, void *data); - bool (*init)(struct file_cache_entry *ce, struct serve_files_priv *priv, - const char *full_path, struct stat *st); + bool (*init)(struct file_cache_entry *ce, + struct serve_files_priv *priv, + const char *full_path, + struct stat *st); void (*free)(void *data); size_t struct_size; }; @@ -131,28 +133,32 @@ struct file_list { static int directory_list_generator(struct coro *coro, void *data); static bool mmap_init(struct file_cache_entry *ce, - struct serve_files_priv *priv, const char *full_path, + struct serve_files_priv *priv, + const char *full_path, struct stat *st); static void mmap_free(void *data); static enum lwan_http_status mmap_serve(struct lwan_request *request, void *data); static bool sendfile_init(struct file_cache_entry *ce, - struct serve_files_priv *priv, const char *full_path, + struct serve_files_priv *priv, + const char *full_path, struct stat *st); static void sendfile_free(void *data); static enum lwan_http_status sendfile_serve(struct lwan_request *request, void *data); static bool dirlist_init(struct file_cache_entry *ce, - struct serve_files_priv *priv, const char *full_path, + struct serve_files_priv *priv, + const char *full_path, struct stat *st); static void dirlist_free(void *data); static enum lwan_http_status dirlist_serve(struct lwan_request *request, void *data); static bool redir_init(struct file_cache_entry *ce, - struct serve_files_priv *priv, const char *full_path, + struct serve_files_priv *priv, + const char *full_path, struct stat *st); static void redir_free(void *data); static enum lwan_http_status redir_serve(struct lwan_request *request, @@ -346,7 +352,8 @@ static void compress_cached_entry(struct mmap_cache_data *md) } static bool mmap_init(struct file_cache_entry *ce, - struct serve_files_priv *priv, const char *full_path, + struct serve_files_priv *priv, + const char *full_path, struct stat *st) { struct mmap_cache_data *md = (struct mmap_cache_data *)(ce + 1); @@ -445,7 +452,8 @@ static int try_open_compressed(const char *relpath, } static bool sendfile_init(struct file_cache_entry *ce, - struct serve_files_priv *priv, const char *full_path, + struct serve_files_priv *priv, + const char *full_path, struct stat *st) { struct sendfile_cache_data *sd = (struct sendfile_cache_data *)(ce + 1); @@ -506,7 +514,8 @@ static const char *get_rel_path(const char *full_path, } static bool dirlist_init(struct file_cache_entry *ce, - struct serve_files_priv *priv, const char *full_path, + struct serve_files_priv *priv, + const char *full_path, struct stat *st __attribute__((unused))) { struct dir_list_cache_data *dd = (struct dir_list_cache_data *)(ce + 1); @@ -516,7 +525,8 @@ static bool dirlist_init(struct file_cache_entry *ce, if (!lwan_strbuf_init(&dd->rendered)) return false; - if (!lwan_tpl_apply_with_buffer(priv->directory_list_tpl, &dd->rendered, &vars)) { + if (!lwan_tpl_apply_with_buffer(priv->directory_list_tpl, &dd->rendered, + &vars)) { lwan_strbuf_free(&dd->rendered); return false; } @@ -527,7 +537,8 @@ static bool dirlist_init(struct file_cache_entry *ce, } static bool redir_init(struct file_cache_entry *ce, - struct serve_files_priv *priv, const char *full_path, + struct serve_files_priv *priv, + const char *full_path, struct stat *st __attribute__((unused))) { struct redir_cache_data *rd = (struct redir_cache_data *)(ce + 1); @@ -540,7 +551,8 @@ static bool redir_init(struct file_cache_entry *ce, } static const struct cache_funcs *get_funcs(struct serve_files_priv *priv, - const char *key, char *full_path, + const char *key, + char *full_path, struct stat *st) { char index_html_path_buf[PATH_MAX]; @@ -611,7 +623,8 @@ static const struct cache_funcs *get_funcs(struct serve_files_priv *priv, static struct file_cache_entry * create_cache_entry_from_funcs(struct serve_files_priv *priv, - const char *full_path, struct stat *st, + const char *full_path, + struct stat *st, const struct cache_funcs *funcs) { struct file_cache_entry *fce; @@ -792,7 +805,7 @@ static void *serve_files_create(const char *prefix, void *args) } static void *serve_files_create_from_hash(const char *prefix, - const struct hash *hash) + const struct hash *hash) { struct lwan_serve_files_settings settings = { .root_path = hash_find(hash, "path"), @@ -801,7 +814,8 @@ static void *serve_files_create_from_hash(const char *prefix, parse_bool(hash_find(hash, "serve_precompressed_files"), true), .auto_index = parse_bool(hash_find(hash, "auto_index"), true), .directory_list_template = hash_find(hash, "directory_list_template"), - .read_ahead = (size_t)parse_long("read_ahead", SERVE_FILES_READ_AHEAD_BYTES), + .read_ahead = + (size_t)parse_long("read_ahead", SERVE_FILES_READ_AHEAD_BYTES), }; return serve_files_create(prefix, &settings); @@ -833,8 +847,10 @@ static ALWAYS_INLINE bool client_has_fresh_content(struct lwan_request *request, static size_t prepare_headers(struct lwan_request *request, enum lwan_http_status return_status, - struct file_cache_entry *fce, size_t size, - const char *compression_type, char *header_buf, + struct file_cache_entry *fce, + size_t size, + const char *compression_type, + char *header_buf, size_t header_buf_size) { struct lwan_key_value additional_headers[3] = { @@ -844,9 +860,8 @@ static size_t prepare_headers(struct lwan_request *request, request->response.content_length = size; if (compression_type) { - additional_headers[1] = (struct lwan_key_value) { - .key = "Content-Encoding", .value = (char *)compression_type - }; + additional_headers[1] = (struct lwan_key_value){ + .key = "Content-Encoding", .value = (char *)compression_type}; } return lwan_prepare_response_header_full(request, return_status, header_buf, @@ -881,7 +896,7 @@ compute_range(struct lwan_request *request, off_t *from, off_t *to, off_t size) /* t < 0: ranges from f to the file size */ if (t < 0) { - *to = size; + *to = size; } else { if (UNLIKELY(__builtin_sub_overflow(t, f, to))) return HTTP_RANGE_UNSATISFIABLE; @@ -953,7 +968,8 @@ static enum lwan_http_status sendfile_serve(struct lwan_request *request, static enum lwan_http_status serve_buffer(struct lwan_request *request, struct file_cache_entry *fce, const char *compression_type, - const void *contents, size_t size, + const void *contents, + size_t size, enum lwan_http_status return_status) { char headers[DEFAULT_BUFFER_SIZE]; @@ -970,7 +986,8 @@ static enum lwan_http_status serve_buffer(struct lwan_request *request, } else { struct iovec response_vec[] = { {.iov_base = headers, .iov_len = header_len}, - {.iov_base = (void *)contents, .iov_len = size}}; + {.iov_base = (void *)contents, .iov_len = size}, + }; lwan_writev(request, response_vec, N_ELEMENTS(response_vec)); } @@ -1044,7 +1061,8 @@ static enum lwan_http_status dirlist_serve(struct lwan_request *request, return HTTP_NOT_FOUND; } - return serve_buffer(request, fce, compression_none, contents, size, HTTP_OK); + return serve_buffer(request, fce, compression_none, contents, size, + HTTP_OK); } static enum lwan_http_status redir_serve(struct lwan_request *request, @@ -1078,7 +1096,8 @@ static enum lwan_http_status redir_serve(struct lwan_request *request, static enum lwan_http_status serve_files_handle_request(struct lwan_request *request, - struct lwan_response *response, void *instance) + struct lwan_response *response, + void *instance) { struct serve_files_priv *priv = instance; enum lwan_http_status return_status; @@ -1116,7 +1135,7 @@ static const struct lwan_module module = { .handle_request = serve_files_handle_request, .flags = HANDLER_REMOVE_LEADING_SLASH | HANDLER_PARSE_IF_MODIFIED_SINCE | HANDLER_PARSE_RANGE | HANDLER_PARSE_ACCEPT_ENCODING | - HANDLER_PARSE_QUERY_STRING + HANDLER_PARSE_QUERY_STRING, }; LWAN_REGISTER_MODULE(serve_files, &module); From 0d6efa9dca89cf77669efa5951c425173c0cb7ff Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 14 Oct 2018 08:19:45 -0700 Subject: [PATCH 0874/2505] Use a macro to build initial request processing flags It's UB to shift a boolean integer, so the macro casts the boolean to an uint32_t before shifting. Also, do a static_asserto to ensure that enum_request_flags has the same size as a uint32_t. --- src/lib/lwan-thread.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index b48d00470..3964de399 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -133,6 +133,12 @@ static ALWAYS_INLINE void destroy_coro(struct death_queue *dq, static ALWAYS_INLINE int min(const int a, const int b) { return a < b ? a : b; } +#define REQUEST_FLAG(bool_, name_) \ + ((enum lwan_request_flags)(((uint32_t)lwan->config.bool_) \ + << REQUEST_##name_##_SHIFT)) +static_assert(sizeof(enum lwan_request_flags) == sizeof(uint32_t), + "lwan_request_flags has the same size as uint32_t"); + __attribute__((noreturn)) static int process_request_coro(struct coro *coro, void *data) { @@ -157,8 +163,8 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, } coro_defer(coro, CORO_DEFER(lwan_strbuf_free), &strbuf); - flags |= lwan->config.proxy_protocol << REQUEST_ALLOW_PROXY_REQS_SHIFT | - lwan->config.allow_cors << REQUEST_ALLOW_CORS_SHIFT; + flags |= REQUEST_FLAG(proxy_protocol, ALLOW_PROXY_REQS) | + REQUEST_FLAG(allow_cors, ALLOW_CORS); while (true) { struct lwan_request request = {.conn = conn, @@ -181,6 +187,8 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, } } +#undef REQUEST_FLAG + static void update_epoll_flags(struct death_queue *dq, struct lwan_connection *conn, int epoll_fd, From cd70564e2cd45a2a3b5c9a5f130c6d3d3c49bd31 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 14 Oct 2018 08:31:27 -0700 Subject: [PATCH 0875/2505] Reindent response macros --- src/lib/lwan-response.c | 66 ++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 31304c45f..67347f318 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -216,39 +216,39 @@ lwan_default_response(struct lwan_request *request, enum lwan_http_status status lwan_response(request, status); } -#define RETURN_0_ON_OVERFLOW(len_) \ - if (UNLIKELY(p_headers + (len_) >= p_headers_end)) return 0 - -#define APPEND_STRING_LEN(const_str_,len_) \ - do { \ - RETURN_0_ON_OVERFLOW(len_); \ - p_headers = mempcpy(p_headers, (const_str_), (len_)); \ - } while(0) - -#define APPEND_STRING(str_) \ - do { \ - size_t len = strlen(str_); \ - APPEND_STRING_LEN((str_), len); \ - } while(0) - -#define APPEND_CHAR(value_) \ - do { \ - RETURN_0_ON_OVERFLOW(1); \ - *p_headers++ = (value_); \ - } while(0) - -#define APPEND_CHAR_NOCHECK(value_) \ - *p_headers++ = (value_) - -#define APPEND_UINT(value_) \ - do { \ - size_t len; \ - char *tmp = uint_to_string((value_), buffer, &len); \ - RETURN_0_ON_OVERFLOW(len); \ - APPEND_STRING_LEN(tmp, len); \ - } while(0) - -#define APPEND_CONSTANT(const_str_) \ +#define RETURN_0_ON_OVERFLOW(len_) \ + if (UNLIKELY(p_headers + (len_) >= p_headers_end)) \ + return 0 + +#define APPEND_STRING_LEN(const_str_, len_) \ + do { \ + RETURN_0_ON_OVERFLOW(len_); \ + p_headers = mempcpy(p_headers, (const_str_), (len_)); \ + } while (0) + +#define APPEND_STRING(str_) \ + do { \ + size_t len = strlen(str_); \ + APPEND_STRING_LEN((str_), len); \ + } while (0) + +#define APPEND_CHAR(value_) \ + do { \ + RETURN_0_ON_OVERFLOW(1); \ + *p_headers++ = (value_); \ + } while (0) + +#define APPEND_CHAR_NOCHECK(value_) *p_headers++ = (value_) + +#define APPEND_UINT(value_) \ + do { \ + size_t len; \ + char *tmp = uint_to_string((value_), buffer, &len); \ + RETURN_0_ON_OVERFLOW(len); \ + APPEND_STRING_LEN(tmp, len); \ + } while (0) + +#define APPEND_CONSTANT(const_str_) \ APPEND_STRING_LEN((const_str_), sizeof(const_str_) - 1) size_t From 31334dab3ee6ecec8bc3170c0b276ed254b2482f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 14 Oct 2018 08:31:42 -0700 Subject: [PATCH 0876/2505] Use string switch to look for overridden headers --- src/lib/lwan-response.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 67347f318..2a2fd2a84 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -296,12 +296,20 @@ lwan_prepare_response_header_full(struct lwan_request *request, const struct lwan_key_value *header; for (header = additional_headers; header->key; header++) { - if (UNLIKELY(streq(header->key, "Server"))) - continue; - if (UNLIKELY(streq(header->key, "Date"))) - date_overridden = true; - if (UNLIKELY(streq(header->key, "Expires"))) - expires_overridden = true; + STRING_SWITCH_L(header->key) { + case MULTICHAR_CONSTANT_L('S', 'e', 'r', 'v'): + if (LIKELY(streq(header->key + 4, "er"))) + continue; + break; + case MULTICHAR_CONSTANT_L('D', 'a', 't', 'e'): + if (LIKELY(*(header->key + 4) == '\0')) + date_overridden = true; + break; + case MULTICHAR_CONSTANT_L('E', 'x', 'p', 'i'): + if (LIKELY(streq(header->key + 4, "res"))) + expires_overridden = true; + break; + } RETURN_0_ON_OVERFLOW(4); APPEND_CHAR_NOCHECK('\r'); From ff7c3ef64b7165ad7b2a586e64cee6ed6a73d50c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 14 Oct 2018 08:34:41 -0700 Subject: [PATCH 0877/2505] Reindent lwan-response.c --- src/lib/lwan-response.c | 121 ++++++++++++++++++++-------------------- 1 file changed, 60 insertions(+), 61 deletions(-) diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 2a2fd2a84..34604e1b3 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -14,7 +14,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #define _GNU_SOURCE @@ -63,13 +64,12 @@ struct error_template_t { const char *long_message; }; -void -lwan_response_init(struct lwan *l) +void lwan_response_init(struct lwan *l) { static struct lwan_var_descriptor error_descriptor[] = { TPL_VAR_STR(struct error_template_t, short_message), TPL_VAR_STR(struct error_template_t, long_message), - TPL_VAR_SENTINEL + TPL_VAR_SENTINEL, }; assert(!error_template); @@ -77,18 +77,17 @@ lwan_response_init(struct lwan *l) lwan_status_debug("Initializing default response"); if (l->config.error_template) { - error_template = lwan_tpl_compile_file(l->config.error_template, - error_descriptor); + error_template = + lwan_tpl_compile_file(l->config.error_template, error_descriptor); } else { - error_template = lwan_tpl_compile_string_full(error_template_str, - error_descriptor, LWAN_TPL_FLAG_CONST_TEMPLATE); + error_template = lwan_tpl_compile_string_full( + error_template_str, error_descriptor, LWAN_TPL_FLAG_CONST_TEMPLATE); } if (UNLIKELY(!error_template)) lwan_status_critical_perror("lwan_tpl_compile_string"); } -void -lwan_response_shutdown(struct lwan *l __attribute__((unused))) +void lwan_response_shutdown(struct lwan *l __attribute__((unused))) { lwan_status_debug("Shutting down response"); assert(error_template); @@ -96,8 +95,7 @@ lwan_response_shutdown(struct lwan *l __attribute__((unused))) } #ifndef NDEBUG -static const char * -get_request_method(struct lwan_request *request) +static const char *get_request_method(struct lwan_request *request) { switch (lwan_request_get_method(request)) { case REQUEST_METHOD_GET: @@ -115,19 +113,17 @@ get_request_method(struct lwan_request *request) } } -static void -log_request(struct lwan_request *request, enum lwan_http_status status) +static void log_request(struct lwan_request *request, + enum lwan_http_status status) { char ip_buffer[INET6_ADDRSTRLEN]; lwan_status_debug("%s [%s] \"%s %s HTTP/%s\" %d %s", - lwan_request_get_remote_address(request, ip_buffer), - request->conn->thread->date.date, - get_request_method(request), - request->original_url.value, - request->flags & REQUEST_IS_HTTP_1_0 ? "1.0" : "1.1", - status, - request->response.mime_type); + lwan_request_get_remote_address(request, ip_buffer), + request->conn->thread->date.date, + get_request_method(request), request->original_url.value, + request->flags & REQUEST_IS_HTTP_1_0 ? "1.0" : "1.1", + status, request->response.mime_type); } #else #define log_request(...) @@ -138,8 +134,7 @@ static const bool has_response_body[REQUEST_METHOD_MASK] = { [REQUEST_METHOD_POST] = true, }; -void -lwan_response(struct lwan_request *request, enum lwan_http_status status) +void lwan_response(struct lwan_request *request, enum lwan_http_status status) { char headers[DEFAULT_HEADERS_SIZE]; @@ -168,9 +163,10 @@ lwan_response(struct lwan_request *request, enum lwan_http_status status) if (request->response.stream.callback) { enum lwan_http_status callback_status; - callback_status = request->response.stream.callback(request, - request->response.stream.data); - /* Reset it after it has been called to avoid eternal recursion on errors */ + callback_status = request->response.stream.callback( + request, request->response.stream.data); + /* Reset it after it has been called to avoid eternal recursion on + * errors */ request->response.stream.callback = NULL; if (callback_status >= HTTP_BAD_REQUEST) /* Status < 400: success */ @@ -178,7 +174,8 @@ lwan_response(struct lwan_request *request, enum lwan_http_status status) return; } - size_t header_len = lwan_prepare_response_header(request, status, headers, sizeof(headers)); + size_t header_len = + lwan_prepare_response_header(request, status, headers, sizeof(headers)); if (UNLIKELY(!header_len)) { lwan_default_response(request, HTTP_INTERNAL_ERROR); return; @@ -188,12 +185,12 @@ lwan_response(struct lwan_request *request, enum lwan_http_status status) struct iovec response_vec[] = { { .iov_base = headers, - .iov_len = header_len + .iov_len = header_len, }, { .iov_base = lwan_strbuf_get_buffer(request->response.buffer), - .iov_len = lwan_strbuf_get_length(request->response.buffer) - } + .iov_len = lwan_strbuf_get_length(request->response.buffer), + }, }; lwan_writev(request, response_vec, N_ELEMENTS(response_vec)); @@ -202,15 +199,16 @@ lwan_response(struct lwan_request *request, enum lwan_http_status status) } } -void -lwan_default_response(struct lwan_request *request, enum lwan_http_status status) +void lwan_default_response(struct lwan_request *request, + enum lwan_http_status status) { request->response.mime_type = "text/html"; - lwan_tpl_apply_with_buffer(error_template, request->response.buffer, - &(struct error_template_t) { + lwan_tpl_apply_with_buffer( + error_template, request->response.buffer, + &(struct error_template_t){ .short_message = lwan_http_status_as_string(status), - .long_message = lwan_http_status_as_descriptive_string(status) + .long_message = lwan_http_status_as_descriptive_string(status), }); lwan_response(request, status); @@ -251,8 +249,8 @@ lwan_default_response(struct lwan_request *request, enum lwan_http_status status #define APPEND_CONSTANT(const_str_) \ APPEND_STRING_LEN((const_str_), sizeof(const_str_) - 1) -size_t -lwan_prepare_response_header_full(struct lwan_request *request, +size_t lwan_prepare_response_header_full( + struct lwan_request *request, enum lwan_http_status status, char headers[], size_t headers_buf_size, @@ -342,7 +340,8 @@ lwan_prepare_response_header_full(struct lwan_request *request, } if (request->flags & REQUEST_ALLOW_CORS) { - APPEND_CONSTANT("\r\nAccess-Control-Allow-Origin: *" + APPEND_CONSTANT( + "\r\nAccess-Control-Allow-Origin: *" "\r\nAccess-Control-Allow-Methods: GET, POST, OPTIONS" "\r\nAccess-Control-Allow-Credentials: true" "\r\nAccess-Control-Allow-Headers: Origin, Accept, Content-Type"); @@ -361,18 +360,17 @@ lwan_prepare_response_header_full(struct lwan_request *request, #undef APPEND_UINT #undef RETURN_0_ON_OVERFLOW -ALWAYS_INLINE size_t -lwan_prepare_response_header(struct lwan_request *request, - enum lwan_http_status status, - char headers[], - size_t headers_buf_size) +ALWAYS_INLINE size_t lwan_prepare_response_header(struct lwan_request *request, + enum lwan_http_status status, + char headers[], + size_t headers_buf_size) { - return lwan_prepare_response_header_full(request, - status, headers, headers_buf_size, request->response.headers); + return lwan_prepare_response_header_full( + request, status, headers, headers_buf_size, request->response.headers); } -bool -lwan_response_set_chunked(struct lwan_request *request, enum lwan_http_status status) +bool lwan_response_set_chunked(struct lwan_request *request, + enum lwan_http_status status) { char buffer[DEFAULT_BUFFER_SIZE]; size_t buffer_len; @@ -381,8 +379,8 @@ lwan_response_set_chunked(struct lwan_request *request, enum lwan_http_status st return false; request->flags |= RESPONSE_CHUNKED_ENCODING; - buffer_len = lwan_prepare_response_header(request, status, - buffer, DEFAULT_BUFFER_SIZE); + buffer_len = lwan_prepare_response_header(request, status, buffer, + DEFAULT_BUFFER_SIZE); if (UNLIKELY(!buffer_len)) return false; @@ -392,8 +390,7 @@ lwan_response_set_chunked(struct lwan_request *request, enum lwan_http_status st return true; } -void -lwan_response_send_chunk(struct lwan_request *request) +void lwan_response_send_chunk(struct lwan_request *request) { if (!(request->flags & RESPONSE_SENT_HEADERS)) { if (UNLIKELY(!lwan_response_set_chunked(request, HTTP_OK))) @@ -408,17 +405,20 @@ lwan_response_send_chunk(struct lwan_request *request) } char chunk_size[3 * sizeof(size_t) + 2]; - int converted_len = snprintf(chunk_size, sizeof(chunk_size), "%zx\r\n", buffer_len); - if (UNLIKELY(converted_len < 0 || (size_t)converted_len >= sizeof(chunk_size))) { + int converted_len = + snprintf(chunk_size, sizeof(chunk_size), "%zx\r\n", buffer_len); + if (UNLIKELY(converted_len < 0 || + (size_t)converted_len >= sizeof(chunk_size))) { coro_yield(request->conn->coro, CONN_CORO_ABORT); __builtin_unreachable(); } size_t chunk_size_len = (size_t)converted_len; struct iovec chunk_vec[] = { - { .iov_base = chunk_size, .iov_len = chunk_size_len }, - { .iov_base = lwan_strbuf_get_buffer(request->response.buffer), .iov_len = buffer_len }, - { .iov_base = "\r\n", .iov_len = 2 } + {.iov_base = chunk_size, .iov_len = chunk_size_len}, + {.iov_base = lwan_strbuf_get_buffer(request->response.buffer), + .iov_len = buffer_len}, + {.iov_base = "\r\n", .iov_len = 2}, }; lwan_writev(request, chunk_vec, N_ELEMENTS(chunk_vec)); @@ -427,9 +427,8 @@ lwan_response_send_chunk(struct lwan_request *request) coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); } -bool -lwan_response_set_event_stream(struct lwan_request *request, - enum lwan_http_status status) +bool lwan_response_set_event_stream(struct lwan_request *request, + enum lwan_http_status status) { char buffer[DEFAULT_BUFFER_SIZE]; size_t buffer_len; @@ -439,8 +438,8 @@ lwan_response_set_event_stream(struct lwan_request *request, request->response.mime_type = "text/event-stream"; request->flags |= RESPONSE_NO_CONTENT_LENGTH; - buffer_len = lwan_prepare_response_header(request, status, - buffer, DEFAULT_BUFFER_SIZE); + buffer_len = lwan_prepare_response_header(request, status, buffer, + DEFAULT_BUFFER_SIZE); if (UNLIKELY(!buffer_len)) return false; From f927ac634497d60658d89e9edd24c474e2818fe1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 14 Oct 2018 16:36:55 -0700 Subject: [PATCH 0878/2505] Revert "Reduce number of config_line copies per call to config_read_line()" This reverts commit c22a60fcf5358e3f613d87d99e82cf3401dda11d. --- src/lib/lwan-config.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index c0cf70466..8b90b2b5b 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -708,10 +708,17 @@ void config_close(struct config *config) bool config_read_line(struct config *conf, struct config_line *cl) { + struct config_line *ptr; + bool ret; + if (conf->error_message) return false; - return parser_next(&conf->parser, &cl); + ret = parser_next(&conf->parser, &ptr); + if (ret) + *cl = *ptr; + + return ret; } static bool find_section_end(struct config *config) From d1a827c3054f953f6ab08999af3f1279dda7d60f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 14 Oct 2018 16:26:20 -0700 Subject: [PATCH 0879/2505] Save memory in response struct by using an anonymous union For streaming responses, not every field in the response struct is needed, as the handlers themselves are responsible for creating the headers themselves. This change saves 8 bytes/request struct. --- src/lib/lwan-mod-serve-files.c | 2 ++ src/lib/lwan-response.c | 26 ++++++++++++-------------- src/lib/lwan.h | 20 ++++++++++++++------ 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index d042f6f2d..a19d03413 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -1121,6 +1121,8 @@ serve_files_handle_request(struct lwan_request *request, response->stream.callback = fce->funcs->serve; response->stream.data = fce; + request->flags |= RESPONSE_STREAM; + return HTTP_OK; out: diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 34604e1b3..df9a67edc 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -136,11 +136,12 @@ static const bool has_response_body[REQUEST_METHOD_MASK] = { void lwan_response(struct lwan_request *request, enum lwan_http_status status) { + const struct lwan_response *response = &request->response; char headers[DEFAULT_HEADERS_SIZE]; if (request->flags & RESPONSE_CHUNKED_ENCODING) { /* Send last, 0-sized chunk */ - lwan_strbuf_reset(request->response.buffer); + lwan_strbuf_reset(response->buffer); lwan_response_send_chunk(request); log_request(request, status); return; @@ -153,24 +154,21 @@ void lwan_response(struct lwan_request *request, enum lwan_http_status status) /* Requests without a MIME Type are errors from handlers that should just be handled by lwan_default_response(). */ - if (UNLIKELY(!request->response.mime_type)) { + if (UNLIKELY(!response->mime_type)) { lwan_default_response(request, status); return; } log_request(request, status); - if (request->response.stream.callback) { - enum lwan_http_status callback_status; + if (request->flags & RESPONSE_STREAM && response->stream.callback) { + status = response->stream.callback(request, response->stream.data); - callback_status = request->response.stream.callback( - request, request->response.stream.data); - /* Reset it after it has been called to avoid eternal recursion on - * errors */ - request->response.stream.callback = NULL; + if (status >= HTTP_BAD_REQUEST) { /* Status < 400: success */ + request->flags &= ~RESPONSE_STREAM; + lwan_default_response(request, status); + } - if (callback_status >= HTTP_BAD_REQUEST) /* Status < 400: success */ - lwan_default_response(request, callback_status); return; } @@ -188,8 +186,8 @@ void lwan_response(struct lwan_request *request, enum lwan_http_status status) .iov_len = header_len, }, { - .iov_base = lwan_strbuf_get_buffer(request->response.buffer), - .iov_len = lwan_strbuf_get_length(request->response.buffer), + .iov_base = lwan_strbuf_get_buffer(response->buffer), + .iov_len = lwan_strbuf_get_length(response->buffer), }, }; @@ -276,7 +274,7 @@ size_t lwan_prepare_response_header_full( /* Do nothing. */ } else { APPEND_CONSTANT("\r\nContent-Length: "); - if (request->response.stream.callback) + if (request->flags & RESPONSE_STREAM) APPEND_UINT(request->response.content_length); else APPEND_UINT(lwan_strbuf_get_length(request->response.buffer)); diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 3f165aebb..5847a3638 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -231,6 +231,8 @@ enum lwan_request_flags { RESPONSE_CHUNKED_ENCODING = 1 << 10, RESPONSE_NO_CONTENT_LENGTH = 1 << 11, RESPONSE_URL_REWRITTEN = 1 << 12, + + RESPONSE_STREAM = 1 << 13, }; enum lwan_connection_flags { @@ -256,17 +258,23 @@ struct lwan_key_value { }; struct lwan_request; + struct lwan_response { struct lwan_strbuf *buffer; const char *mime_type; size_t content_length; - const struct lwan_key_value *headers; - struct { - enum lwan_http_status (*callback)(struct lwan_request *request, - void *data); - void *data; - } stream; + union { + struct { + const struct lwan_key_value *headers; + }; + + struct { + enum lwan_http_status (*callback)(struct lwan_request *request, + void *data); + void *data; + } stream; + }; }; struct lwan_value { From 782dddbd58844b557065c9ee5f8a01000db581f2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 14 Oct 2018 21:06:20 -0700 Subject: [PATCH 0880/2505] Remove `content_length` from response struct It was only used by streaming handlers, that need to generate the header values anyway. --- src/lib/lwan-mod-serve-files.c | 87 ++++++++++++++++++---------------- src/lib/lwan-response.c | 7 +-- src/lib/lwan.h | 1 - 3 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index a19d03413..ffa2c6d4e 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -38,6 +38,7 @@ #include "lwan-io-wrappers.h" #include "lwan-mod-serve-files.h" #include "lwan-template.h" +#include "int-to-str.h" #include "auto-index-icons.h" @@ -849,19 +850,29 @@ static size_t prepare_headers(struct lwan_request *request, enum lwan_http_status return_status, struct file_cache_entry *fce, size_t size, - const char *compression_type, + const char *additional_header_name, + const char *additional_header_value, char *header_buf, size_t header_buf_size) { - struct lwan_key_value additional_headers[3] = { - [0] = {.key = "Last-Modified", .value = fce->last_modified.string}, + char content_length[3 * sizeof(size_t)]; + size_t discard; + struct lwan_key_value additional_headers[4] = { + { + .key = "Last-Modified", + .value = fce->last_modified.string, + }, + { + .key = "Content-Length", + .value = uint_to_string(size, content_length, &discard), + }, }; - request->response.content_length = size; - - if (compression_type) { - additional_headers[1] = (struct lwan_key_value){ - .key = "Content-Encoding", .value = (char *)compression_type}; + if (additional_header_name && additional_header_value) { + additional_headers[2] = (struct lwan_key_value){ + .key = (char *)additional_header_name, + .value = (char *)additional_header_value, + }; } return lwan_prepare_response_header_full(request, return_status, header_buf, @@ -951,8 +962,9 @@ static enum lwan_http_status sendfile_serve(struct lwan_request *request, } } - header_len = prepare_headers(request, return_status, fce, size, compressed, - headers, DEFAULT_HEADERS_SIZE); + header_len = + prepare_headers(request, return_status, fce, size, "Content-Encoding", + compressed, headers, DEFAULT_HEADERS_SIZE); if (UNLIKELY(!header_len)) return HTTP_INTERNAL_ERROR; @@ -965,19 +977,21 @@ static enum lwan_http_status sendfile_serve(struct lwan_request *request, return return_status; } -static enum lwan_http_status serve_buffer(struct lwan_request *request, - struct file_cache_entry *fce, - const char *compression_type, - const void *contents, - size_t size, - enum lwan_http_status return_status) +static enum lwan_http_status +serve_buffer_full(struct lwan_request *request, + struct file_cache_entry *fce, + const char *additional_header_name, + const char *additional_header_val, + const void *contents, + size_t size, + enum lwan_http_status return_status) { char headers[DEFAULT_BUFFER_SIZE]; size_t header_len; - header_len = - prepare_headers(request, return_status, fce, size, compression_type, - headers, DEFAULT_HEADERS_SIZE); + header_len = prepare_headers(request, return_status, fce, size, + additional_header_name, additional_header_val, + headers, DEFAULT_HEADERS_SIZE); if (UNLIKELY(!header_len)) return HTTP_INTERNAL_ERROR; @@ -995,6 +1009,17 @@ static enum lwan_http_status serve_buffer(struct lwan_request *request, return return_status; } +static enum lwan_http_status serve_buffer(struct lwan_request *request, + struct file_cache_entry *fce, + const char *compression_type, + const void *contents, + size_t size, + enum lwan_http_status return_status) +{ + return serve_buffer_full(request, fce, "Content-Encoding", compression_type, + contents, size, return_status); +} + static enum lwan_http_status mmap_serve(struct lwan_request *request, void *data) { @@ -1070,28 +1095,10 @@ static enum lwan_http_status redir_serve(struct lwan_request *request, { struct file_cache_entry *fce = data; struct redir_cache_data *rd = (struct redir_cache_data *)(fce + 1); - char header_buf[DEFAULT_BUFFER_SIZE]; - size_t header_buf_size; - struct lwan_key_value additional_headers[2] = { - [0] = {.key = "Location", .value = rd->redir_to}, - }; - - request->response.content_length = strlen(rd->redir_to); - - header_buf_size = lwan_prepare_response_header_full( - request, HTTP_MOVED_PERMANENTLY, header_buf, DEFAULT_BUFFER_SIZE, - additional_headers); - if (UNLIKELY(!header_buf_size)) - return HTTP_INTERNAL_ERROR; - - struct iovec response_vec[] = { - {.iov_base = header_buf, .iov_len = header_buf_size}, - {.iov_base = rd->redir_to, .iov_len = request->response.content_length}, - }; - - lwan_writev(request, response_vec, N_ELEMENTS(response_vec)); - return HTTP_MOVED_PERMANENTLY; + return serve_buffer_full(request, fce, "Location", rd->redir_to, + rd->redir_to, strlen(rd->redir_to), + HTTP_MOVED_PERMANENTLY); } static enum lwan_http_status diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index df9a67edc..3d2e2d9fc 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -272,12 +272,9 @@ size_t lwan_prepare_response_header_full( APPEND_CONSTANT("\r\nTransfer-Encoding: chunked"); } else if (request->flags & RESPONSE_NO_CONTENT_LENGTH) { /* Do nothing. */ - } else { + } else if (!(request->flags & RESPONSE_STREAM)) { APPEND_CONSTANT("\r\nContent-Length: "); - if (request->flags & RESPONSE_STREAM) - APPEND_UINT(request->response.content_length); - else - APPEND_UINT(lwan_strbuf_get_length(request->response.buffer)); + APPEND_UINT(lwan_strbuf_get_length(request->response.buffer)); } APPEND_CONSTANT("\r\nContent-Type: "); diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 5847a3638..a4e82e24f 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -262,7 +262,6 @@ struct lwan_request; struct lwan_response { struct lwan_strbuf *buffer; const char *mime_type; - size_t content_length; union { struct { From 57dbdaf10e1d1d614886563a4700f956523d1061 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 15 Oct 2018 07:59:39 -0700 Subject: [PATCH 0881/2505] Assert stack pointer is within bounds when resuming coroutine Do this only on debug builds. Should help determining when there's a stack overflow within a coroutine. Might be a good idea to add a stack canary for the coroutine later, too -- it's cheaper to check and can be on all the time. --- src/lib/lwan-coro.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index eede35286..5f3b60d55 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -218,7 +218,9 @@ coro_reset(struct coro *coro, coro_function_t func, void *data) * 16-bytes boundary so SSE will work properly, but should be * aligned on an 8-byte boundary right after calling a function. */ uintptr_t rsp = (uintptr_t) stack + CORO_STACK_MIN; - coro->context[9 /* RSP */] = (rsp & ~0xful) - 0x8ul; + +#define STACK_PTR 9 + coro->context[STACK_PTR] = (rsp & ~0xful) - 0x8ul; #elif defined(__i386__) stack = (unsigned char *)(uintptr_t)(stack + CORO_STACK_MIN); @@ -234,7 +236,9 @@ coro_reset(struct coro *coro, coro_function_t func, void *data) *argp++ = (uintptr_t)data; coro->context[5 /* EIP */] = (uintptr_t) coro_entry_point; - coro->context[6 /* ESP */] = (uintptr_t) stack; + +#define STACK_PTR 6 + coro->context[STACK_PTR] = (uintptr_t) stack; #else getcontext(&coro->context); @@ -276,6 +280,12 @@ coro_resume(struct coro *coro) { assert(coro); +#if defined(STACK_PTR) + assert(coro->context[STACK_PTR] >= (uintptr_t)coro->stack && + coro->context[STACK_PTR] <= + (uintptr_t)(coro->stack + CORO_STACK_MIN)); +#endif + coro_swapcontext(&coro->switcher->caller, &coro->context); memcpy(&coro->context, &coro->switcher->callee, sizeof(coro->context)); From 8d7fe66b26ed980b0662ca2fe9fbfeed025515bd Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 16 Oct 2018 05:27:58 -0700 Subject: [PATCH 0882/2505] Make it possible to watch file descriptors The idea here is to re-use the thread queue to signal when a coroutine should be resumed: writing a negative file descriptor will signal that, if the coroutine responsible for the absolute value is suspended, it should be resumed instead. This will allow requests to sleep until there's activity on a particular file descriptor. Since the thread queue is SPSC, only the main thread is capable of watching these file descriptors. As a result, it now contains its own epoll. To keep things idiomatic, instead of passing callbacks for each event type, coroutines are used instead: the result of coro_yield() are the events reported back by epoll_wait(). This interface is still considered private, hence only declared in lwan-private.h. --- src/lib/lwan-private.h | 7 +++ src/lib/lwan.c | 133 ++++++++++++++++++++++++++++++----------- src/lib/lwan.h | 10 ++++ 3 files changed, 115 insertions(+), 35 deletions(-) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 4e1509c8e..5730088d5 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -21,6 +21,13 @@ #include "lwan.h" +struct lwan_fd_watch *lwan_watch_fd(struct lwan *l, + int fd, + uint32_t events, + coro_function_t coro_fn, + void *data); +void lwan_unwatch_fd(struct lwan *l, struct lwan_fd_watch *w); + void lwan_set_thread_name(const char *name); void lwan_response_init(struct lwan *l); diff --git a/src/lib/lwan.c b/src/lib/lwan.c index f7617f8d5..f085d5aaf 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -25,12 +25,12 @@ #include #include #include -#include #include #include #include #include #include +#include #include #include @@ -642,17 +642,9 @@ static void sigint_handler(int signal_number __attribute__((unused))) main_socket = -1; } -static bool -wait_herd(void) -{ - struct pollfd fds = { .fd = (int)main_socket, .events = POLLIN }; - - return poll(&fds, 1, -1) > 0; -} - enum herd_accept { HERD_MORE = 0, HERD_GONE = -1, HERD_SHUTDOWN = 1 }; -static enum herd_accept +static ALWAYS_INLINE enum herd_accept accept_one(struct lwan *l, uint64_t *cores) { int fd = accept4((int)main_socket, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC); @@ -676,54 +668,125 @@ accept_one(struct lwan *l, uint64_t *cores) lwan_status_info("Main socket closed for unknown reasons"); } return HERD_SHUTDOWN; + + default: + lwan_status_perror("accept"); + return HERD_MORE; + } +} + +static int +accept_connection_coro(struct coro *coro, void *data) +{ + struct lwan *l = data; + uint64_t cores = 0; + + while (coro_yield(coro, 1) & ~(EPOLLHUP | EPOLLRDHUP | EPOLLERR)) { + enum herd_accept ha; + + do { + ha = accept_one(l, &cores); + } while (ha == HERD_MORE); + + if (UNLIKELY(ha > HERD_MORE)) + break; + + if (LIKELY(cores)) { + for (unsigned short t = 0; t < l->thread.count; t++) { + if (cores & UINT64_C(1)<thread.threads[t]); + } + + cores = 0; + } } - lwan_status_perror("accept"); - return HERD_MORE; + return 0; } -static inline enum herd_accept -accept_herd(struct lwan *l, uint64_t *cores) +struct lwan_fd_watch *lwan_watch_fd(struct lwan *l, + int fd, + uint32_t events, + coro_function_t coro_fn, + void *data) { - enum herd_accept ha; + struct lwan_fd_watch *watch; - if (!wait_herd()) - return HERD_GONE; + watch = lwan_fd_watch_array_append(&l->fd_watches); + if (!watch) + return NULL; + + watch->coro = coro_new(&l->switcher, coro_fn, data); + if (!watch->coro) + goto out; + + struct epoll_event ev = {.events = events, .data.ptr = watch->coro}; + if (epoll_ctl(l->epfd, EPOLL_CTL_ADD, fd, &ev) < 0) + goto out; - do { - ha = accept_one(l, cores); - } while (ha == HERD_MORE); + watch->fd = fd; + return watch; - return ha; +out: + l->fd_watches.base.elements--; + return NULL; } -void -lwan_main_loop(struct lwan *l) +void lwan_unwatch_fd(struct lwan *l, struct lwan_fd_watch *w) { - uint64_t cores = 0; + if (l->main_socket != w->fd) { + if (epoll_ctl(l->epfd, EPOLL_CTL_DEL, w->fd, NULL) < 0) + lwan_status_perror("Could not unwatch fd %d", w->fd); + } + + coro_free(w->coro); +} + +void lwan_main_loop(struct lwan *l) +{ + struct epoll_event evs[16]; + struct lwan_fd_watch *watch; assert(main_socket == -1); main_socket = l->main_socket; + + l->epfd = epoll_create1(EPOLL_CLOEXEC); + if (l->epfd < 0) + lwan_status_critical_perror("epoll_create1"); + if (signal(SIGINT, sigint_handler) == SIG_ERR) lwan_status_critical("Could not set signal handler"); + watch = lwan_watch_fd(l, l->main_socket, EPOLLIN | EPOLLHUP | EPOLLRDHUP, + accept_connection_coro, l); + if (!watch) + lwan_status_critical("Could not watch main socket"); + lwan_status_info("Ready to serve"); - while (LIKELY(main_socket >= 0)) { - enum herd_accept ha = accept_herd(l, &cores); + while (true) { + int n_evs = epoll_wait(l->epfd, evs, N_ELEMENTS(evs), -1); - if (UNLIKELY(ha > HERD_MORE)) - break; - - if (LIKELY(cores)) { - for (unsigned short t = 0; t < l->thread.count; t++) { - if (cores & UINT64_C(1)<thread.threads[t]); - } + if (UNLIKELY(n_evs < 0)) { + if (main_socket < 0) + goto out; + if (errno == EINTR || errno == EAGAIN) + continue; + goto out; + } - cores = 0; + for (int i = 0; i < n_evs; i++) { + if (!coro_resume_value(evs[i].data.ptr, (int)evs[i].events)) + goto out; } } + +out: + LWAN_ARRAY_FOREACH (&l->fd_watches, watch) { + lwan_unwatch_fd(l, watch); + } + + close(l->epfd); } size_t diff --git a/src/lib/lwan.h b/src/lib/lwan.h index a4e82e24f..6ec3cf86f 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -403,6 +403,13 @@ struct lwan_config { bool allow_post_temp_file; }; +struct lwan_fd_watch { + struct coro *coro; + int fd; +}; + +DEFINE_ARRAY_TYPE(lwan_fd_watch_array, struct lwan_fd_watch) + struct lwan { struct lwan_trie url_map_trie; struct lwan_connection *conns; @@ -418,8 +425,11 @@ struct lwan { unsigned short count; } thread; + struct lwan_fd_watch_array fd_watches; struct lwan_config config; + struct coro_switcher switcher; int main_socket; + int epfd; }; void lwan_set_url_map(struct lwan *l, const struct lwan_url_map *map); From db813cdd4a57817d9bf2ab491ab7fce1d31ae5db Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 16 Oct 2018 06:28:45 -0700 Subject: [PATCH 0883/2505] Move lwan_nextpow2() to lwan-private.h as a static inline function There's no need to export this function: most of the time, HAVE_BUILTIN_CLZLL will be available, so this function will be just slightly more than a BSR instruction. --- src/lib/lwan-private.h | 29 ++++++++++++++++++++++++++++- src/lib/lwan.c | 27 --------------------------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 5730088d5..2d2642f9e 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -19,6 +19,8 @@ #pragma once +#include + #include "lwan.h" struct lwan_fd_watch *lwan_watch_fd(struct lwan *l, @@ -71,7 +73,32 @@ uint8_t lwan_char_isspace(char ch) __attribute__((pure)); uint8_t lwan_char_isxdigit(char ch) __attribute__((pure)); uint8_t lwan_char_isdigit(char ch) __attribute__((pure)); -size_t lwan_nextpow2(size_t number); +static ALWAYS_INLINE size_t lwan_nextpow2(size_t number) +{ +#if defined(HAVE_BUILTIN_CLZLL) + static const int size_bits = (int)sizeof(number) * CHAR_BIT; + + if (sizeof(size_t) == sizeof(unsigned int)) { + return (size_t)1 << (size_bits - __builtin_clz((unsigned int)number)); + } else if (sizeof(size_t) == sizeof(unsigned long)) { + return (size_t)1 << (size_bits - __builtin_clzl((unsigned long)number)); + } else if (sizeof(size_t) == sizeof(unsigned long long)) { + return (size_t)1 << (size_bits - __builtin_clzll((unsigned long long)number)); + } else { + (void)size_bits; + } +#endif + + number--; + number |= number >> 1; + number |= number >> 2; + number |= number >> 4; + number |= number >> 8; + number |= number >> 16; + + return number + 1; +} + #ifdef HAVE_LUA #include diff --git a/src/lib/lwan.c b/src/lib/lwan.c index f085d5aaf..ba7f54073 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -789,33 +789,6 @@ void lwan_main_loop(struct lwan *l) close(l->epfd); } -size_t -lwan_nextpow2(size_t number) -{ -#if defined(HAVE_BUILTIN_CLZLL) - static const int size_bits = (int)sizeof(number) * CHAR_BIT; - - if (sizeof(size_t) == sizeof(unsigned int)) { - return (size_t)1 << (size_bits - __builtin_clz((unsigned int)number)); - } else if (sizeof(size_t) == sizeof(unsigned long)) { - return (size_t)1 << (size_bits - __builtin_clzl((unsigned long)number)); - } else if (sizeof(size_t) == sizeof(unsigned long long)) { - return (size_t)1 << (size_bits - __builtin_clzll((unsigned long long)number)); - } else { - (void)size_bits; - } -#endif - - number--; - number |= number >> 1; - number |= number >> 2; - number |= number >> 4; - number |= number >> 8; - number |= number >> 16; - - return number + 1; -} - #ifdef CLOCK_MONOTONIC_COARSE __attribute__((constructor)) static void detect_fastest_monotonic_clock(void) { From 8fe3480df2eae76d98ae1ceff9b643a4d8e81ddd Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 16 Oct 2018 08:18:25 -0700 Subject: [PATCH 0884/2505] Allow Lua methods to be registered outside lwan-lua.c It's now possible for user code to extend the methods available to Lua handlers by declaring methods with LWAN_LUA_METHOD(). All current methods have been declared using this way. --- src/lib/lwan-lua.c | 58 ++++++++++++++++++++++++++++++---------------- src/lib/lwan.h | 13 +++++++++++ 2 files changed, 51 insertions(+), 20 deletions(-) diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index d6b79032b..7c5876724 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -36,7 +36,7 @@ static ALWAYS_INLINE struct lwan_request *userdata_as_request(lua_State *L) return *((struct lwan_request **)luaL_checkudata(L, 1, request_metatable_name)); } -static int req_say_cb(lua_State *L) +LWAN_LUA_METHOD(say) { struct lwan_request *request = userdata_as_request(L); size_t response_str_len; @@ -48,7 +48,7 @@ static int req_say_cb(lua_State *L) return 0; } -static int req_send_event_cb(lua_State *L) +LWAN_LUA_METHOD(send_event) { struct lwan_request *request = userdata_as_request(L); size_t event_str_len; @@ -61,7 +61,7 @@ static int req_send_event_cb(lua_State *L) return 0; } -static int req_set_response_cb(lua_State *L) +LWAN_LUA_METHOD(set_response) { struct lwan_request *request = userdata_as_request(L); size_t response_str_len; @@ -87,17 +87,17 @@ static int request_param_getter(lua_State *L, return 1; } -static int req_query_param_cb(lua_State *L) +LWAN_LUA_METHOD(query_param) { return request_param_getter(L, lwan_request_get_query_param); } -static int req_post_param_cb(lua_State *L) +LWAN_LUA_METHOD(post_param) { return request_param_getter(L, lwan_request_get_post_param); } -static int req_cookie_cb(lua_State *L) +LWAN_LUA_METHOD(cookie) { return request_param_getter(L, lwan_request_get_cookie); } @@ -117,7 +117,7 @@ static bool append_key_value(lua_State *L, struct coro *coro, return kv->value != NULL; } -static int req_set_headers_cb(lua_State *L) +LWAN_LUA_METHOD(set_headers) { const int table_index = 2; const int key_index = 1 + table_index; @@ -182,7 +182,7 @@ static int req_set_headers_cb(lua_State *L) return 1; } -static int req_sleep_cb(lua_State *L) +LWAN_LUA_METHOD(sleep) { struct lwan_request *request = userdata_as_request(L); lua_Integer ms = lua_tointeger(L, -1); @@ -192,17 +192,35 @@ static int req_sleep_cb(lua_State *L) return 0; } -static const struct luaL_reg lwan_request_meta_regs[] = { - { "query_param", req_query_param_cb }, - { "post_param", req_post_param_cb }, - { "set_response", req_set_response_cb }, - { "say", req_say_cb }, - { "send_event", req_send_event_cb }, - { "cookie", req_cookie_cb }, - { "set_headers", req_set_headers_cb }, - { "sleep", req_sleep_cb }, - { NULL, NULL } -}; +DEFINE_ARRAY_TYPE(lwan_lua_method_array, luaL_reg) +static struct lwan_lua_method_array lua_methods; + +__attribute__((constructor)) static void register_lua_methods(void) +{ + extern const struct lwan_lua_method_info SECTION_START(lwan_lua_method); + extern const struct lwan_lua_method_info SECTION_END(lwan_lua_method); + const struct lwan_lua_method_info *info; + luaL_reg *r; + + for (info = __start_lwan_lua_method; info < __stop_lwan_lua_method; + info++) { + r = lwan_lua_method_array_append(&lua_methods); + if (!r) { + lwan_status_critical("Could not register Lua method `%s`", + info->name); + } + + r->name = info->name; + r->func = info->func; + } + + r = lwan_lua_method_array_append(&lua_methods); + if (!r) + lwan_status_critical("Could not add Lua method sentinel"); + + r->name = NULL; + r->func = NULL; +} const char *lwan_lua_state_last_error(lua_State *L) { @@ -220,7 +238,7 @@ lua_State *lwan_lua_create_state(const char *script_file, const char *script) luaL_openlibs(L); luaL_newmetatable(L, request_metatable_name); - luaL_register(L, NULL, lwan_request_meta_regs); + luaL_register(L, NULL, lua_methods.base.base); lua_setfield(L, -1, "__index"); if (script_file) { diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 6ec3cf86f..4dcc9eef4 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -81,6 +81,14 @@ extern "C" { LWAN_HANDLER_DECLARE(name_); \ LWAN_HANDLER_DEFINE(name_) +#define LWAN_LUA_METHOD(name_) \ + static int lwan_lua_method_##name_(lua_State *L); \ + static const struct lwan_lua_method_info \ + __attribute__((used, section(LWAN_SECTION_NAME(lwan_lua_method)))) \ + lwan_lua_method_info_##name_ = {.name = #name_, \ + .func = lwan_lua_method_##name_}; \ + static int lwan_lua_method_##name_(lua_State *L) + #ifdef DISABLE_INLINE_FUNCTIONS #define ALWAYS_INLINE #else @@ -343,6 +351,11 @@ struct lwan_module_info { const struct lwan_module *module; }; +struct lwan_lua_method_info { + const char *name; + int (*func)(); +}; + struct lwan_handler_info { const char *name; enum lwan_http_status (*handler)(struct lwan_request *request, From 9df39d46b4b869d9fb96182b552e2ef98bbdcccf Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 16 Oct 2018 08:31:21 -0700 Subject: [PATCH 0885/2505] When accepting nudges, ignore EINTR errors from read() --- src/lib/lwan-thread.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 3964de399..6e1214de7 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -316,7 +316,10 @@ static void accept_nudge(int pipe_fd, uint64_t event; int new_fd; - if (read(pipe_fd, &event, sizeof(event)) < 0) { + while (read(pipe_fd, &event, sizeof(event)) < 0) { + if (errno == EINTR) + continue; + return; } From 95fc78757062f24862a3918e7e4273b6238ab209 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 18 Oct 2018 20:56:15 -0700 Subject: [PATCH 0886/2505] Plug memory leak when unwatching file descriptors Since it's currently not possible to remove elements from an lwan_array, just mark an element of the lwan_fd_watch_array as invalid -- and try looking at the current array before trying to allocate a new element. Not optimal, but easier to do right now. --- src/lib/lwan.c | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index ba7f54073..f9f29315f 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -711,24 +711,37 @@ struct lwan_fd_watch *lwan_watch_fd(struct lwan *l, void *data) { struct lwan_fd_watch *watch; + bool found_watch = false; - watch = lwan_fd_watch_array_append(&l->fd_watches); - if (!watch) - return NULL; + LWAN_ARRAY_FOREACH (&l->fd_watches, watch) { + if (watch->coro == NULL) { + found_watch = true; + break; + } + } + + if (!found_watch) { + watch = lwan_fd_watch_array_append(&l->fd_watches); + if (!watch) + return NULL; + } watch->coro = coro_new(&l->switcher, coro_fn, data); if (!watch->coro) goto out; struct epoll_event ev = {.events = events, .data.ptr = watch->coro}; - if (epoll_ctl(l->epfd, EPOLL_CTL_ADD, fd, &ev) < 0) + if (epoll_ctl(l->epfd, EPOLL_CTL_ADD, fd, &ev) < 0) { + coro_free(watch->coro); goto out; + } watch->fd = fd; return watch; out: - l->fd_watches.base.elements--; + watch->coro = NULL; + watch->fd = -1; return NULL; } @@ -740,6 +753,8 @@ void lwan_unwatch_fd(struct lwan *l, struct lwan_fd_watch *w) } coro_free(w->coro); + w->coro = NULL; + w->fd = -1; } void lwan_main_loop(struct lwan *l) From 4f0283bb0ce9e47d8cada9e791bf4c5775377903 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 19 Oct 2018 19:09:33 -0700 Subject: [PATCH 0887/2505] No need to generate an ACTION_LAST item on template parse error --- src/lib/lwan-template.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index 736d591cc..4a2274829 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -1137,16 +1137,7 @@ static bool parser_shutdown(struct parser *parser, struct lexeme *lexeme) success = false; } - if (success) - success = post_process_template(parser); - - if (!success) { - /* Emit a ACTION_LAST chunk so that lwan_tpl_free() knows when to stop - */ - emit_chunk(parser, ACTION_LAST, 0, NULL); - } - - return success; + return success && post_process_template(parser); } static bool parse_string(struct lwan_tpl *tpl, From 5df230a0adc5777823f26cc9181aadc38e22948f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 19 Oct 2018 07:53:59 -0700 Subject: [PATCH 0888/2505] Compile Freegeoip templates with the "constant" flag Shouldn't affect performance in any measurable way, but this reduces the amount of calls to malloc() during intialization, and keeps the template strings closer together (which is good for cache locality). --- src/samples/freegeoip/freegeoip.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/samples/freegeoip/freegeoip.c b/src/samples/freegeoip/freegeoip.c index 9272e5c9f..1bb33d60e 100644 --- a/src/samples/freegeoip/freegeoip.c +++ b/src/samples/freegeoip/freegeoip.c @@ -386,17 +386,18 @@ LWAN_HANDLER(templated_output) return HTTP_OK; } -static struct template_mime -compile_template(const char *template, const char *mime_type) +static struct template_mime compile_template(const char *template, + const char *mime_type) { - struct lwan_tpl *tpl = lwan_tpl_compile_string(template, template_descriptor); + struct lwan_tpl *tpl = lwan_tpl_compile_string_full( + template, template_descriptor, LWAN_TPL_FLAG_CONST_TEMPLATE); if (!tpl) { lwan_status_critical("Could not compile template for mime-type %s", - mime_type); + mime_type); } - return (struct template_mime) { .tpl = tpl, .mime_type = mime_type }; + return (struct template_mime){.tpl = tpl, .mime_type = mime_type}; } int From 96f4751312672dde8229738749fffebd4db99686 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 19 Oct 2018 07:59:52 -0700 Subject: [PATCH 0889/2505] Reindent freegeoip.c --- src/samples/freegeoip/freegeoip.c | 240 +++++++++++++++--------------- 1 file changed, 118 insertions(+), 122 deletions(-) diff --git a/src/samples/freegeoip/freegeoip.c b/src/samples/freegeoip/freegeoip.c index 1bb33d60e..f8dcb07db 100644 --- a/src/samples/freegeoip/freegeoip.c +++ b/src/samples/freegeoip/freegeoip.c @@ -17,14 +17,15 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #define _DEFAULT_SOURCE #include -#include -#include #include +#include +#include #include "lwan.h" #include "lwan-cache.h" @@ -70,75 +71,72 @@ static const struct lwan_var_descriptor template_descriptor[] = { TPL_VAR_STR(struct ip_info, metro.area), TPL_VAR_STR(struct ip_info, ip), TPL_VAR_STR(struct ip_info, callback), - TPL_VAR_SENTINEL + TPL_VAR_SENTINEL, }; -static const char json_template_str[] = \ - "{{callback?}}{{callback}}({{/callback?}}" \ - "{" \ - "\"country_code\":\"{{country.code}}\"," \ - "\"country_name\":\"{{country.name}}\"," \ - "\"region_code\":\"{{region.code}}\"," \ - "\"region_name\":\"{{region.name}}\"," \ - "\"city\":\"{{city.name}}\"," \ - "\"zipcode\":\"{{city.zip_code}}\"," \ - "\"latitude\":{{latitude}}," \ - "\"longitude\":{{longitude}}," \ - "\"metro_code\":\"{{metro.code}}\"," \ - "\"areacode\":\"{{metro.area}}\"," \ - "\"ip\":\"{{ip}}\"" \ +static const char json_template_str[] = + "{{callback?}}{{callback}}({{/callback?}}" + "{" + "\"country_code\":\"{{country.code}}\"," + "\"country_name\":\"{{country.name}}\"," + "\"region_code\":\"{{region.code}}\"," + "\"region_name\":\"{{region.name}}\"," + "\"city\":\"{{city.name}}\"," + "\"zipcode\":\"{{city.zip_code}}\"," + "\"latitude\":{{latitude}}," + "\"longitude\":{{longitude}}," + "\"metro_code\":\"{{metro.code}}\"," + "\"areacode\":\"{{metro.area}}\"," + "\"ip\":\"{{ip}}\"" "}" "{{callback?}});{{/callback?}}"; -static const char xml_template_str[] = \ - "" \ - "" \ - "{{ip}}" \ - "{{country.code}}" \ - "{{country.name}}" \ - "{{region.code}}" \ - "{{region.name}}" \ - "{{city.name}}" \ - "{{city.zip_code}}" \ - "{{latitude}}" \ - "{{longitude}}" \ - "{{metro.code}}" \ - "{{metro.area}}" \ +static const char xml_template_str[] = + "" + "" + "{{ip}}" + "{{country.code}}" + "{{country.name}}" + "{{region.code}}" + "{{region.name}}" + "{{city.name}}" + "{{city.zip_code}}" + "{{latitude}}" + "{{longitude}}" + "{{metro.code}}" + "{{metro.area}}" ""; -static const char csv_template_str[] = \ - "\"{{ip}}\"," \ - "\"{{country.code}}\"," \ - "\"{{country.name}}\"," \ - "\"{{region.code}}\"," \ - "\"{{region.name}}\"," \ - "\"{{city.name}}\"," - "\"{{city.zip_code}}\"," \ - "\"{{latitude}}\"," \ - "\"{{longitude}}\"," \ - "\"{{metro.code}}\"," \ - "\"{{metro.area}}\""; - - -static const char ip_to_city_query[] = \ - "SELECT " \ - " city_location.country_code, country_blocks.country_name," \ - " city_location.region_code, region_names.region_name," \ - " city_location.city_name, city_location.postal_code," \ - " city_location.latitude, city_location.longitude," \ - " city_location.metro_code, city_location.area_code " \ - "FROM city_blocks " \ - " NATURAL JOIN city_location " \ - " INNER JOIN country_blocks ON " \ - " city_location.country_code = country_blocks.country_code " \ - " INNER JOIN region_names ON " \ - " city_location.country_code = region_names.country_code " \ - " AND " \ - " city_location.region_code = region_names.region_code " \ - "WHERE city_blocks.ip_start <= ? " \ +static const char csv_template_str[] = "\"{{ip}}\"," + "\"{{country.code}}\"," + "\"{{country.name}}\"," + "\"{{region.code}}\"," + "\"{{region.name}}\"," + "\"{{city.name}}\"," + "\"{{city.zip_code}}\"," + "\"{{latitude}}\"," + "\"{{longitude}}\"," + "\"{{metro.code}}\"," + "\"{{metro.area}}\""; + +static const char ip_to_city_query[] = + "SELECT " + " city_location.country_code, country_blocks.country_name," + " city_location.region_code, region_names.region_name," + " city_location.city_name, city_location.postal_code," + " city_location.latitude, city_location.longitude," + " city_location.metro_code, city_location.area_code " + "FROM city_blocks " + " NATURAL JOIN city_location " + " INNER JOIN country_blocks ON " + " city_location.country_code = country_blocks.country_code " + " INNER JOIN region_names ON " + " city_location.country_code = region_names.country_code " + " AND " + " city_location.region_code = region_names.region_code " + "WHERE city_blocks.ip_start <= ? " "ORDER BY city_blocks.ip_start DESC LIMIT 1"; - union ip_to_octet { unsigned char octet[sizeof(in_addr_t)]; in_addr_t ip; @@ -150,8 +148,10 @@ struct ip_net { }; /* http://en.wikipedia.org/wiki/Reserved_IP_addresses */ -#define ADDRESS(o1, o2, o3, o4) \ - { .octet[0] = o1, .octet[1] = o2, .octet[2] = o3, .octet[3] = o4 } +#define ADDRESS(o1, o2, o3, o4) \ + { \ + .octet[0] = o1, .octet[1] = o2, .octet[2] = o3, .octet[3] = o4 \ + } static const struct ip_net reserved_ips[] = { {ADDRESS(0, 0, 0, 0), ADDRESS(255, 0, 0, 0)}, @@ -174,7 +174,6 @@ static const struct ip_net reserved_ips[] = { #undef ADDRESS - #if QUERIES_PER_HOUR != 0 struct query_limit { struct cache_entry base; @@ -187,18 +186,20 @@ static struct cache *query_limit; static struct cache *cache = NULL; static sqlite3 *db = NULL; -static bool -net_contains_ip(const struct ip_net *net, in_addr_t ip) +static bool net_contains_ip(const struct ip_net *net, in_addr_t ip) { - union ip_to_octet _ip = { .ip = ip }; - return (net->ip.octet[0] & net->mask.octet[0]) == (_ip.octet[0] & net->mask.octet[0]) && \ - (net->ip.octet[1] & net->mask.octet[1]) == (_ip.octet[1] & net->mask.octet[1]) && \ - (net->ip.octet[2] & net->mask.octet[2]) == (_ip.octet[2] & net->mask.octet[2]) && \ - (net->ip.octet[3] & net->mask.octet[3]) == (_ip.octet[3] & net->mask.octet[3]); + union ip_to_octet _ip = {.ip = ip}; + return (net->ip.octet[0] & net->mask.octet[0]) == + (_ip.octet[0] & net->mask.octet[0]) && + (net->ip.octet[1] & net->mask.octet[1]) == + (_ip.octet[1] & net->mask.octet[1]) && + (net->ip.octet[2] & net->mask.octet[2]) == + (_ip.octet[2] & net->mask.octet[2]) && + (net->ip.octet[3] & net->mask.octet[3]) == + (_ip.octet[3] & net->mask.octet[3]); } -static bool -is_reserved_ip(in_addr_t ip) +static bool is_reserved_ip(in_addr_t ip) { size_t i; for (i = 0; i < N_ELEMENTS(reserved_ips); i++) { @@ -208,9 +209,8 @@ is_reserved_ip(in_addr_t ip) return false; } -static void -destroy_ipinfo(struct cache_entry *entry, - void *context __attribute__((unused))) +static void destroy_ipinfo(struct cache_entry *entry, + void *context __attribute__((unused))) { struct ip_info *ip_info = (struct ip_info *)entry; @@ -229,8 +229,7 @@ destroy_ipinfo(struct cache_entry *entry, free(ip_info); } -static ALWAYS_INLINE char * -text_column_helper(sqlite3_stmt *stmt, int ind) +static ALWAYS_INLINE char *text_column_helper(sqlite3_stmt *stmt, int ind) { const unsigned char *value; @@ -238,8 +237,8 @@ text_column_helper(sqlite3_stmt *stmt, int ind) return value ? strdup((char *)value) : NULL; } -static struct cache_entry * -create_ipinfo(const char *key, void *context __attribute__((unused))) +static struct cache_entry *create_ipinfo(const char *key, + void *context __attribute__((unused))) { sqlite3_stmt *stmt; struct ip_info *ip_info = NULL; @@ -258,8 +257,7 @@ create_ipinfo(const char *key, void *context __attribute__((unused))) goto end_no_finalize; } - if (sqlite3_prepare(db, ip_to_city_query, - sizeof(ip_to_city_query) - 1, + if (sqlite3_prepare(db, ip_to_city_query, sizeof(ip_to_city_query) - 1, &stmt, NULL) != SQLITE_OK) goto end_no_finalize; @@ -297,9 +295,10 @@ create_ipinfo(const char *key, void *context __attribute__((unused))) } #if QUERIES_PER_HOUR != 0 -static struct cache_entry * -create_query_limit(const char *key __attribute__((unused)), - void *context __attribute__((unused))) +static struct cache_entry *create_query_limit(const char *key + __attribute__((unused)), + void *context + __attribute__((unused))) { struct query_limit *entry = malloc(sizeof(*entry)); if (LIKELY(entry)) @@ -307,16 +306,15 @@ create_query_limit(const char *key __attribute__((unused)), return (struct cache_entry *)entry; } -static void -destroy_query_limit(struct cache_entry *entry, - void *context __attribute__((unused))) +static void destroy_query_limit(struct cache_entry *entry, + void *context __attribute__((unused))) { free(entry); } #endif -static struct ip_info * -internal_query(struct lwan_request *request, const char *ip_address) +static struct ip_info *internal_query(struct lwan_request *request, + const char *ip_address) { const char *query; @@ -329,8 +327,8 @@ internal_query(struct lwan_request *request, const char *ip_address) if (UNLIKELY(!query)) return NULL; - return (struct ip_info *)cache_coro_get_and_ref_entry(cache, - request->conn->coro, query); + return (struct ip_info *)cache_coro_get_and_ref_entry( + cache, request->conn->coro, query); } #if QUERIES_PER_HOUR != 0 @@ -340,8 +338,8 @@ static bool is_rate_limited(const char *ip_address) int error; struct query_limit *limit; - limit = (struct query_limit *) - cache_get_and_ref_entry(query_limit, ip_address, &error); + limit = (struct query_limit *)cache_get_and_ref_entry(query_limit, + ip_address, &error); if (!limit) return true; @@ -400,48 +398,46 @@ static struct template_mime compile_template(const char *template, return (struct template_mime){.tpl = tpl, .mime_type = mime_type}; } -int -main(void) +int main(void) { struct lwan l; lwan_init(&l); - struct template_mime json_tpl = compile_template(json_template_str, - "application/json; charset=UTF-8"); - struct template_mime csv_tpl = compile_template(csv_template_str, - "application/csv; charset=UTF-8"); - struct template_mime xml_tpl = compile_template(xml_template_str, - "text/plain; charset=UTF-8"); + struct template_mime json_tpl = + compile_template(json_template_str, "application/json; charset=UTF-8"); + struct template_mime csv_tpl = + compile_template(csv_template_str, "application/csv; charset=UTF-8"); + struct template_mime xml_tpl = + compile_template(xml_template_str, "text/plain; charset=UTF-8"); - int result = sqlite3_open_v2("./db/ipdb.sqlite", &db, - SQLITE_OPEN_READONLY, NULL); + int result = + sqlite3_open_v2("./db/ipdb.sqlite", &db, SQLITE_OPEN_READONLY, NULL); if (result != SQLITE_OK) - lwan_status_critical("Could not open database: %s", - sqlite3_errmsg(db)); + lwan_status_critical("Could not open database: %s", sqlite3_errmsg(db)); cache = cache_create(create_ipinfo, destroy_ipinfo, NULL, 10); #if QUERIES_PER_HOUR != 0 lwan_status_info("Limiting to %d queries per hour per client", - QUERIES_PER_HOUR); - query_limit = cache_create(create_query_limit, - destroy_query_limit, NULL, 3600); + QUERIES_PER_HOUR); + query_limit = + cache_create(create_query_limit, destroy_query_limit, NULL, 3600); #else lwan_status_info("Rate-limiting disabled"); #endif const struct lwan_url_map default_map[] = { - { .prefix = "/json/", - .handler = LWAN_HANDLER_REF(templated_output), - .data = &json_tpl }, - { .prefix = "/xml/", - .handler = LWAN_HANDLER_REF(templated_output), - .data = &xml_tpl }, - { .prefix = "/csv/", - .handler = LWAN_HANDLER_REF(templated_output), - .data = &csv_tpl }, - { .prefix = "/", SERVE_FILES("./static") }, - { .prefix = NULL } + {.prefix = "/json/", + .handler = LWAN_HANDLER_REF(templated_output), + .data = &json_tpl}, + {.prefix = "/xml/", + .handler = LWAN_HANDLER_REF(templated_output), + .data = &xml_tpl}, + {.prefix = "/csv/", + .handler = LWAN_HANDLER_REF(templated_output), + .data = &csv_tpl}, + {.prefix = "/", SERVE_FILES("./static")}, + {.prefix = NULL}, }; lwan_set_url_map(&l, default_map); From 67174c7dec362bea128f7d0662ccf34f76e854b2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 19 Oct 2018 08:51:30 -0700 Subject: [PATCH 0890/2505] Reduce size of lwan_request struct even further Create some accessor functions to obtain the information from the helper struct, which is now public (although still opaque). This opens the door to lazily-parsing the values. --- src/bin/testrunner/main.c | 39 +++++----- src/lib/lwan-mod-serve-files.c | 14 ++-- src/lib/lwan-request.c | 132 ++++++++++++++++++++++----------- src/lib/lwan.h | 17 ++--- 4 files changed, 123 insertions(+), 79 deletions(-) diff --git a/src/bin/testrunner/main.c b/src/bin/testrunner/main.c index 74e929343..0bb51ea43 100644 --- a/src/bin/testrunner/main.c +++ b/src/bin/testrunner/main.c @@ -105,27 +105,27 @@ LWAN_HANDLER(test_post_will_it_blend) static const char type[] = "application/json"; static const char request_body[] = "{\"will-it-blend\": true}"; static const char response_body[] = "{\"did-it-blend\": \"oh-hell-yeah\"}"; + const struct lwan_value *content_type = + lwan_request_get_content_type(request); + const struct lwan_value *body = lwan_request_get_request_body(request); - if (!request->header.content_type) + if (!content_type->value) return HTTP_BAD_REQUEST; - if (!request->header.content_type->value) + if (content_type->len != sizeof(type) - 1) return HTTP_BAD_REQUEST; - if (request->header.content_type->len != sizeof(type) - 1) - return HTTP_BAD_REQUEST; - if (memcmp(request->header.content_type->value, type, sizeof(type) - 1) != 0) + if (memcmp(content_type->value, type, sizeof(type) - 1) != 0) return HTTP_BAD_REQUEST; - if (!request->header.body) - return HTTP_BAD_REQUEST; - if (!request->header.body->value) + if (!body->value) return HTTP_BAD_REQUEST; - if (request->header.body->len != sizeof(request_body) - 1) + if (body->len != sizeof(request_body) - 1) return HTTP_BAD_REQUEST; - if (memcmp(request->header.body->value, request_body, sizeof(request_body) - 1) != 0) + if (memcmp(body->value, request_body, sizeof(request_body) - 1) != 0) return HTTP_BAD_REQUEST; response->mime_type = type; - lwan_strbuf_set_static(response->buffer, response_body, sizeof(response_body) -1); + lwan_strbuf_set_static(response->buffer, response_body, + sizeof(response_body) - 1); return HTTP_OK; } @@ -134,22 +134,23 @@ LWAN_HANDLER(test_post_big) { static const char type[] = "x-test/trololo"; size_t i, sum = 0; + const struct lwan_value *content_type = + lwan_request_get_content_type(request); + const struct lwan_value *body = lwan_request_get_request_body(request); - if (!request->header.content_type) - return HTTP_BAD_REQUEST; - if (!request->header.content_type->value) + if (!content_type->value) return HTTP_BAD_REQUEST; - if (request->header.content_type->len != sizeof(type) - 1) + if (content_type->len != sizeof(type) - 1) return HTTP_BAD_REQUEST; - if (memcmp(request->header.content_type->value, type, sizeof(type) - 1) != 0) + if (memcmp(content_type->value, type, sizeof(type) - 1) != 0) return HTTP_BAD_REQUEST; - for (i = 0; i < request->header.body->len; i++) - sum += (size_t)request->header.body->value[i]; + for (i = 0; i < body->len; i++) + sum += (size_t)body->value[i]; response->mime_type = "application/json"; lwan_strbuf_printf(response->buffer, "{\"received\": %zu, \"sum\": %zu}", - request->header.body->len, sum); + body->len, sum); return HTTP_OK; } diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index ffa2c6d4e..4c27c77eb 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -842,8 +842,10 @@ static void serve_files_destroy(void *data) static ALWAYS_INLINE bool client_has_fresh_content(struct lwan_request *request, time_t mtime) { - return request->header.if_modified_since && - mtime <= request->header.if_modified_since; + time_t header; + int r = lwan_request_get_if_modified_since(request, &header); + + return LIKELY(!r) ? mtime <= header : false; } static size_t prepare_headers(struct lwan_request *request, @@ -884,12 +886,10 @@ static enum lwan_http_status compute_range(struct lwan_request *request, off_t *from, off_t *to, off_t size) { off_t f, t; + int r = lwan_request_get_range(request, &f, &t); - f = request->header.range.from; - t = request->header.range.to; - - /* No Range: header present: both t and f are -1 */ - if (LIKELY(t <= 0 && f <= 0)) { + /* No Range: header present */ + if (LIKELY(r < 0 || (f < 0 && t < 0))) { *from = 0; *to = size; diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index a6d82c838..929347efb 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -48,12 +48,21 @@ enum lwan_read_finalizer { FINALIZER_ERROR_TIMEOUT }; -struct request_parser_helper { +struct lwan_request_parser_helper { struct lwan_value *buffer; /* The whole request buffer */ char *next_request; /* For pipelined requests */ struct lwan_value accept_encoding; /* Accept-Encoding: */ - struct lwan_value if_modified_since;/* If-Modified-Since: */ - struct lwan_value range; /* Range: */ + + struct { /* If-Modified-Since: */ + struct lwan_value raw; + time_t parsed; + } if_modified_since; + + struct { /* Range: */ + struct lwan_value raw; + off_t from, to; + } range; + struct lwan_value cookie; /* Cookie: */ struct lwan_value query_string; /* Stuff after ? and before # */ @@ -393,27 +402,24 @@ identity_decode(char *input __attribute__((unused))) } static void -parse_cookies(struct lwan_request *request, struct request_parser_helper *helper) +parse_cookies(struct lwan_request *request, struct lwan_request_parser_helper *helper) { parse_key_values(request, &helper->cookie, &request->cookies, identity_decode, ';'); } static void -parse_query_string(struct lwan_request *request, struct request_parser_helper *helper) +parse_query_string(struct lwan_request *request, struct lwan_request_parser_helper *helper) { parse_key_values(request, &helper->query_string, &request->query_params, url_decode, '&'); } static void -parse_post_data(struct lwan_request *request, struct request_parser_helper *helper) +parse_post_data(struct lwan_request *request, struct lwan_request_parser_helper *helper) { static const char content_type[] = "application/x-www-form-urlencoded"; - request->header.body = &helper->post_data; - request->header.content_type = &helper->content_type; - if (helper->content_type.len < sizeof(content_type) - 1) return; if (UNLIKELY(strncmp(helper->content_type.value, content_type, sizeof(content_type) - 1))) @@ -425,7 +431,7 @@ parse_post_data(struct lwan_request *request, struct request_parser_helper *help static void parse_fragment_and_query(struct lwan_request *request, - struct request_parser_helper *helper, const char *space) + struct lwan_request_parser_helper *helper, const char *space) { /* Most of the time, fragments are small -- so search backwards */ char *fragment = memrchr(request->url.value, '#', request->url.len); @@ -449,7 +455,7 @@ parse_fragment_and_query(struct lwan_request *request, static char * identify_http_path(struct lwan_request *request, char *buffer, - struct request_parser_helper *helper) + struct lwan_request_parser_helper *helper) { static const size_t minimal_request_line_len = sizeof("/ HTTP/1.0") - 1; char *space, *end_of_line; @@ -503,7 +509,7 @@ identify_http_path(struct lwan_request *request, char *buffer, (struct lwan_value){.value = value, .len = (size_t)(end - value)}; \ }) -static char *parse_headers(struct request_parser_helper *helper, +static char *parse_headers(struct lwan_request_parser_helper *helper, char *buffer, char *buffer_end) { @@ -562,10 +568,10 @@ static char *parse_headers(struct request_parser_helper *helper, helper->cookie = HEADER("Cookie"); break; case MULTICHAR_CONSTANT_L('I','f','-','M'): - helper->if_modified_since = HEADER("If-Modified-Since"); + helper->if_modified_since.raw = HEADER("If-Modified-Since"); break; case MULTICHAR_CONSTANT_L('R','a','n','g'): - helper->range = HEADER("Range"); + helper->range.raw = HEADER("Range"); break; default: STRING_SWITCH_SMALL(p) { @@ -583,26 +589,26 @@ static char *parse_headers(struct request_parser_helper *helper, #undef HEADER static void -parse_if_modified_since(struct lwan_request *request, struct request_parser_helper *helper) +parse_if_modified_since(struct lwan_request *request, struct lwan_request_parser_helper *helper) { time_t parsed; - if (UNLIKELY(!helper->if_modified_since.len)) + if (UNLIKELY(!helper->if_modified_since.raw.len)) return; - if (UNLIKELY(lwan_parse_rfc_time(helper->if_modified_since.value, &parsed) < 0)) + if (UNLIKELY(lwan_parse_rfc_time(helper->if_modified_since.raw.value, &parsed) < 0)) return; - request->header.if_modified_since = parsed; + helper->if_modified_since.parsed = parsed; } static void -parse_range(struct lwan_request *request, struct request_parser_helper *helper) +parse_range(struct lwan_request *request, struct lwan_request_parser_helper *helper) { - if (UNLIKELY(helper->range.len <= (sizeof("bytes=") - 1))) + if (UNLIKELY(helper->range.raw.len <= (sizeof("bytes=") - 1))) return; - char *range = helper->range.value; + char *range = helper->range.raw.value; if (UNLIKELY(strncmp(range, "bytes=", sizeof("bytes=") - 1))) return; @@ -613,29 +619,29 @@ parse_range(struct lwan_request *request, struct request_parser_helper *helper) if (UNLIKELY(from > OFF_MAX || to > OFF_MAX)) goto invalid_range; - request->header.range.from = (off_t)from; - request->header.range.to = (off_t)to; + helper->range.from = (off_t)from; + helper->range.to = (off_t)to; } else if (sscanf(range, "-%"SCNu64, &to) == 1) { if (UNLIKELY(to > OFF_MAX)) goto invalid_range; - request->header.range.from = 0; - request->header.range.to = (off_t)to; + helper->range.from = 0; + helper->range.to = (off_t)to; } else if (sscanf(range, "%"SCNu64"-", &from) == 1) { if (UNLIKELY(from > OFF_MAX)) goto invalid_range; - request->header.range.from = (off_t)from; - request->header.range.to = -1; + helper->range.from = (off_t)from; + helper->range.to = -1; } else { invalid_range: - request->header.range.from = -1; - request->header.range.to = -1; + helper->range.from = -1; + helper->range.to = -1; } } static void -parse_accept_encoding(struct lwan_request *request, struct request_parser_helper *helper) +parse_accept_encoding(struct lwan_request *request, struct lwan_request_parser_helper *helper) { if (!helper->accept_encoding.len) return; @@ -666,7 +672,7 @@ ignore_leading_whitespace(char *buffer) } static ALWAYS_INLINE void -compute_keep_alive_flag(struct lwan_request *request, struct request_parser_helper *helper) +compute_keep_alive_flag(struct lwan_request *request, struct lwan_request_parser_helper *helper) { bool is_keep_alive; if (request->flags & REQUEST_IS_HTTP_1_0) @@ -680,8 +686,8 @@ compute_keep_alive_flag(struct lwan_request *request, struct request_parser_help } static enum lwan_http_status read_from_request_socket(struct lwan_request *request, - struct lwan_value *buffer, struct request_parser_helper *helper, const size_t buffer_size, - enum lwan_read_finalizer (*finalizer)(size_t total_read, size_t buffer_size, struct request_parser_helper *helper, int n_packets)) + struct lwan_value *buffer, struct lwan_request_parser_helper *helper, const size_t buffer_size, + enum lwan_read_finalizer (*finalizer)(size_t total_read, size_t buffer_size, struct lwan_request_parser_helper *helper, int n_packets)) { ssize_t n; size_t total_read = 0; @@ -751,7 +757,7 @@ static enum lwan_http_status read_from_request_socket(struct lwan_request *reque } static enum lwan_read_finalizer read_request_finalizer(size_t total_read, - size_t buffer_size, struct request_parser_helper *helper, int n_packets) + size_t buffer_size, struct lwan_request_parser_helper *helper, int n_packets) { /* 16 packets should be enough to read a request (without the body, as * is the case for POST requests). This yields a timeout error to avoid @@ -780,14 +786,14 @@ static enum lwan_read_finalizer read_request_finalizer(size_t total_read, } static ALWAYS_INLINE enum lwan_http_status -read_request(struct lwan_request *request, struct request_parser_helper *helper) +read_request(struct lwan_request *request, struct lwan_request_parser_helper *helper) { return read_from_request_socket(request, helper->buffer, helper, DEFAULT_BUFFER_SIZE, read_request_finalizer); } static enum lwan_read_finalizer post_data_finalizer(size_t total_read, - size_t buffer_size, struct request_parser_helper *helper, int n_packets) + size_t buffer_size, struct lwan_request_parser_helper *helper, int n_packets) { if (buffer_size == total_read) return FINALIZER_DONE; @@ -947,7 +953,7 @@ alloc_post_buffer(struct coro *coro, size_t size, bool allow_file) } static enum lwan_http_status -read_post_data(struct lwan_request *request, struct request_parser_helper *helper) +read_post_data(struct lwan_request *request, struct lwan_request_parser_helper *helper) { /* Holy indirection, Batman! */ struct lwan_config *config = &request->conn->thread->lwan->config; @@ -1012,7 +1018,7 @@ parse_proxy_protocol(struct lwan_request *request, char *buffer) } static enum lwan_http_status -parse_http_request(struct lwan_request *request, struct request_parser_helper *helper) +parse_http_request(struct lwan_request *request, struct lwan_request_parser_helper *helper) { char *buffer = helper->buffer->value; @@ -1051,7 +1057,7 @@ parse_http_request(struct lwan_request *request, struct request_parser_helper *h static enum lwan_http_status prepare_for_response(struct lwan_url_map *url_map, struct lwan_request *request, - struct request_parser_helper *helper) + struct lwan_request_parser_helper *helper) { request->url.value += url_map->prefix_len; request->url.len -= url_map->prefix_len; @@ -1109,7 +1115,7 @@ prepare_for_response(struct lwan_url_map *url_map, } static bool -handle_rewrite(struct lwan_request *request, struct request_parser_helper *helper) +handle_rewrite(struct lwan_request *request, struct lwan_request_parser_helper *helper) { request->flags &= ~RESPONSE_URL_REWRITTEN; @@ -1129,14 +1135,13 @@ char * lwan_process_request(struct lwan *l, struct lwan_request *request, struct lwan_value *buffer, char *next_request) { - enum lwan_http_status status; - struct lwan_url_map *url_map; - - struct request_parser_helper helper = { + struct lwan_request_parser_helper helper = { .buffer = buffer, .next_request = next_request, .error_when_n_packets = calculate_n_packets(DEFAULT_BUFFER_SIZE) }; + enum lwan_http_status status; + struct lwan_url_map *url_map; status = read_request(request, &helper); if (UNLIKELY(status != HTTP_OK)) { @@ -1288,3 +1293,42 @@ void lwan_request_sleep(struct lwan_request *request, uint64_t ms) assert(!(conn->flags & CONN_SUSPENDED_BY_TIMER)); assert(!(conn->flags & CONN_RESUMED_FROM_TIMER)); } + +ALWAYS_INLINE int +lwan_request_get_range(struct lwan_request *request, off_t *from, off_t *to) +{ + const struct lwan_request_parser_helper *helper = request->helper; + + if (LIKELY(helper->range.raw.len)) { + *from = helper->range.from; + *to = helper->range.to; + return 0; + } + + return -ENOENT; +} + +ALWAYS_INLINE int +lwan_request_get_if_modified_since(struct lwan_request *request, time_t *value) +{ + const struct lwan_request_parser_helper *helper = request->helper; + + if (LIKELY(helper->if_modified_since.raw.len)) { + *value = helper->if_modified_since.parsed; + return 0; + } + + return -ENOENT; +} + +ALWAYS_INLINE const struct lwan_value * +lwan_request_get_request_body(struct lwan_request *request) +{ + return &request->helper->post_data; +} + +ALWAYS_INLINE const struct lwan_value * +lwan_request_get_content_type(struct lwan_request *request) +{ + return &request->helper->content_type; +} diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 4dcc9eef4..954c939bf 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -308,6 +308,8 @@ struct lwan_proxy { DEFINE_ARRAY_TYPE(lwan_key_value_array, struct lwan_key_value) +struct lwan_request_parser_helper; + struct lwan_request { enum lwan_request_flags flags; int fd; @@ -320,15 +322,7 @@ struct lwan_request { struct timeout timeout; - struct { - time_t if_modified_since; - struct { - off_t from; - off_t to; - } range; - struct lwan_value *body; - struct lwan_value *content_type; - } header; + struct lwan_request_parser_helper *helper; struct lwan_response response; }; @@ -514,6 +508,11 @@ lwan_request_get_method(const struct lwan_request *request) return (enum lwan_request_flags)(request->flags & REQUEST_METHOD_MASK); } +int lwan_request_get_range(struct lwan_request *request, off_t *from, off_t *to); +int lwan_request_get_if_modified_since(struct lwan_request *request, time_t *value); +const struct lwan_value *lwan_request_get_request_body(struct lwan_request *request); +const struct lwan_value *lwan_request_get_content_type(struct lwan_request *request); + #if defined(__cplusplus) } #endif From e6805ba01d5b2738db8002e4eba9be08c0150fee Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 20 Oct 2018 06:43:41 -0700 Subject: [PATCH 0891/2505] Reduce amount of calls to epoll_ctl() when handling a new client Minor, but this also reduces the possibility of false sharing happening when writing to the connection array by the main thread and any I/O thread. --- src/lib/lwan-thread.c | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 6e1214de7..bbbff972a 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -306,11 +306,10 @@ static ALWAYS_INLINE void spawn_coro(struct lwan_connection *conn, } static void accept_nudge(int pipe_fd, - struct spsc_queue *pending_fds, + struct lwan_thread *t, struct lwan_connection *conns, struct death_queue *dq, struct coro_switcher *switcher, - struct timeouts *wheel, int epoll_fd) { uint64_t event; @@ -323,14 +322,18 @@ static void accept_nudge(int pipe_fd, return; } - while (spsc_queue_pop(pending_fds, &new_fd)) { + while (spsc_queue_pop(&t->pending_fds, &new_fd)) { struct lwan_connection *conn = &conns[new_fd]; + struct epoll_event ev = {.events = events_by_write_flag[1], + .data.ptr = conn}; - spawn_coro(conn, switcher, dq); - update_epoll_flags(dq, conn, epoll_fd, CONN_CORO_MAY_RESUME); + if (LIKELY(!epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_fd, &ev))) { + *conn = (struct lwan_connection){.thread = t}; + spawn_coro(conn, switcher, dq); + } } - timeouts_add(wheel, &dq->timeout, 1000); + timeouts_add(t->wheel, &dq->timeout, 1000); } static bool process_pending_timers(struct death_queue *dq, @@ -443,8 +446,8 @@ static void *thread_io_loop(void *data) struct lwan_connection *conn; if (UNLIKELY(!event->data.ptr)) { - accept_nudge(read_pipe_fd, &t->pending_fds, lwan->conns, - &dq, &switcher, t->wheel, epoll_fd); + accept_nudge(read_pipe_fd, t, lwan->conns, &dq, &switcher, + epoll_fd); continue; } @@ -532,17 +535,7 @@ void lwan_thread_nudge(struct lwan_thread *t) void lwan_thread_add_client(struct lwan_thread *t, int fd) { - struct epoll_event event = {.events = events_by_write_flag[1]}; - int i = 0; - - t->lwan->conns[fd] = (struct lwan_connection){.thread = t}; - - if (UNLIKELY(epoll_ctl(t->epoll_fd, EPOLL_CTL_ADD, fd, &event) < 0)) { - lwan_status_perror("EPOLL_CTL_ADD failed"); - goto drop_connection; - } - - do { + for (int i = 0; i < 10; i++) { bool pushed = spsc_queue_push(&t->pending_fds, fd); if (LIKELY(pushed)) @@ -550,9 +543,8 @@ void lwan_thread_add_client(struct lwan_thread *t, int fd) /* Queue is full; nudge the thread to consume it. */ lwan_thread_nudge(t); - } while (i++ < 10); + } -drop_connection: lwan_status_error("Dropping connection %d", fd); /* FIXME: send "busy" response now, even without receiving request? */ close(fd); From 15825f2e514082efd41650fee13076cb044e7d07 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 20 Oct 2018 16:40:35 -0700 Subject: [PATCH 0892/2505] Fix crash after 67174c7d --- src/lib/lwan-request.c | 43 ++++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 929347efb..86f30e9a7 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -685,10 +685,17 @@ compute_keep_alive_flag(struct lwan_request *request, struct lwan_request_parser request->conn->flags &= ~CONN_KEEP_ALIVE; } -static enum lwan_http_status read_from_request_socket(struct lwan_request *request, - struct lwan_value *buffer, struct lwan_request_parser_helper *helper, const size_t buffer_size, - enum lwan_read_finalizer (*finalizer)(size_t total_read, size_t buffer_size, struct lwan_request_parser_helper *helper, int n_packets)) +static enum lwan_http_status +read_from_request_socket(struct lwan_request *request, + struct lwan_value *buffer, + const size_t buffer_size, + enum lwan_read_finalizer (*finalizer)( + size_t total_read, + size_t buffer_size, + struct lwan_request_parser_helper *helper, + int n_packets)) { + struct lwan_request_parser_helper *helper = request->helper; ssize_t n; size_t total_read = 0; int n_packets = 0; @@ -702,9 +709,9 @@ static enum lwan_http_status read_from_request_socket(struct lwan_request *reque goto try_to_finalize; } - for (; ; n_packets++) { + for (;; n_packets++) { n = read(request->fd, buffer->value + total_read, - (size_t)(buffer_size - total_read)); + (size_t)(buffer_size - total_read)); /* Client has shutdown orderly, nothing else to do; kill coro */ if (UNLIKELY(n == 0)) { coro_yield(request->conn->coro, CONN_CORO_ABORT); @@ -786,10 +793,11 @@ static enum lwan_read_finalizer read_request_finalizer(size_t total_read, } static ALWAYS_INLINE enum lwan_http_status -read_request(struct lwan_request *request, struct lwan_request_parser_helper *helper) +read_request(struct lwan_request *request) { - return read_from_request_socket(request, helper->buffer, helper, - DEFAULT_BUFFER_SIZE, read_request_finalizer); + return read_from_request_socket(request, request->helper->buffer, + DEFAULT_BUFFER_SIZE, + read_request_finalizer); } static enum lwan_read_finalizer post_data_finalizer(size_t total_read, @@ -952,9 +960,9 @@ alloc_post_buffer(struct coro *coro, size_t size, bool allow_file) return ptr; } -static enum lwan_http_status -read_post_data(struct lwan_request *request, struct lwan_request_parser_helper *helper) +static enum lwan_http_status read_post_data(struct lwan_request *request) { + struct lwan_request_parser_helper *helper = request->helper; /* Holy indirection, Batman! */ struct lwan_config *config = &request->conn->thread->lwan->config; const size_t max_post_data_size = config->max_post_data_size; @@ -986,7 +994,7 @@ read_post_data(struct lwan_request *request, struct lwan_request_parser_helper * } new_buffer = alloc_post_buffer(request->conn->coro, post_data_size + 1, - config->allow_post_temp_file); + config->allow_post_temp_file); if (UNLIKELY(!new_buffer)) return HTTP_INTERNAL_ERROR; @@ -999,9 +1007,10 @@ read_post_data(struct lwan_request *request, struct lwan_request_parser_helper * helper->error_when_time = time(NULL) + config->keep_alive_timeout; helper->error_when_n_packets = calculate_n_packets(post_data_size); - struct lwan_value buffer = { .value = new_buffer, .len = post_data_size - have }; - return read_from_request_socket(request, &buffer, helper, buffer.len, - post_data_finalizer); + struct lwan_value buffer = {.value = new_buffer, + .len = post_data_size - have}; + return read_from_request_socket(request, &buffer, buffer.len, + post_data_finalizer); } static char * @@ -1104,7 +1113,7 @@ prepare_for_response(struct lwan_url_map *url_map, return HTTP_NOT_ALLOWED; } - status = read_post_data(request, helper); + status = read_post_data(request); if (UNLIKELY(status != HTTP_OK)) return status; @@ -1143,7 +1152,9 @@ lwan_process_request(struct lwan *l, struct lwan_request *request, enum lwan_http_status status; struct lwan_url_map *url_map; - status = read_request(request, &helper); + request->helper = &helper; + + status = read_request(request); if (UNLIKELY(status != HTTP_OK)) { /* This request was bad, but maybe there's a good one in the * pipeline. */ From 982ece24e54886ca84f1a2d1d5959a489c758053 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 20 Oct 2018 06:56:07 -0700 Subject: [PATCH 0893/2505] Simplify how new coroutines are spawned --- src/lib/lwan-thread.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index bbbff972a..5f2f0cc86 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -286,22 +286,24 @@ static void update_date_cache(struct lwan_thread *thread) } static ALWAYS_INLINE void spawn_coro(struct lwan_connection *conn, + struct lwan_thread *t, struct coro_switcher *switcher, struct death_queue *dq) { assert(!conn->coro); assert(!(conn->flags & CONN_IS_ALIVE)); - assert(!(conn->flags & CONN_SHOULD_RESUME_CORO)); - conn->coro = coro_new(switcher, process_request_coro, conn); + *conn = (struct lwan_connection) { + .coro = coro_new(switcher, process_request_coro, conn), + .flags = CONN_IS_ALIVE | CONN_SHOULD_RESUME_CORO, + .time_to_die = dq->time + dq->keep_alive_timeout, + .thread = t, + }; if (UNLIKELY(!conn->coro)) { lwan_status_error("Could not create coroutine"); return; } - conn->flags = CONN_IS_ALIVE | CONN_SHOULD_RESUME_CORO; - conn->time_to_die = dq->time + dq->keep_alive_timeout; - death_queue_insert(dq, conn); } @@ -327,10 +329,8 @@ static void accept_nudge(int pipe_fd, struct epoll_event ev = {.events = events_by_write_flag[1], .data.ptr = conn}; - if (LIKELY(!epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_fd, &ev))) { - *conn = (struct lwan_connection){.thread = t}; - spawn_coro(conn, switcher, dq); - } + if (LIKELY(!epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_fd, &ev))) + spawn_coro(conn, t, switcher, dq); } timeouts_add(t->wheel, &dq->timeout, 1000); From 0318fc53f6ba2d88fe54d924ebbca9cbb9f0ac16 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 21 Oct 2018 09:59:05 -0700 Subject: [PATCH 0894/2505] Go back to using level-triggered epoll events This dramatically reduces the amount of system calls necessary to handle requests. When running testrunner, making 1M requests with 1k clients, this table shows the number of epoll_ctl() calls: /hello /chunked With this patch: 1005 2005 Previously: 20000005 2000132 The same approach was problematic with pipelined requests; however, this version fixes that by flipping the flags (from EPOLLIN to EPOLLOUT) when there's a potential request in the pipeline, forcing the connection coroutine to be resumed. The hello world benchmark is ~15% faster. --- src/lib/lwan-io-wrappers.c | 10 ++++++++++ src/lib/lwan-response.c | 2 ++ src/lib/lwan-thread.c | 10 ++++++++-- src/lib/lwan.h | 1 + src/lib/missing.c | 4 +--- 5 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index 0ede7f3ad..8c3a60f4f 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -44,6 +44,8 @@ lwan_writev(struct lwan_request *request, struct iovec *iov, int iov_count) switch (errno) { case EAGAIN: + request->conn->flags |= CONN_FLIP_FLAGS; + /* fallthrough */ case EINTR: goto try_again; default: @@ -85,6 +87,8 @@ lwan_send(struct lwan_request *request, const void *buf, size_t count, int flags switch (errno) { case EAGAIN: + request->conn->flags |= CONN_FLIP_FLAGS; + /* fallthrough */ case EINTR: goto try_again; default: @@ -127,6 +131,8 @@ lwan_sendfile(struct lwan_request *request, int in_fd, off_t offset, size_t coun if (written < 0) { switch (errno) { case EAGAIN: + request->conn->flags |= CONN_FLIP_FLAGS; + /* fallthrough */ case EINTR: coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); continue; @@ -175,6 +181,8 @@ lwan_sendfile(struct lwan_request *request, int in_fd, off_t offset, size_t coun if (UNLIKELY(r < 0)) { switch (errno) { case EAGAIN: + request->conn->flags |= CONN_FLIP_FLAGS; + /* fallthrough */ case EBUSY: case EINTR: coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); @@ -207,6 +215,8 @@ try_pread(struct coro *coro, int fd, void *buffer, size_t len, off_t offset) switch (errno) { case EAGAIN: + request->conn->flags |= CONN_FLIP_FLAGS; + /* fallthrough */ case EINTR: goto try_again; default: diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 3d2e2d9fc..95773e5a0 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -380,6 +380,7 @@ bool lwan_response_set_chunked(struct lwan_request *request, return false; request->flags |= RESPONSE_SENT_HEADERS; + request->conn->flags |= CONN_FLIP_FLAGS; lwan_send(request, buffer, buffer_len, MSG_MORE); return true; @@ -439,6 +440,7 @@ bool lwan_response_set_event_stream(struct lwan_request *request, return false; request->flags |= RESPONSE_SENT_HEADERS; + request->conn->flags |= CONN_FLIP_FLAGS; lwan_send(request, buffer, buffer_len, MSG_MORE); return true; diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 5f2f0cc86..1a63333a1 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -46,7 +46,7 @@ struct death_queue { static const uint32_t events_by_write_flag[] = { EPOLLOUT | EPOLLRDHUP | EPOLLERR, - EPOLLIN | EPOLLRDHUP | EPOLLERR | EPOLLET + EPOLLIN | EPOLLRDHUP | EPOLLERR }; static inline int death_queue_node_to_idx(struct death_queue *dq, @@ -180,6 +180,9 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, lwan_process_request(lwan, &request, &buffer, next_request); coro_deferred_run(coro, generation); + if (next_request && *next_request) + conn->flags |= CONN_FLIP_FLAGS; + coro_yield(coro, CONN_CORO_MAY_RESUME); lwan_strbuf_reset(&strbuf); @@ -246,7 +249,10 @@ static ALWAYS_INLINE void resume_coro_if_needed(struct death_queue *dq, return; } - update_epoll_flags(dq, conn, epoll_fd, yield_result); + if (conn->flags & CONN_FLIP_FLAGS) { + conn->flags &= ~CONN_FLIP_FLAGS; + update_epoll_flags(dq, conn, epoll_fd, yield_result); + } } static void death_queue_kill_waiting(struct death_queue *dq) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 954c939bf..762f7ddf3 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -252,6 +252,7 @@ enum lwan_connection_flags { CONN_MUST_READ = 1 << 4, CONN_SUSPENDED_BY_TIMER = 1 << 5, CONN_RESUMED_FROM_TIMER = 1 << 6, + CONN_FLIP_FLAGS = 1 << 7, }; enum lwan_connection_coro_yield { diff --git a/src/lib/missing.c b/src/lib/missing.c index 8c61fb61e..cb0613fb9 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -158,10 +158,8 @@ epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) case EPOLL_CTL_ADD: case EPOLL_CTL_MOD: { int events = 0; - /* EV_CLEAR should be set only if EPOLLET is there, but Lwan doesn't - * always set EPOLLET. In the meantime, force EV_CLEAR every time. */ uintptr_t udata = (uintptr_t)event->data.ptr; - int flags = EV_ADD | EV_CLEAR; + int flags = EV_ADD; if (event->events & EPOLLIN) { events = EVFILT_READ; From 190d4e96d7831447dc79b00b15fac2e3e1a4761a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 21 Oct 2018 11:17:52 -0700 Subject: [PATCH 0895/2505] Flip flags if read_from_request_socket() receives EAGAIN --- src/lib/lwan-request.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 86f30e9a7..b0a768255 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -721,6 +721,8 @@ read_from_request_socket(struct lwan_request *request, if (UNLIKELY(n < 0)) { switch (errno) { case EAGAIN: + request->conn->flags |= CONN_FLIP_FLAGS; + /* fallthrough */ case EINTR: yield_and_read_again: request->conn->flags |= CONN_MUST_READ; From b3ab672b8595d2a3ecbaf81464b0ad782ab00879 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 21 Oct 2018 11:18:07 -0700 Subject: [PATCH 0896/2505] Fix timers after moving to level-triggered events --- src/lib/lwan-thread.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 1a63333a1..e5dd7386e 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -237,6 +237,9 @@ static ALWAYS_INLINE void resume_coro_if_needed(struct death_queue *dq, struct lwan_connection *conn, int epoll_fd) { + const enum lwan_connection_flags update_mask = + CONN_FLIP_FLAGS | CONN_RESUMED_FROM_TIMER | CONN_SUSPENDED_BY_TIMER; + assert(conn->coro); if (!(conn->flags & CONN_SHOULD_RESUME_CORO)) @@ -249,7 +252,7 @@ static ALWAYS_INLINE void resume_coro_if_needed(struct death_queue *dq, return; } - if (conn->flags & CONN_FLIP_FLAGS) { + if (conn->flags & update_mask) { conn->flags &= ~CONN_FLIP_FLAGS; update_epoll_flags(dq, conn, epoll_fd, yield_result); } From 57d784cbb615d51e290a3a300118a41109464169 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 21 Oct 2018 11:18:44 -0700 Subject: [PATCH 0897/2505] Calculate which block digits to draw only when time changes --- src/samples/clock/main.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/samples/clock/main.c b/src/samples/clock/main.c index 20c57c47d..0455e4ffa 100644 --- a/src/samples/clock/main.c +++ b/src/samples/clock/main.c @@ -167,17 +167,18 @@ LWAN_HANDLER(blocks) while (total_waited <= 3600000) { uint64_t timeout; time_t curtime; - char digits[5]; curtime = time(NULL); if (curtime != last) { + char digits[5]; + strftime(digits, sizeof(digits), "%H%M", localtime(&curtime)); last = curtime; odd_second = last & 1; - } - for (int i = 0; i < 4; i++) - blocks.states[i].num_to_draw = digits[i] - '0'; + for (int i = 0; i < 4; i++) + blocks.states[i].num_to_draw = digits[i] - '0'; + } timeout = blocks_draw(&blocks, odd_second); total_waited += timeout; From 22ddac90546d4d65406c68744bb27a0b950117eb Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 21 Oct 2018 14:20:56 -0700 Subject: [PATCH 0898/2505] No need to NULLify next/prev pointers when removing from DQ This doesn't seem to be an issue any longer, so remove these stores. --- src/lib/lwan-thread.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index e5dd7386e..0d1afbdd1 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -77,10 +77,6 @@ static void death_queue_remove(struct death_queue *dq, struct lwan_connection *next = death_queue_idx_to_node(dq, node->next); next->prev = node->prev; prev->next = node->next; - - /* FIXME: This shouldn't be required; there may be a bug somewhere when - * a few million requests are attended to. */ - node->next = node->prev = -1; } static bool death_queue_empty(struct death_queue *dq) From bbc7e002e3f518804c396b5d510d972fad31ed06 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 21 Oct 2018 14:27:47 -0700 Subject: [PATCH 0899/2505] Allow Lua scripts to return HTTP status code other than '200 OK' --- README.md | 5 +++++ src/lib/lwan-mod-lua.c | 12 +++++++++++- src/scripts/testsuite.py | 11 +++++++++++ test.lua | 8 ++++++++ 4 files changed, 35 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8e21c2192..7c5e06d57 100644 --- a/README.md +++ b/README.md @@ -372,6 +372,11 @@ information from the request, or to set the response, as seen below: - `req:set_headers(tbl)` sets the response headers from the table `tbl`; a header may be specified multiple times by using a table, rather than a string, in the table value (`{'foo'={'bar', 'baz'}}`); must be called before sending any response with `say()` or `send_event()` - `req:sleep(ms)` pauses the current handler for the specified amount of milliseconds +Handler functions may return either `nil` (in which case, a `200 OK` response +is generated), or a number matching an HTTP status code. Attempting to return +an invalid HTTP status code or anything other than a number or `nil` will result +in a `500 Internal Server Error` response being thrown. + | Option | Type | Default | Description | |--------|------|---------|-------------| | `default_type` | `str` | `text/plain` | Default MIME-Type for responses | diff --git a/src/lib/lwan-mod-lua.c b/src/lib/lwan-mod-lua.c index e2d5b7b86..8ec72357c 100644 --- a/src/lib/lwan-mod-lua.c +++ b/src/lib/lwan-mod-lua.c @@ -209,7 +209,17 @@ static enum lwan_http_status lua_handle_request(struct lwan_request *request, n_arguments = 0; break; case 0: - return HTTP_OK; + if (lua_isnil(L, -1)) + return HTTP_OK; + + if (lua_isnumber(L, -1)) { + lua_Integer code = lua_tointeger(L, -1); + + if (code >= 100 && code <= 999) + return (enum lwan_http_status)code; + } + + return HTTP_INTERNAL_ERROR; default: lwan_status_error("Error from Lua script: %s", lua_tostring(L, -1)); return HTTP_INTERNAL_ERROR; diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index dfbd99e8c..49f5aa566 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -461,6 +461,16 @@ def test_chunked_encoding(self): 'Last chunk\n') class TestLua(LwanTest): + def test_brew_coffee(self): + r = requests.get('http://127.0.0.1:8080/lua/brew_coffee') + + self.assertEqual(r.status_code, 418) + + def test_invalid_Code(self): + r = requests.get('http://127.0.0.1:8080/lua/invalid_code') + + self.assertEqual(r.status_code, 500) + def test_inline(self): r = requests.get('http://localhost:8080/inline') self.assertResponseHtml(r) @@ -759,6 +769,7 @@ def test_pipelined_requests(self): self.assertTrue(s in responses) responses = responses.replace(s, '') + class TestArtificialResponse(LwanTest): def test_brew_coffee(self): r = requests.get('http://127.0.0.1:8080/brew-coffee') diff --git a/test.lua b/test.lua index 2dd8553aa..dfb2d06d9 100644 --- a/test.lua +++ b/test.lua @@ -41,6 +41,14 @@ function handle_get_random(req) req:set_response("Random number: " .. math.random()) end +function handle_get_brew_coffee(req) + return 418 +end + +function handle_get_invalid_code(req) + return 42 +end + function string.starts(String, Start) -- From http://lua-users.org/wiki/StringRecipes return string.sub(String, 1, string.len(Start)) == Start From 4dcb4ed12c6332188c0c77a021b1bf3862c78fc5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 21 Oct 2018 18:18:15 -0700 Subject: [PATCH 0900/2505] Ensure content_length has enough space for uint_to_string() --- src/lib/lwan-mod-serve-files.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 4c27c77eb..795c673b9 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -857,7 +857,7 @@ static size_t prepare_headers(struct lwan_request *request, char *header_buf, size_t header_buf_size) { - char content_length[3 * sizeof(size_t)]; + char content_length[INT_TO_STR_BUFFER_SIZE]; size_t discard; struct lwan_key_value additional_headers[4] = { { From a629bd3553cb16591a4f165f369692943038d871 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 21 Oct 2018 18:52:52 -0700 Subject: [PATCH 0901/2505] Simplify INT_TO_STR_BUFFER_SIZE expression `3 * sizeof(some integer type)` will always return a good approximation of how many characters are necessary to represent a value in decimal, with the NUL terminator byte included. There's no need for a `+ 1`. --- src/lib/int-to-str.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/int-to-str.h b/src/lib/int-to-str.h index c5ec9b22b..98793611c 100644 --- a/src/lib/int-to-str.h +++ b/src/lib/int-to-str.h @@ -21,7 +21,7 @@ #include #include -#define INT_TO_STR_BUFFER_SIZE (3 * sizeof(size_t) + 1) +#define INT_TO_STR_BUFFER_SIZE (3 * sizeof(size_t)) char *int_to_string(ssize_t value, char buffer[static INT_TO_STR_BUFFER_SIZE], From bda70897fb0baf77765148248ad119f3e6002e3a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 22 Oct 2018 07:59:37 -0700 Subject: [PATCH 0902/2505] Fix the uncommon case of receiving EAGAIN while reading request --- src/lib/lwan-request.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index b0a768255..e8fdc5a51 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -722,6 +722,7 @@ read_from_request_socket(struct lwan_request *request, switch (errno) { case EAGAIN: request->conn->flags |= CONN_FLIP_FLAGS; + coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); /* fallthrough */ case EINTR: yield_and_read_again: From 85e40cd65b2fd938838b48bb930daa63f04ebd4e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 23 Oct 2018 07:16:18 -0700 Subject: [PATCH 0903/2505] Reindent missing/ directory and missing.c --- src/lib/missing.c | 210 ++++++++++++----------------- src/lib/missing/fcntl.h | 31 ++--- src/lib/missing/ioprio.h | 5 +- src/lib/missing/libproc.h | 3 +- src/lib/missing/limits.h | 20 +-- src/lib/missing/linux/capability.h | 2 +- src/lib/missing/pthread.h | 7 +- src/lib/missing/stdlib.h | 36 ++--- src/lib/missing/string.h | 50 +++---- src/lib/missing/sys/epoll.h | 32 +++-- src/lib/missing/sys/mman.h | 3 +- src/lib/missing/sys/sendfile.h | 7 +- src/lib/missing/sys/socket.h | 9 +- src/lib/missing/sys/syscall.h | 3 +- src/lib/missing/sys/types.h | 3 +- src/lib/missing/time.h | 19 +-- src/lib/missing/unistd.h | 3 +- 17 files changed, 212 insertions(+), 231 deletions(-) diff --git a/src/lib/missing.c b/src/lib/missing.c index cb0613fb9..fee52bfbc 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -14,7 +14,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include @@ -33,8 +34,7 @@ #include "lwan.h" #ifndef HAVE_MEMPCPY -void * -mempcpy(void *dest, const void *src, size_t len) +void *mempcpy(void *dest, const void *src, size_t len) { char *p = memcpy(dest, src, len); return p + len; @@ -42,8 +42,7 @@ mempcpy(void *dest, const void *src, size_t len) #endif #ifndef HAVE_MEMRCHR -void * -memrchr(const void *s, int c, size_t n) +void *memrchr(const void *s, int c, size_t n) { const char *end = (const char *)s + n + 1; const char *prev = NULL; @@ -59,98 +58,91 @@ memrchr(const void *s, int c, size_t n) #endif #ifndef HAVE_PIPE2 -int -pipe2(int pipefd[2], int flags) +int pipe2(int pipefd[2], int flags) { - int r; + int r; - r = pipe(pipefd); - if (r < 0) - return r; + r = pipe(pipefd); + if (r < 0) + return r; - if (fcntl(pipefd[0], F_SETFL, flags) < 0 || fcntl(pipefd[1], F_SETFL, flags) < 0) { - int saved_errno = errno; + if (fcntl(pipefd[0], F_SETFL, flags) < 0 || + fcntl(pipefd[1], F_SETFL, flags) < 0) { + int saved_errno = errno; - close(pipefd[0]); - close(pipefd[1]); + close(pipefd[0]); + close(pipefd[1]); - errno = saved_errno; - return -1; - } + errno = saved_errno; + return -1; + } - return 0; + return 0; } #endif #ifndef HAVE_ACCEPT4 -int -accept4(int sock, struct sockaddr *addr, socklen_t *addrlen, int flags) +int accept4(int sock, struct sockaddr *addr, socklen_t *addrlen, int flags) { - int fd = accept(sock, addr, addrlen); - int newflags = 0; - - if (fd < 0) - return fd; - - if (flags & SOCK_NONBLOCK) { - newflags |= O_NONBLOCK; - flags &= ~SOCK_NONBLOCK; - } - if (flags & SOCK_CLOEXEC) { - newflags |= O_CLOEXEC; - flags &= ~SOCK_CLOEXEC; - } - if (flags) { - errno = -EINVAL; - return -1; - } - - if (fcntl(fd, F_SETFL, newflags) < 0) { - int saved_errno = errno; - - close(fd); - - errno = saved_errno; - return -1; - } - - return fd; + int fd = accept(sock, addr, addrlen); + int newflags = 0; + + if (fd < 0) + return fd; + + if (flags & SOCK_NONBLOCK) { + newflags |= O_NONBLOCK; + flags &= ~SOCK_NONBLOCK; + } + if (flags & SOCK_CLOEXEC) { + newflags |= O_CLOEXEC; + flags &= ~SOCK_CLOEXEC; + } + if (flags) { + errno = -EINVAL; + return -1; + } + + if (fcntl(fd, F_SETFL, newflags) < 0) { + int saved_errno = errno; + + close(fd); + + errno = saved_errno; + return -1; + } + + return fd; } #endif #ifndef HAVE_CLOCK_GETTIME -int -clock_gettime(clockid_t clk_id, struct timespec *ts) +int clock_gettime(clockid_t clk_id, struct timespec *ts) { - switch (clk_id) { - case CLOCK_MONOTONIC: - case CLOCK_MONOTONIC_COARSE: - /* FIXME: time() isn't monotonic */ - ts->tv_sec = time(NULL); - ts->tv_nsec = 0; - return 0; - } - - errno = EINVAL; - return -1; + switch (clk_id) { + case CLOCK_MONOTONIC: + case CLOCK_MONOTONIC_COARSE: + /* FIXME: time() isn't monotonic */ + ts->tv_sec = time(NULL); + ts->tv_nsec = 0; + return 0; + } + + errno = EINVAL; + return -1; } #endif #if !defined(HAVE_EPOLL) && defined(HAVE_KQUEUE) -#include #include #include +#include #include "hash.h" -int -epoll_create1(int flags __attribute__((unused))) -{ - return kqueue(); -} +int epoll_create1(int flags __attribute__((unused))) { return kqueue(); } -int -epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) +int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) { struct kevent ev; @@ -193,8 +185,7 @@ epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) return kevent(epfd, &ev, 1, NULL, 0, NULL); } -static struct timespec * -to_timespec(struct timespec *t, int ms) +static struct timespec *to_timespec(struct timespec *t, int ms) { if (ms < 0) return NULL; @@ -205,8 +196,7 @@ to_timespec(struct timespec *t, int ms) return t; } -int -epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) +int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) { struct epoll_event *ev = events; struct kevent evs[maxevents]; @@ -226,8 +216,8 @@ epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) for (i = 0; i < r; i++) { struct kevent *kev = &evs[i]; - uint32_t mask = (uint32_t)(uintptr_t)hash_find(coalesce, - (void*)(intptr_t)evs[i].ident); + uint32_t mask = (uint32_t)(uintptr_t)hash_find( + coalesce, (void *)(intptr_t)evs[i].ident); if (kev->flags & EV_ERROR) mask |= EPOLLERR; @@ -239,13 +229,14 @@ epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) else if (kev->filter == EVFILT_WRITE && (uintptr_t)evs[i].udata != 1) mask |= EPOLLOUT; - hash_add(coalesce, (void*)(intptr_t)evs[i].ident, (void *)(uintptr_t)mask); + hash_add(coalesce, (void *)(intptr_t)evs[i].ident, + (void *)(uintptr_t)mask); } for (i = 0; i < r; i++) { void *maskptr; - maskptr = hash_find(coalesce, (void*)(intptr_t)evs[i].ident); + maskptr = hash_find(coalesce, (void *)(intptr_t)evs[i].ident); if (maskptr) { struct kevent *kev = &evs[i]; @@ -267,8 +258,7 @@ epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) #include #endif -int -proc_pidpath(pid_t pid, void *buffer, size_t buffersize) +int proc_pidpath(pid_t pid, void *buffer, size_t buffersize) { ssize_t path_len; @@ -309,10 +299,9 @@ proc_pidpath(pid_t pid, void *buffer, size_t buffersize) #elif defined(__FreeBSD__) #include -int -proc_pidpath(pid_t pid, void *buffer, size_t buffersize) +int proc_pidpath(pid_t pid, void *buffer, size_t buffersize) { - int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; + int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1}; size_t path_len = buffersize; if (getpid() != pid) { @@ -329,8 +318,7 @@ proc_pidpath(pid_t pid, void *buffer, size_t buffersize) #elif defined(HAVE_DLADDR) && !defined(__APPLE__) #include -int -proc_pidpath(pid_t pid, void *buffer, size_t buffersize) +int proc_pidpath(pid_t pid, void *buffer, size_t buffersize) { Dl_info info; @@ -366,16 +354,11 @@ proc_pidpath(pid_t pid, void *buffer, size_t buffersize) #if defined(__linux__) #include -long -gettid(void) -{ - return syscall(SYS_gettid); -} +long gettid(void) { return syscall(SYS_gettid); } #elif defined(__FreeBSD__) #include -long -gettid(void) +long gettid(void) { long ret; @@ -386,17 +369,9 @@ gettid(void) #elif defined(__APPLE__) && MAC_OS_X_VERSION_MAX_ALLOWED >= 101200 #include -long -gettid(void) -{ - return syscall(SYS_thread_selfid); -} +long gettid(void) { return syscall(SYS_thread_selfid); } #else -long -gettid(void) -{ - return (long)pthread_self(); -} +long gettid(void) { return (long)pthread_self(); } #endif #if defined(__APPLE__) @@ -407,17 +382,15 @@ gettid(void) #include -static int -get_current_proc_info(struct kinfo_proc *kp) +static int get_current_proc_info(struct kinfo_proc *kp) { - int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid() }; + int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()}; size_t len = sizeof(*kp); return sysctl(mib, N_ELEMENTS(mib), kp, &len, NULL, 0); } -int -getresuid(uid_t *ruid, uid_t *euid, uid_t *suid) +int getresuid(uid_t *ruid, uid_t *euid, uid_t *suid) { struct kinfo_proc kp; @@ -432,20 +405,17 @@ getresuid(uid_t *ruid, uid_t *euid, uid_t *suid) return -1; } -int -setresuid(uid_t ruid, uid_t euid, uid_t suid __attribute__((unused))) +int setresuid(uid_t ruid, uid_t euid, uid_t suid __attribute__((unused))) { return setreuid(ruid, euid); } -int -setresgid(gid_t rgid, gid_t egid, gid_t sgid __attribute__((unused))) +int setresgid(gid_t rgid, gid_t egid, gid_t sgid __attribute__((unused))) { return setregid(rgid, egid); } -int -getresgid(gid_t *rgid, gid_t *egid, gid_t *sgid) +int getresgid(gid_t *rgid, gid_t *egid, gid_t *sgid) { struct kinfo_proc kp; @@ -489,13 +459,10 @@ int mkostemp(char *tmpl, int flags) #endif #if !defined(HAVE_RAWMEMCHR) -void *rawmemchr(const void *ptr, char c) -{ - return memchr(ptr, c, SIZE_MAX); -} +void *rawmemchr(const void *ptr, char c) { return memchr(ptr, c, SIZE_MAX); } #endif -#if !defined (HAVE_REALLOCARRAY) +#if !defined(HAVE_REALLOCARRAY) /* $OpenBSD: reallocarray.c,v 1.2 2014/12/08 03:45:00 bcook Exp $ */ /* * Copyright (c) 2008 Otto Moerbeek @@ -524,11 +491,12 @@ void *rawmemchr(const void *ptr, char c) * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW */ -#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4)) +#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4)) static inline bool umull_overflow(size_t a, size_t b, size_t *out) { - if ((a >= MUL_NO_OVERFLOW || b >= MUL_NO_OVERFLOW) && a > 0 && SIZE_MAX / a < b) + if ((a >= MUL_NO_OVERFLOW || b >= MUL_NO_OVERFLOW) && a > 0 && + SIZE_MAX / a < b) return true; *out = a * b; return false; diff --git a/src/lib/missing/fcntl.h b/src/lib/missing/fcntl.h index 73875a9b4..59f296628 100644 --- a/src/lib/missing/fcntl.h +++ b/src/lib/missing/fcntl.h @@ -14,7 +14,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include_next @@ -23,31 +24,31 @@ #define MISSING_FCNTL_H #ifndef O_NOATIME -# define O_NOATIME 0 +#define O_NOATIME 0 #endif #ifndef O_PATH -# define O_PATH 0 +#define O_PATH 0 #endif #if defined(__linux__) /* Definitions for O_TMPFILE obtained from glibc/Linux kernel. */ -# if !defined(__O_TMPFILE) -# if defined(__alpha__) -# define __O_TMPFILE 0100000000 -# elif defined(__sparc__) || defined(__sparc64__) -# define __O_TMPFILE 0200000000 -# elif defined(__parisc__) || defined(__hppa__) -# define __O_TMPFILE 0400000000 -# else -# define __O_TMPFILE 020000000 -# endif -# endif +#if !defined(__O_TMPFILE) +#if defined(__alpha__) +#define __O_TMPFILE 0100000000 +#elif defined(__sparc__) || defined(__sparc64__) +#define __O_TMPFILE 0200000000 +#elif defined(__parisc__) || defined(__hppa__) +#define __O_TMPFILE 0400000000 +#else +#define __O_TMPFILE 020000000 +#endif +#endif /* a horrid kludge trying to make sure that this will fail on old kernels */ #ifndef O_TMPFILE -# define O_TMPFILE (__O_TMPFILE | O_DIRECTORY) +#define O_TMPFILE (__O_TMPFILE | O_DIRECTORY) #endif #endif /* __linux__ */ diff --git a/src/lib/missing/ioprio.h b/src/lib/missing/ioprio.h index 046878309..6e2095de3 100644 --- a/src/lib/missing/ioprio.h +++ b/src/lib/missing/ioprio.h @@ -14,13 +14,14 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #pragma once -#include #include +#include #if defined(__linux__) && defined(SYS_ioprio_set) diff --git a/src/lib/missing/libproc.h b/src/lib/missing/libproc.h index f5ccb236a..f280b6ed3 100644 --- a/src/lib/missing/libproc.h +++ b/src/lib/missing/libproc.h @@ -14,7 +14,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #if defined(__APPLE__) diff --git a/src/lib/missing/limits.h b/src/lib/missing/limits.h index 647dd7143..85ca9541e 100644 --- a/src/lib/missing/limits.h +++ b/src/lib/missing/limits.h @@ -14,7 +14,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include_next @@ -23,25 +24,24 @@ #define MISSING_LIMITS_H #ifndef PATH_MAX -# define PATH_MAX 4096 +#define PATH_MAX 4096 #endif - #ifndef OPEN_MAX -# include +#include -# ifdef NOFILE -# define OPEN_MAX NOFILE -# else -# define OPEN_MAX 65535 -# endif +#ifdef NOFILE +#define OPEN_MAX NOFILE +#else +#define OPEN_MAX 65535 +#endif #endif #ifndef OFF_MAX #include -# define OFF_MAX ~((off_t)1 << (sizeof(off_t) * CHAR_BIT - 1)) +#define OFF_MAX ~((off_t)1 << (sizeof(off_t) * CHAR_BIT - 1)) #endif #endif /* MISSING_LIMITS_H */ diff --git a/src/lib/missing/linux/capability.h b/src/lib/missing/linux/capability.h index f5c8a72ff..0645e467d 100644 --- a/src/lib/missing/linux/capability.h +++ b/src/lib/missing/linux/capability.h @@ -24,9 +24,9 @@ #if defined(__linux__) && defined(HAVE_LINUX_CAPABILITY) #include_next +#include #include #include -#include static inline int capset(struct __user_cap_header_struct *header, struct __user_cap_data_struct *data) diff --git a/src/lib/missing/pthread.h b/src/lib/missing/pthread.h index 4a900713f..2a9551568 100644 --- a/src/lib/missing/pthread.h +++ b/src/lib/missing/pthread.h @@ -14,7 +14,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include_next @@ -31,7 +32,9 @@ typedef struct pthread_barrier { pthread_cond_t cond; } pthread_barrier_t; -int pthread_barrier_init(pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restrict attr, unsigned int count); +int pthread_barrier_init(pthread_barrier_t *restrict barrier, + const pthread_barrierattr_t *restrict attr, + unsigned int count); int pthread_barrier_destroy(pthread_barrier_t *barrier); int pthread_barrier_wait(pthread_barrier_t *barrier); #endif diff --git a/src/lib/missing/stdlib.h b/src/lib/missing/stdlib.h index 89d5c313d..65d778ce5 100644 --- a/src/lib/missing/stdlib.h +++ b/src/lib/missing/stdlib.h @@ -14,7 +14,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include_next @@ -23,23 +24,20 @@ #define MISSING_STDLIB_H #if defined(__GLIBC__) -# include -# if defined(__GLIBC_PREREQ) && __GLIBC_PREREQ(2, 17) -# define HAVE_SECURE_GETENV -# endif -# if defined(__GLIBC_PREREQ) && __GLIBC_PREREQ(2, 19) -# define HAVE_MKOSTEMPS -# endif -# if defined(__GLIBC_PREREQ) && __GLIBC_PREREQ(2, 7) -# define HAVE_CORRECT_UMASK_TMPFILE -# endif +#include +#if defined(__GLIBC_PREREQ) && __GLIBC_PREREQ(2, 17) +#define HAVE_SECURE_GETENV +#endif +#if defined(__GLIBC_PREREQ) && __GLIBC_PREREQ(2, 19) +#define HAVE_MKOSTEMPS +#endif +#if defined(__GLIBC_PREREQ) && __GLIBC_PREREQ(2, 7) +#define HAVE_CORRECT_UMASK_TMPFILE +#endif #endif #if !defined(HAVE_SECURE_GETENV) -static inline char *secure_getenv(const char *name) -{ - return getenv(name); -} +static inline char *secure_getenv(const char *name) { return getenv(name); } #endif #if !defined(HAVE_MKOSTEMP) @@ -47,9 +45,13 @@ int mkostemp(char *tmpl, int flags); #endif #if defined(HAVE_CORRECT_UMASK_TMPFILE) -# define umask_for_tmpfile(mask_) ({ (void)(mask_); 0U; }) +#define umask_for_tmpfile(mask_) \ + ({ \ + (void)(mask_); \ + 0U; \ + }) #else -# define umask_for_tmpfile(mask_) umask(mask_) +#define umask_for_tmpfile(mask_) umask(mask_) #endif #if !defined(HAVE_REALLOCARRAY) diff --git a/src/lib/missing/string.h b/src/lib/missing/string.h index 5bf881272..f45886ef1 100644 --- a/src/lib/missing/string.h +++ b/src/lib/missing/string.h @@ -14,7 +14,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include_next @@ -22,31 +23,32 @@ #ifndef MISSING_STRING_H #define MISSING_STRING_H -#define strndupa_impl(s, l) ({ \ - char *strndupa_tmp_s = alloca(l + 1); \ - strndupa_tmp_s[l] = '\0'; \ - memcpy(strndupa_tmp_s, s, l); \ -}) +#define strndupa_impl(s, l) \ + ({ \ + char *strndupa_tmp_s = alloca(l + 1); \ + strndupa_tmp_s[l] = '\0'; \ + memcpy(strndupa_tmp_s, s, l); \ + }) #ifndef strndupa -# define strndupa(s, l) strndupa_impl((s), strnlen((s), (l))) -# undef NEED_ALLOCA_H -# define NEED_ALLOCA_H +#define strndupa(s, l) strndupa_impl((s), strnlen((s), (l))) +#undef NEED_ALLOCA_H +#define NEED_ALLOCA_H #endif #ifndef strdupa -# define strdupa(s) strndupa((s), strlen(s)) -# undef NEED_ALLOCA_H -# define NEED_ALLOCA_H +#define strdupa(s) strndupa((s), strlen(s)) +#undef NEED_ALLOCA_H +#define NEED_ALLOCA_H #endif #ifdef NEED_ALLOCA_H -# undef NEED_ALLOCA_H -# ifdef HAVE_ALLOCA_H -# include -# else -# include -# endif +#undef NEED_ALLOCA_H +#ifdef HAVE_ALLOCA_H +#include +#else +#include +#endif #endif #ifndef HAVE_RAWMEMCHR @@ -61,17 +63,15 @@ void *mempcpy(void *dest, const void *src, size_t len); void *memrchr(const void *s, int c, size_t n); #endif -static inline int -streq(const char *a, const char *b) +static inline int streq(const char *a, const char *b) { - return strcmp(a, b) == 0; + return strcmp(a, b) == 0; } -static inline void * -mempmove(void *dest, const void *src, size_t len) +static inline void *mempmove(void *dest, const void *src, size_t len) { - unsigned char *d = memmove(dest, src, len); - return d + len; + unsigned char *d = memmove(dest, src, len); + return d + len; } #endif /* MISSING_STRING_H */ diff --git a/src/lib/missing/sys/epoll.h b/src/lib/missing/sys/epoll.h index e93bd4a34..2175879e8 100644 --- a/src/lib/missing/sys/epoll.h +++ b/src/lib/missing/sys/epoll.h @@ -14,7 +14,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #if !defined(HAVE_EPOLL) @@ -22,24 +23,18 @@ #include enum { - EPOLLIN = 1<<0, - EPOLLOUT = 1<<1, - EPOLLONESHOT = 1<<2, - EPOLLRDHUP = 1<<3, - EPOLLERR = 1<<4, - EPOLLET = 1<<5, + EPOLLIN = 1 << 0, + EPOLLOUT = 1 << 1, + EPOLLONESHOT = 1 << 2, + EPOLLRDHUP = 1 << 3, + EPOLLERR = 1 << 4, + EPOLLET = 1 << 5, EPOLLHUP = EPOLLRDHUP } epoll_event_flag; -enum { - EPOLL_CTL_ADD, - EPOLL_CTL_MOD, - EPOLL_CTL_DEL -} epoll_op; +enum { EPOLL_CTL_ADD, EPOLL_CTL_MOD, EPOLL_CTL_DEL } epoll_op; -enum { - EPOLL_CLOEXEC = 1<<0 -} epoll_create_flags; +enum { EPOLL_CLOEXEC = 1 << 0 } epoll_create_flags; struct epoll_event { uint32_t events; @@ -49,11 +44,14 @@ struct epoll_event { uint32_t u32; uint64_t u64; } data; -}; +}; int epoll_create1(int flags); int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); -int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); +int epoll_wait(int epfd, + struct epoll_event *events, + int maxevents, + int timeout); #else #include_next diff --git a/src/lib/missing/sys/mman.h b/src/lib/missing/sys/mman.h index cb6a2dfda..6572327ea 100644 --- a/src/lib/missing/sys/mman.h +++ b/src/lib/missing/sys/mman.h @@ -14,7 +14,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include_next diff --git a/src/lib/missing/sys/sendfile.h b/src/lib/missing/sys/sendfile.h index 01f776ccb..50fa3c57d 100644 --- a/src/lib/missing/sys/sendfile.h +++ b/src/lib/missing/sys/sendfile.h @@ -14,14 +14,15 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #if defined(__FreeBSD__) || defined(__APPLE__) -# include +#include #elif defined(__OpenBSD__) /* OpenBSD has no sendfile(); implement the Lwan wrapper directly in * lwan-io-wrappers.c */ #else -# include_next +#include_next #endif diff --git a/src/lib/missing/sys/socket.h b/src/lib/missing/sys/socket.h index 68d9ec4a8..a1d6ad419 100644 --- a/src/lib/missing/sys/socket.h +++ b/src/lib/missing/sys/socket.h @@ -14,7 +14,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include_next @@ -23,15 +24,15 @@ #define MISSING_SYS_SOCKET_H #ifndef MSG_MORE -# define MSG_MORE 0 +#define MSG_MORE 0 #endif #ifndef SOCK_CLOEXEC -# define SOCK_CLOEXEC 0 +#define SOCK_CLOEXEC 0 #endif #ifndef SOCK_NONBLOCK -# define SOCK_NONBLOCK 00004000 +#define SOCK_NONBLOCK 00004000 #endif #ifndef HAVE_ACCEPT4 diff --git a/src/lib/missing/sys/syscall.h b/src/lib/missing/sys/syscall.h index 12a9fea64..ae435acd2 100644 --- a/src/lib/missing/sys/syscall.h +++ b/src/lib/missing/sys/syscall.h @@ -14,7 +14,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef __CYGWIN__ diff --git a/src/lib/missing/sys/types.h b/src/lib/missing/sys/types.h index f178de8ad..cfdad62c0 100644 --- a/src/lib/missing/sys/types.h +++ b/src/lib/missing/sys/types.h @@ -14,7 +14,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include_next diff --git a/src/lib/missing/time.h b/src/lib/missing/time.h index 551da5685..e6e38d65a 100644 --- a/src/lib/missing/time.h +++ b/src/lib/missing/time.h @@ -14,7 +14,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include_next @@ -26,18 +27,18 @@ typedef int clockid_t; int clock_gettime(clockid_t clk_id, struct timespec *ts); -# ifndef CLOCK_MONOTONIC_COARSE -# define CLOCK_MONOTONIC_COARSE 0 -# endif +#ifndef CLOCK_MONOTONIC_COARSE +#define CLOCK_MONOTONIC_COARSE 0 +#endif -# ifndef CLOCK_MONOTONIC -# define CLOCK_MONOTONIC 1 -# endif +#ifndef CLOCK_MONOTONIC +#define CLOCK_MONOTONIC 1 +#endif #elif !defined(CLOCK_MONOTONIC_COARSE) && defined(CLOCK_MONOTONIC_FAST) -# define CLOCK_MONOTONIC_COARSE CLOCK_MONOTONIC_FAST /* FreeBSD */ +#define CLOCK_MONOTONIC_COARSE CLOCK_MONOTONIC_FAST /* FreeBSD */ #elif !defined(CLOCK_MONOTONIC_COARSE) && defined(CLOCK_MONOTONIC_RAW_APPROX) -# define CLOCK_MONOTONIC_COARSE CLOCK_MONOTONIC_RAW_APPROX /* macOS */ +#define CLOCK_MONOTONIC_COARSE CLOCK_MONOTONIC_RAW_APPROX /* macOS */ #endif #endif /* MISSING_TIME_H */ diff --git a/src/lib/missing/unistd.h b/src/lib/missing/unistd.h index c7e4e0419..c74ca29d3 100644 --- a/src/lib/missing/unistd.h +++ b/src/lib/missing/unistd.h @@ -14,7 +14,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include_next From 728b38d16a91b79fd86c23c93a0ee61f74d4d7f6 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 23 Oct 2018 07:17:47 -0700 Subject: [PATCH 0904/2505] Reindent queue.[ch] --- src/lib/queue.c | 60 ++++++++++++++++++++++++++----------------------- src/lib/queue.h | 7 +++--- 2 files changed, 36 insertions(+), 31 deletions(-) diff --git a/src/lib/queue.c b/src/lib/queue.c index f5600763f..bd48dba40 100644 --- a/src/lib/queue.c +++ b/src/lib/queue.c @@ -3,49 +3,60 @@ * Based on public domain C++ version by mstump[1]. Released under * the same license terms. * - * [1] https://github.com/mstump/queues/blob/master/include/spsc-bounded-queue.hpp + * [1] + * https://github.com/mstump/queues/blob/master/include/spsc-bounded-queue.hpp */ -#include +#include +#include #include #include -#include -#include +#include #include "queue.h" #include "lwan-private.h" #if !defined(ATOMIC_RELAXED) -#define ATOMIC_RELAXED __ATOMIC_RELAXED -#define ATOMIC_ACQUIRE __ATOMIC_ACQUIRE -#define ATOMIC_RELEASE __ATOMIC_RELEASE +#define ATOMIC_RELAXED __ATOMIC_RELAXED +#define ATOMIC_ACQUIRE __ATOMIC_ACQUIRE +#define ATOMIC_RELEASE __ATOMIC_RELEASE #endif #if defined(__GNUC__) -# if (__GNUC__ * 100 + __GNUC_MINOR__ >= 470) -# define HAS_GCC_ATOMIC 1 -# else -# define HAS_SYNC_ATOMIC 1 +#if (__GNUC__ * 100 + __GNUC_MINOR__ >= 470) +#define HAS_GCC_ATOMIC 1 +#else +#define HAS_SYNC_ATOMIC 1 #endif #endif #if HAS_GCC_ATOMIC -#define ATOMIC_INIT(P, V) do { (P) = (V); } while (0) +#define ATOMIC_INIT(P, V) \ + do { \ + (P) = (V); \ + } while (0) -#define ATOMIC_LOAD(P, O) __atomic_load_n((P), (O)) -#define ATOMIC_STORE(P, V, O) __atomic_store_n((P), (V), (O)) +#define ATOMIC_LOAD(P, O) __atomic_load_n((P), (O)) +#define ATOMIC_STORE(P, V, O) __atomic_store_n((P), (V), (O)) #elif HAS_SYNC_ATOMIC -#define ATOMIC_INIT(P, V) do { (P) = (V); } while(0) +#define ATOMIC_INIT(P, V) \ + do { \ + (P) = (V); \ + } while (0) -#define ATOMIC_LOAD(P, O) __sync_fetch_and_add((P), 0) -#define ATOMIC_STORE(P, V, O) ({ __sync_synchronize(); __sync_lock_test_and_set((P), (V)); }) +#define ATOMIC_LOAD(P, O) __sync_fetch_and_add((P), 0) +#define ATOMIC_STORE(P, V, O) \ + ({ \ + __sync_synchronize(); \ + __sync_lock_test_and_set((P), (V)); \ + }) #else @@ -53,8 +64,7 @@ #endif -int -spsc_queue_init(struct spsc_queue *q, size_t size) +int spsc_queue_init(struct spsc_queue *q, size_t size) { if (size == 0) return -EINVAL; @@ -73,14 +83,9 @@ spsc_queue_init(struct spsc_queue *q, size_t size) return 0; } -void -spsc_queue_free(struct spsc_queue *q) -{ - free(q->buffer); -} +void spsc_queue_free(struct spsc_queue *q) { free(q->buffer); } -bool -spsc_queue_push(struct spsc_queue *q, int input) +bool spsc_queue_push(struct spsc_queue *q, int input) { const size_t head = ATOMIC_LOAD(&q->head, ATOMIC_RELAXED); @@ -94,8 +99,7 @@ spsc_queue_push(struct spsc_queue *q, int input) return false; } -bool -spsc_queue_pop(struct spsc_queue *q, int *output) +bool spsc_queue_pop(struct spsc_queue *q, int *output) { const size_t tail = ATOMIC_LOAD(&q->tail, ATOMIC_RELAXED); diff --git a/src/lib/queue.h b/src/lib/queue.h index 7c78ce7d1..f0b68292b 100644 --- a/src/lib/queue.h +++ b/src/lib/queue.h @@ -3,13 +3,14 @@ * Based on public domain C++ version by mstump[1]. Released under * the same license terms. * - * [1] https://github.com/mstump/queues/blob/master/include/spsc-bounded-queue.hpp + * [1] + * https://github.com/mstump/queues/blob/master/include/spsc-bounded-queue.hpp */ #pragma once -#include #include +#include struct spsc_queue { size_t size; @@ -21,7 +22,7 @@ struct spsc_queue { char cache_line_pad1[64 - sizeof(size_t)]; size_t tail; - char cache_line_pad2[64 - sizeof(size_t)]; + char cache_line_pad2[64 - sizeof(size_t)]; }; int spsc_queue_init(struct spsc_queue *q, size_t size); From 447f0cb7faeb5da1168d626f9789cdf6d49b2ae8 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 23 Oct 2018 07:38:44 -0700 Subject: [PATCH 0905/2505] Reset connection flags to 0 if coro_new() fails --- src/lib/lwan-thread.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 0d1afbdd1..dcc3280b0 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -305,6 +305,7 @@ static ALWAYS_INLINE void spawn_coro(struct lwan_connection *conn, .thread = t, }; if (UNLIKELY(!conn->coro)) { + conn->flags = 0; lwan_status_error("Could not create coroutine"); return; } From 69a0ac0466b3bb960a1d3711c0d0e36677de1532 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 23 Oct 2018 07:42:18 -0700 Subject: [PATCH 0906/2505] Ignore all read() errors on eventfd/pipe while accepting nudge --- src/lib/lwan-thread.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index dcc3280b0..9dad8a7c0 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -323,12 +323,10 @@ static void accept_nudge(int pipe_fd, uint64_t event; int new_fd; - while (read(pipe_fd, &event, sizeof(event)) < 0) { - if (errno == EINTR) - continue; - - return; - } + /* Errors are ignored here as pipe_fd serves just as a way to wake the + * thread from epoll_wait(). It's fine to consume the queue at this + * point, regardless of the error type. */ + (void)read(pipe_fd, &event, sizeof(event)); while (spsc_queue_pop(&t->pending_fds, &new_fd)) { struct lwan_connection *conn = &conns[new_fd]; From 1fe1a32a67377b0609caf8e4184d113c7b710e65 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 23 Oct 2018 08:06:37 -0700 Subject: [PATCH 0907/2505] Simplify connection pre-scheduling even further Not only makes pre-scheduling work on non-x86_64 architectures, but also get rid of the fd_to_thread array allocated in struct lwan. The idea here is that all pre-allocated connections gets assigned a thread at startup, and this information is used when scheduling them. --- src/lib/lwan-thread.c | 25 +++++++++++-------------- src/lib/lwan.c | 13 +++---------- src/lib/lwan.h | 4 ---- 3 files changed, 14 insertions(+), 28 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 9dad8a7c0..8f9e189ff 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -570,6 +570,7 @@ void lwan_thread_init(struct lwan *l) for (short i = 0; i < l->thread.count; i++) create_thread(l, &l->thread.threads[i]); + const unsigned int total_conns = l->thread.max_fd * l->thread.count; #ifdef __x86_64__ static_assert(sizeof(struct lwan_connection) == 32, "Two connections per cache line"); @@ -581,20 +582,20 @@ void lwan_thread_init(struct lwan *l) * them can fill up a cache line. This formula will group two connections * per thread in a way that false-sharing is avoided. */ - l->thread.fd_to_thread_mask = - (unsigned int)lwan_nextpow2((size_t)((l->thread.count - 1) * 2)); - l->thread.fd_to_thread = - calloc(l->thread.fd_to_thread_mask, sizeof(unsigned int)); + uint32_t n_threads = (uint32_t)lwan_nextpow2((size_t)((l->thread.count - 1) * 2)); + uint32_t *fd_to_thread = alloca(n_threads * sizeof(uint32_t)); - if (!l->thread.fd_to_thread) - lwan_status_critical("Could not allocate fd_to_thread array"); - - for (unsigned int i = 0; i < l->thread.fd_to_thread_mask; i++) { + for (unsigned int i = 0; i < n_threads; i++) { /* TODO: do not assume the CPU topology */ - l->thread.fd_to_thread[i] = (i / 2) % l->thread.count; + fd_to_thread[i] = (i / 2) % l->thread.count; } + n_threads--; /* Transform count into mask for AND below */ - l->thread.fd_to_thread_mask--; + for (unsigned int i = 0; i < total_conns; i++) + l->conns[i].thread = &l->thread.threads[fd_to_thread[i & n_threads]]; +#else + for (unsigned int i = 0; i < total_conns; i++) + l->conns[i].thread = &l->thread.threads[i % l->thread.count]; #endif pthread_barrier_wait(&l->thread.barrier); @@ -630,8 +631,4 @@ void lwan_thread_shutdown(struct lwan *l) } free(l->thread.threads); - -#ifdef __x86_64__ - free(l->thread.fd_to_thread); -#endif } diff --git a/src/lib/lwan.c b/src/lib/lwan.c index f9f29315f..4e2922fab 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -612,18 +612,11 @@ void lwan_shutdown(struct lwan *l) static ALWAYS_INLINE unsigned int schedule_client(struct lwan *l, int fd) { - unsigned int thread; + struct lwan_thread *thread = l->conns[fd].thread; -#ifdef __x86_64__ - thread = l->thread.fd_to_thread[(unsigned int)fd & l->thread.fd_to_thread_mask]; -#else - static unsigned int counter = 0; - thread = counter++ % l->thread.count; -#endif - - lwan_thread_add_client(&l->thread.threads[thread], fd); + lwan_thread_add_client(thread, fd); - return thread; + return (unsigned int)(thread - l->thread.threads); } static volatile sig_atomic_t main_socket = -1; diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 762f7ddf3..99b5ba82e 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -423,10 +423,6 @@ struct lwan { struct lwan_connection *conns; struct { -#ifdef __x86_64__ - unsigned int *fd_to_thread; - unsigned int fd_to_thread_mask; -#endif pthread_barrier_t barrier; struct lwan_thread *threads; unsigned int max_fd; From 29f1ad8a2938e83bf7c2687ff188f847ace0a179 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 23 Oct 2018 20:25:42 -0700 Subject: [PATCH 0908/2505] Ensure connections have been assigned a thread, and that it's valid Also ensure this points to a valid thread struct --- src/lib/lwan-thread.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 8f9e189ff..d1392463b 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -291,12 +291,17 @@ static void update_date_cache(struct lwan_thread *thread) } static ALWAYS_INLINE void spawn_coro(struct lwan_connection *conn, - struct lwan_thread *t, struct coro_switcher *switcher, struct death_queue *dq) { + struct lwan_thread *t = conn->thread; + assert(!conn->coro); assert(!(conn->flags & CONN_IS_ALIVE)); + assert(t); + assert((uintptr_t)t >= (uintptr_t)dq->lwan->thread.threads); + assert((uintptr_t)t < + (uintptr_t)(dq->lwan->thread.threads + dq->lwan->thread.count)); *conn = (struct lwan_connection) { .coro = coro_new(switcher, process_request_coro, conn), @@ -334,7 +339,7 @@ static void accept_nudge(int pipe_fd, .data.ptr = conn}; if (LIKELY(!epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_fd, &ev))) - spawn_coro(conn, t, switcher, dq); + spawn_coro(conn, switcher, dq); } timeouts_add(t->wheel, &dq->timeout, 1000); From d6e6db4b903a2109d737d42ef7317c28a12f3c58 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 24 Oct 2018 07:48:20 -0700 Subject: [PATCH 0909/2505] No need to keep all fd_watches in an array Allocate them individually and use normal error recovery strategies to free them up when time comes. --- src/lib/lwan.c | 53 ++++++++++++++++++++++---------------------------- src/lib/lwan.h | 3 --- 2 files changed, 23 insertions(+), 33 deletions(-) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 4e2922fab..d94e9f479 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -527,6 +527,18 @@ static char *dup_or_null(const char *s) return s ? strdup(s) : NULL; } +static void lwan_fd_watch_init(struct lwan *l) +{ + l->epfd = epoll_create1(EPOLL_CLOEXEC); + if (l->epfd < 0) + lwan_status_critical_perror("epoll_create1"); +} + +static void lwan_fd_watch_shutdown(struct lwan *l) +{ + close(l->epfd); +} + void lwan_init_with_config(struct lwan *l, const struct lwan_config *config) { /* Load defaults */ @@ -585,6 +597,7 @@ void lwan_init_with_config(struct lwan *l, const struct lwan_config *config) lwan_thread_init(l); lwan_socket_init(l); lwan_http_authorize_init(); + lwan_fd_watch_init(l); } void lwan_shutdown(struct lwan *l) @@ -608,6 +621,7 @@ void lwan_shutdown(struct lwan *l) lwan_status_shutdown(l); lwan_http_authorize_shutdown(); lwan_readahead_shutdown(); + lwan_fd_watch_shutdown(l); } static ALWAYS_INLINE unsigned int schedule_client(struct lwan *l, int fd) @@ -704,20 +718,10 @@ struct lwan_fd_watch *lwan_watch_fd(struct lwan *l, void *data) { struct lwan_fd_watch *watch; - bool found_watch = false; - - LWAN_ARRAY_FOREACH (&l->fd_watches, watch) { - if (watch->coro == NULL) { - found_watch = true; - break; - } - } - if (!found_watch) { - watch = lwan_fd_watch_array_append(&l->fd_watches); - if (!watch) - return NULL; - } + watch = malloc(sizeof(*watch)); + if (!watch) + return NULL; watch->coro = coro_new(&l->switcher, coro_fn, data); if (!watch->coro) @@ -733,8 +737,7 @@ struct lwan_fd_watch *lwan_watch_fd(struct lwan *l, return watch; out: - watch->coro = NULL; - watch->fd = -1; + free(watch); return NULL; } @@ -746,8 +749,7 @@ void lwan_unwatch_fd(struct lwan *l, struct lwan_fd_watch *w) } coro_free(w->coro); - w->coro = NULL; - w->fd = -1; + free(w); } void lwan_main_loop(struct lwan *l) @@ -758,10 +760,6 @@ void lwan_main_loop(struct lwan *l) assert(main_socket == -1); main_socket = l->main_socket; - l->epfd = epoll_create1(EPOLL_CLOEXEC); - if (l->epfd < 0) - lwan_status_critical_perror("epoll_create1"); - if (signal(SIGINT, sigint_handler) == SIG_ERR) lwan_status_critical("Could not set signal handler"); @@ -777,24 +775,19 @@ void lwan_main_loop(struct lwan *l) if (UNLIKELY(n_evs < 0)) { if (main_socket < 0) - goto out; + break; if (errno == EINTR || errno == EAGAIN) continue; - goto out; + break; } for (int i = 0; i < n_evs; i++) { if (!coro_resume_value(evs[i].data.ptr, (int)evs[i].events)) - goto out; + break; } } -out: - LWAN_ARRAY_FOREACH (&l->fd_watches, watch) { - lwan_unwatch_fd(l, watch); - } - - close(l->epfd); + lwan_unwatch_fd(l, watch); } #ifdef CLOCK_MONOTONIC_COARSE diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 99b5ba82e..ffd02501d 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -416,8 +416,6 @@ struct lwan_fd_watch { int fd; }; -DEFINE_ARRAY_TYPE(lwan_fd_watch_array, struct lwan_fd_watch) - struct lwan { struct lwan_trie url_map_trie; struct lwan_connection *conns; @@ -429,7 +427,6 @@ struct lwan { unsigned short count; } thread; - struct lwan_fd_watch_array fd_watches; struct lwan_config config; struct coro_switcher switcher; int main_socket; From 39a46fbd8424bc730a8ce842a9687a07d02ce431 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 25 Oct 2018 09:05:11 -0700 Subject: [PATCH 0910/2505] Do not use dynamic memory allocation while printing status --- src/lib/lwan-status.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-status.c b/src/lib/lwan-status.c index 30ef7a757..43ebea92d 100644 --- a/src/lib/lwan-status.c +++ b/src/lib/lwan-status.c @@ -183,17 +183,16 @@ status_out(const char *file, const int line, const char *func, enum lwan_status_type type, const char *fmt, va_list values) #endif { - char *output; + char output[2 * 80 /* 2 * ${COLUMNS} */]; int len; - len = vasprintf(&output, fmt, values); + len = vsnprintf(output, sizeof(output), fmt, values); if (len >= 0) { #ifdef NDEBUG status_out_msg(type, output, (size_t)len); #else status_out_msg(file, line, func, type, output, (size_t)len); #endif - free(output); } } From d436b4e935af9b1556d9cb82388d7fd9a0f82f1e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 25 Oct 2018 09:05:39 -0700 Subject: [PATCH 0911/2505] Reindent lwan-status.c --- src/lib/lwan-status.c | 108 ++++++++++++++++++++++-------------------- 1 file changed, 56 insertions(+), 52 deletions(-) diff --git a/src/lib/lwan-status.c b/src/lib/lwan-status.c index 43ebea92d..f9ccb6566 100644 --- a/src/lib/lwan-status.c +++ b/src/lib/lwan-status.c @@ -14,7 +14,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #define _GNU_SOURCE @@ -31,12 +32,12 @@ #include "lwan-private.h" enum lwan_status_type { - STATUS_INFO = 1<<0, - STATUS_WARNING = 1<<1, - STATUS_ERROR = 1<<2, - STATUS_PERROR = 1<<3, - STATUS_CRITICAL = 1<<4, - STATUS_DEBUG = 1<<5, + STATUS_INFO = 1 << 0, + STATUS_WARNING = 1 << 1, + STATUS_ERROR = 1 << 2, + STATUS_PERROR = 1 << 3, + STATUS_CRITICAL = 1 << 4, + STATUS_DEBUG = 1 << 5, }; static volatile bool quiet = false; @@ -45,25 +46,20 @@ static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; static bool can_use_colors(void); -void -lwan_status_init(struct lwan *l) +void lwan_status_init(struct lwan *l) { #ifdef NDEBUG quiet = l->config.quiet; #else quiet = false; - (void) l; + (void)l; #endif use_colors = can_use_colors(); } -void -lwan_status_shutdown(struct lwan *l __attribute__((unused))) -{ -} +void lwan_status_shutdown(struct lwan *l __attribute__((unused))) {} -static bool -can_use_colors(void) +static bool can_use_colors(void) { const char *term; @@ -77,8 +73,8 @@ can_use_colors(void) return true; } -static const char * -get_color_start_for_type(enum lwan_status_type type, size_t *len_out) +static const char *get_color_start_for_type(enum lwan_status_type type, + size_t *len_out) { const char *retval; @@ -102,9 +98,9 @@ get_color_start_for_type(enum lwan_status_type type, size_t *len_out) return retval; } -static const char * -get_color_end_for_type(enum lwan_status_type type __attribute__((unused)), - size_t *len_out) +static const char *get_color_end_for_type(enum lwan_status_type type + __attribute__((unused)), + size_t *len_out) { static const char *retval = "\033[0m"; @@ -117,8 +113,7 @@ get_color_end_for_type(enum lwan_status_type type __attribute__((unused)), return retval; } -static inline char * -strerror_thunk_r(int error_number, char *buffer, size_t len) +static inline char *strerror_thunk_r(int error_number, char *buffer, size_t len) { #ifdef __GLIBC__ return strerror_r(error_number, buffer, len); @@ -133,11 +128,16 @@ static void #ifdef NDEBUG status_out_msg(enum lwan_status_type type, const char *msg, size_t msg_len) #else -status_out_msg(const char *file, const int line, const char *func, - enum lwan_status_type type, const char *msg, size_t msg_len) +status_out_msg(const char *file, + const int line, + const char *func, + enum lwan_status_type type, + const char *msg, + size_t msg_len) #endif { - int error_number = errno; /* Make sure no library call below modifies errno */ + int error_number = + errno; /* Make sure no library call below modifies errno */ size_t start_len, end_len; const char *start_color = get_color_start_for_type(type, &start_len); const char *end_color = get_color_end_for_type(type, &end_len); @@ -179,8 +179,12 @@ static void #ifdef NDEBUG status_out(enum lwan_status_type type, const char *fmt, va_list values) #else -status_out(const char *file, const int line, const char *func, - enum lwan_status_type type, const char *fmt, va_list values) +status_out(const char *file, + const int line, + const char *func, + enum lwan_status_type type, + const char *fmt, + va_list values) #endif { char output[2 * 80 /* 2 * ${COLUMNS} */]; @@ -197,32 +201,32 @@ status_out(const char *file, const int line, const char *func, } #ifdef NDEBUG -#define IMPLEMENT_FUNCTION(fn_name_, type_) \ - void \ - lwan_status_##fn_name_(const char *fmt, ...) \ - { \ - if (!quiet) { \ - va_list values; \ - va_start(values, fmt); \ - status_out(type_, fmt, values); \ - va_end(values); \ - } \ - if ((type_) & STATUS_CRITICAL) exit(1); \ +#define IMPLEMENT_FUNCTION(fn_name_, type_) \ + void lwan_status_##fn_name_(const char *fmt, ...) \ + { \ + if (!quiet) { \ + va_list values; \ + va_start(values, fmt); \ + status_out(type_, fmt, values); \ + va_end(values); \ + } \ + if ((type_)&STATUS_CRITICAL) \ + exit(1); \ } #else -#define IMPLEMENT_FUNCTION(fn_name_, type_) \ - void \ - lwan_status_##fn_name_##_debug(const char *file, \ - const int line, const char *func, \ - const char *fmt, ...) \ - { \ - if (!quiet) { \ - va_list values; \ - va_start(values, fmt); \ - status_out(file, line, func, type_, fmt, values); \ - va_end(values); \ - } \ - if ((type_) & STATUS_CRITICAL) abort(); \ +#define IMPLEMENT_FUNCTION(fn_name_, type_) \ + void lwan_status_##fn_name_##_debug(const char *file, const int line, \ + const char *func, const char *fmt, \ + ...) \ + { \ + if (!quiet) { \ + va_list values; \ + va_start(values, fmt); \ + status_out(file, line, func, type_, fmt, values); \ + va_end(values); \ + } \ + if ((type_)&STATUS_CRITICAL) \ + abort(); \ } IMPLEMENT_FUNCTION(debug, STATUS_DEBUG) From 31ade045f5ba8d2198c6e4621790f120bbf304a5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 27 Oct 2018 10:43:29 -0700 Subject: [PATCH 0912/2505] Use -1 as invalid file descriptors in lwan-readahead --- src/lib/lwan-readahead.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-readahead.c b/src/lib/lwan-readahead.c index 105a60002..24671d4c7 100644 --- a/src/lib/lwan-readahead.c +++ b/src/lib/lwan-readahead.c @@ -49,7 +49,7 @@ struct lwan_readahead_cmd { }; } __attribute__((packed)); -static int readahead_pipe_fd[2]; +static int readahead_pipe_fd[2] = {-1, -1}; static pthread_t readahead_self; void lwan_readahead_shutdown(void) @@ -58,7 +58,8 @@ void lwan_readahead_shutdown(void) .cmd = SHUTDOWN, }; - if (readahead_pipe_fd[0] == readahead_pipe_fd[1]) + if (readahead_pipe_fd[0] == readahead_pipe_fd[1] && + readahead_pipe_fd[0] == -1) return; lwan_status_debug("Shutting down readahead thread"); @@ -68,7 +69,7 @@ void lwan_readahead_shutdown(void) close(readahead_pipe_fd[0]); close(readahead_pipe_fd[1]); - readahead_pipe_fd[0] = readahead_pipe_fd[1] = 0; + readahead_pipe_fd[0] = readahead_pipe_fd[1] = -1; } void lwan_readahead_queue(int fd, off_t off, size_t size) From dfbac8bb4f992883e7cf91f901b707350fc571e5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 25 Oct 2018 09:04:43 -0700 Subject: [PATCH 0913/2505] Reduce time that lwan_status lock is held --- src/lib/lwan-status.c | 68 +++++++++++++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 22 deletions(-) diff --git a/src/lib/lwan-status.c b/src/lib/lwan-status.c index f9ccb6566..91188b6e1 100644 --- a/src/lib/lwan-status.c +++ b/src/lib/lwan-status.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include "lwan-private.h" @@ -42,12 +43,13 @@ enum lwan_status_type { static volatile bool quiet = false; static bool use_colors; -static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_spinlock_t spinlock; static bool can_use_colors(void); void lwan_status_init(struct lwan *l) { + pthread_spin_init(&spinlock, PTHREAD_PROCESS_PRIVATE); #ifdef NDEBUG quiet = l->config.quiet; #else @@ -124,6 +126,20 @@ static inline char *strerror_thunk_r(int error_number, char *buffer, size_t len) #endif } +#define VEC_STR(s, l) \ + (struct iovec) { .iov_base = (void *)s, .iov_len = (size_t)(l) } +#define VEC_LITERAL(l) VEC_STR(l, sizeof(l) - 1) +#define VEC_PRINTF(fmt, ...) \ + ({ \ + const size_t len = (size_t)(out - buffer) + sizeof(buffer); \ + int r = snprintf(out, len, fmt, __VA_ARGS__); \ + if (UNLIKELY(r < 0 || r >= (int)len)) \ + goto out; \ + struct iovec v = VEC_STR(out, r); \ + out += r + 1; \ + v; \ + }) + static void #ifdef NDEBUG status_out_msg(enum lwan_status_type type, const char *msg, size_t msg_len) @@ -136,43 +152,51 @@ status_out_msg(const char *file, size_t msg_len) #endif { - int error_number = - errno; /* Make sure no library call below modifies errno */ size_t start_len, end_len; const char *start_color = get_color_start_for_type(type, &start_len); const char *end_color = get_color_end_for_type(type, &end_len); - - if (UNLIKELY(pthread_mutex_lock(&mutex) < 0)) - perror("pthread_mutex_lock"); + char buffer[3 * 80 /* 3 * ${COLUMNS} */]; + int saved_errno = errno; + struct iovec vec[16]; + char *out = buffer; + int last_vec = 0; #ifndef NDEBUG + char *base_name = basename(strdupa(file)); if (use_colors) { - fprintf(stdout, "\033[32;1m%ld\033[0m", gettid()); - fprintf(stdout, " \033[3m%s:%d\033[0m", basename(strdupa(file)), line); - fprintf(stdout, " \033[33m%s()\033[0m ", func); + vec[last_vec++] = VEC_PRINTF("\033[32;1m%ld\033[0m", gettid()); + vec[last_vec++] = VEC_PRINTF(" \033[3m%s:%d\033[0m", base_name, line); + vec[last_vec++] = VEC_PRINTF(" \033[33m%s()\033[0m ", func); } else { - fprintf(stdout, "%ld: ", gettid()); - fprintf(stdout, "%s:%d ", basename(strdupa(file)), line); - fprintf(stdout, "%s() ", func); + vec[last_vec++] = VEC_PRINTF("%ld: ", gettid()); + vec[last_vec++] = VEC_PRINTF("%s:%d ", base_name, line); + vec[last_vec++] = VEC_PRINTF("%s() ", func); } #endif - fwrite(start_color, start_len, 1, stdout); - fwrite(msg, msg_len, 1, stdout); + vec[last_vec++] = VEC_STR(start_color, start_len); + vec[last_vec++] = VEC_STR(msg, msg_len); if (type & STATUS_PERROR) { - char buffer[512]; - char *errmsg = strerror_thunk_r(error_number, buffer, sizeof(buffer) - 1); + char errbuf[64]; + char *errmsg = + strerror_thunk_r(saved_errno, errbuf, sizeof(errbuf) - 1); - fprintf(stdout, ": %s (error number %d)", errmsg, error_number); + vec[last_vec++] = + VEC_PRINTF(": %s (error number %d)", errmsg, saved_errno); } - fputc('.', stdout); - fwrite(end_color, end_len, 1, stdout); - fputc('\n', stdout); + vec[last_vec++] = VEC_LITERAL("."); + vec[last_vec++] = VEC_STR(end_color, end_len); + vec[last_vec++] = VEC_LITERAL("\n"); + +out: + if (LIKELY(!pthread_spin_lock(&spinlock))) { + writev(fileno(stdout), vec, last_vec); + pthread_spin_unlock(&spinlock); + } - if (UNLIKELY(pthread_mutex_unlock(&mutex) < 0)) - perror("pthread_mutex_unlock"); + errno = saved_errno; } static void From f66abec09b99703b99b453758c2ebbd4b88f2237 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 29 Oct 2018 09:04:11 -0700 Subject: [PATCH 0914/2505] Abstract the iovec-building code into lwan_vecbuf This can be later used by the lwan-response code, to create response headers. --- src/lib/lwan-status.c | 62 +++++++++++++++--------------- src/lib/lwan-vecbuf.h | 87 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 32 deletions(-) create mode 100644 src/lib/lwan-vecbuf.h diff --git a/src/lib/lwan-status.c b/src/lib/lwan-status.c index 91188b6e1..45b582aa2 100644 --- a/src/lib/lwan-status.c +++ b/src/lib/lwan-status.c @@ -27,10 +27,10 @@ #include #include #include -#include #include #include "lwan-private.h" +#include "lwan-vecbuf.h" enum lwan_status_type { STATUS_INFO = 1 << 0, @@ -126,19 +126,7 @@ static inline char *strerror_thunk_r(int error_number, char *buffer, size_t len) #endif } -#define VEC_STR(s, l) \ - (struct iovec) { .iov_base = (void *)s, .iov_len = (size_t)(l) } -#define VEC_LITERAL(l) VEC_STR(l, sizeof(l) - 1) -#define VEC_PRINTF(fmt, ...) \ - ({ \ - const size_t len = (size_t)(out - buffer) + sizeof(buffer); \ - int r = snprintf(out, len, fmt, __VA_ARGS__); \ - if (UNLIKELY(r < 0 || r >= (int)len)) \ - goto out; \ - struct iovec v = VEC_STR(out, r); \ - out += r + 1; \ - v; \ - }) +DEFINE_VECBUF_TYPE(status_vb, 16, 80 * 3) static void #ifdef NDEBUG @@ -155,44 +143,54 @@ status_out_msg(const char *file, size_t start_len, end_len; const char *start_color = get_color_start_for_type(type, &start_len); const char *end_color = get_color_end_for_type(type, &end_len); - char buffer[3 * 80 /* 3 * ${COLUMNS} */]; + struct status_vb vb; int saved_errno = errno; - struct iovec vec[16]; - char *out = buffer; - int last_vec = 0; + + status_vb_init(&vb); #ifndef NDEBUG char *base_name = basename(strdupa(file)); if (use_colors) { - vec[last_vec++] = VEC_PRINTF("\033[32;1m%ld\033[0m", gettid()); - vec[last_vec++] = VEC_PRINTF(" \033[3m%s:%d\033[0m", base_name, line); - vec[last_vec++] = VEC_PRINTF(" \033[33m%s()\033[0m ", func); + if (status_vb_append_printf(&vb, "\033[32;1m%ld\033[0m", gettid()) < 0) + goto out; + if (status_vb_append_printf(&vb, " \033[3m%s:%d\033[0m", base_name, + line) < 0) + goto out; + if (status_vb_append_printf(&vb, " \033[33m%s()\033[0m ", func) < 0) + goto out; } else { - vec[last_vec++] = VEC_PRINTF("%ld: ", gettid()); - vec[last_vec++] = VEC_PRINTF("%s:%d ", base_name, line); - vec[last_vec++] = VEC_PRINTF("%s() ", func); + if (status_vb_append_printf(&vb, "%ld: ", gettid()) < 0) + goto out; + if (status_vb_append_printf(&vb, "%s:%d ", base_name, line) < 0) + goto out; + if (status_vb_append_printf(&vb, "%s() ", func) < 0) + goto out; } #endif - vec[last_vec++] = VEC_STR(start_color, start_len); - vec[last_vec++] = VEC_STR(msg, msg_len); + if (status_vb_append_str_len(&vb, start_color, start_len) < 0) + goto out; + if (status_vb_append_str_len(&vb, msg, msg_len) < 0) + goto out; if (type & STATUS_PERROR) { char errbuf[64]; char *errmsg = strerror_thunk_r(saved_errno, errbuf, sizeof(errbuf) - 1); - vec[last_vec++] = - VEC_PRINTF(": %s (error number %d)", errmsg, saved_errno); + if (status_vb_append_printf(&vb, ": %s (error number %d)", errmsg, + saved_errno) < 0) + goto out; } - vec[last_vec++] = VEC_LITERAL("."); - vec[last_vec++] = VEC_STR(end_color, end_len); - vec[last_vec++] = VEC_LITERAL("\n"); + if (status_vb_append_str_len(&vb, end_color, end_len) < 0) + goto out; + if (status_vb_append_str_len(&vb, "\n", 1) < 0) + goto out; out: if (LIKELY(!pthread_spin_lock(&spinlock))) { - writev(fileno(stdout), vec, last_vec); + writev(fileno(stdout), vb.iovec, vb.n); pthread_spin_unlock(&spinlock); } diff --git a/src/lib/lwan-vecbuf.h b/src/lib/lwan-vecbuf.h new file mode 100644 index 000000000..e9a063a95 --- /dev/null +++ b/src/lib/lwan-vecbuf.h @@ -0,0 +1,87 @@ +/* + * lwan - simple web server + * Copyright (c) 2018 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#pragma once + +#include +#include +#include +#include + +#define DEFINE_VECBUF_TYPE(type_name_, vec_n_, buf_n_) \ + struct type_name_ { \ + struct iovec iovec[vec_n_]; \ + char buffer[buf_n_]; \ + char *ptr; \ + int n; \ + }; \ + \ + static ALWAYS_INLINE size_t type_name_##_buflen( \ + const struct type_name_ *vb) \ + { \ + return (size_t)(vb->ptr - vb->buffer) + (buf_n_); \ + } \ + \ + static ALWAYS_INLINE void type_name_##_init(struct type_name_ *vb) \ + { \ + vb->ptr = vb->buffer; \ + vb->n = 0; \ + } \ + \ + static int type_name_##_append_str_len(struct type_name_ *vb, \ + const char *s, size_t len) \ + { \ + if (UNLIKELY(vb->n >= (vec_n_))) \ + return -ENOSPC; \ + \ + vb->iovec[vb->n++] = \ + (struct iovec){.iov_base = (void *)s, .iov_len = len}; \ + \ + return 0; \ + } \ + \ + static ALWAYS_INLINE int type_name_##_append_str(struct type_name_ *vb, \ + const char *s) \ + { \ + return type_name_##_append_str_len(vb, s, strlen(s)); \ + } \ + \ + static int type_name_##_append_printf(struct type_name_ *vb, \ + const char *fmt, ...) \ + { \ + size_t bl = type_name_##_buflen(vb); \ + va_list v; \ + int len; \ + \ + va_start(v, fmt); \ + len = vsnprintf(vb->ptr, bl, fmt, v); \ + va_end(v); \ + \ + if (UNLIKELY(len < 0)) \ + return -errno; \ + if (UNLIKELY(len >= (int)bl)) \ + return -ENOSPC; \ + \ + int ret = type_name_##_append_str_len(vb, vb->ptr, (size_t)len); \ + if (LIKELY(!ret)) \ + vb->ptr += len; /* No +1 for \0: iov_len takes care of it */ \ + \ + return ret; \ + } From 1a68f1c2621355268d13046d89c138ac2e7ccd3b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 1 Nov 2018 08:20:57 -0700 Subject: [PATCH 0915/2505] Headers were being ignored after first request in the same connection The parse_headers() refactor introduced a bug, where the headers for a request would only be parsed if it were the first request made in a connection in the cases where multiple packets had to be sent to compose a request (say, if manually making a request through nc): a request would be fulfilled right after receiving the verb, path, and HTTP version, rather than wait for either an empty line, or the headers. Fix this by checking if there's anything in the pipeline other than the terminating NUL byte, that's always written by the read_from_request_socket() function. Also, change the signature from `char *process_request(...)` to `bool process_request(...)`, and return `true` if all headers could be parsed, and `false` iff the `headers_start` array overflows. --- src/lib/lwan-request.c | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index e8fdc5a51..9773ad3dd 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -509,9 +509,9 @@ identify_http_path(struct lwan_request *request, char *buffer, (struct lwan_value){.value = value, .len = (size_t)(end - value)}; \ }) -static char *parse_headers(struct lwan_request_parser_helper *helper, - char *buffer, - char *buffer_end) +static bool parse_headers(struct lwan_request_parser_helper *helper, + char *buffer, + char *buffer_end) { char *header_start[64]; size_t n_headers = 0; @@ -521,17 +521,20 @@ static char *parse_headers(struct lwan_request_parser_helper *helper, char *next_hdr = memchr(next_chr, '\r', (size_t)(buffer_end - p)); if (!next_hdr) - break; + goto process; header_start[n_headers++] = next_chr; header_start[n_headers++] = next_hdr; if (next_hdr == next_chr) - break; + goto process; p = next_hdr + 2; } + return false; /* Header array isn't large enough */ + +process: for (size_t i = 0; i < n_headers; i += 2) { char *p = header_start[i]; char *end = header_start[i + 1]; @@ -575,14 +578,16 @@ static char *parse_headers(struct lwan_request_parser_helper *helper, break; default: STRING_SWITCH_SMALL(p) { - case MULTICHAR_CONSTANT_SMALL('\r','\n'): - helper->next_request = p + sizeof("\r\n") - 1; - return p; + case MULTICHAR_CONSTANT_SMALL('\r', '\n'): + if (p[2] != '\0') + helper->next_request = p + sizeof("\r\n") - 1; + goto out; } } } - return buffer; +out: + return true; } #undef HEADER_RAW @@ -1030,7 +1035,8 @@ parse_proxy_protocol(struct lwan_request *request, char *buffer) } static enum lwan_http_status -parse_http_request(struct lwan_request *request, struct lwan_request_parser_helper *helper) +parse_http_request(struct lwan_request *request, + struct lwan_request_parser_helper *helper) { char *buffer = helper->buffer->value; @@ -1052,8 +1058,8 @@ parse_http_request(struct lwan_request *request, struct lwan_request_parser_help if (UNLIKELY(!buffer)) return HTTP_BAD_REQUEST; - buffer = parse_headers(helper, buffer, helper->buffer->value + helper->buffer->len); - if (UNLIKELY(!buffer)) + if (UNLIKELY(!parse_headers(helper, buffer, + helper->buffer->value + helper->buffer->len))) return HTTP_BAD_REQUEST; ssize_t decoded_len = url_decode(request->url.value); From c3aca9d6dab236df94dc806efbd8f294d96bb950 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 1 Nov 2018 18:33:30 -0700 Subject: [PATCH 0916/2505] Remove intermediary funciton to format messages before printing status The vecbuf has a printf method already; modify it to take a va_list instead of manipulating the vararg itself, and get rid of the intermediary status_out(): make status_out_msg() the new status_out(). Throws away some code, and reduces pressure on the stack. --- src/lib/lwan-status.c | 41 ++++++++--------------------------------- src/lib/lwan-vecbuf.h | 24 ++++++++++++++++-------- 2 files changed, 24 insertions(+), 41 deletions(-) diff --git a/src/lib/lwan-status.c b/src/lib/lwan-status.c index 45b582aa2..49f240b98 100644 --- a/src/lib/lwan-status.c +++ b/src/lib/lwan-status.c @@ -130,14 +130,14 @@ DEFINE_VECBUF_TYPE(status_vb, 16, 80 * 3) static void #ifdef NDEBUG -status_out_msg(enum lwan_status_type type, const char *msg, size_t msg_len) +status_out(enum lwan_status_type type, const char *fmt, va_list values) #else -status_out_msg(const char *file, - const int line, - const char *func, - enum lwan_status_type type, - const char *msg, - size_t msg_len) +status_out(const char *file, + const int line, + const char *func, + enum lwan_status_type type, + const char *fmt, + va_list values) #endif { size_t start_len, end_len; @@ -170,7 +170,7 @@ status_out_msg(const char *file, if (status_vb_append_str_len(&vb, start_color, start_len) < 0) goto out; - if (status_vb_append_str_len(&vb, msg, msg_len) < 0) + if (status_vb_append_vprintf(&vb, fmt, values) < 0) goto out; if (type & STATUS_PERROR) { @@ -197,31 +197,6 @@ status_out_msg(const char *file, errno = saved_errno; } -static void -#ifdef NDEBUG -status_out(enum lwan_status_type type, const char *fmt, va_list values) -#else -status_out(const char *file, - const int line, - const char *func, - enum lwan_status_type type, - const char *fmt, - va_list values) -#endif -{ - char output[2 * 80 /* 2 * ${COLUMNS} */]; - int len; - - len = vsnprintf(output, sizeof(output), fmt, values); - if (len >= 0) { -#ifdef NDEBUG - status_out_msg(type, output, (size_t)len); -#else - status_out_msg(file, line, func, type, output, (size_t)len); -#endif - } -} - #ifdef NDEBUG #define IMPLEMENT_FUNCTION(fn_name_, type_) \ void lwan_status_##fn_name_(const char *fmt, ...) \ diff --git a/src/lib/lwan-vecbuf.h b/src/lib/lwan-vecbuf.h index e9a063a95..125b02baa 100644 --- a/src/lib/lwan-vecbuf.h +++ b/src/lib/lwan-vecbuf.h @@ -63,16 +63,11 @@ return type_name_##_append_str_len(vb, s, strlen(s)); \ } \ \ - static int type_name_##_append_printf(struct type_name_ *vb, \ - const char *fmt, ...) \ + static int type_name_##_append_vprintf(struct type_name_ *vb, \ + const char *fmt, va_list v) \ { \ size_t bl = type_name_##_buflen(vb); \ - va_list v; \ - int len; \ - \ - va_start(v, fmt); \ - len = vsnprintf(vb->ptr, bl, fmt, v); \ - va_end(v); \ + int len = vsnprintf(vb->ptr, bl, fmt, v); \ \ if (UNLIKELY(len < 0)) \ return -errno; \ @@ -84,4 +79,17 @@ vb->ptr += len; /* No +1 for \0: iov_len takes care of it */ \ \ return ret; \ + } \ + \ + static int type_name_##_append_printf(struct type_name_ *vb, \ + const char *fmt, ...) \ + { \ + va_list v; \ + int ret; \ + \ + va_start(v, fmt); \ + ret = type_name_##_append_vprintf(vb, fmt, v); \ + va_end(v); \ + \ + return ret; \ } From 53279bc99a21ccc1d7aee3730c2c87400f36b2f2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 1 Nov 2018 19:03:27 -0700 Subject: [PATCH 0917/2505] Simplify how the colors of status messages are looked up --- src/lib/lwan-status.c | 76 ++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 45 deletions(-) diff --git a/src/lib/lwan-status.c b/src/lib/lwan-status.c index 49f240b98..421839a17 100644 --- a/src/lib/lwan-status.c +++ b/src/lib/lwan-status.c @@ -33,12 +33,14 @@ #include "lwan-vecbuf.h" enum lwan_status_type { - STATUS_INFO = 1 << 0, - STATUS_WARNING = 1 << 1, - STATUS_ERROR = 1 << 2, - STATUS_PERROR = 1 << 3, - STATUS_CRITICAL = 1 << 4, - STATUS_DEBUG = 1 << 5, + STATUS_INFO = 0, + STATUS_WARNING = 1, + STATUS_ERROR = 2, + STATUS_DEBUG = 3, + STATUS_PERROR = 4, + /* [5..7] are unused so that CRITICAL can be ORed with previous items */ + STATUS_CRITICAL = 8, + STATUS_NONE = 9, }; static volatile bool quiet = false; @@ -75,45 +77,32 @@ static bool can_use_colors(void) return true; } -static const char *get_color_start_for_type(enum lwan_status_type type, - size_t *len_out) +static int status_index(enum lwan_status_type type) { - const char *retval; - if (!use_colors) - retval = ""; - else if (type & STATUS_INFO) - retval = "\033[36m"; - else if (type & STATUS_WARNING) - retval = "\033[33m"; - else if (type & STATUS_CRITICAL) - retval = "\033[31;1m"; - else if (type & STATUS_DEBUG) - retval = "\033[37m"; - else if (type & STATUS_PERROR) - retval = "\033[35m"; - else - retval = "\033[32m"; - - *len_out = strlen(retval); - - return retval; + return STATUS_NONE; + + return (int)type; } -static const char *get_color_end_for_type(enum lwan_status_type type - __attribute__((unused)), - size_t *len_out) -{ - static const char *retval = "\033[0m"; +#define V(c) { .value = c, .len = sizeof(c) - 1 } +static const struct lwan_value start_colors[] = { + [STATUS_INFO] = V("\033[36m"), [STATUS_WARNING] = V("\033[33m"), + [STATUS_DEBUG] = V("\033[37m"), [STATUS_PERROR] = V("\033[35m"), + [STATUS_CRITICAL] = V("\033[31;1m"), [STATUS_NONE] = V(""), +}; - if (!use_colors) { - *len_out = 0; - return ""; - } +static inline struct lwan_value start_color(enum lwan_status_type type) +{ + return start_colors[status_index(type)]; +} - *len_out = strlen(retval); - return retval; +static inline struct lwan_value end_color(enum lwan_status_type type) +{ + return use_colors ? (struct lwan_value)V("\033[0m\n") + : (struct lwan_value)V("\n"); } +#undef V static inline char *strerror_thunk_r(int error_number, char *buffer, size_t len) { @@ -140,9 +129,8 @@ status_out(const char *file, va_list values) #endif { - size_t start_len, end_len; - const char *start_color = get_color_start_for_type(type, &start_len); - const char *end_color = get_color_end_for_type(type, &end_len); + struct lwan_value start = start_color(type); + struct lwan_value end = end_color(type); struct status_vb vb; int saved_errno = errno; @@ -168,7 +156,7 @@ status_out(const char *file, } #endif - if (status_vb_append_str_len(&vb, start_color, start_len) < 0) + if (status_vb_append_str_len(&vb, start.value, start.len) < 0) goto out; if (status_vb_append_vprintf(&vb, fmt, values) < 0) goto out; @@ -183,9 +171,7 @@ status_out(const char *file, goto out; } - if (status_vb_append_str_len(&vb, end_color, end_len) < 0) - goto out; - if (status_vb_append_str_len(&vb, "\n", 1) < 0) + if (status_vb_append_str_len(&vb, end.value, end.len) < 0) goto out; out: From 577129971480e82eb9423d50a00627e754766cec Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 2 Nov 2018 08:02:32 -0700 Subject: [PATCH 0918/2505] Go back to using printf() for lwan-status It's not only cleaner, but faster than vecbuf. I don't want to spend time optimizing this further. Also, instead of using a spinlock, just use flockfile()/funlockfile(). --- src/lib/lwan-status.c | 53 ++++++------------------ src/lib/lwan-vecbuf.h | 95 ------------------------------------------- 2 files changed, 13 insertions(+), 135 deletions(-) delete mode 100644 src/lib/lwan-vecbuf.h diff --git a/src/lib/lwan-status.c b/src/lib/lwan-status.c index 421839a17..51912778b 100644 --- a/src/lib/lwan-status.c +++ b/src/lib/lwan-status.c @@ -30,7 +30,6 @@ #include #include "lwan-private.h" -#include "lwan-vecbuf.h" enum lwan_status_type { STATUS_INFO = 0, @@ -38,20 +37,18 @@ enum lwan_status_type { STATUS_ERROR = 2, STATUS_DEBUG = 3, STATUS_PERROR = 4, - /* [5..7] are unused so that CRITICAL can be ORed with previous items */ + STATUS_NONE = 5, + /* [6,7] are unused so that CRITICAL can be ORed with previous items */ STATUS_CRITICAL = 8, - STATUS_NONE = 9, }; static volatile bool quiet = false; static bool use_colors; -static pthread_spinlock_t spinlock; static bool can_use_colors(void); void lwan_status_init(struct lwan *l) { - pthread_spin_init(&spinlock, PTHREAD_PROCESS_PRIVATE); #ifdef NDEBUG quiet = l->config.quiet; #else @@ -79,10 +76,7 @@ static bool can_use_colors(void) static int status_index(enum lwan_status_type type) { - if (!use_colors) - return STATUS_NONE; - - return (int)type; + return use_colors ? (int)type : STATUS_NONE; } #define V(c) { .value = c, .len = sizeof(c) - 1 } @@ -115,8 +109,6 @@ static inline char *strerror_thunk_r(int error_number, char *buffer, size_t len) #endif } -DEFINE_VECBUF_TYPE(status_vb, 16, 80 * 3) - static void #ifdef NDEBUG status_out(enum lwan_status_type type, const char *fmt, va_list values) @@ -131,54 +123,35 @@ status_out(const char *file, { struct lwan_value start = start_color(type); struct lwan_value end = end_color(type); - struct status_vb vb; int saved_errno = errno; - status_vb_init(&vb); + flockfile(stdout); #ifndef NDEBUG char *base_name = basename(strdupa(file)); if (use_colors) { - if (status_vb_append_printf(&vb, "\033[32;1m%ld\033[0m", gettid()) < 0) - goto out; - if (status_vb_append_printf(&vb, " \033[3m%s:%d\033[0m", base_name, - line) < 0) - goto out; - if (status_vb_append_printf(&vb, " \033[33m%s()\033[0m ", func) < 0) - goto out; + printf("\033[32;1m%ld\033[0m", gettid()); + printf(" \033[3m%s:%d\033[0m", base_name, line); + printf(" \033[33m%s()\033[0m ", func); } else { - if (status_vb_append_printf(&vb, "%ld: ", gettid()) < 0) - goto out; - if (status_vb_append_printf(&vb, "%s:%d ", base_name, line) < 0) - goto out; - if (status_vb_append_printf(&vb, "%s() ", func) < 0) - goto out; + printf("%ld %s:%d %s() ", gettid(), base_name, line, func); } #endif - if (status_vb_append_str_len(&vb, start.value, start.len) < 0) - goto out; - if (status_vb_append_vprintf(&vb, fmt, values) < 0) - goto out; + printf("%.*s", (int)start.len, start.value); + vprintf(fmt, values); if (type & STATUS_PERROR) { char errbuf[64]; char *errmsg = strerror_thunk_r(saved_errno, errbuf, sizeof(errbuf) - 1); - if (status_vb_append_printf(&vb, ": %s (error number %d)", errmsg, - saved_errno) < 0) - goto out; + printf(": %s (error number %d)", errmsg, saved_errno); } - if (status_vb_append_str_len(&vb, end.value, end.len) < 0) - goto out; + printf("%.*s", (int)end.len, end.value); -out: - if (LIKELY(!pthread_spin_lock(&spinlock))) { - writev(fileno(stdout), vb.iovec, vb.n); - pthread_spin_unlock(&spinlock); - } + funlockfile(stdout); errno = saved_errno; } diff --git a/src/lib/lwan-vecbuf.h b/src/lib/lwan-vecbuf.h deleted file mode 100644 index 125b02baa..000000000 --- a/src/lib/lwan-vecbuf.h +++ /dev/null @@ -1,95 +0,0 @@ -/* - * lwan - simple web server - * Copyright (c) 2018 Leandro A. F. Pereira - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - */ - -#pragma once - -#include -#include -#include -#include - -#define DEFINE_VECBUF_TYPE(type_name_, vec_n_, buf_n_) \ - struct type_name_ { \ - struct iovec iovec[vec_n_]; \ - char buffer[buf_n_]; \ - char *ptr; \ - int n; \ - }; \ - \ - static ALWAYS_INLINE size_t type_name_##_buflen( \ - const struct type_name_ *vb) \ - { \ - return (size_t)(vb->ptr - vb->buffer) + (buf_n_); \ - } \ - \ - static ALWAYS_INLINE void type_name_##_init(struct type_name_ *vb) \ - { \ - vb->ptr = vb->buffer; \ - vb->n = 0; \ - } \ - \ - static int type_name_##_append_str_len(struct type_name_ *vb, \ - const char *s, size_t len) \ - { \ - if (UNLIKELY(vb->n >= (vec_n_))) \ - return -ENOSPC; \ - \ - vb->iovec[vb->n++] = \ - (struct iovec){.iov_base = (void *)s, .iov_len = len}; \ - \ - return 0; \ - } \ - \ - static ALWAYS_INLINE int type_name_##_append_str(struct type_name_ *vb, \ - const char *s) \ - { \ - return type_name_##_append_str_len(vb, s, strlen(s)); \ - } \ - \ - static int type_name_##_append_vprintf(struct type_name_ *vb, \ - const char *fmt, va_list v) \ - { \ - size_t bl = type_name_##_buflen(vb); \ - int len = vsnprintf(vb->ptr, bl, fmt, v); \ - \ - if (UNLIKELY(len < 0)) \ - return -errno; \ - if (UNLIKELY(len >= (int)bl)) \ - return -ENOSPC; \ - \ - int ret = type_name_##_append_str_len(vb, vb->ptr, (size_t)len); \ - if (LIKELY(!ret)) \ - vb->ptr += len; /* No +1 for \0: iov_len takes care of it */ \ - \ - return ret; \ - } \ - \ - static int type_name_##_append_printf(struct type_name_ *vb, \ - const char *fmt, ...) \ - { \ - va_list v; \ - int ret; \ - \ - va_start(v, fmt); \ - ret = type_name_##_append_vprintf(vb, fmt, v); \ - va_end(v); \ - \ - return ret; \ - } From ab8ae08cefbe17995e4f642060fc10b473d07a13 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 2 Nov 2018 17:44:28 -0700 Subject: [PATCH 0919/2505] Fix unused parameter warnings GCC doesn't emit these warnings, but Clang does. Fix them. --- src/lib/lwan-request.c | 8 ++++---- src/lib/lwan-status.c | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 9773ad3dd..6cfbc5c06 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -594,7 +594,7 @@ static bool parse_headers(struct lwan_request_parser_helper *helper, #undef HEADER static void -parse_if_modified_since(struct lwan_request *request, struct lwan_request_parser_helper *helper) +parse_if_modified_since(struct lwan_request_parser_helper *helper) { time_t parsed; @@ -608,7 +608,7 @@ parse_if_modified_since(struct lwan_request *request, struct lwan_request_parser } static void -parse_range(struct lwan_request *request, struct lwan_request_parser_helper *helper) +parse_range(struct lwan_request_parser_helper *helper) { if (UNLIKELY(helper->range.raw.len <= (sizeof("bytes=") - 1))) return; @@ -1092,10 +1092,10 @@ prepare_for_response(struct lwan_url_map *url_map, parse_query_string(request, helper); if (url_map->flags & HANDLER_PARSE_IF_MODIFIED_SINCE) - parse_if_modified_since(request, helper); + parse_if_modified_since(helper); if (url_map->flags & HANDLER_PARSE_RANGE) - parse_range(request, helper); + parse_range(helper); if (url_map->flags & HANDLER_PARSE_ACCEPT_ENCODING) parse_accept_encoding(request, helper); diff --git a/src/lib/lwan-status.c b/src/lib/lwan-status.c index 51912778b..93cca008e 100644 --- a/src/lib/lwan-status.c +++ b/src/lib/lwan-status.c @@ -91,7 +91,7 @@ static inline struct lwan_value start_color(enum lwan_status_type type) return start_colors[status_index(type)]; } -static inline struct lwan_value end_color(enum lwan_status_type type) +static inline struct lwan_value end_color(void) { return use_colors ? (struct lwan_value)V("\033[0m\n") : (struct lwan_value)V("\n"); @@ -122,7 +122,7 @@ status_out(const char *file, #endif { struct lwan_value start = start_color(type); - struct lwan_value end = end_color(type); + struct lwan_value end = end_color(); int saved_errno = errno; flockfile(stdout); From 377dad73a84ce2a88364e4c9e45ec261fba04b04 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 3 Nov 2018 10:23:12 -0700 Subject: [PATCH 0920/2505] Use CPU topology to calculate fd_to_thread on x86_64 --- src/lib/lwan-thread.c | 94 +++++++++++++++++++++++++++++++++++++++++-- src/lib/lwan.c | 10 ++--- src/lib/lwan.h | 2 + 3 files changed, 97 insertions(+), 9 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index d1392463b..0cf0ba1c8 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -559,6 +559,94 @@ void lwan_thread_add_client(struct lwan_thread *t, int fd) close(fd); } +#if defined(__linux__) && defined(__x86_64__) +static bool read_cpu_topology(struct lwan *l, uint32_t siblings[]) +{ + char path[PATH_MAX]; + + for (unsigned short i = 0; i < l->n_cpus; i++) { + FILE *sib; + uint32_t id, sibling; + + snprintf(path, sizeof(path), + "/sys/devices/system/cpu/cpu%hd/topology/thread_siblings_list", + i); + + sib = fopen(path, "re"); + if (!sib) { + lwan_status_warning("Could not open `%s` to determine CPU topology", + path); + return false; + } + + switch (fscanf(sib, "%u-%u", &id, &sibling)) { + case 1: /* No SMT */ + siblings[i] = id; + break; + case 2: /* SMT */ + siblings[i] = sibling; + break; + default: + lwan_status_critical("%s has invalid format", path); + __builtin_unreachable(); + } + + fclose(sib); + } + + return true; +} + +static void +siblings_to_schedtable(struct lwan *l, uint32_t input[], uint32_t output[]) +{ + int *seen = alloca(l->n_cpus * sizeof(int)); + int n_output = 0; + + for (uint32_t i = 0; i < l->n_cpus; i++) + seen[i] = -1; + + for (uint32_t i = 0; i < l->n_cpus; i++) { + if (seen[input[i]] < 0) { + seen[input[i]] = (int)i; + } else { + output[n_output++] = (uint32_t)seen[input[i]]; + output[n_output++] = i; + } + } + + if (!n_output) + memcpy(output, seen, l->n_cpus * sizeof(int)); +} + +static void preschedule_by_topology(struct lwan *l, + uint32_t fd_to_thread[], + uint32_t n_threads) +{ + uint32_t *siblings = alloca(l->n_cpus * sizeof(uint32_t)); + + if (!read_cpu_topology(l, siblings)) { + for (uint32_t i = 0; i < n_threads; i++) + fd_to_thread[i] = (i / 2) % l->thread.count; + } else { + uint32_t *affinity = alloca(l->n_cpus * sizeof(uint32_t)); + + siblings_to_schedtable(l, siblings, affinity); + + for (uint32_t i = 0; i < n_threads; i++) + fd_to_thread[i] = affinity[i % l->n_cpus]; + } +} +#else +static void preschedule_by_topology(struct lwan *l, + uint32_t fd_to_thread[], + uint32_t n_threads) +{ + for (uint32_t i = 0; i < n_threads; i++) + fd_to_thread[i] = (i / 2) % l->thread.count; +} +#endif + void lwan_thread_init(struct lwan *l) { if (pthread_barrier_init(&l->thread.barrier, NULL, @@ -590,10 +678,8 @@ void lwan_thread_init(struct lwan *l) uint32_t n_threads = (uint32_t)lwan_nextpow2((size_t)((l->thread.count - 1) * 2)); uint32_t *fd_to_thread = alloca(n_threads * sizeof(uint32_t)); - for (unsigned int i = 0; i < n_threads; i++) { - /* TODO: do not assume the CPU topology */ - fd_to_thread[i] = (i / 2) % l->thread.count; - } + preschedule_by_topology(l, fd_to_thread, n_threads); + n_threads--; /* Transform count into mask for AND below */ for (unsigned int i = 0; i < total_conns; i++) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index d94e9f479..9fedce382 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -564,17 +564,17 @@ void lwan_init_with_config(struct lwan *l, const struct lwan_config *config) /* Continue initialization as normal. */ lwan_status_debug("Initializing lwan web server"); - unsigned short n_cpus = get_number_of_cpus(); + l->n_cpus = get_number_of_cpus(); if (!l->config.n_threads) { - l->thread.count = n_cpus; + l->thread.count = l->n_cpus; if (l->thread.count == 1) l->thread.count = 2; - } else if (l->config.n_threads > 3 * n_cpus) { - l->thread.count = (short unsigned int)(n_cpus * 3); + } else if (l->config.n_threads > 3 * l->n_cpus) { + l->thread.count = (short unsigned int)(l->n_cpus * 3); lwan_status_warning("%d threads requested, but only %d online CPUs; " "capping to %d threads", - l->config.n_threads, n_cpus, 3 * n_cpus); + l->config.n_threads, l->n_cpus, 3 * l->n_cpus); } else if (l->config.n_threads > 63) { l->thread.count = 64; diff --git a/src/lib/lwan.h b/src/lib/lwan.h index ffd02501d..0aabc8690 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -431,6 +431,8 @@ struct lwan { struct coro_switcher switcher; int main_socket; int epfd; + + unsigned short n_cpus; }; void lwan_set_url_map(struct lwan *l, const struct lwan_url_map *map); From 04fa8957606bdb2256b26cde167e89c7db781c6a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 3 Nov 2018 12:12:33 -0700 Subject: [PATCH 0921/2505] Clean up topology-aware precheduling --- src/lib/lwan-thread.c | 47 +++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 0cf0ba1c8..9e0517848 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -598,52 +598,50 @@ static bool read_cpu_topology(struct lwan *l, uint32_t siblings[]) } static void -siblings_to_schedtable(struct lwan *l, uint32_t input[], uint32_t output[]) +siblings_to_schedtbl(struct lwan *l, uint32_t siblings[], uint32_t schedtbl[]) { int *seen = alloca(l->n_cpus * sizeof(int)); - int n_output = 0; + int n_schedtbl = 0; for (uint32_t i = 0; i < l->n_cpus; i++) seen[i] = -1; for (uint32_t i = 0; i < l->n_cpus; i++) { - if (seen[input[i]] < 0) { - seen[input[i]] = (int)i; + if (seen[siblings[i]] < 0) { + seen[siblings[i]] = (int)i; } else { - output[n_output++] = (uint32_t)seen[input[i]]; - output[n_output++] = i; + schedtbl[n_schedtbl++] = (uint32_t)seen[siblings[i]]; + schedtbl[n_schedtbl++] = i; } } - if (!n_output) - memcpy(output, seen, l->n_cpus * sizeof(int)); + if (!n_schedtbl) + memcpy(schedtbl, seen, l->n_cpus * sizeof(int)); } -static void preschedule_by_topology(struct lwan *l, - uint32_t fd_to_thread[], - uint32_t n_threads) +static void +topology_to_schedtbl(struct lwan *l, uint32_t schedtbl[], uint32_t n_threads) { uint32_t *siblings = alloca(l->n_cpus * sizeof(uint32_t)); if (!read_cpu_topology(l, siblings)) { for (uint32_t i = 0; i < n_threads; i++) - fd_to_thread[i] = (i / 2) % l->thread.count; + schedtbl[i] = (i / 2) % l->thread.count; } else { uint32_t *affinity = alloca(l->n_cpus * sizeof(uint32_t)); - siblings_to_schedtable(l, siblings, affinity); + siblings_to_schedtbl(l, siblings, affinity); for (uint32_t i = 0; i < n_threads; i++) - fd_to_thread[i] = affinity[i % l->n_cpus]; + schedtbl[i] = affinity[i % l->n_cpus]; } } -#else -static void preschedule_by_topology(struct lwan *l, - uint32_t fd_to_thread[], - uint32_t n_threads) +#elif defined(__x86_64__) +static void +topology_to_schedtbl(struct lwan *l, uint32_t schedtbl[], uint32_t n_threads) { for (uint32_t i = 0; i < n_threads; i++) - fd_to_thread[i] = (i / 2) % l->thread.count; + schedtbl[i] = (i / 2) % l->thread.count; } #endif @@ -672,18 +670,19 @@ void lwan_thread_init(struct lwan *l) * fast path. * * Since struct lwan_connection is guaranteed to be 32-byte long, two of - * them can fill up a cache line. This formula will group two connections - * per thread in a way that false-sharing is avoided. + * them can fill up a cache line. Assume siblings share cache lines and + * use the CPU topology to group two connections per cache line in such + * a way that false sharing is avoided. */ uint32_t n_threads = (uint32_t)lwan_nextpow2((size_t)((l->thread.count - 1) * 2)); - uint32_t *fd_to_thread = alloca(n_threads * sizeof(uint32_t)); + uint32_t *schedtbl = alloca(n_threads * sizeof(uint32_t)); - preschedule_by_topology(l, fd_to_thread, n_threads); + topology_to_schedtbl(l, schedtbl, n_threads); n_threads--; /* Transform count into mask for AND below */ for (unsigned int i = 0; i < total_conns; i++) - l->conns[i].thread = &l->thread.threads[fd_to_thread[i & n_threads]]; + l->conns[i].thread = &l->thread.threads[schedtbl[i & n_threads]]; #else for (unsigned int i = 0; i < total_conns; i++) l->conns[i].thread = &l->thread.threads[i % l->thread.count]; From 453f0d4e6595cb1c9c66de98e2331a5a4d97f1ec Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 3 Nov 2018 12:14:13 -0700 Subject: [PATCH 0922/2505] Set thread affinity based on CPU topology --- src/lib/lwan-thread.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 9e0517848..5858fe496 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -636,6 +637,21 @@ topology_to_schedtbl(struct lwan *l, uint32_t schedtbl[], uint32_t n_threads) schedtbl[i] = affinity[i % l->n_cpus]; } } + +static void +adjust_threads_affinity(struct lwan *l, uint32_t *schedtbl, uint32_t mask) +{ + for (uint32_t i = 0; i < l->thread.count; i++) { + cpu_set_t set; + + CPU_ZERO(&set); + CPU_SET(schedtbl[i & mask], &set); + + if (pthread_setaffinity_np(l->thread.threads[i].self, sizeof(set), + &set)) + lwan_status_warning("Could not set affinity for thread %d", i); + } +} #elif defined(__x86_64__) static void topology_to_schedtbl(struct lwan *l, uint32_t schedtbl[], uint32_t n_threads) @@ -643,6 +659,11 @@ topology_to_schedtbl(struct lwan *l, uint32_t schedtbl[], uint32_t n_threads) for (uint32_t i = 0; i < n_threads; i++) schedtbl[i] = (i / 2) % l->thread.count; } + +static void +adjust_threads_affinity(struct lwan *l, uint32_t *schedtbl, uint32_t n) +{ +} #endif void lwan_thread_init(struct lwan *l) @@ -680,7 +701,7 @@ void lwan_thread_init(struct lwan *l) topology_to_schedtbl(l, schedtbl, n_threads); n_threads--; /* Transform count into mask for AND below */ - + adjust_threads_affinity(l, schedtbl, n_threads); for (unsigned int i = 0; i < total_conns; i++) l->conns[i].thread = &l->thread.threads[schedtbl[i & n_threads]]; #else From 8a013efe8b2c0d881a091c0a9946e89fbc88956e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 3 Nov 2018 17:01:23 -0700 Subject: [PATCH 0923/2505] Avoid using writev() when sending a response This yields a ~10% increase in requests per second. --- src/lib/lwan-response.c | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 95773e5a0..d50a32e16 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -180,18 +180,26 @@ void lwan_response(struct lwan_request *request, enum lwan_http_status status) } if (has_response_body[lwan_request_get_method(request)]) { - struct iovec response_vec[] = { - { - .iov_base = headers, - .iov_len = header_len, - }, - { - .iov_base = lwan_strbuf_get_buffer(response->buffer), - .iov_len = lwan_strbuf_get_length(response->buffer), - }, - }; - - lwan_writev(request, response_vec, N_ELEMENTS(response_vec)); + char *resp_buf = lwan_strbuf_get_buffer(response->buffer); + size_t resp_len = lwan_strbuf_get_length(response->buffer); + + if (sizeof(headers) - header_len > resp_len) { + memcpy(headers + header_len, resp_buf, resp_len); + lwan_send(request, headers, header_len + resp_len, 0); + } else { + struct iovec response_vec[] = { + { + .iov_base = headers, + .iov_len = header_len, + }, + { + .iov_base = resp_buf, + .iov_len = resp_len, + }, + }; + + lwan_writev(request, response_vec, N_ELEMENTS(response_vec)); + } } else { lwan_send(request, headers, header_len, 0); } From 7e7a35ba28e76768fad3f254e47756fdf23ac00d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 19 Oct 2018 18:51:37 -0700 Subject: [PATCH 0924/2505] Lazily parse headers, only when accessor functions are called Removes some checks from the fast path, and reduces the size of the request struct. --- src/bin/lwan/main.c | 38 +----- src/bin/testrunner/main.c | 6 +- src/lib/lwan-mod-lua.c | 3 +- src/lib/lwan-mod-serve-files.c | 4 +- src/lib/lwan-request.c | 210 +++++++++++++++++++++------------ src/lib/lwan.h | 49 +++++--- 6 files changed, 170 insertions(+), 140 deletions(-) diff --git a/src/bin/lwan/main.c b/src/bin/lwan/main.c index d7bf5f8fd..8a450cabc 100644 --- a/src/bin/lwan/main.c +++ b/src/bin/lwan/main.c @@ -36,48 +36,12 @@ static void print_module_info(void) { extern const struct lwan_module_info SECTION_START(lwan_module); extern const struct lwan_module_info SECTION_END(lwan_module); - static const struct { - enum lwan_handler_flags flag; - const char *str; - } flag2str[] = { - {.flag = HANDLER_PARSE_QUERY_STRING, .str = "parse-query-string"}, - {.flag = HANDLER_PARSE_IF_MODIFIED_SINCE, .str = "parse-if-modified-since"}, - {.flag = HANDLER_PARSE_RANGE, .str = "parse-range"}, - {.flag = HANDLER_PARSE_ACCEPT_ENCODING, .str = "parse-accept-encoding"}, - {.flag = HANDLER_PARSE_POST_DATA, .str = "parse-post-data"}, - {.flag = HANDLER_CAN_REWRITE_URL, .str = "can-rewrite"}, - {.flag = HANDLER_PARSE_COOKIES, .str = "parse-cookies"}, - }; const struct lwan_module_info *module; - struct lwan_strbuf buf; - - if (!lwan_strbuf_init(&buf)) - return; printf("Available modules:\n"); for (module = __start_lwan_module; module < __stop_lwan_module; module++) { - size_t len; - size_t i; - - for (i = 0; i < N_ELEMENTS(flag2str); i++) { - if (!(module->module->flags & flag2str[i].flag)) - continue; - if (!lwan_strbuf_append_printf(&buf, "%s, ", flag2str[i].str)) - goto next_module; - } - - len = lwan_strbuf_get_length(&buf); - if (len) { - printf(" * %s (%.*s)\n", module->name, (int)(len - 2), - lwan_strbuf_get_buffer(&buf)); - } else { - printf(" * %s\n", module->name); - } -next_module: - lwan_strbuf_reset(&buf); + printf(" * %s\n", module->name); } - - lwan_strbuf_free(&buf); } static void diff --git a/src/bin/testrunner/main.c b/src/bin/testrunner/main.c index 0bb51ea43..fba0caa13 100644 --- a/src/bin/testrunner/main.c +++ b/src/bin/testrunner/main.c @@ -178,7 +178,7 @@ LWAN_HANDLER(hello_world) lwan_strbuf_append_str(response->buffer, "\n\nCookies\n", 0); lwan_strbuf_append_str(response->buffer, "-------\n\n", 0); - LWAN_ARRAY_FOREACH(&request->cookies, iter) { + LWAN_ARRAY_FOREACH(lwan_request_get_cookies(request), iter) { lwan_strbuf_append_printf(response->buffer, "Key = \"%s\"; Value = \"%s\"\n", iter->key, iter->value); } @@ -186,7 +186,7 @@ LWAN_HANDLER(hello_world) lwan_strbuf_append_str(response->buffer, "\n\nQuery String Variables\n", 0); lwan_strbuf_append_str(response->buffer, "----------------------\n\n", 0); - LWAN_ARRAY_FOREACH(&request->query_params, iter) { + LWAN_ARRAY_FOREACH(lwan_request_get_query_params(request), iter) { lwan_strbuf_append_printf(response->buffer, "Key = \"%s\"; Value = \"%s\"\n", iter->key, iter->value); } @@ -197,7 +197,7 @@ LWAN_HANDLER(hello_world) lwan_strbuf_append_str(response->buffer, "\n\nPOST data\n", 0); lwan_strbuf_append_str(response->buffer, "---------\n\n", 0); - LWAN_ARRAY_FOREACH(&request->post_data, iter) { + LWAN_ARRAY_FOREACH(lwan_request_get_post_params(request), iter) { lwan_strbuf_append_printf(response->buffer, "Key = \"%s\"; Value = \"%s\"\n", iter->key, iter->value); } diff --git a/src/lib/lwan-mod-lua.c b/src/lib/lwan-mod-lua.c index 8ec72357c..d9bf12c8a 100644 --- a/src/lib/lwan-mod-lua.c +++ b/src/lib/lwan-mod-lua.c @@ -310,8 +310,7 @@ static const struct lwan_module module = { .create_from_hash = lua_create_from_hash, .destroy = lua_destroy, .handle_request = lua_handle_request, - .flags = HANDLER_PARSE_QUERY_STRING | HANDLER_REMOVE_LEADING_SLASH | - HANDLER_PARSE_COOKIES + .flags = HANDLER_REMOVE_LEADING_SLASH, }; LWAN_REGISTER_MODULE(lua, &module); diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 795c673b9..431bb1075 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -1142,9 +1142,7 @@ static const struct lwan_module module = { .create_from_hash = serve_files_create_from_hash, .destroy = serve_files_destroy, .handle_request = serve_files_handle_request, - .flags = HANDLER_REMOVE_LEADING_SLASH | HANDLER_PARSE_IF_MODIFIED_SINCE | - HANDLER_PARSE_RANGE | HANDLER_PARSE_ACCEPT_ENCODING | - HANDLER_PARSE_QUERY_STRING, + .flags = HANDLER_REMOVE_LEADING_SLASH | HANDLER_PARSE_ACCEPT_ENCODING, }; LWAN_REGISTER_MODULE(serve_files, &module); diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 6cfbc5c06..68f3ad9f5 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -64,14 +64,14 @@ struct lwan_request_parser_helper { } range; struct lwan_value cookie; /* Cookie: */ - struct lwan_value query_string; /* Stuff after ? and before # */ struct lwan_value fragment; /* Stuff after # */ - struct lwan_value content_length; /* Content-Length: */ + struct lwan_value authorization; /* Authorization: */ struct lwan_value post_data; /* Request body for POST */ struct lwan_value content_type; /* Content-Type: for POST */ + struct lwan_value content_length; /* Content-Length: */ time_t error_when_time; /* Time to abort request read */ int error_when_n_packets; /* Max. number of packets */ @@ -339,10 +339,11 @@ reset_key_value_array(void *data) lwan_key_value_array_reset(array); } -static void -parse_key_values(struct lwan_request *request, - struct lwan_value *helper_value, struct lwan_key_value_array *array, - ssize_t (*decode_value)(char *value), const char separator) +static void parse_key_values(struct lwan_request *request, + struct lwan_value *helper_value, + struct lwan_key_value_array *array, + ssize_t (*decode_value)(char *value), + const char separator) { struct lwan_key_value *kv; char *ptr = helper_value->value; @@ -401,38 +402,38 @@ identity_decode(char *input __attribute__((unused))) return 1; } -static void -parse_cookies(struct lwan_request *request, struct lwan_request_parser_helper *helper) +static void parse_cookies(struct lwan_request *request) { - parse_key_values(request, &helper->cookie, &request->cookies, - identity_decode, ';'); + parse_key_values(request, &request->helper->cookie, &request->cookies, + identity_decode, ';'); } -static void -parse_query_string(struct lwan_request *request, struct lwan_request_parser_helper *helper) +static void parse_query_string(struct lwan_request *request) { - parse_key_values(request, &helper->query_string, &request->query_params, - url_decode, '&'); + parse_key_values(request, &request->helper->query_string, + &request->query_params, url_decode, '&'); } -static void -parse_post_data(struct lwan_request *request, struct lwan_request_parser_helper *helper) +static void parse_post_data(struct lwan_request *request) { + struct lwan_request_parser_helper *helper = request->helper; static const char content_type[] = "application/x-www-form-urlencoded"; if (helper->content_type.len < sizeof(content_type) - 1) return; - if (UNLIKELY(strncmp(helper->content_type.value, content_type, sizeof(content_type) - 1))) + if (UNLIKELY(strncmp(helper->content_type.value, content_type, + sizeof(content_type) - 1))) return; - parse_key_values(request, &helper->post_data, &request->post_data, - url_decode, '&'); + parse_key_values(request, &helper->post_data, &request->post_params, + url_decode, '&'); } -static void -parse_fragment_and_query(struct lwan_request *request, - struct lwan_request_parser_helper *helper, const char *space) +static void parse_fragment_and_query(struct lwan_request *request, + const char *space) { + struct lwan_request_parser_helper *helper = request->helper; + /* Most of the time, fragments are small -- so search backwards */ char *fragment = memrchr(request->url.value, '#', request->url.len); if (fragment) { @@ -448,15 +449,16 @@ parse_fragment_and_query(struct lwan_request *request, if (query_string) { *query_string = '\0'; helper->query_string.value = query_string + 1; - helper->query_string.len = (size_t)((fragment ? fragment : space) - query_string - 1); + helper->query_string.len = + (size_t)((fragment ? fragment : space) - query_string - 1); request->url.len -= helper->query_string.len + 1; } } static char * -identify_http_path(struct lwan_request *request, char *buffer, - struct lwan_request_parser_helper *helper) +identify_http_path(struct lwan_request *request, char *buffer) { + struct lwan_request_parser_helper *helper = request->helper; static const size_t minimal_request_line_len = sizeof("/ HTTP/1.0") - 1; char *space, *end_of_line; @@ -475,7 +477,7 @@ identify_http_path(struct lwan_request *request, char *buffer, request->url.value = buffer; request->url.len = (size_t)(space - buffer); - parse_fragment_and_query(request, helper, space); + parse_fragment_and_query(request, space); request->original_url = request->url; *space++ = '\0'; @@ -646,8 +648,10 @@ parse_range(struct lwan_request_parser_helper *helper) } static void -parse_accept_encoding(struct lwan_request *request, struct lwan_request_parser_helper *helper) +parse_accept_encoding(struct lwan_request *request) { + struct lwan_request_parser_helper *helper = request->helper; + if (!helper->accept_encoding.len) return; @@ -676,14 +680,16 @@ ignore_leading_whitespace(char *buffer) return buffer; } -static ALWAYS_INLINE void -compute_keep_alive_flag(struct lwan_request *request, struct lwan_request_parser_helper *helper) +static ALWAYS_INLINE void compute_keep_alive_flag(struct lwan_request *request) { + struct lwan_request_parser_helper *helper = request->helper; bool is_keep_alive; + if (request->flags & REQUEST_IS_HTTP_1_0) is_keep_alive = (helper->connection == 'k'); else is_keep_alive = (helper->connection != 'c'); + if (is_keep_alive) request->conn->flags |= CONN_KEEP_ALIVE; else @@ -771,8 +777,11 @@ read_from_request_socket(struct lwan_request *request, return HTTP_INTERNAL_ERROR; } -static enum lwan_read_finalizer read_request_finalizer(size_t total_read, - size_t buffer_size, struct lwan_request_parser_helper *helper, int n_packets) +static enum lwan_read_finalizer +read_request_finalizer(size_t total_read, + size_t buffer_size, + struct lwan_request_parser_helper *helper, + int n_packets) { /* 16 packets should be enough to read a request (without the body, as * is the case for POST requests). This yields a timeout error to avoid @@ -808,8 +817,11 @@ read_request(struct lwan_request *request) read_request_finalizer); } -static enum lwan_read_finalizer post_data_finalizer(size_t total_read, - size_t buffer_size, struct lwan_request_parser_helper *helper, int n_packets) +static enum lwan_read_finalizer +post_data_finalizer(size_t total_read, + size_t buffer_size, + struct lwan_request_parser_helper *helper, + int n_packets) { if (buffer_size == total_read) return FINALIZER_DONE; @@ -1035,9 +1047,9 @@ parse_proxy_protocol(struct lwan_request *request, char *buffer) } static enum lwan_http_status -parse_http_request(struct lwan_request *request, - struct lwan_request_parser_helper *helper) +parse_http_request(struct lwan_request *request) { + struct lwan_request_parser_helper *helper = request->helper; char *buffer = helper->buffer->value; if (request->flags & REQUEST_ALLOW_PROXY_REQS) { @@ -1054,7 +1066,7 @@ parse_http_request(struct lwan_request *request, if (UNLIKELY(!path)) return HTTP_NOT_ALLOWED; - buffer = identify_http_path(request, path, helper); + buffer = identify_http_path(request, path); if (UNLIKELY(!buffer)) return HTTP_BAD_REQUEST; @@ -1067,42 +1079,26 @@ parse_http_request(struct lwan_request *request, return HTTP_BAD_REQUEST; request->original_url.len = request->url.len = (size_t)decoded_len; - compute_keep_alive_flag(request, helper); + compute_keep_alive_flag(request); return HTTP_OK; } -static enum lwan_http_status -prepare_for_response(struct lwan_url_map *url_map, - struct lwan_request *request, - struct lwan_request_parser_helper *helper) +static enum lwan_http_status prepare_for_response(struct lwan_url_map *url_map, + struct lwan_request *request) { + struct lwan_request_parser_helper *helper = request->helper; + request->url.value += url_map->prefix_len; request->url.len -= url_map->prefix_len; if (url_map->flags & HANDLER_MUST_AUTHORIZE) { - if (!lwan_http_authorize(request, - &helper->authorization, - url_map->authorization.realm, - url_map->authorization.password_file)) + if (!lwan_http_authorize(request, &helper->authorization, + url_map->authorization.realm, + url_map->authorization.password_file)) return HTTP_NOT_AUTHORIZED; } - if (url_map->flags & HANDLER_PARSE_QUERY_STRING) - parse_query_string(request, helper); - - if (url_map->flags & HANDLER_PARSE_IF_MODIFIED_SINCE) - parse_if_modified_since(helper); - - if (url_map->flags & HANDLER_PARSE_RANGE) - parse_range(helper); - - if (url_map->flags & HANDLER_PARSE_ACCEPT_ENCODING) - parse_accept_encoding(request, helper); - - if (url_map->flags & HANDLER_PARSE_COOKIES) - parse_cookies(request, helper); - if (url_map->flags & HANDLER_REMOVE_LEADING_SLASH) { while (*request->url.value == '/' && request->url.len > 0) { ++request->url.value; @@ -1110,10 +1106,13 @@ prepare_for_response(struct lwan_url_map *url_map, } } + if (url_map->flags & HANDLER_PARSE_ACCEPT_ENCODING) + parse_accept_encoding(request); + if (lwan_request_get_method(request) == REQUEST_METHOD_POST) { enum lwan_http_status status; - if (!(url_map->flags & HANDLER_PARSE_POST_DATA)) { + if (!(url_map->flags & HANDLER_HAS_POST_DATA)) { /* FIXME: Discard POST data here? If a POST request is sent * to a handler that is not supposed to handle a POST request, * the next request in the pipeline will fail because the @@ -1125,20 +1124,18 @@ prepare_for_response(struct lwan_url_map *url_map, status = read_post_data(request); if (UNLIKELY(status != HTTP_OK)) return status; - - parse_post_data(request, helper); } return HTTP_OK; } -static bool -handle_rewrite(struct lwan_request *request, struct lwan_request_parser_helper *helper) +static bool handle_rewrite(struct lwan_request *request) { + struct lwan_request_parser_helper *helper = request->helper; + request->flags &= ~RESPONSE_URL_REWRITTEN; - parse_fragment_and_query(request, helper, - request->url.value + request->url.len); + parse_fragment_and_query(request, request->url.value + request->url.len); helper->urls_rewritten++; if (UNLIKELY(helper->urls_rewritten > 4)) { @@ -1149,14 +1146,15 @@ handle_rewrite(struct lwan_request *request, struct lwan_request_parser_helper * return true; } -char * -lwan_process_request(struct lwan *l, struct lwan_request *request, - struct lwan_value *buffer, char *next_request) +char *lwan_process_request(struct lwan *l, + struct lwan_request *request, + struct lwan_value *buffer, + char *next_request) { struct lwan_request_parser_helper helper = { .buffer = buffer, .next_request = next_request, - .error_when_n_packets = calculate_n_packets(DEFAULT_BUFFER_SIZE) + .error_when_n_packets = calculate_n_packets(DEFAULT_BUFFER_SIZE), }; enum lwan_http_status status; struct lwan_url_map *url_map; @@ -1178,7 +1176,7 @@ lwan_process_request(struct lwan *l, struct lwan_request *request, __builtin_unreachable(); } - status = parse_http_request(request, &helper); + status = parse_http_request(request); if (UNLIKELY(status != HTTP_OK)) { lwan_default_response(request, status); goto out; @@ -1191,7 +1189,7 @@ lwan_process_request(struct lwan *l, struct lwan_request *request, goto out; } - status = prepare_for_response(url_map, request, &helper); + status = prepare_for_response(url_map, request); if (UNLIKELY(status != HTTP_OK)) { lwan_default_response(request, status); goto out; @@ -1200,7 +1198,7 @@ lwan_process_request(struct lwan *l, struct lwan_request *request, status = url_map->handler(request, &request->response, url_map->data); if (UNLIKELY(url_map->flags & HANDLER_CAN_REWRITE_URL)) { if (request->flags & RESPONSE_URL_REWRITTEN) { - if (LIKELY(handle_rewrite(request, &helper))) + if (LIKELY(handle_rewrite(request))) goto lookup_again; goto out; } @@ -1232,18 +1230,33 @@ value_lookup(const struct lwan_key_value_array *array, const char *key) const char * lwan_request_get_query_param(struct lwan_request *request, const char *key) { + if (!(request->flags & REQUEST_PARSED_QUERY_STRING)) { + parse_query_string(request); + request->flags |= REQUEST_PARSED_QUERY_STRING; + } + return value_lookup(&request->query_params, key); } const char * lwan_request_get_post_param(struct lwan_request *request, const char *key) { - return value_lookup(&request->post_data, key); + if (!(request->flags & REQUEST_PARSED_POST_DATA)) { + parse_post_data(request); + request->flags |= REQUEST_PARSED_POST_DATA; + } + + return value_lookup(&request->post_params, key); } const char * lwan_request_get_cookie(struct lwan_request *request, const char *key) { + if (!(request->flags & REQUEST_PARSED_COOKIES)) { + parse_cookies(request); + request->flags |= REQUEST_PARSED_COOKIES; + } + return value_lookup(&request->cookies, key); } @@ -1317,7 +1330,12 @@ void lwan_request_sleep(struct lwan_request *request, uint64_t ms) ALWAYS_INLINE int lwan_request_get_range(struct lwan_request *request, off_t *from, off_t *to) { - const struct lwan_request_parser_helper *helper = request->helper; + struct lwan_request_parser_helper *helper = request->helper; + + if (!(request->flags & REQUEST_PARSED_RANGE)) { + parse_range(helper); + request->flags |= REQUEST_PARSED_RANGE; + } if (LIKELY(helper->range.raw.len)) { *from = helper->range.from; @@ -1331,7 +1349,12 @@ lwan_request_get_range(struct lwan_request *request, off_t *from, off_t *to) ALWAYS_INLINE int lwan_request_get_if_modified_since(struct lwan_request *request, time_t *value) { - const struct lwan_request_parser_helper *helper = request->helper; + struct lwan_request_parser_helper *helper = request->helper; + + if (!(request->flags & REQUEST_PARSED_IF_MODIFIED_SINCE)) { + parse_if_modified_since(helper); + request->flags |= REQUEST_PARSED_IF_MODIFIED_SINCE; + } if (LIKELY(helper->if_modified_since.raw.len)) { *value = helper->if_modified_since.parsed; @@ -1352,3 +1375,36 @@ lwan_request_get_content_type(struct lwan_request *request) { return &request->helper->content_type; } + +ALWAYS_INLINE const struct lwan_key_value_array * +lwan_request_get_cookies(struct lwan_request *request) +{ + if (!(request->flags & REQUEST_PARSED_COOKIES)) { + parse_cookies(request); + request->flags |= REQUEST_PARSED_COOKIES; + } + + return &request->cookies; +} + +ALWAYS_INLINE const struct lwan_key_value_array * +lwan_request_get_query_params(struct lwan_request *request) +{ + if (!(request->flags & REQUEST_PARSED_QUERY_STRING)) { + parse_query_string(request); + request->flags |= REQUEST_PARSED_QUERY_STRING; + } + + return &request->query_params; +} + +ALWAYS_INLINE const struct lwan_key_value_array * +lwan_request_get_post_params(struct lwan_request *request) +{ + if (!(request->flags & REQUEST_PARSED_POST_DATA)) { + parse_post_data(request); + request->flags |= REQUEST_PARSED_POST_DATA; + } + + return &request->post_params; +} diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 0aabc8690..27cf74cde 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -195,18 +195,14 @@ enum lwan_http_status { }; enum lwan_handler_flags { - HANDLER_PARSE_QUERY_STRING = 1 << 0, - HANDLER_PARSE_IF_MODIFIED_SINCE = 1 << 1, - HANDLER_PARSE_RANGE = 1 << 2, - HANDLER_PARSE_ACCEPT_ENCODING = 1 << 3, - HANDLER_PARSE_POST_DATA = 1 << 4, - HANDLER_MUST_AUTHORIZE = 1 << 5, - HANDLER_REMOVE_LEADING_SLASH = 1 << 6, - HANDLER_CAN_REWRITE_URL = 1 << 7, - HANDLER_PARSE_COOKIES = 1 << 8, - HANDLER_DATA_IS_HASH_TABLE = 1 << 9, - - HANDLER_PARSE_MASK = 1 << 0 | 1 << 1 | 1 << 2 | 1 << 3 | 1 << 4 | 1 << 8 + HANDLER_HAS_POST_DATA = 1 << 0, + HANDLER_MUST_AUTHORIZE = 1 << 1, + HANDLER_REMOVE_LEADING_SLASH = 1 << 2, + HANDLER_CAN_REWRITE_URL = 1 << 3, + HANDLER_DATA_IS_HASH_TABLE = 1 << 4, + HANDLER_PARSE_ACCEPT_ENCODING = 1 << 5, + + HANDLER_PARSE_MASK = HANDLER_HAS_POST_DATA, }; enum lwan_request_flags { @@ -241,6 +237,13 @@ enum lwan_request_flags { RESPONSE_URL_REWRITTEN = 1 << 12, RESPONSE_STREAM = 1 << 13, + + REQUEST_PARSED_QUERY_STRING = 1 << 14, + REQUEST_PARSED_IF_MODIFIED_SINCE = 1 << 15, + REQUEST_PARSED_RANGE = 1 << 16, + REQUEST_PARSED_POST_DATA = 1 << 17, + REQUEST_PARSED_COOKIES = 1 << 18, + REQUEST_PARSED_ACCEPT_ENCODING = 1 << 19, }; enum lwan_connection_flags { @@ -319,11 +322,10 @@ struct lwan_request { struct lwan_connection *conn; struct lwan_proxy *proxy; - struct lwan_key_value_array query_params, post_data, cookies; - struct timeout timeout; struct lwan_request_parser_helper *helper; + struct lwan_key_value_array cookies, query_params, post_params; struct lwan_response response; }; @@ -504,10 +506,21 @@ lwan_request_get_method(const struct lwan_request *request) return (enum lwan_request_flags)(request->flags & REQUEST_METHOD_MASK); } -int lwan_request_get_range(struct lwan_request *request, off_t *from, off_t *to); -int lwan_request_get_if_modified_since(struct lwan_request *request, time_t *value); -const struct lwan_value *lwan_request_get_request_body(struct lwan_request *request); -const struct lwan_value *lwan_request_get_content_type(struct lwan_request *request); +int lwan_request_get_range(struct lwan_request *request, + off_t *from, + off_t *to); +int lwan_request_get_if_modified_since(struct lwan_request *request, + time_t *value); +const struct lwan_value * +lwan_request_get_request_body(struct lwan_request *request); +const struct lwan_value * +lwan_request_get_content_type(struct lwan_request *request); +const struct lwan_key_value_array * +lwan_request_get_cookies(struct lwan_request *request); +const struct lwan_key_value_array * +lwan_request_get_query_params(struct lwan_request *request); +const struct lwan_key_value_array * +lwan_request_get_post_params(struct lwan_request *request); #if defined(__cplusplus) } From 6d9be84e3502e32d0bb03ba69cae016751c18aa4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 5 Nov 2018 07:53:34 -0800 Subject: [PATCH 0925/2505] Allow reading any arbitrary request header Closes #255. --- src/bin/testrunner/main.c | 17 ++++++++++++++++ src/lib/lwan-request.c | 42 +++++++++++++++++++++++++++++++++++---- src/lib/lwan.h | 2 ++ src/scripts/testsuite.py | 15 ++++++++++++++ testrunner.conf | 2 ++ 5 files changed, 74 insertions(+), 4 deletions(-) diff --git a/src/bin/testrunner/main.c b/src/bin/testrunner/main.c index fba0caa13..ff636973d 100644 --- a/src/bin/testrunner/main.c +++ b/src/bin/testrunner/main.c @@ -237,6 +237,23 @@ LWAN_HANDLER(sleep) return HTTP_OK; } + +LWAN_HANDLER(custom_header) +{ + const char *hdr = lwan_request_get_query_param(request, "hdr"); + + if (!hdr) + return HTTP_NOT_FOUND; + + const char *val = lwan_request_get_header(request, hdr); + if (!val) + return HTTP_NOT_FOUND; + + response->mime_type = "text/plain"; + lwan_strbuf_printf(response->buffer, "Header value: '%s'", val); + return HTTP_OK; +} + int main() { diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 68f3ad9f5..d844c2169 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -73,6 +74,9 @@ struct lwan_request_parser_helper { struct lwan_value content_type; /* Content-Type: for POST */ struct lwan_value content_length; /* Content-Length: */ + char *header_start[64]; /* Headers: n: start, n+1: end */ + size_t n_header_start; /* len(header_start) */ + time_t error_when_time; /* Time to abort request read */ int error_when_n_packets; /* Max. number of packets */ int urls_rewritten; /* Times URLs have been rewritten */ @@ -515,10 +519,11 @@ static bool parse_headers(struct lwan_request_parser_helper *helper, char *buffer, char *buffer_end) { - char *header_start[64]; + char **header_start = helper->header_start; size_t n_headers = 0; + bool ret = false; - for (char *p = buffer + 1; n_headers < N_ELEMENTS(header_start);) { + for (char *p = buffer + 1; n_headers < N_ELEMENTS(helper->header_start);) { char *next_chr = p; char *next_hdr = memchr(next_chr, '\r', (size_t)(buffer_end - p)); @@ -534,9 +539,11 @@ static bool parse_headers(struct lwan_request_parser_helper *helper, p = next_hdr + 2; } - return false; /* Header array isn't large enough */ + goto out; /* Header array isn't large enough */ process: + ret = true; + for (size_t i = 0; i < n_headers; i += 2) { char *p = header_start[i]; char *end = header_start[i + 1]; @@ -589,7 +596,8 @@ static bool parse_headers(struct lwan_request_parser_helper *helper, } out: - return true; + helper->n_header_start = n_headers; + return ret; } #undef HEADER_RAW @@ -1260,6 +1268,32 @@ lwan_request_get_cookie(struct lwan_request *request, const char *key) return value_lookup(&request->cookies, key); } +const char *lwan_request_get_header(const struct lwan_request *request, + const char *header) +{ + char name[64]; + int r; + + r = snprintf(name, sizeof(name), "%s: ", header); + if (UNLIKELY(r < 0 || r >= (int)sizeof(name))) + return NULL; + + for (size_t i = 0; i < request->helper->n_header_start; i += 2) { + const char *start = request->helper->header_start[i]; + char *end = request->helper->header_start[i + 1]; + + if (UNLIKELY(end - start < r)) + continue; + + if (!strncasecmp(start, name, (size_t)r)) { + *end = '\0'; + return start + r; + } + } + + return NULL; +} + ALWAYS_INLINE int lwan_connection_get_fd(const struct lwan *lwan, const struct lwan_connection *conn) { diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 27cf74cde..086670e6b 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -521,6 +521,8 @@ const struct lwan_key_value_array * lwan_request_get_query_params(struct lwan_request *request); const struct lwan_key_value_array * lwan_request_get_post_params(struct lwan_request *request); +const char *lwan_request_get_header(const struct lwan_request *request, + const char *header); #if defined(__cplusplus) } diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index 49f5aa566..cd3afb8eb 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -785,5 +785,20 @@ def test_sleep(self): self.assertTrue(1.450 < diff < 1.550) + +class TestRequest(LwanTest): + def test_custom_header_exists(self): + h = {'Marco': 'Polo'} + r = requests.get('http://127.0.0.1:8080/customhdr?hdr=Marco', headers = h) + + self.assertEqual(r.text, "Header value: 'Polo'") + + def test_custom_header_does_not_exist(self): + h = {'Marco': 'Polo'} + r = requests.get('http://127.0.0.1:8080/customhdr?hdr=Polo', headers = h) + + self.assertEqual(r.status_code, 404) + + if __name__ == '__main__': unittest.main() diff --git a/testrunner.conf b/testrunner.conf index 5c8b8d451..cc624bdb3 100644 --- a/testrunner.conf +++ b/testrunner.conf @@ -28,6 +28,8 @@ max_post_data_size = 1000000 straitjacket listener *:8080 { + &custom_header /customhdr + &sleep /sleep &hello_world /hello From 8dc03d2fe104bd7dec2493ca7afc76eaba4cf83d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 7 Nov 2018 07:39:32 -0800 Subject: [PATCH 0926/2505] Ensure removed node from death queue points to nowhere Not doing this causes an infinite loop while shutting down the DQ, as death_queue_empty() returns false. --- src/lib/lwan-thread.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 5858fe496..d5cb36fc7 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -78,6 +78,8 @@ static void death_queue_remove(struct death_queue *dq, struct lwan_connection *next = death_queue_idx_to_node(dq, node->next); next->prev = node->prev; prev->next = node->next; + + node->next = node->prev = -1; } static bool death_queue_empty(struct death_queue *dq) From 1cac5e211b952fdc4a71639f7cfb46e011f6bc20 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 9 Nov 2018 08:17:35 -0800 Subject: [PATCH 0927/2505] Add function to determine if input contains only base64 characters --- src/lib/base64.c | 11 +++++++++++ src/lib/base64.h | 2 ++ 2 files changed, 13 insertions(+) diff --git a/src/lib/base64.c b/src/lib/base64.c index 91f7234df..da27cdcdb 100644 --- a/src/lib/base64.c +++ b/src/lib/base64.c @@ -45,6 +45,17 @@ static const unsigned char base64_decode_table[256] = { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}; +bool +base64_validate(const unsigned char *src, size_t len) +{ + for (size_t i = 0; i < len; i++) { + if (base64_decode_table[src[i]] == 0x80) + return false; + } + + return true; +} + /** * base64_encode - Base64 encode * @src: Data to be encoded diff --git a/src/lib/base64.h b/src/lib/base64.h index d7f5a3fcc..dc6ccb71f 100644 --- a/src/lib/base64.h +++ b/src/lib/base64.h @@ -1,5 +1,6 @@ #pragma once +#include #include unsigned char *base64_encode(const unsigned char *src, size_t len, @@ -7,3 +8,4 @@ unsigned char *base64_encode(const unsigned char *src, size_t len, unsigned char *base64_decode(const unsigned char *src, size_t len, size_t *out_len); +bool base64_validate(const unsigned char *src, size_t len); From fa7bf0047350ba40e5274956c76ace359454fd38 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 9 Nov 2018 08:18:15 -0800 Subject: [PATCH 0928/2505] Do not include newline in base64-encoded string --- src/lib/base64.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/lib/base64.c b/src/lib/base64.c index da27cdcdb..d85e04e02 100644 --- a/src/lib/base64.c +++ b/src/lib/base64.c @@ -115,9 +115,6 @@ base64_encode(const unsigned char *src, size_t len, size_t *out_len) line_len += 4; } - if (line_len) - *pos++ = '\n'; - *pos = '\0'; if (out_len) *out_len = (size_t)(pos - out); From 8a3da6070d8bf712544ee66cab31e7b619ed6b91 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 9 Nov 2018 08:18:42 -0800 Subject: [PATCH 0929/2505] Add I/O wrappers for readv() and recv() --- src/lib/lwan-io-wrappers.c | 81 ++++++++++++++++++++++++++++++++++++++ src/lib/lwan-io-wrappers.h | 4 ++ 2 files changed, 85 insertions(+) diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index 8c3a60f4f..792815a01 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -75,6 +75,51 @@ lwan_writev(struct lwan_request *request, struct iovec *iov, int iov_count) __builtin_unreachable(); } +ssize_t +lwan_readv(struct lwan_request *request, struct iovec *iov, int iov_count) +{ + ssize_t total_bytes_read = 0; + int curr_iov = 0; + + for (int tries = MAX_FAILED_TRIES; tries;) { + ssize_t bytes_read = readv(request->fd, iov + curr_iov, iov_count - curr_iov); + if (UNLIKELY(bytes_read < 0)) { + /* FIXME: Consider short writes as another try as well? */ + tries--; + + switch (errno) { + case EAGAIN: + request->conn->flags |= CONN_FLIP_FLAGS; + /* fallthrough */ + case EINTR: + goto try_again; + default: + goto out; + } + } + + total_bytes_read += bytes_read; + + while (curr_iov < iov_count && bytes_read >= (ssize_t)iov[curr_iov].iov_len) { + bytes_read -= (ssize_t)iov[curr_iov].iov_len; + curr_iov++; + } + + if (curr_iov == iov_count) + return total_bytes_read; + + iov[curr_iov].iov_base = (char *)iov[curr_iov].iov_base + bytes_read; + iov[curr_iov].iov_len -= (size_t)bytes_read; + +try_again: + coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); + } + +out: + coro_yield(request->conn->coro, CONN_CORO_ABORT); + __builtin_unreachable(); +} + ssize_t lwan_send(struct lwan_request *request, const void *buf, size_t count, int flags) { @@ -111,6 +156,42 @@ lwan_send(struct lwan_request *request, const void *buf, size_t count, int flags __builtin_unreachable(); } +ssize_t +lwan_recv(struct lwan_request *request, void *buf, size_t count, int flags) +{ + ssize_t total_recv = 0; + + for (int tries = MAX_FAILED_TRIES; tries;) { + ssize_t recvd = recv(request->fd, buf, count, flags); + if (UNLIKELY(recvd < 0)) { + tries--; + + switch (errno) { + case EAGAIN: + request->conn->flags |= CONN_FLIP_FLAGS; + /* fallthrough */ + case EINTR: + goto try_again; + default: + goto out; + } + } + + total_recv += recvd; + if ((size_t)total_recv == count) + return total_recv; + if ((size_t)total_recv < count) + buf = (char *)buf + recvd; + +try_again: + coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); + } + +out: + coro_yield(request->conn->coro, CONN_CORO_ABORT); + __builtin_unreachable(); +} + #if defined(__linux__) static inline size_t min_size(size_t a, size_t b) { diff --git a/src/lib/lwan-io-wrappers.h b/src/lib/lwan-io-wrappers.h index aef5dda41..de60ab025 100644 --- a/src/lib/lwan-io-wrappers.h +++ b/src/lib/lwan-io-wrappers.h @@ -32,3 +32,7 @@ void lwan_sendfile(struct lwan_request *request, int in_fd, off_t offset, size_t count, const char *header, size_t header_len); +ssize_t lwan_recv(struct lwan_request *request, + void *buf, size_t count, int flags); +ssize_t lwan_readv(struct lwan_request *request, + struct iovec *iov, int iov_count); From d82dd9a16bff6fa35dd9c789612114c1df1f603a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 9 Nov 2018 08:19:37 -0800 Subject: [PATCH 0930/2505] Add library to calculate SHA1 hash --- src/lib/CMakeLists.txt | 1 + src/lib/sha1.c | 313 +++++++++++++++++++++++++++++++++++++++++ src/lib/sha1.h | 20 +++ 3 files changed, 334 insertions(+) create mode 100644 src/lib/sha1.c create mode 100644 src/lib/sha1.h diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index fe689e23e..b3238ee3e 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -40,6 +40,7 @@ set(SOURCES realpathat.c sd-daemon.c timeout.c + sha1.c ) if (HAVE_LUA) diff --git a/src/lib/sha1.c b/src/lib/sha1.c new file mode 100644 index 000000000..ebe12e504 --- /dev/null +++ b/src/lib/sha1.c @@ -0,0 +1,313 @@ +/* +SHA-1 in C +By Steve Reid +100% Public Domain + +Test Vectors (from FIPS PUB 180-1) +"abc" + A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D +"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 +A million repetitions of "a" + 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F +*/ + +/* #define LITTLE_ENDIAN * This should be #define'd already, if true. */ +/* #define SHA1HANDSOFF * Copies data before messing with it. */ + +#define SHA1HANDSOFF + +#include "sha1.h" +#include +#include +#include +#include /* for u_int*_t */ + +#ifndef BYTE_ORDER +#if (BSD >= 199103) +#include +#else +#if defined(linux) || defined(__linux__) +#include +#else +#define LITTLE_ENDIAN 1234 /* least-significant byte first (vax, pc) */ +#define BIG_ENDIAN 4321 /* most-significant byte first (IBM, net) */ +#define PDP_ENDIAN 3412 /* LSB first in word, MSW first in long (pdp)*/ + +#if defined(vax) || defined(ns32000) || defined(sun386) || \ + defined(__i386__) || defined(MIPSEL) || defined(_MIPSEL) || \ + defined(BIT_ZERO_ON_RIGHT) || defined(__alpha__) || defined(__alpha) +#define BYTE_ORDER LITTLE_ENDIAN +#endif + +#if defined(sel) || defined(pyr) || defined(mc68000) || defined(sparc) || \ + defined(is68k) || defined(tahoe) || defined(ibm032) || defined(ibm370) || \ + defined(MIPSEB) || defined(_MIPSEB) || defined(_IBMR2) || defined(DGUX) || \ + defined(apollo) || defined(__convex__) || defined(_CRAY) || \ + defined(__hppa) || defined(__hp9000) || defined(__hp9000s300) || \ + defined(__hp9000s700) || defined(BIT_ZERO_ON_LEFT) || defined(m68k) || \ + defined(__sparc) +#define BYTE_ORDER BIG_ENDIAN +#endif +#endif /* linux */ +#endif /* BSD */ +#endif /* BYTE_ORDER */ + +#if defined(__BYTE_ORDER) && !defined(BYTE_ORDER) +#if (__BYTE_ORDER == __LITTLE_ENDIAN) +#define BYTE_ORDER LITTLE_ENDIAN +#else +#define BYTE_ORDER BIG_ENDIAN +#endif +#endif + +#if !defined(BYTE_ORDER) || \ + (BYTE_ORDER != BIG_ENDIAN && BYTE_ORDER != LITTLE_ENDIAN && \ + BYTE_ORDER != PDP_ENDIAN) +/* you must determine what the correct bit order is for + * your compiler - the next line is an intentional error + * which will force your compiles to bomb until you fix + * the above macros. + */ +#error "Undefined or invalid BYTE_ORDER" +#endif + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +/* blk0() and blk() perform the initial expand. */ +/* I got the idea of expanding during the round function from SSLeay */ +#if BYTE_ORDER == LITTLE_ENDIAN +#define blk0(i) \ + (block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) | \ + (rol(block->l[i], 8) & 0x00FF00FF)) +#elif BYTE_ORDER == BIG_ENDIAN +#define blk0(i) block->l[i] +#else +#error "Endianness not defined!" +#endif +#define blk(i) \ + (block->l[i & 15] = rol(block->l[(i + 13) & 15] ^ block->l[(i + 8) & 15] ^ \ + block->l[(i + 2) & 15] ^ block->l[i & 15], \ + 1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v, w, x, y, z, i) \ + z += ((w & (x ^ y)) ^ y) + blk0(i) + 0x5A827999 + rol(v, 5); \ + w = rol(w, 30); +#define R1(v, w, x, y, z, i) \ + z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \ + w = rol(w, 30); +#define R2(v, w, x, y, z, i) \ + z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \ + w = rol(w, 30); +#define R3(v, w, x, y, z, i) \ + z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \ + w = rol(w, 30); +#define R4(v, w, x, y, z, i) \ + z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \ + w = rol(w, 30); + +/* Hash a single 512-bit block. This is the core of the algorithm. */ + +static void sha1_transform(u_int32_t state[5], const unsigned char buffer[64]) +{ + u_int32_t a, b, c, d, e; + typedef union { + unsigned char c[64]; + u_int32_t l[16]; + } CHAR64LONG16; +#ifdef SHA1HANDSOFF + CHAR64LONG16 block[1]; /* use array to appear as a pointer */ + memcpy(block, buffer, 64); +#else + /* The following had better never be used because it causes the + * pointer-to-const buffer to be cast into a pointer to non-const. + * And the result is written through. I threw a "const" in, hoping + * this will cause a diagnostic. + */ + CHAR64LONG16 *block = (const CHAR64LONG16 *)buffer; +#endif + /* Copy context->state[] to working vars */ + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(a, b, c, d, e, 0); + R0(e, a, b, c, d, 1); + R0(d, e, a, b, c, 2); + R0(c, d, e, a, b, 3); + R0(b, c, d, e, a, 4); + R0(a, b, c, d, e, 5); + R0(e, a, b, c, d, 6); + R0(d, e, a, b, c, 7); + R0(c, d, e, a, b, 8); + R0(b, c, d, e, a, 9); + R0(a, b, c, d, e, 10); + R0(e, a, b, c, d, 11); + R0(d, e, a, b, c, 12); + R0(c, d, e, a, b, 13); + R0(b, c, d, e, a, 14); + R0(a, b, c, d, e, 15); + R1(e, a, b, c, d, 16); + R1(d, e, a, b, c, 17); + R1(c, d, e, a, b, 18); + R1(b, c, d, e, a, 19); + R2(a, b, c, d, e, 20); + R2(e, a, b, c, d, 21); + R2(d, e, a, b, c, 22); + R2(c, d, e, a, b, 23); + R2(b, c, d, e, a, 24); + R2(a, b, c, d, e, 25); + R2(e, a, b, c, d, 26); + R2(d, e, a, b, c, 27); + R2(c, d, e, a, b, 28); + R2(b, c, d, e, a, 29); + R2(a, b, c, d, e, 30); + R2(e, a, b, c, d, 31); + R2(d, e, a, b, c, 32); + R2(c, d, e, a, b, 33); + R2(b, c, d, e, a, 34); + R2(a, b, c, d, e, 35); + R2(e, a, b, c, d, 36); + R2(d, e, a, b, c, 37); + R2(c, d, e, a, b, 38); + R2(b, c, d, e, a, 39); + R3(a, b, c, d, e, 40); + R3(e, a, b, c, d, 41); + R3(d, e, a, b, c, 42); + R3(c, d, e, a, b, 43); + R3(b, c, d, e, a, 44); + R3(a, b, c, d, e, 45); + R3(e, a, b, c, d, 46); + R3(d, e, a, b, c, 47); + R3(c, d, e, a, b, 48); + R3(b, c, d, e, a, 49); + R3(a, b, c, d, e, 50); + R3(e, a, b, c, d, 51); + R3(d, e, a, b, c, 52); + R3(c, d, e, a, b, 53); + R3(b, c, d, e, a, 54); + R3(a, b, c, d, e, 55); + R3(e, a, b, c, d, 56); + R3(d, e, a, b, c, 57); + R3(c, d, e, a, b, 58); + R3(b, c, d, e, a, 59); + R4(a, b, c, d, e, 60); + R4(e, a, b, c, d, 61); + R4(d, e, a, b, c, 62); + R4(c, d, e, a, b, 63); + R4(b, c, d, e, a, 64); + R4(a, b, c, d, e, 65); + R4(e, a, b, c, d, 66); + R4(d, e, a, b, c, 67); + R4(c, d, e, a, b, 68); + R4(b, c, d, e, a, 69); + R4(a, b, c, d, e, 70); + R4(e, a, b, c, d, 71); + R4(d, e, a, b, c, 72); + R4(c, d, e, a, b, 73); + R4(b, c, d, e, a, 74); + R4(a, b, c, d, e, 75); + R4(e, a, b, c, d, 76); + R4(d, e, a, b, c, 77); + R4(c, d, e, a, b, 78); + R4(b, c, d, e, a, 79); + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + + /* Wipe variables */ + a = b = c = d = e = 0; + __asm__ volatile("" + : + : "g"(&a), "g"(&b), "g"(&c), "g"(&d), "g"(&e) + : "memory"); + +#ifdef SHA1HANDSOFF + memset(block, '\0', sizeof(block)); + __asm__ volatile("" : : "g"(block) : "memory"); +#endif +} + +/* sha1_init - Initialize new context */ + +void sha1_init(sha1_context *context) +{ + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + +/* Run your data through this. */ + +void sha1_update(sha1_context *context, const unsigned char *data, size_t len) +{ + size_t i; + size_t j; + + j = context->count[0]; + if ((context->count[0] += len << 3) < j) + context->count[1]++; + context->count[1] += (len >> 29); + + j = (j >> 3) & 63; + + if ((j + len) > 63) { + i = 64 - j; + memcpy(&context->buffer[j], data, i); + sha1_transform(context->state, context->buffer); + + for (; i + 63 < len; i += 64) + sha1_transform(context->state, &data[i]); + + j = 0; + } else { + i = 0; + } + + memcpy(&context->buffer[j], &data[i], len - i); +} + +/* Add padding and return the message digest. */ + +void sha1_finalize(sha1_context *context, unsigned char digest[20]) +{ + unsigned i; + unsigned char finalcount[8]; + unsigned char c; + + for (i = 0; i < 8; i++) { + finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] >> + ((3 - (i & 3)) * 8)) & + 255); /* Endian independent */ + } + + c = 0200; + sha1_update(context, &c, 1); + while ((context->count[0] & 504) != 448) { + c = 0000; + sha1_update(context, &c, 1); + } + sha1_update(context, finalcount, 8); /* Should cause a sha1_transform() */ + for (i = 0; i < 20; i++) { + digest[i] = + (unsigned char)((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & + 255); + } + + /* Wipe variables */ + memset(context, '\0', sizeof(*context)); + __asm__ volatile("" : : "g"(context) : "memory"); + + memset(&finalcount, '\0', sizeof(finalcount)); + __asm__ volatile("" : : "g"(finalcount) : "memory"); +} diff --git a/src/lib/sha1.h b/src/lib/sha1.h new file mode 100644 index 000000000..943698296 --- /dev/null +++ b/src/lib/sha1.h @@ -0,0 +1,20 @@ +/* +SHA-1 in C +By Steve Reid +100% Public Domain +*/ + +#pragma once + +#include +#include + +typedef struct { + size_t count[2]; + uint32_t state[5]; + unsigned char buffer[64]; +} sha1_context; + +void sha1_init(sha1_context* context); +void sha1_update(sha1_context* context, const unsigned char* data, size_t len); +void sha1_finalize(sha1_context* context, unsigned char digest[20]); From 7976f74a0fe2cf5f1ef606304a8db3092bc524b8 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 9 Nov 2018 08:20:17 -0800 Subject: [PATCH 0931/2505] Allow multiple values in Connection: header This is a list, pretty much like the Accept-Encoding header. --- src/lib/lwan-request.c | 49 +++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index d844c2169..3fcd8915a 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -74,13 +74,14 @@ struct lwan_request_parser_helper { struct lwan_value content_type; /* Content-Type: for POST */ struct lwan_value content_length; /* Content-Length: */ + struct lwan_value connection; /* Connection: */ + char *header_start[64]; /* Headers: n: start, n+1: end */ size_t n_header_start; /* len(header_start) */ time_t error_when_time; /* Time to abort request read */ int error_when_n_packets; /* Max. number of packets */ int urls_rewritten; /* Times URLs have been rewritten */ - char connection; /* k=keep-alive, c=close, u=upgrade */ }; struct proxy_header_v2 { @@ -499,19 +500,14 @@ identify_http_path(struct lwan_request *request, char *buffer) return end_of_line + 1; } -#define HEADER_RAW(hdr) \ +#define HEADER(hdr) \ ({ \ p += sizeof(hdr) - 1; \ if (UNLIKELY(string_as_int16(p) != \ MULTICHAR_CONSTANT_SMALL(':', ' '))) \ continue; \ *end = '\0'; \ - p + sizeof(": ") - 1; \ - }) - -#define HEADER(hdr) \ - ({ \ - char *value = HEADER_RAW(hdr); \ + char *value = p + sizeof(": ") - 1; \ (struct lwan_value){.value = value, .len = (size_t)(end - value)}; \ }) @@ -562,7 +558,7 @@ static bool parse_headers(struct lwan_request_parser_helper *helper, helper->authorization = HEADER("Authorization"); break; case MULTICHAR_CONSTANT_L('C','o','n','n'): - helper->connection = *HEADER_RAW("Connection") | 0x20; + helper->connection = HEADER("Connection"); break; case MULTICHAR_CONSTANT_L('C','o','n','t'): p += sizeof("Content") - 1; @@ -688,17 +684,36 @@ ignore_leading_whitespace(char *buffer) return buffer; } -static ALWAYS_INLINE void compute_keep_alive_flag(struct lwan_request *request) +static ALWAYS_INLINE void parse_connection_header(struct lwan_request *request) { struct lwan_request_parser_helper *helper = request->helper; - bool is_keep_alive; + bool has_keep_alive = false; + bool has_close = false; - if (request->flags & REQUEST_IS_HTTP_1_0) - is_keep_alive = (helper->connection == 'k'); - else - is_keep_alive = (helper->connection != 'c'); + for (const char *p = helper->connection.value; *p; p++) { + STRING_SWITCH_L(p) { + case MULTICHAR_CONSTANT_L('k','e','e','p'): + case MULTICHAR_CONSTANT_L(' ', 'k','e','e'): + has_keep_alive = true; + break; + case MULTICHAR_CONSTANT_L('c','l','o','s'): + case MULTICHAR_CONSTANT_L(' ', 'c','l','o'): + has_close = true; + break; + case MULTICHAR_CONSTANT_L('u','p','g','r'): + case MULTICHAR_CONSTANT_L(' ', 'u','p','g'): + request->conn->flags |= CONN_IS_UPGRADE; + break; + } + + if (!(p = strchr(p, ','))) + break; + } + + if (LIKELY(!(request->flags & REQUEST_IS_HTTP_1_0))) + has_keep_alive = !has_close; - if (is_keep_alive) + if (has_keep_alive) request->conn->flags |= CONN_KEEP_ALIVE; else request->conn->flags &= ~CONN_KEEP_ALIVE; @@ -1087,7 +1102,7 @@ parse_http_request(struct lwan_request *request) return HTTP_BAD_REQUEST; request->original_url.len = request->url.len = (size_t)decoded_len; - compute_keep_alive_flag(request); + parse_connection_header(request); return HTTP_OK; } From 120d12e20380f3a4da3562bdbd5af36c4a885ed6 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 9 Nov 2018 08:21:04 -0800 Subject: [PATCH 0932/2505] Add function to grow a string buffer by a given length --- src/lib/lwan-strbuf.c | 10 ++++++++++ src/lib/lwan-strbuf.h | 1 + 2 files changed, 11 insertions(+) diff --git a/src/lib/lwan-strbuf.c b/src/lib/lwan-strbuf.c index 6d5ed505c..3f087c868 100644 --- a/src/lib/lwan-strbuf.c +++ b/src/lib/lwan-strbuf.c @@ -249,6 +249,16 @@ bool lwan_strbuf_grow_to(struct lwan_strbuf *s, size_t new_size) return grow_buffer_if_needed(s, new_size + 1); } +bool lwan_strbuf_grow_by(struct lwan_strbuf *s, size_t offset) +{ + size_t new_size; + + if (__builtin_add_overflow(offset, s->used, &new_size)) + return false; + + return lwan_strbuf_grow_to(s, new_size); +} + void lwan_strbuf_reset(struct lwan_strbuf *s) { if (s->flags & STATIC) { diff --git a/src/lib/lwan-strbuf.h b/src/lib/lwan-strbuf.h index 74d2e7815..ecee5d853 100644 --- a/src/lib/lwan-strbuf.h +++ b/src/lib/lwan-strbuf.h @@ -51,6 +51,7 @@ bool lwan_strbuf_printf(struct lwan_strbuf *s1, const char *fmt, ...) __attribute__((format(printf, 2, 3))); bool lwan_strbuf_grow_to(struct lwan_strbuf *s, size_t new_size); +bool lwan_strbuf_grow_by(struct lwan_strbuf *s, size_t offset); #define lwan_strbuf_get_length(s) (((struct lwan_strbuf *)(s))->used) #define lwan_strbuf_get_buffer(s) (((struct lwan_strbuf *)(s))->value.buffer) From 859f504fdb2c915cab385020f2e2e948d93f0da3 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 9 Nov 2018 08:22:42 -0800 Subject: [PATCH 0933/2505] Add experimental RFC6455 (WebSockets) support There's still a ton to do here regarding the API, and how this plays with the I/O loop in Lwan. The provided sample will only send data from Lwan to the client, but a prototype implementation for the read side is implemented as well (needs sample application, though). A few things have to be addressed first. Of note: 1) PING packets can't be handled automatically by Lwan. The websockets read function has to be called by the coroutine. This makes it difficult to write websockets servers that will only ever send data, but not receive anything. 2) It's not clear yet how to create a server that can't just react to packets that have been sent by the client, as the read function will block indefinitely, even buffering continuation frames until a packet with the FIN bit set arrives. The only way to do this correctly would be having two websockets connections to the server: one only to receive commands, and one only to send commands. This works around the limitation but creates other problems; Lwan can clearly handle this, but there has to be a better way. This should also impact how (1) is handled. 3) No extensions, such as per-message compression, are supported yet. These are doable with zlib that Lwan already links with, it just has to be implemented. I don't know how widely used this is, too, to justify the complexity. --- src/lib/lwan-request.c | 66 ++++++++- src/lib/lwan-response.c | 207 +++++++++++++++++++++++++-- src/lib/lwan-tables.c | 1 + src/lib/lwan.h | 9 ++ src/samples/CMakeLists.txt | 1 + src/samples/websocket/CMakeLists.txt | 8 ++ src/samples/websocket/main.c | 80 +++++++++++ 7 files changed, 363 insertions(+), 9 deletions(-) create mode 100644 src/samples/websocket/CMakeLists.txt create mode 100644 src/samples/websocket/main.c diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 3fcd8915a..bd333ca93 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -37,9 +37,12 @@ #include "lwan-private.h" +#include "base64.h" +#include "list.h" #include "lwan-config.h" #include "lwan-http-authorize.h" -#include "list.h" +#include "lwan-io-wrappers.h" +#include "sha1.h" enum lwan_read_finalizer { FINALIZER_DONE, @@ -1107,6 +1110,67 @@ parse_http_request(struct lwan_request *request) return HTTP_OK; } +enum lwan_http_status +lwan_request_websocket_upgrade(struct lwan_request *request) +{ + static const unsigned char websocket_uuid[] = + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + char header_buf[DEFAULT_HEADERS_SIZE]; + size_t header_buf_len; + unsigned char digest[20]; + sha1_context ctx; + char *encoded; + + if (UNLIKELY(request->flags & RESPONSE_SENT_HEADERS)) + return HTTP_INTERNAL_ERROR; + + if (UNLIKELY(!(request->conn->flags & CONN_IS_UPGRADE))) + return HTTP_BAD_REQUEST; + + const char *upgrade = lwan_request_get_header(request, "Upgrade"); + if (UNLIKELY(!upgrade || !streq(upgrade, "websocket"))) + return HTTP_BAD_REQUEST; + + const char *sec_websocket_key = + lwan_request_get_header(request, "Sec-WebSocket-Key"); + if (UNLIKELY(!sec_websocket_key)) + return HTTP_BAD_REQUEST; + const size_t sec_websocket_key_len = strlen(sec_websocket_key); + if (UNLIKELY(!base64_validate((void *)sec_websocket_key, sec_websocket_key_len))) + return HTTP_BAD_REQUEST; + + sha1_init(&ctx); + sha1_update(&ctx, (void *)sec_websocket_key, sec_websocket_key_len); + sha1_update(&ctx, websocket_uuid, sizeof(websocket_uuid) - 1); + sha1_finalize(&ctx, digest); + + encoded = (char *)base64_encode(digest, sizeof(digest), NULL); + if (UNLIKELY(!encoded)) + return HTTP_INTERNAL_ERROR; + coro_defer(request->conn->coro, CORO_DEFER(free), encoded); + + request->flags |= RESPONSE_NO_CONTENT_LENGTH; + header_buf_len = lwan_prepare_response_header_full( + request, HTTP_SWITCHING_PROTOCOLS, header_buf, sizeof(header_buf), + (struct lwan_key_value[]){ + /* Connection: Upgrade is implicit if conn->flags & CONN_IS_UPGRADE */ + {.key = "Sec-WebSocket-Accept", .value = encoded}, + {.key = "Upgrade", .value = "websocket"}, + {}, + }); + if (LIKELY(header_buf_len)) { + request->conn->flags |= (CONN_FLIP_FLAGS | CONN_IS_WEBSOCKET); + + lwan_send(request, header_buf, header_buf_len, 0); + + coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); + + return HTTP_SWITCHING_PROTOCOLS; + } + + return HTTP_INTERNAL_ERROR; +} + static enum lwan_http_status prepare_for_response(struct lwan_url_map *url_map, struct lwan_request *request) { diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index d50a32e16..f12890616 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -285,14 +285,6 @@ size_t lwan_prepare_response_header_full( APPEND_UINT(lwan_strbuf_get_length(request->response.buffer)); } - APPEND_CONSTANT("\r\nContent-Type: "); - APPEND_STRING(request->response.mime_type); - - if (request->conn->flags & CONN_KEEP_ALIVE) - APPEND_CONSTANT("\r\nConnection: keep-alive"); - else - APPEND_CONSTANT("\r\nConnection: close"); - if ((status < HTTP_BAD_REQUEST && additional_headers)) { const struct lwan_key_value *header; @@ -332,6 +324,21 @@ size_t lwan_prepare_response_header_full( } } + if (request->conn->flags & CONN_IS_UPGRADE) { + APPEND_CONSTANT("\r\nConnection: Upgrade"); + } else { + if (request->conn->flags & CONN_KEEP_ALIVE) { + APPEND_CONSTANT("\r\nConnection: keep-alive"); + } else { + APPEND_CONSTANT("\r\nConnection: close"); + } + + if (LIKELY(request->response.mime_type)) { + APPEND_CONSTANT("\r\nContent-Type: "); + APPEND_STRING(request->response.mime_type); + } + } + if (LIKELY(!date_overridden)) { APPEND_CONSTANT("\r\nDate: "); APPEND_STRING_LEN(request->conn->thread->date.date, 29); @@ -502,3 +509,187 @@ void lwan_response_send_event(struct lwan_request *request, const char *event) coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); } + +enum ws_opcode { + WS_OPCODE_CONTINUATION = 0, + WS_OPCODE_TEXT = 1, + WS_OPCODE_BINARY = 2, + WS_OPCODE_CLOSE = 8, + WS_OPCODE_PING = 9, + WS_OPCODE_PONG = 10, +}; + +static void write_websocket_frame(struct lwan_request *request, + unsigned char header_byte, + char *msg, + size_t len) +{ + struct iovec vec[4]; + uint8_t net_len_byte; + uint16_t net_len_short; + uint64_t net_len_long; + int last = 0; + + vec[last++] = (struct iovec){.iov_base = &header_byte, .iov_len = 1}; + + if (len <= 125) { + net_len_byte = (uint8_t)len; + + vec[last++] = (struct iovec){.iov_base = &net_len_byte, .iov_len = 1}; + } else if (len <= 65535) { + net_len_short = htons((uint16_t)len); + + vec[last++] = (struct iovec){.iov_base = (char[]){0x7e}, .iov_len = 1}; + vec[last++] = (struct iovec){.iov_base = &net_len_short, .iov_len = 2}; + } else { + net_len_long = htobe64((uint64_t)len); + + vec[last++] = (struct iovec){.iov_base = (char[]){0x7f}, .iov_len = 1}; + vec[last++] = (struct iovec){.iov_base = &net_len_long, .iov_len = 8}; + } + + vec[last++] = (struct iovec){.iov_base = msg, .iov_len = len}; + + lwan_writev(request, vec, last); +} + +void lwan_response_websocket_write(struct lwan_request *request) +{ + size_t len = lwan_strbuf_get_length(request->response.buffer); + char *msg = lwan_strbuf_get_buffer(request->response.buffer); + /* FIXME: does it make a difference if we use WS_OPCODE_TEXT or + * WS_OPCODE_BINARY? */ + unsigned char header = 0x80 | WS_OPCODE_TEXT; + + if (!(request->conn->flags & CONN_IS_WEBSOCKET)) + return; + + write_websocket_frame(request, header, msg, len); + lwan_strbuf_reset(request->response.buffer); +} + +static void send_websocket_pong(struct lwan_request *request, size_t len) +{ + size_t generation; + char *temp; + + if (UNLIKELY(len > 125)) { + lwan_status_debug("Received PING opcode with length %zu." + "Max is 125. Aborting connection.", + len); + goto abort; + } + + generation = coro_deferred_get_generation(request->conn->coro); + + temp = coro_malloc(request->conn->coro, len); + if (UNLIKELY(!temp)) + goto abort; + + lwan_recv(request, temp, len, 0); + write_websocket_frame(request, WS_OPCODE_PONG, temp, len); + + coro_deferred_run(request->conn->coro, generation); + + return; + +abort: + coro_yield(request->conn->coro, CONN_CORO_ABORT); + __builtin_unreachable(); +} + +bool lwan_response_websocket_read(struct lwan_request *request) +{ + uint16_t header; + uint64_t len_frame; + char *msg; + bool continuation = false; + bool fin; + + if (!(request->conn->flags & CONN_IS_WEBSOCKET)) + return false; + + lwan_strbuf_reset(request->response.buffer); + +next_frame: + lwan_recv(request, &header, sizeof(header), 0); + + fin = (header & 0x8000); + + switch ((enum ws_opcode)((header & 0xf00) >> 8)) { + case WS_OPCODE_CONTINUATION: + continuation = true; + break; + case WS_OPCODE_TEXT: + case WS_OPCODE_BINARY: + break; + case WS_OPCODE_CLOSE: + request->conn->flags &= ~CONN_IS_WEBSOCKET; + break; + case WS_OPCODE_PING: + /* FIXME: handling PING packets here doesn't seem ideal; they won't be + * handled, for instance, if the user never receives data from the + * websocket. */ + send_websocket_pong(request, header & 0x7f); + goto next_frame; + default: + lwan_status_debug( + "Received unexpected WebSockets opcode: 0x%x, ignoring", + (header & 0xf00) >> 8); + goto next_frame; + } + + switch (header & 0x7f) { + default: + len_frame = (uint64_t)(header & 0x7f); + break; + case 0x7e: + lwan_recv(request, &len_frame, 2, 0); + len_frame = (uint64_t)ntohs((uint16_t)len_frame); + break; + case 0x7f: + lwan_recv(request, &len_frame, 8, 0); + len_frame = be64toh(len_frame); + break; + } + + size_t cur_len = lwan_strbuf_get_length(request->response.buffer); + + if (UNLIKELY(!lwan_strbuf_grow_by(request->response.buffer, len_frame))) { + coro_yield(request->conn->coro, CONN_CORO_ABORT); + __builtin_unreachable(); + } + + msg = lwan_strbuf_get_buffer(request->response.buffer) + cur_len; + + if (LIKELY(header & 0x80)) { + /* Payload is masked; should always be true on Client->Server comms but + * don't assume this is always the case. */ + union { + char as_char[4]; + uint32_t as_int; + } masks; + struct iovec vec[] = { + {.iov_base = masks.as_char, .iov_len = sizeof(masks.as_char)}, + {.iov_base = msg, .iov_len = len_frame}, + }; + + lwan_readv(request, vec, N_ELEMENTS(vec)); + + if (masks.as_int != 0x00000000) { + for (uint64_t i = 0; i < len_frame; i++) + msg[i] ^= masks.as_char[i % sizeof(masks)]; + } + } else { + lwan_recv(request, msg, len_frame, 0); + } + + if (continuation && !fin) { + coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); + continuation = false; + + goto next_frame; + } + + return request->conn->flags & CONN_IS_WEBSOCKET; +} diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index 02fb3a05e..31a9b1798 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -115,6 +115,7 @@ static const struct { const char *status; const char *description; } status_table[] = { + STATUS(101, "Switching protcols", "Protocol is switching over from HTTP"), STATUS(200, "OK", "Success!"), STATUS(206, "Partial content", "Delivering part of requested resource."), STATUS(301, "Moved permanently", "This content has moved to another place."), diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 086670e6b..50c0784f7 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -173,6 +173,7 @@ static ALWAYS_INLINE int16_t string_as_int16(const char *s) #endif enum lwan_http_status { + HTTP_SWITCHING_PROTOCOLS = 101, HTTP_OK = 200, HTTP_PARTIAL_CONTENT = 206, HTTP_MOVED_PERMANENTLY = 301, @@ -256,6 +257,8 @@ enum lwan_connection_flags { CONN_SUSPENDED_BY_TIMER = 1 << 5, CONN_RESUMED_FROM_TIMER = 1 << 6, CONN_FLIP_FLAGS = 1 << 7, + CONN_IS_UPGRADE = 1 << 8, + CONN_IS_WEBSOCKET = 1 << 9, }; enum lwan_connection_coro_yield { @@ -469,6 +472,9 @@ bool lwan_response_set_event_stream(struct lwan_request *request, enum lwan_http_status status); void lwan_response_send_event(struct lwan_request *request, const char *event); +void lwan_response_websocket_write(struct lwan_request *request); +bool lwan_response_websocket_read(struct lwan_request *request); + const char *lwan_http_status_as_string(enum lwan_http_status status) __attribute__((const)) __attribute__((warn_unused_result)); const char *lwan_http_status_as_string_with_code(enum lwan_http_status status) @@ -524,6 +530,9 @@ lwan_request_get_post_params(struct lwan_request *request); const char *lwan_request_get_header(const struct lwan_request *request, const char *header); +enum lwan_http_status +lwan_request_websocket_upgrade(struct lwan_request *request); + #if defined(__cplusplus) } #endif diff --git a/src/samples/CMakeLists.txt b/src/samples/CMakeLists.txt index 0c2175065..031b2b4f8 100644 --- a/src/samples/CMakeLists.txt +++ b/src/samples/CMakeLists.txt @@ -4,4 +4,5 @@ if (NOT ${CMAKE_BUILD_TYPE} MATCHES "Coverage") add_subdirectory(hello) add_subdirectory(hello-no-meta) add_subdirectory(clock) + add_subdirectory(websocket) endif() diff --git a/src/samples/websocket/CMakeLists.txt b/src/samples/websocket/CMakeLists.txt new file mode 100644 index 000000000..89cad2684 --- /dev/null +++ b/src/samples/websocket/CMakeLists.txt @@ -0,0 +1,8 @@ +add_executable(websocket + main.c +) + +target_link_libraries(websocket + ${LWAN_COMMON_LIBS} + ${ADDITIONAL_LIBRARIES} +) diff --git a/src/samples/websocket/main.c b/src/samples/websocket/main.c new file mode 100644 index 000000000..920862c12 --- /dev/null +++ b/src/samples/websocket/main.c @@ -0,0 +1,80 @@ +/* + * lwan - simple web server + * Copyright (c) 2018 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include + +#include "lwan.h" + +LWAN_HANDLER(ws) +{ + enum lwan_http_status status = lwan_request_websocket_upgrade(request); + + if (status != HTTP_SWITCHING_PROTOCOLS) + return status; + + while (true) { + lwan_strbuf_printf(response->buffer, "Some random integer: %d", rand()); + lwan_response_websocket_write(request); + lwan_request_sleep(request, 1000); + } + + return HTTP_OK; +} + +LWAN_HANDLER(index) +{ + static const char message[] = "\n" + " \n" + " \n" + " \n" + " \n" + "

\n" + " \n" + ""; + + request->response.mime_type = "text/html"; + lwan_strbuf_set_static(response->buffer, message, sizeof(message) - 1); + + return HTTP_OK; +} + +int main(void) +{ + const struct lwan_url_map default_map[] = { + {.prefix = "/ws", .handler = LWAN_HANDLER_REF(ws)}, + {.prefix = "/", .handler = LWAN_HANDLER_REF(index)}, + {}, + }; + struct lwan l; + + lwan_init(&l); + + lwan_set_url_map(&l, default_map); + lwan_main_loop(&l); + + lwan_shutdown(&l); + + return 0; +} From d24295e6a7753daa706d263c6545d823c3132fa4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 9 Nov 2018 08:48:57 -0800 Subject: [PATCH 0934/2505] Do not try parsing Connection header if it's not present --- src/lib/lwan-request.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index bd333ca93..3896c20a9 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -693,6 +693,9 @@ static ALWAYS_INLINE void parse_connection_header(struct lwan_request *request) bool has_keep_alive = false; bool has_close = false; + if (!helper->connection.len) + goto out; + for (const char *p = helper->connection.value; *p; p++) { STRING_SWITCH_L(p) { case MULTICHAR_CONSTANT_L('k','e','e','p'): @@ -713,6 +716,7 @@ static ALWAYS_INLINE void parse_connection_header(struct lwan_request *request) break; } +out: if (LIKELY(!(request->flags & REQUEST_IS_HTTP_1_0))) has_keep_alive = !has_close; From 4fc1428d2a9fd3f12c2c83051c7037f4e148e3c2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 9 Nov 2018 08:52:12 -0800 Subject: [PATCH 0935/2505] Do not zero out helper->header_start for every request That's 512 bytes that don't need to be zeroed out for every request. --- src/lib/lwan-request.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 3896c20a9..2dce2f6aa 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -44,6 +44,8 @@ #include "lwan-io-wrappers.h" #include "sha1.h" +#define N_HEADER_START 64 + enum lwan_read_finalizer { FINALIZER_DONE, FINALIZER_TRY_AGAIN, @@ -79,7 +81,7 @@ struct lwan_request_parser_helper { struct lwan_value connection; /* Connection: */ - char *header_start[64]; /* Headers: n: start, n+1: end */ + char **header_start; /* Headers: n: start, n+1: end */ size_t n_header_start; /* len(header_start) */ time_t error_when_time; /* Time to abort request read */ @@ -522,7 +524,7 @@ static bool parse_headers(struct lwan_request_parser_helper *helper, size_t n_headers = 0; bool ret = false; - for (char *p = buffer + 1; n_headers < N_ELEMENTS(helper->header_start);) { + for (char *p = buffer + 1; n_headers < N_HEADER_START;) { char *next_chr = p; char *next_hdr = memchr(next_chr, '\r', (size_t)(buffer_end - p)); @@ -1242,10 +1244,12 @@ char *lwan_process_request(struct lwan *l, struct lwan_value *buffer, char *next_request) { + char *header_start[N_HEADER_START]; struct lwan_request_parser_helper helper = { .buffer = buffer, .next_request = next_request, .error_when_n_packets = calculate_n_packets(DEFAULT_BUFFER_SIZE), + .header_start = header_start, }; enum lwan_http_status status; struct lwan_url_map *url_map; From 95a8aed7faa6c2441fe23d5fdbe7500e855a7c39 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 10 Nov 2018 10:07:38 -0800 Subject: [PATCH 0936/2505] Add "header" metamethod to request metamethod Allows obtaining any request header from Lua scripts, too. --- src/lib/lwan-lua.c | 8 +++++++- src/lib/lwan-request.c | 14 +++++++------- src/lib/lwan.h | 5 +++-- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index 7c5876724..1dc27dd87 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -73,7 +73,8 @@ LWAN_LUA_METHOD(set_response) } static int request_param_getter(lua_State *L, - const char *(*getter)(struct lwan_request *req, const char *key)) + const char *(*getter)(struct lwan_request *req, + const char *key)) { struct lwan_request *request = userdata_as_request(L); const char *key_str = lua_tostring(L, -1); @@ -87,6 +88,11 @@ static int request_param_getter(lua_State *L, return 1; } +LWAN_LUA_METHOD(header) +{ + return request_param_getter(L, lwan_request_get_header); +} + LWAN_LUA_METHOD(query_param) { return request_param_getter(L, lwan_request_get_query_param); diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 2dce2f6aa..bacd3e60d 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1322,8 +1322,8 @@ value_lookup(const struct lwan_key_value_array *array, const char *key) return NULL; } -const char * -lwan_request_get_query_param(struct lwan_request *request, const char *key) +const char *lwan_request_get_query_param(struct lwan_request *request, + const char *key) { if (!(request->flags & REQUEST_PARSED_QUERY_STRING)) { parse_query_string(request); @@ -1333,8 +1333,8 @@ lwan_request_get_query_param(struct lwan_request *request, const char *key) return value_lookup(&request->query_params, key); } -const char * -lwan_request_get_post_param(struct lwan_request *request, const char *key) +const char *lwan_request_get_post_param(struct lwan_request *request, + const char *key) { if (!(request->flags & REQUEST_PARSED_POST_DATA)) { parse_post_data(request); @@ -1344,8 +1344,8 @@ lwan_request_get_post_param(struct lwan_request *request, const char *key) return value_lookup(&request->post_params, key); } -const char * -lwan_request_get_cookie(struct lwan_request *request, const char *key) +const char *lwan_request_get_cookie(struct lwan_request *request, + const char *key) { if (!(request->flags & REQUEST_PARSED_COOKIES)) { parse_cookies(request); @@ -1355,7 +1355,7 @@ lwan_request_get_cookie(struct lwan_request *request, const char *key) return value_lookup(&request->cookies, key); } -const char *lwan_request_get_header(const struct lwan_request *request, +const char *lwan_request_get_header(struct lwan_request *request, const char *header) { char name[64]; diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 50c0784f7..6b1901303 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -461,6 +461,9 @@ const char *lwan_request_get_query_param(struct lwan_request *request, const char *lwan_request_get_cookie(struct lwan_request *request, const char *key) __attribute__((warn_unused_result, pure)); +const char *lwan_request_get_header(struct lwan_request *request, + const char *header) + __attribute__((warn_unused_result, pure)); void lwan_request_sleep(struct lwan_request *request, uint64_t ms); @@ -527,8 +530,6 @@ const struct lwan_key_value_array * lwan_request_get_query_params(struct lwan_request *request); const struct lwan_key_value_array * lwan_request_get_post_params(struct lwan_request *request); -const char *lwan_request_get_header(const struct lwan_request *request, - const char *header); enum lwan_http_status lwan_request_websocket_upgrade(struct lwan_request *request); From 4c3474fce3516a12753b59bca8391510b54a29bd Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 10 Nov 2018 10:09:24 -0800 Subject: [PATCH 0937/2505] Reindent lwan-lua.c --- src/lib/lwan-lua.c | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index 1dc27dd87..fd155c3b2 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -14,7 +14,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #define _GNU_SOURCE @@ -33,7 +34,9 @@ static const char *request_metatable_name = "Lwan.Request"; static ALWAYS_INLINE struct lwan_request *userdata_as_request(lua_State *L) { - return *((struct lwan_request **)luaL_checkudata(L, 1, request_metatable_name)); + struct lwan_request **r = luaL_checkudata(L, 1, request_metatable_name); + + return *r; } LWAN_LUA_METHOD(say) @@ -42,7 +45,8 @@ LWAN_LUA_METHOD(say) size_t response_str_len; const char *response_str = lua_tolstring(L, -1, &response_str_len); - lwan_strbuf_set_static(request->response.buffer, response_str, response_str_len); + lwan_strbuf_set_static(request->response.buffer, response_str, + response_str_len); lwan_response_send_chunk(request); return 0; @@ -108,8 +112,11 @@ LWAN_LUA_METHOD(cookie) return request_param_getter(L, lwan_request_get_cookie); } -static bool append_key_value(lua_State *L, struct coro *coro, - struct lwan_key_value_array *arr, char *key, int value_index) +static bool append_key_value(lua_State *L, + struct coro *coro, + struct lwan_key_value_array *arr, + char *key, + int value_index) { struct lwan_key_value *kv; @@ -166,7 +173,8 @@ LWAN_LUA_METHOD(set_headers) for (; lua_next(L, value_index) != 0; lua_pop(L, 1)) { if (!lua_isstring(L, nested_value_index)) continue; - if (!append_key_value(L, coro, headers, key, nested_value_index)) + if (!append_key_value(L, coro, headers, key, + nested_value_index)) goto out; } } @@ -249,12 +257,13 @@ lua_State *lwan_lua_create_state(const char *script_file, const char *script) if (script_file) { if (UNLIKELY(luaL_dofile(L, script_file) != 0)) { - lwan_status_error("Error opening Lua script %s: %s", - script_file, lua_tostring(L, -1)); + lwan_status_error("Error opening Lua script %s: %s", script_file, + lua_tostring(L, -1)); goto close_lua_state; } } else if (UNLIKELY(luaL_dostring(L, script) != 0)) { - lwan_status_error("Error evaluating Lua script %s", lua_tostring(L, -1)); + lwan_status_error("Error evaluating Lua script %s", + lua_tostring(L, -1)); goto close_lua_state; } @@ -267,7 +276,8 @@ lua_State *lwan_lua_create_state(const char *script_file, const char *script) void lwan_lua_state_push_request(lua_State *L, struct lwan_request *request) { - struct lwan_request **userdata = lua_newuserdata(L, sizeof(struct lwan_request *)); + struct lwan_request **userdata = + lua_newuserdata(L, sizeof(struct lwan_request *)); *userdata = request; luaL_getmetatable(L, request_metatable_name); From d5571489837391a2874729220e8febda3d08a603 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 11 Nov 2018 09:26:19 -0800 Subject: [PATCH 0938/2505] lwan_request_get_header() isn't pure, remove __attribute__ It modifies memory (by writing a NUL terminator when the header is found), so can't have the pure attribute. --- src/lib/lwan.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 6b1901303..2adcd4c03 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -463,7 +463,7 @@ const char *lwan_request_get_cookie(struct lwan_request *request, __attribute__((warn_unused_result, pure)); const char *lwan_request_get_header(struct lwan_request *request, const char *header) - __attribute__((warn_unused_result, pure)); + __attribute__((warn_unused_result)); void lwan_request_sleep(struct lwan_request *request, uint64_t ms); From 6418f3d182a34878f9f00d4b83b98ee964cb14e2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 10 Nov 2018 13:15:58 -0800 Subject: [PATCH 0939/2505] Expose WebSocket functions to Lua scripts --- src/lib/lwan-lua.c | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index fd155c3b2..5ca6468a8 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -112,6 +112,42 @@ LWAN_LUA_METHOD(cookie) return request_param_getter(L, lwan_request_get_cookie); } +LWAN_LUA_METHOD(ws_upgrade) +{ + struct lwan_request *request = userdata_as_request(L); + enum lwan_http_status status = lwan_request_websocket_upgrade(request); + + lua_pushinteger(L, status); + + return 1; +} + +LWAN_LUA_METHOD(ws_write) +{ + struct lwan_request *request = userdata_as_request(L); + size_t data_len; + const char *data_str = lua_tolstring(L, -1, &data_len); + + lwan_strbuf_set_static(request->response.buffer, data_str, data_len); + lwan_response_websocket_write(request); + + return 0; +} + +LWAN_LUA_METHOD(ws_read) +{ + struct lwan_request *request = userdata_as_request(L); + + if (lwan_response_websocket_read(request)) { + lua_pushlstring(L, lwan_strbuf_get_buffer(request->response.buffer), + lwan_strbuf_get_length(request->response.buffer)); + } else { + lua_pushnil(L); + } + + return 1; +} + static bool append_key_value(lua_State *L, struct coro *coro, struct lwan_key_value_array *arr, From 74accdae2f3eae60182d65e962c491ccdbb97b88 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 26 Nov 2018 05:57:31 -0800 Subject: [PATCH 0940/2505] Fix typo in HTTP response code 101 (protcol -> protocol) --- src/lib/lwan-tables.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index 31a9b1798..0bf5f03c4 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -115,7 +115,7 @@ static const struct { const char *status; const char *description; } status_table[] = { - STATUS(101, "Switching protcols", "Protocol is switching over from HTTP"), + STATUS(101, "Switching protocols", "Protocol is switching over from HTTP"), STATUS(200, "OK", "Success!"), STATUS(206, "Partial content", "Delivering part of requested resource."), STATUS(301, "Moved permanently", "This content has moved to another place."), From 15b618b3aad5826a3f6cce947175b57bc8dc3bd9 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 26 Nov 2018 06:05:14 -0800 Subject: [PATCH 0941/2505] If readv() or recv() wrappers get EAGAIN, ensure EPOLLIN is set --- src/lib/lwan-io-wrappers.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index 792815a01..ee575ecdb 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -89,7 +89,7 @@ lwan_readv(struct lwan_request *request, struct iovec *iov, int iov_count) switch (errno) { case EAGAIN: - request->conn->flags |= CONN_FLIP_FLAGS; + request->conn->flags |= CONN_FLIP_FLAGS | CONN_MUST_READ; /* fallthrough */ case EINTR: goto try_again; @@ -168,7 +168,7 @@ lwan_recv(struct lwan_request *request, void *buf, size_t count, int flags) switch (errno) { case EAGAIN: - request->conn->flags |= CONN_FLIP_FLAGS; + request->conn->flags |= CONN_FLIP_FLAGS | CONN_MUST_READ; /* fallthrough */ case EINTR: goto try_again; From b5d438fb90d2a8b3f18d7593d4783b1ac4a9129d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 2 Dec 2018 02:36:19 -0800 Subject: [PATCH 0942/2505] Consider integer overflow when building string in rewrite module Although user input is passed to append_str(), it's never going to be larger than the whole HTTP request, and that must fit within the stack of a coroutine, so the possibility of overflowing a 64-bit size_t are practically impossible. While not technically necessary in this case, it's cheap to check for overflow, so do it even if for clarity alone. --- src/lib/lwan-mod-rewrite.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index bc475ebfd..a9435ab7c 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -101,13 +101,16 @@ static enum lwan_http_status module_rewrite_as(struct lwan_request *request, return HTTP_OK; } -static bool append_str(struct str_builder *builder, const char *src, - size_t src_len) +static bool +append_str(struct str_builder *builder, const char *src, size_t src_len) { - size_t total_size = builder->len + src_len; + size_t total_size; char *dest; - if (total_size >= builder->size) + if (UNLIKELY(__builtin_add_overflow(builder->len, src_len, &total_size))) + return false; + + if (UNLIKELY(total_size >= builder->size)) return false; dest = mempcpy(builder->buffer + builder->len, src, src_len); From 97ae0cfa0d04ffa35ff0cecd4ddd12963c95dc13 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 8 Dec 2018 06:20:38 -0800 Subject: [PATCH 0943/2505] Simplify get_handle_prefix() by using a lookup table --- src/lib/lwan-mod-lua.c | 45 +++++++++++++++++------------------------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/src/lib/lwan-mod-lua.c b/src/lib/lwan-mod-lua.c index d9bf12c8a..cd89bc710 100644 --- a/src/lib/lwan-mod-lua.c +++ b/src/lib/lwan-mod-lua.c @@ -99,39 +99,30 @@ static void unref_thread(void *data1, void *data2) luaL_unref(L, LUA_REGISTRYINDEX, thread_ref); } -static ALWAYS_INLINE const char *get_handle_prefix(struct lwan_request *request, - size_t *len) +static ALWAYS_INLINE struct lwan_value +get_handle_prefix(struct lwan_request *request) { - switch (lwan_request_get_method(request)) { - case REQUEST_METHOD_GET: - *len = sizeof("handle_get_"); - return "handle_get_"; - case REQUEST_METHOD_POST: - *len = sizeof("handle_post_"); - return "handle_post_"; - case REQUEST_METHOD_HEAD: - *len = sizeof("handle_head_"); - return "handle_head_"; - case REQUEST_METHOD_OPTIONS: - *len = sizeof("handle_options_"); - return "handle_options_"; - case REQUEST_METHOD_DELETE: - *len = sizeof("handle_delete_"); - return "handle_delete_"; - default: - return NULL; - } + static const struct lwan_value method2name[REQUEST_METHOD_MASK] = { +#define M(s) {.value = s, .len = sizeof(s) - 1} + [REQUEST_METHOD_GET] = M("handle_get_"), + [REQUEST_METHOD_POST] = M("handle_post_"), + [REQUEST_METHOD_HEAD] = M("handle_head_"), + [REQUEST_METHOD_OPTIONS] = M("handle_options_"), + [REQUEST_METHOD_DELETE] = M("handle_delete_"), +#undef M + }; + + return method2name[lwan_request_get_method(request)]; } static bool get_handler_function(lua_State *L, struct lwan_request *request) { char handler_name[128]; - size_t handle_prefix_len; - const char *handle_prefix = get_handle_prefix(request, &handle_prefix_len); + struct lwan_value handle_prefix = get_handle_prefix(request); - if (UNLIKELY(!handle_prefix)) + if (UNLIKELY(!handle_prefix.len)) return false; - if (UNLIKELY(request->url.len >= sizeof(handler_name) - handle_prefix_len)) + if (UNLIKELY(request->url.len >= sizeof(handler_name) - handle_prefix.len)) return false; char *url; @@ -152,10 +143,10 @@ static bool get_handler_function(lua_State *L, struct lwan_request *request) url_len = 4; } - if (UNLIKELY((handle_prefix_len + url_len + 1) > sizeof(handler_name))) + if (UNLIKELY((handle_prefix.len + url_len + 1) > sizeof(handler_name))) return false; - char *method_name = mempcpy(handler_name, handle_prefix, handle_prefix_len); + char *method_name = mempcpy(handler_name, handle_prefix.value, handle_prefix.len); memcpy(method_name - 1, url, url_len + 1); lua_getglobal(L, handler_name); From 7b812c9ac82cedfaf976cedbbe5ffefda57d5670 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 8 Dec 2018 06:20:53 -0800 Subject: [PATCH 0944/2505] Weekdays and Months are always 3-characters long Specify the precision in the formatting string. --- src/lib/lwan-time.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-time.c b/src/lib/lwan-time.c index e85af947c..86eb61a0b 100644 --- a/src/lib/lwan-time.c +++ b/src/lib/lwan-time.c @@ -131,9 +131,9 @@ int lwan_format_rfc_time(const time_t in, char out[static 30]) weekday = weekdays + tm.tm_wday * 3; month = months + tm.tm_mon * 3; - r = snprintf(out, 30, "%.*s, %02d %.*s %04d %02d:%02d:%02d GMT", - 3, weekday, tm.tm_mday, 3, month, - tm.tm_year + 1900, tm.tm_hour, tm.tm_min, tm.tm_sec); + r = snprintf(out, 30, "%.3s, %02d %.3s %04d %02d:%02d:%02d GMT", weekday, + tm.tm_mday, month, tm.tm_year + 1900, tm.tm_hour, tm.tm_min, + tm.tm_sec); if (UNLIKELY(r < 0 || r > 30)) return -EINVAL; From 7e212b1d05ef26a08feec877f7b8a50583614487 Mon Sep 17 00:00:00 2001 From: Tomohito Nakayama Date: Sun, 16 Dec 2018 20:23:48 +0900 Subject: [PATCH 0945/2505] Fix build on Mac due to lack of be64toh() --- src/lib/lwan-response.c | 1 + src/lib/missing/endian.h | 123 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 src/lib/missing/endian.h diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index f12890616..8d917abc9 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -25,6 +25,7 @@ #include #include #include +#include #include "lwan-private.h" diff --git a/src/lib/missing/endian.h b/src/lib/missing/endian.h new file mode 100644 index 000000000..ec7697c69 --- /dev/null +++ b/src/lib/missing/endian.h @@ -0,0 +1,123 @@ +// This file originates to a work of Mathias Panzenböck, portable_endian.h, of +// which he puts in public domain. +// +// License text was as next. +// +// "License": Public Domain +// I, Mathias Panzenböck, place this file hereby into the public domain. Use +// it at your own risk for whatever you like. In case there are jurisdictions +// that don't support putting things in the public domain you can also consider +// it to be "dual licensed" under the BSD, MIT and Apache licenses, if you want +// to. This code is trivial anyway. Consider it an example on how to get the +// endian conversion functions on different platforms. + +#pragma once + +#if (defined(_WIN16) || defined(_WIN32) || defined(_WIN64)) && \ + !defined(__WINDOWS__) + +#define __WINDOWS__ + +#endif + +#if defined(__linux__) || defined(__CYGWIN__) + +#include_next + +#elif defined(__APPLE__) + +#include + +#define htobe16(x) OSSwapHostToBigInt16(x) +#define htole16(x) OSSwapHostToLittleInt16(x) +#define be16toh(x) OSSwapBigToHostInt16(x) +#define le16toh(x) OSSwapLittleToHostInt16(x) + +#define htobe32(x) OSSwapHostToBigInt32(x) +#define htole32(x) OSSwapHostToLittleInt32(x) +#define be32toh(x) OSSwapBigToHostInt32(x) +#define le32toh(x) OSSwapLittleToHostInt32(x) + +#define htobe64(x) OSSwapHostToBigInt64(x) +#define htole64(x) OSSwapHostToLittleInt64(x) +#define be64toh(x) OSSwapBigToHostInt64(x) +#define le64toh(x) OSSwapLittleToHostInt64(x) + +#define __BYTE_ORDER BYTE_ORDER +#define __BIG_ENDIAN BIG_ENDIAN +#define __LITTLE_ENDIAN LITTLE_ENDIAN +#define __PDP_ENDIAN PDP_ENDIAN + +#elif defined(__OpenBSD__) + +#include + +#elif defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) + +#include + +#define be16toh(x) betoh16(x) +#define le16toh(x) letoh16(x) + +#define be32toh(x) betoh32(x) +#define le32toh(x) letoh32(x) + +#define be64toh(x) betoh64(x) +#define le64toh(x) letoh64(x) + +#elif defined(__WINDOWS__) + +#include +#include + +#if BYTE_ORDER == LITTLE_ENDIAN + +#define htobe16(x) htons(x) +#define htole16(x) (x) +#define be16toh(x) ntohs(x) +#define le16toh(x) (x) + +#define htobe32(x) htonl(x) +#define htole32(x) (x) +#define be32toh(x) ntohl(x) +#define le32toh(x) (x) + +#define htobe64(x) htonll(x) +#define htole64(x) (x) +#define be64toh(x) ntohll(x) +#define le64toh(x) (x) + +#elif BYTE_ORDER == BIG_ENDIAN + +/* that would be xbox 360 */ +#define htobe16(x) (x) +#define htole16(x) __builtin_bswap16(x) +#define be16toh(x) (x) +#define le16toh(x) __builtin_bswap16(x) + +#define htobe32(x) (x) +#define htole32(x) __builtin_bswap32(x) +#define be32toh(x) (x) +#define le32toh(x) __builtin_bswap32(x) + +#define htobe64(x) (x) +#define htole64(x) __builtin_bswap64(x) +#define be64toh(x) (x) +#define le64toh(x) __builtin_bswap64(x) + +#else + +#error byte order not supported + +#endif + +#define __BYTE_ORDER BYTE_ORDER +#define __BIG_ENDIAN BIG_ENDIAN +#define __LITTLE_ENDIAN LITTLE_ENDIAN +#define __PDP_ENDIAN PDP_ENDIAN + +#else + +#error platform not supported + +#endif From d6b02b19779204291d6b9dafa7acc49fe484be92 Mon Sep 17 00:00:00 2001 From: halosghost Date: Thu, 3 Jan 2019 18:34:13 -0600 Subject: [PATCH 0946/2505] Fix errant Libs line for pkg-config file --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b7b7d09d5..c8e8f354b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,7 +42,7 @@ foreach (pc_file luajit lua lua51 lua5.1 lua-5.1) pkg_check_modules(LUA ${pc_file}>=5.1.0 ${pc_file}<=5.1.999) endif () if (LUA_FOUND) - list(APPEND ADDITIONAL_LIBRARIES "-l${LUA_LIBRARIES} ${LUA_LDFLAGS}") + list(APPEND ADDITIONAL_LIBRARIES "${LUA_LDFLAGS}") include_directories(${LUA_INCLUDE_DIRS}) break() endif() From 74a47f983365baf11ce399398cb2a61d2682142b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 28 Dec 2018 08:26:26 -0800 Subject: [PATCH 0947/2505] Fix build on OpenBSD We really need an OpenBSD build bot. --- src/lib/lwan-io-wrappers.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index ee575ecdb..7584b380f 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -283,8 +283,11 @@ lwan_sendfile(struct lwan_request *request, int in_fd, off_t offset, size_t coun #else static inline size_t min_size(size_t a, size_t b) { return (a > b) ? b : a; } -static off_t -try_pread(struct coro *coro, int fd, void *buffer, size_t len, off_t offset) +static off_t try_pread(struct lwan_request *request, + int fd, + void *buffer, + size_t len, + off_t offset) { ssize_t total_read = 0; @@ -310,11 +313,11 @@ try_pread(struct coro *coro, int fd, void *buffer, size_t len, off_t offset) if ((size_t)total_read == len) return offset; try_again: - coro_yield(coro, CONN_CORO_MAY_RESUME); + coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); } out: - coro_yield(coro, CONN_CORO_ABORT); + coro_yield(request->conn->coro, CONN_CORO_ABORT); __builtin_unreachable(); } @@ -332,7 +335,7 @@ void lwan_sendfile(struct lwan_request *request, while (count) { size_t to_read = min_size(count, sizeof(buffer)); - offset = try_pread(request->conn->coro, in_fd, buffer, to_read, offset); + offset = try_pread(request, in_fd, buffer, to_read, offset); lwan_send(request, buffer, to_read, 0); count -= to_read; } From 4b83cfeed3f420e1b80eb3d4edc7804f036c80e2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 28 Dec 2018 08:27:33 -0800 Subject: [PATCH 0948/2505] Update symbol filter script: remove unneeded symbols, add new ones --- src/lib/liblwan.sym | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/liblwan.sym b/src/lib/liblwan.sym index fb77a8a15..7c3e5a01c 100644 --- a/src/lib/liblwan.sym +++ b/src/lib/liblwan.sym @@ -22,7 +22,6 @@ global: lwan_main_loop; - lwan_process_request; lwan_request_get_cookie; lwan_request_get_post_param; lwan_request_get_query_param; @@ -62,6 +61,8 @@ global: lwan_writev; lwan_send; lwan_sendfile; + lwan_recv; + lwan_readv; lwan_strbuf_append_char; lwan_strbuf_append_printf; From b3c81af09a6b2331307b0af07c06a96f62b39d41 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 28 Dec 2018 08:31:47 -0800 Subject: [PATCH 0949/2505] Batch reads in readahead thread --- src/lib/lwan-readahead.c | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/lib/lwan-readahead.c b/src/lib/lwan-readahead.c index 24671d4c7..4f2b7032f 100644 --- a/src/lib/lwan-readahead.c +++ b/src/lib/lwan-readahead.c @@ -104,8 +104,9 @@ static void *lwan_readahead_loop(void *data __attribute__((unused))) lwan_set_thread_name("readahead"); while (true) { - struct lwan_readahead_cmd cmd; - ssize_t n_bytes = read(readahead_pipe_fd[0], &cmd, sizeof(cmd)); + struct lwan_readahead_cmd cmd[16]; + ssize_t n_bytes = read(readahead_pipe_fd[0], cmd, sizeof(cmd)); + ssize_t cmds; if (UNLIKELY(n_bytes < 0)) { if (errno == EAGAIN || errno == EINTR) @@ -113,21 +114,27 @@ static void *lwan_readahead_loop(void *data __attribute__((unused))) lwan_status_perror("Ignoring error while reading from pipe (%d)", readahead_pipe_fd[0]); continue; - } else if (UNLIKELY(n_bytes != (ssize_t)sizeof(cmd))) { - lwan_status_debug( - "Ignoring incomplete command for readahead thread"); + } else if (UNLIKELY(n_bytes % (ssize_t)sizeof(cmd[0]))) { + lwan_status_warning("Ignoring readahead packet read of %zd bytes", + n_bytes); continue; } - switch (cmd.cmd) { - case READAHEAD: - readahead(cmd.readahead.fd, cmd.readahead.off, cmd.readahead.size); - break; - case MADVISE: - madvise(cmd.madvise.addr, cmd.madvise.length, MADV_WILLNEED); - break; - case SHUTDOWN: - goto out; + cmds = n_bytes / (ssize_t)sizeof(struct lwan_readahead_cmd); + for (ssize_t i = 0; i < cmds; i++) { + switch (cmd[i].cmd) { + case READAHEAD: + readahead(cmd[i].readahead.fd, cmd[i].readahead.off, + cmd[i].readahead.size); + break; + case MADVISE: + madvise(cmd[i].madvise.addr, cmd[i].madvise.length, + MADV_WILLNEED); + mlock(cmd[i].madvise.addr, cmd[i].madvise.length); + break; + case SHUTDOWN: + goto out; + } } } From a701b92618d6d71efdf4c36dc2e46ae44e1492ce Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 28 Dec 2018 08:32:52 -0800 Subject: [PATCH 0950/2505] lwan_strbuf_append_printf() should have printf attribute --- src/lib/lwan-strbuf.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-strbuf.h b/src/lib/lwan-strbuf.h index ecee5d853..927f29142 100644 --- a/src/lib/lwan-strbuf.h +++ b/src/lib/lwan-strbuf.h @@ -43,7 +43,8 @@ void lwan_strbuf_reset(struct lwan_strbuf *s); bool lwan_strbuf_append_char(struct lwan_strbuf *s, const char c); bool lwan_strbuf_append_str(struct lwan_strbuf *s1, const char *s2, size_t sz); -bool lwan_strbuf_append_printf(struct lwan_strbuf *s, const char *fmt, ...); +bool lwan_strbuf_append_printf(struct lwan_strbuf *s, const char *fmt, ...) + __attribute__((format(printf, 2, 3))); bool lwan_strbuf_set_static(struct lwan_strbuf *s1, const char *s2, size_t sz); bool lwan_strbuf_set(struct lwan_strbuf *s1, const char *s2, size_t sz); From fc5681130aeecab3679695cfdd1e8f0e2aad9616 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 5 Jan 2019 19:44:28 -0800 Subject: [PATCH 0951/2505] Move dq to its own file This is in preparation to having an alternative worker thread implementation, so that dq can be shared among them. --- src/lib/CMakeLists.txt | 1 + src/lib/lwan-dq.c | 128 +++++++++++++++++++++++++++++++++++++++++ src/lib/lwan-dq.h | 44 ++++++++++++++ src/lib/lwan-thread.c | 121 +------------------------------------- 4 files changed, 176 insertions(+), 118 deletions(-) create mode 100644 src/lib/lwan-dq.c create mode 100644 src/lib/lwan-dq.h diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index b3238ee3e..0a34e6ce2 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -41,6 +41,7 @@ set(SOURCES sd-daemon.c timeout.c sha1.c + lwan-dq.c ) if (HAVE_LUA) diff --git a/src/lib/lwan-dq.c b/src/lib/lwan-dq.c new file mode 100644 index 000000000..8aa810a06 --- /dev/null +++ b/src/lib/lwan-dq.c @@ -0,0 +1,128 @@ +/* + * lwan - simple web server + * Copyright (c) 2019 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include + +#include "lwan-private.h" +#include "lwan-dq.h" + +static inline int death_queue_node_to_idx(struct death_queue *dq, + struct lwan_connection *conn) +{ + return (conn == &dq->head) ? -1 : (int)(ptrdiff_t)(conn - dq->conns); +} + +static inline struct lwan_connection * +death_queue_idx_to_node(struct death_queue *dq, int idx) +{ + return (idx < 0) ? &dq->head : &dq->conns[idx]; +} + +void death_queue_insert(struct death_queue *dq, + struct lwan_connection *new_node) +{ + new_node->next = -1; + new_node->prev = dq->head.prev; + struct lwan_connection *prev = death_queue_idx_to_node(dq, dq->head.prev); + dq->head.prev = prev->next = death_queue_node_to_idx(dq, new_node); +} + +static void death_queue_remove(struct death_queue *dq, + struct lwan_connection *node) +{ + struct lwan_connection *prev = death_queue_idx_to_node(dq, node->prev); + struct lwan_connection *next = death_queue_idx_to_node(dq, node->next); + + next->prev = node->prev; + prev->next = node->next; + + node->next = node->prev = -1; +} + +bool death_queue_empty(struct death_queue *dq) { return dq->head.next < 0; } + +void death_queue_move_to_last(struct death_queue *dq, + struct lwan_connection *conn) +{ + /* + * If the connection isn't keep alive, it might have a coroutine that + * should be resumed. If that's the case, schedule for this request to + * die according to the keep alive timeout. + * + * If it's not a keep alive connection, or the coroutine shouldn't be + * resumed -- then just mark it to be reaped right away. + */ + conn->time_to_die = dq->time; + if (conn->flags & (CONN_KEEP_ALIVE | CONN_SHOULD_RESUME_CORO)) + conn->time_to_die += dq->keep_alive_timeout; + + death_queue_remove(dq, conn); + death_queue_insert(dq, conn); +} + +void death_queue_init(struct death_queue *dq, const struct lwan *lwan) +{ + dq->lwan = lwan; + dq->conns = lwan->conns; + dq->time = 0; + dq->keep_alive_timeout = lwan->config.keep_alive_timeout; + dq->head.next = dq->head.prev = -1; + dq->timeout = (struct timeout){}; +} + +void death_queue_kill(struct death_queue *dq, struct lwan_connection *conn) +{ + death_queue_remove(dq, conn); + if (LIKELY(conn->coro)) { + coro_free(conn->coro); + conn->coro = NULL; + } + if (conn->flags & CONN_IS_ALIVE) { + conn->flags &= ~CONN_IS_ALIVE; + close(lwan_connection_get_fd(dq->lwan, conn)); + } +} + +void death_queue_kill_waiting(struct death_queue *dq) +{ + dq->time++; + + while (!death_queue_empty(dq)) { + struct lwan_connection *conn = + death_queue_idx_to_node(dq, dq->head.next); + + if (conn->time_to_die > dq->time) + return; + + death_queue_kill(dq, conn); + } + + /* Death queue exhausted: reset epoch */ + dq->time = 0; +} + +void death_queue_kill_all(struct death_queue *dq) +{ + while (!death_queue_empty(dq)) { + struct lwan_connection *conn = + death_queue_idx_to_node(dq, dq->head.next); + death_queue_kill(dq, conn); + } +} diff --git a/src/lib/lwan-dq.h b/src/lib/lwan-dq.h new file mode 100644 index 000000000..895f97520 --- /dev/null +++ b/src/lib/lwan-dq.h @@ -0,0 +1,44 @@ +/* + * lwan - simple web server + * Copyright (c) 2019 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#pragma once + +struct death_queue { + const struct lwan *lwan; + struct lwan_connection *conns; + struct lwan_connection head; + struct timeout timeout; + unsigned time; + unsigned short keep_alive_timeout; +}; + +void death_queue_init(struct death_queue *dq, const struct lwan *l); + +void death_queue_insert(struct death_queue *dq, + struct lwan_connection *new_node); +void death_queue_kill(struct death_queue *dq, struct lwan_connection *node); +void death_queue_move_to_last(struct death_queue *dq, + struct lwan_connection *conn); + +void death_queue_kill_waiting(struct death_queue *dq); +void death_queue_kill_all(struct death_queue *dq); + +bool death_queue_empty(struct death_queue *dq); + diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index d5cb36fc7..63567fcfc 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -34,102 +34,14 @@ #endif #include "lwan-private.h" +#include "lwan-dq.h" #include "list.h" -struct death_queue { - const struct lwan *lwan; - struct lwan_connection *conns; - struct lwan_connection head; - struct timeout timeout; - unsigned time; - unsigned short keep_alive_timeout; -}; - static const uint32_t events_by_write_flag[] = { EPOLLOUT | EPOLLRDHUP | EPOLLERR, EPOLLIN | EPOLLRDHUP | EPOLLERR }; -static inline int death_queue_node_to_idx(struct death_queue *dq, - struct lwan_connection *conn) -{ - return (conn == &dq->head) ? -1 : (int)(ptrdiff_t)(conn - dq->conns); -} - -static inline struct lwan_connection * -death_queue_idx_to_node(struct death_queue *dq, int idx) -{ - return (idx < 0) ? &dq->head : &dq->conns[idx]; -} - -static void death_queue_insert(struct death_queue *dq, - struct lwan_connection *new_node) -{ - new_node->next = -1; - new_node->prev = dq->head.prev; - struct lwan_connection *prev = death_queue_idx_to_node(dq, dq->head.prev); - dq->head.prev = prev->next = death_queue_node_to_idx(dq, new_node); -} - -static void death_queue_remove(struct death_queue *dq, - struct lwan_connection *node) -{ - struct lwan_connection *prev = death_queue_idx_to_node(dq, node->prev); - struct lwan_connection *next = death_queue_idx_to_node(dq, node->next); - next->prev = node->prev; - prev->next = node->next; - - node->next = node->prev = -1; -} - -static bool death_queue_empty(struct death_queue *dq) -{ - return dq->head.next < 0; -} - -static void death_queue_move_to_last(struct death_queue *dq, - struct lwan_connection *conn) -{ - /* - * If the connection isn't keep alive, it might have a coroutine that - * should be resumed. If that's the case, schedule for this request to - * die according to the keep alive timeout. - * - * If it's not a keep alive connection, or the coroutine shouldn't be - * resumed -- then just mark it to be reaped right away. - */ - conn->time_to_die = dq->time; - if (conn->flags & (CONN_KEEP_ALIVE | CONN_SHOULD_RESUME_CORO)) - conn->time_to_die += dq->keep_alive_timeout; - - death_queue_remove(dq, conn); - death_queue_insert(dq, conn); -} - -static void death_queue_init(struct death_queue *dq, const struct lwan *lwan) -{ - dq->lwan = lwan; - dq->conns = lwan->conns; - dq->time = 0; - dq->keep_alive_timeout = lwan->config.keep_alive_timeout; - dq->head.next = dq->head.prev = -1; - dq->timeout = (struct timeout) {}; -} - -static ALWAYS_INLINE void destroy_coro(struct death_queue *dq, - struct lwan_connection *conn) -{ - death_queue_remove(dq, conn); - if (LIKELY(conn->coro)) { - coro_free(conn->coro); - conn->coro = NULL; - } - if (conn->flags & CONN_IS_ALIVE) { - conn->flags &= ~CONN_IS_ALIVE; - close(lwan_connection_get_fd(dq->lwan, conn)); - } -} - static ALWAYS_INLINE int min(const int a, const int b) { return a < b ? a : b; } #define REQUEST_FLAG(bool_, name_) \ @@ -247,7 +159,7 @@ static ALWAYS_INLINE void resume_coro_if_needed(struct death_queue *dq, enum lwan_connection_coro_yield yield_result = coro_resume(conn->coro); /* CONN_CORO_ABORT is -1, but comparing with 0 is cheaper */ if (UNLIKELY(yield_result < CONN_CORO_MAY_RESUME)) { - destroy_coro(dq, conn); + death_queue_kill(dq, conn); return; } @@ -257,33 +169,6 @@ static ALWAYS_INLINE void resume_coro_if_needed(struct death_queue *dq, } } -static void death_queue_kill_waiting(struct death_queue *dq) -{ - dq->time++; - - while (!death_queue_empty(dq)) { - struct lwan_connection *conn = - death_queue_idx_to_node(dq, dq->head.next); - - if (conn->time_to_die > dq->time) - return; - - destroy_coro(dq, conn); - } - - /* Death queue exhausted: reset epoch */ - dq->time = 0; -} - -static void death_queue_kill_all(struct death_queue *dq) -{ - while (!death_queue_empty(dq)) { - struct lwan_connection *conn = - death_queue_idx_to_node(dq, dq->head.next); - destroy_coro(dq, conn); - } -} - static void update_date_cache(struct lwan_thread *thread) { time_t now = time(NULL); @@ -466,7 +351,7 @@ static void *thread_io_loop(void *data) conn = event->data.ptr; if (UNLIKELY(event->events & (EPOLLRDHUP | EPOLLHUP))) { - destroy_coro(&dq, conn); + death_queue_kill(&dq, conn); continue; } From 78748786cb2f48e4a99e4f9c7489bfc979e74d6e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 10 Jan 2019 08:20:30 -0800 Subject: [PATCH 0952/2505] If 'noexecstack' linker option is available, use it This should be the default, but make it explicit. --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index c8e8f354b..be17fb14b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -188,6 +188,8 @@ else () HAVE_READ_ONLY_GOT) enable_c_flag_if_avail(-fno-plt CMAKE_C_FLAGS HAVE_NO_PLT) + enable_c_flag_if_avail(-Wl,-z,noexecstack CMAKE_EXE_LINKER_FLAGS + HAVE_NOEXEC_STACK) endif () From 1aecf1bebc264e5ec21a585e86d69326deaa48fd Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 10 Jan 2019 08:21:17 -0800 Subject: [PATCH 0953/2505] Clean up template::apply() by renaming dispatching macros --- src/lib/lwan-template.c | 46 ++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index 4a2274829..0c8dfbeb1 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -1343,7 +1343,7 @@ static struct chunk *apply(struct lwan_tpl *tpl, [ACTION_APPLY_TPL] = &&action_apply_tpl, [ACTION_START_ITER] = &&action_start_iter, [ACTION_END_ITER] = &&action_end_iter, - [ACTION_LAST] = &&finalize + [ACTION_LAST] = &&finalize, }; struct coro_switcher switcher; struct coro *coro = NULL; @@ -1352,41 +1352,41 @@ static struct chunk *apply(struct lwan_tpl *tpl, if (UNLIKELY(!chunk)) return NULL; -#define DISPATCH() \ +#define DISPATCH_ACTION() \ do { \ goto *dispatch_table[chunk->action]; \ } while (false) -#define NEXT_ACTION() \ +#define DISPATCH_NEXT_ACTION() \ do { \ chunk++; \ - DISPATCH(); \ + DISPATCH_ACTION(); \ } while (false) - DISPATCH(); + DISPATCH_ACTION(); action_append: lwan_strbuf_append_str(buf, lwan_strbuf_get_buffer(chunk->data), lwan_strbuf_get_length(chunk->data)); - NEXT_ACTION(); + DISPATCH_NEXT_ACTION(); action_append_char: lwan_strbuf_append_char(buf, (char)(uintptr_t)chunk->data); - NEXT_ACTION(); + DISPATCH_NEXT_ACTION(); action_variable: { struct lwan_var_descriptor *descriptor = chunk->data; descriptor->append_to_strbuf(buf, (char *)variables + descriptor->offset); - NEXT_ACTION(); + DISPATCH_NEXT_ACTION(); } action_variable_str: lwan_append_str_to_strbuf(buf, (char *)variables + (uintptr_t)chunk->data); - NEXT_ACTION(); + DISPATCH_NEXT_ACTION(); action_variable_str_escape: lwan_append_str_escaped_to_strbuf(buf, (char *)variables + - (uintptr_t)chunk->data); - NEXT_ACTION(); + (uintptr_t)chunk->data); + DISPATCH_NEXT_ACTION(); action_if_variable_not_empty: { struct chunk_descriptor *cd = chunk->data; @@ -1399,26 +1399,26 @@ action_if_variable_not_empty: { } else { chunk = apply(tpl, chunk + 1, buf, variables, cd->chunk); } - NEXT_ACTION(); + DISPATCH_NEXT_ACTION(); } action_end_if_variable_not_empty: if (LIKELY(data == chunk)) goto finalize; - NEXT_ACTION(); + DISPATCH_NEXT_ACTION(); action_apply_tpl: { struct lwan_strbuf *tmp = lwan_tpl_apply(chunk->data, variables); lwan_strbuf_append_str(buf, lwan_strbuf_get_buffer(tmp), lwan_strbuf_get_length(tmp)); lwan_strbuf_free(tmp); - NEXT_ACTION(); + DISPATCH_NEXT_ACTION(); } action_start_iter: if (UNLIKELY(coro != NULL)) { lwan_status_warning("Coroutine is not NULL when starting iteration"); - NEXT_ACTION(); + DISPATCH_NEXT_ACTION(); } struct chunk_descriptor *cd = chunk->data; @@ -1438,12 +1438,12 @@ action_apply_tpl: { coro = NULL; if (negate) - DISPATCH(); - NEXT_ACTION(); + DISPATCH_ACTION(); + DISPATCH_NEXT_ACTION(); } chunk = apply(tpl, chunk + 1, buf, variables, chunk); - DISPATCH(); + DISPATCH_ACTION(); action_end_iter: if (data == chunk->data) @@ -1452,23 +1452,23 @@ action_apply_tpl: { if (UNLIKELY(!coro)) { if (!chunk->flags) lwan_status_warning("Coroutine is NULL when finishing iteration"); - NEXT_ACTION(); + DISPATCH_NEXT_ACTION(); } if (!coro_resume_value(coro, 0)) { coro_free(coro); coro = NULL; - NEXT_ACTION(); + DISPATCH_NEXT_ACTION(); } chunk = apply(tpl, ((struct chunk *)chunk->data) + 1, buf, variables, chunk->data); - DISPATCH(); + DISPATCH_ACTION(); finalize: return chunk; -#undef DISPATCH -#undef NEXT_ACTION +#undef DISPATCH_ACTION +#undef DISPATCH_NEXT_ACTION } bool lwan_tpl_apply_with_buffer(struct lwan_tpl *tpl, From 50589a4cd3c06cbffd98875e02de899da27ae7f9 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 10 Jan 2019 08:22:14 -0800 Subject: [PATCH 0954/2505] add_url_map() never fails, so no need to null-check its return value --- src/lib/lwan.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 9fedce382..b9c89bfcb 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -273,9 +273,6 @@ void lwan_set_url_map(struct lwan *l, const struct lwan_url_map *map) for (; map->prefix; map++) { struct lwan_url_map *copy = add_url_map(&l->url_map_trie, NULL, map); - if (UNLIKELY(!copy)) - continue; - if (copy->module && copy->module->create) { copy->data = copy->module->create (map->prefix, copy->args); copy->flags = copy->module->flags; From 187f528e9b733c3f0d4fd02f76b809ba0e54ec94 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 10 Jan 2019 08:23:07 -0800 Subject: [PATCH 0955/2505] Remove periods from HTTP code descriptions No need for those to be there. --- src/lib/lwan-tables.c | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index 0bf5f03c4..39c320f85 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -116,25 +116,25 @@ static const struct { const char *description; } status_table[] = { STATUS(101, "Switching protocols", "Protocol is switching over from HTTP"), - STATUS(200, "OK", "Success!"), - STATUS(206, "Partial content", "Delivering part of requested resource."), - STATUS(301, "Moved permanently", "This content has moved to another place."), - STATUS(304, "Not modified", "The content has not changed since previous request."), - STATUS(307, "Temporary Redirect", "This content can be temporarily found at a different location."), - STATUS(400, "Bad request", "The client has issued a bad request."), - STATUS(401, "Not authorized", "Client has no authorization to access this resource."), - STATUS(403, "Forbidden", "Access to this resource has been denied."), - STATUS(404, "Not found", "The requested resource could not be found on this server."), - STATUS(405, "Not allowed", "The requested method is not allowed by this server."), - STATUS(408, "Request timeout", "Client did not produce a request within expected timeframe."), - STATUS(413, "Request too large", "The request entity is too large."), - STATUS(416, "Requested range unsatisfiable", "The server can't supply the requested portion of the requested resource."), - STATUS(418, "I'm a teapot", "Client requested to brew coffee but device is a teapot."), - STATUS(420, "Client too high", "Client is too high to make a request."), - STATUS(500, "Internal server error", "The server encountered an internal error that couldn't be recovered from."), - STATUS(501, "Not implemented", "Server lacks the ability to fulfil the request."), - STATUS(503, "Service unavailable", "The server is either overloaded or down for maintenance."), - STATUS(520, "Server too high", "The server is too high to answer the request."), + STATUS(200, "OK", "Success"), + STATUS(206, "Partial content", "Delivering part of requested resource"), + STATUS(301, "Moved permanently", "This content has moved to another place"), + STATUS(304, "Not modified", "The content has not changed since previous request"), + STATUS(307, "Temporary Redirect", "This content can be temporarily found at a different location"), + STATUS(400, "Bad request", "The client has issued a bad request"), + STATUS(401, "Not authorized", "Client has no authorization to access this resource"), + STATUS(403, "Forbidden", "Access to this resource has been denied"), + STATUS(404, "Not found", "The requested resource could not be found on this server"), + STATUS(405, "Not allowed", "The requested method is not allowed by this server"), + STATUS(408, "Request timeout", "Client did not produce a request within expected timeframe"), + STATUS(413, "Request too large", "The request entity is too large"), + STATUS(416, "Requested range unsatisfiable", "The server can't supply the requested portion of the requested resource"), + STATUS(418, "I'm a teapot", "Client requested to brew coffee but device is a teapot"), + STATUS(420, "Client too high", "Client is too high to make a request"), + STATUS(500, "Internal server error", "The server encountered an internal error that couldn't be recovered from"), + STATUS(501, "Not implemented", "Server lacks the ability to fulfil the request"), + STATUS(503, "Service unavailable", "The server is either overloaded or down for maintenance"), + STATUS(520, "Server too high", "The server is too high to answer the request"), }; #undef STATUS From 4e3ea58457f1bb94a09c4116c24586a2ce6aea44 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 16 Jan 2019 21:41:07 -0800 Subject: [PATCH 0956/2505] Always allocate file cache entries with the same size --- src/lib/lwan-mod-serve-files.c | 58 ++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 431bb1075..59f29fcf2 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -74,8 +74,7 @@ struct cache_funcs { struct serve_files_priv *priv, const char *full_path, struct stat *st); - void (*free)(void *data); - size_t struct_size; + void (*free)(struct file_cache_entry *ce); }; struct mmap_cache_data { @@ -111,6 +110,13 @@ struct file_cache_entry { const char *mime_type; const struct cache_funcs *funcs; + + union { + struct mmap_cache_data mmap_cache_data; + struct sendfile_cache_data sendfile_cache_data; + struct dir_list_cache_data dir_list_cache_data; + struct redir_cache_data redir_cache_data; + }; }; struct file_list { @@ -137,7 +143,7 @@ static bool mmap_init(struct file_cache_entry *ce, struct serve_files_priv *priv, const char *full_path, struct stat *st); -static void mmap_free(void *data); +static void mmap_free(struct file_cache_entry *ce); static enum lwan_http_status mmap_serve(struct lwan_request *request, void *data); @@ -145,7 +151,7 @@ static bool sendfile_init(struct file_cache_entry *ce, struct serve_files_priv *priv, const char *full_path, struct stat *st); -static void sendfile_free(void *data); +static void sendfile_free(struct file_cache_entry *ce); static enum lwan_http_status sendfile_serve(struct lwan_request *request, void *data); @@ -153,7 +159,7 @@ static bool dirlist_init(struct file_cache_entry *ce, struct serve_files_priv *priv, const char *full_path, struct stat *st); -static void dirlist_free(void *data); +static void dirlist_free(struct file_cache_entry *ce); static enum lwan_http_status dirlist_serve(struct lwan_request *request, void *data); @@ -161,7 +167,7 @@ static bool redir_init(struct file_cache_entry *ce, struct serve_files_priv *priv, const char *full_path, struct stat *st); -static void redir_free(void *data); +static void redir_free(struct file_cache_entry *ce); static enum lwan_http_status redir_serve(struct lwan_request *request, void *data); @@ -169,28 +175,24 @@ static const struct cache_funcs mmap_funcs = { .init = mmap_init, .free = mmap_free, .serve = mmap_serve, - .struct_size = sizeof(struct mmap_cache_data), }; static const struct cache_funcs sendfile_funcs = { .init = sendfile_init, .free = sendfile_free, .serve = sendfile_serve, - .struct_size = sizeof(struct sendfile_cache_data), }; static const struct cache_funcs dirlist_funcs = { .init = dirlist_init, .free = dirlist_free, .serve = dirlist_serve, - .struct_size = sizeof(struct dir_list_cache_data), }; static const struct cache_funcs redir_funcs = { .init = redir_init, .free = redir_free, .serve = redir_serve, - .struct_size = sizeof(struct redir_cache_data), }; static const struct lwan_var_descriptor file_list_desc[] = { @@ -357,7 +359,7 @@ static bool mmap_init(struct file_cache_entry *ce, const char *full_path, struct stat *st) { - struct mmap_cache_data *md = (struct mmap_cache_data *)(ce + 1); + struct mmap_cache_data *md = &ce->mmap_cache_data; const char *path = full_path + priv->root_path_len; int file_fd; bool success; @@ -457,7 +459,7 @@ static bool sendfile_init(struct file_cache_entry *ce, const char *full_path, struct stat *st) { - struct sendfile_cache_data *sd = (struct sendfile_cache_data *)(ce + 1); + struct sendfile_cache_data *sd = &ce->sendfile_cache_data; const char *relpath = full_path + priv->root_path_len; ce->mime_type = lwan_determine_mime_type_for_file_name(relpath); @@ -519,7 +521,7 @@ static bool dirlist_init(struct file_cache_entry *ce, const char *full_path, struct stat *st __attribute__((unused))) { - struct dir_list_cache_data *dd = (struct dir_list_cache_data *)(ce + 1); + struct dir_list_cache_data *dd = &ce->dir_list_cache_data; struct file_list vars = {.full_path = full_path, .rel_path = get_rel_path(full_path, priv)}; @@ -542,7 +544,7 @@ static bool redir_init(struct file_cache_entry *ce, const char *full_path, struct stat *st __attribute__((unused))) { - struct redir_cache_data *rd = (struct redir_cache_data *)(ce + 1); + struct redir_cache_data *rd = &ce->redir_cache_data; if (asprintf(&rd->redir_to, "%s/", full_path + priv->root_path_len) < 0) return false; @@ -630,7 +632,7 @@ create_cache_entry_from_funcs(struct serve_files_priv *priv, { struct file_cache_entry *fce; - fce = malloc(sizeof(*fce) + funcs->struct_size); + fce = malloc(sizeof(*fce)); if (UNLIKELY(!fce)) return NULL; @@ -652,7 +654,7 @@ static void destroy_cache_entry(struct cache_entry *entry, { struct file_cache_entry *fce = (struct file_cache_entry *)entry; - fce->funcs->free(fce + 1); + fce->funcs->free(fce); free(fce); } @@ -692,17 +694,17 @@ static struct cache_entry *create_cache_entry(const char *key, void *context) return (struct cache_entry *)fce; } -static void mmap_free(void *data) +static void mmap_free(struct file_cache_entry *fce) { - struct mmap_cache_data *md = data; + struct mmap_cache_data *md = &fce->mmap_cache_data; munmap(md->uncompressed.contents, md->uncompressed.size); free(md->compressed.contents); } -static void sendfile_free(void *data) +static void sendfile_free(struct file_cache_entry *fce) { - struct sendfile_cache_data *sd = data; + struct sendfile_cache_data *sd = &fce->sendfile_cache_data; if (sd->compressed.fd >= 0) close(sd->compressed.fd); @@ -710,16 +712,16 @@ static void sendfile_free(void *data) close(sd->uncompressed.fd); } -static void dirlist_free(void *data) +static void dirlist_free(struct file_cache_entry *fce) { - struct dir_list_cache_data *dd = data; + struct dir_list_cache_data *dd = &fce->dir_list_cache_data; lwan_strbuf_free(&dd->rendered); } -static void redir_free(void *data) +static void redir_free(struct file_cache_entry *fce) { - struct redir_cache_data *rd = data; + struct redir_cache_data *rd = &fce->redir_cache_data; free(rd->redir_to); } @@ -922,7 +924,7 @@ static enum lwan_http_status sendfile_serve(struct lwan_request *request, void *data) { struct file_cache_entry *fce = data; - struct sendfile_cache_data *sd = (struct sendfile_cache_data *)(fce + 1); + struct sendfile_cache_data *sd = &fce->sendfile_cache_data; char headers[DEFAULT_BUFFER_SIZE]; size_t header_len; enum lwan_http_status return_status; @@ -1024,7 +1026,7 @@ static enum lwan_http_status mmap_serve(struct lwan_request *request, void *data) { struct file_cache_entry *fce = data; - struct mmap_cache_data *md = (struct mmap_cache_data *)(fce + 1); + struct mmap_cache_data *md = &fce->mmap_cache_data; void *contents; size_t size; const char *compressed; @@ -1061,7 +1063,7 @@ static enum lwan_http_status dirlist_serve(struct lwan_request *request, void *data) { struct file_cache_entry *fce = data; - struct dir_list_cache_data *dd = (struct dir_list_cache_data *)(fce + 1); + struct dir_list_cache_data *dd = &fce->dir_list_cache_data; const char *icon; const void *contents; size_t size; @@ -1094,7 +1096,7 @@ static enum lwan_http_status redir_serve(struct lwan_request *request, void *data) { struct file_cache_entry *fce = data; - struct redir_cache_data *rd = (struct redir_cache_data *)(fce + 1); + struct redir_cache_data *rd = &fce->redir_cache_data; return serve_buffer_full(request, fce, "Location", rd->redir_to, rd->redir_to, strlen(rd->redir_to), From f25aa625295bbc3b31e2a386d8a189751c71f896 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 20 Jan 2019 09:11:32 -0800 Subject: [PATCH 0957/2505] Simplify how variable descriptors are declared This removes a parameter for all TPL_VAR_*() macros, and makes them use a TPL_STRUCT macro (that has to be defined prior to declaring the descriptor array). Still not the ideal API, but I'm happier with how it looks. This breaks the API. --- src/lib/lwan-mod-serve-files.c | 24 ++++++++------- src/lib/lwan-response.c | 15 ++++----- src/lib/lwan-template.h | 44 +++++++++++++-------------- src/samples/clock/main.c | 8 +++-- src/samples/freegeoip/freegeoip.c | 20 +++++------- src/samples/techempower/techempower.c | 13 ++++---- 6 files changed, 63 insertions(+), 61 deletions(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 59f29fcf2..a32ff3471 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -195,25 +195,27 @@ static const struct cache_funcs redir_funcs = { .serve = redir_serve, }; +#undef TPL_STRUCT +#define TPL_STRUCT struct file_list static const struct lwan_var_descriptor file_list_desc[] = { - TPL_VAR_STR_ESCAPE(struct file_list, full_path), - TPL_VAR_STR_ESCAPE(struct file_list, rel_path), - TPL_VAR_SEQUENCE(struct file_list, - file_list, + TPL_VAR_STR_ESCAPE(full_path), + TPL_VAR_STR_ESCAPE(rel_path), + TPL_VAR_SEQUENCE(file_list, directory_list_generator, ((const struct lwan_var_descriptor[]){ - TPL_VAR_STR(struct file_list, file_list.icon), - TPL_VAR_STR(struct file_list, file_list.icon_alt), - TPL_VAR_STR(struct file_list, file_list.name), - TPL_VAR_STR(struct file_list, file_list.type), - TPL_VAR_INT(struct file_list, file_list.size), - TPL_VAR_STR(struct file_list, file_list.unit), - TPL_VAR_STR(struct file_list, file_list.zebra_class), + TPL_VAR_STR(file_list.icon), + TPL_VAR_STR(file_list.icon_alt), + TPL_VAR_STR(file_list.name), + TPL_VAR_STR(file_list.type), + TPL_VAR_INT(file_list.size), + TPL_VAR_STR(file_list.unit), + TPL_VAR_STR(file_list.zebra_class), TPL_VAR_SENTINEL, })), TPL_VAR_SENTINEL, }; + static const char *directory_list_tpl_str = "\n" "\n" diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 8d917abc9..098b8c5db 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -60,18 +60,18 @@ static const char *error_template_str = "\n" "\n" "\n" - "{{rel_path?}}

Index of {{rel_path}}

{{/rel_path?}}\n" - "{{^rel_path?}}

Index of /

{{/rel_path?}}\n" + "{{rel_path?}}

Index of {{rel_path}}

\n{{/rel_path?}}" + "{{^rel_path?}}

Index of /

\n{{/rel_path?}}" + "{{readme?}}
{{readme}}
\n{{/readme?}}" " \n" " \n" " \n" @@ -523,6 +528,57 @@ static const char *get_rel_path(const char *full_path, return priv->prefix; } +static const char *dirlist_find_readme(struct dir_list_cache_data *dd, + struct serve_files_priv *priv, + const char *full_path) +{ + static const char *candidates[] = {"readme", "readme.txt", "read.me", + "README.TXT", "README"}; + int fd = -1; + + lwan_strbuf_init(&dd->readme); + + if (!priv->auto_index_readme) + return NULL; + + for (size_t i = 0; i < N_ELEMENTS(candidates); i++) { + char buffer[PATH_MAX]; + int r; + + r = snprintf(buffer, PATH_MAX, "%s/%s", full_path, candidates[i]); + if (r < 0 || r >= PATH_MAX) + continue; + + fd = open(buffer, O_RDONLY | O_CLOEXEC); + if (fd < 0) + continue; + + while (true) { + ssize_t n = read(fd, buffer, sizeof(buffer)); + + if (n < 0) { + if (errno == EINTR) + continue; + goto error; + } + if (!n) + break; + + if (!lwan_strbuf_append_str(&dd->readme, buffer, (size_t)n)) + goto error; + } + + close(fd); + return lwan_strbuf_get_buffer(&dd->readme); + } + +error: + lwan_strbuf_reset(&dd->readme); + if (fd >= 0) + close(fd); + return NULL; +} + static bool dirlist_init(struct file_cache_entry *ce, struct serve_files_priv *priv, const char *full_path, @@ -530,7 +586,8 @@ static bool dirlist_init(struct file_cache_entry *ce, { struct dir_list_cache_data *dd = &ce->dir_list_cache_data; struct file_list vars = {.full_path = full_path, - .rel_path = get_rel_path(full_path, priv)}; + .rel_path = get_rel_path(full_path, priv), + .readme = dirlist_find_readme(dd, priv, full_path)}; if (!lwan_strbuf_init(&dd->rendered)) return false; @@ -724,6 +781,7 @@ static void dirlist_free(struct file_cache_entry *fce) struct dir_list_cache_data *dd = &fce->dir_list_cache_data; lwan_strbuf_free(&dd->rendered); + lwan_strbuf_free(&dd->readme); } static void redir_free(struct file_cache_entry *fce) @@ -797,6 +855,7 @@ static void *serve_files_create(const char *prefix, void *args) settings->index_html ? settings->index_html : "index.html"; priv->serve_precompressed_files = settings->serve_precompressed_files; priv->auto_index = settings->auto_index; + priv->auto_index_readme = settings->auto_index_readme; priv->read_ahead = settings->read_ahead; return priv; @@ -826,6 +885,8 @@ static void *serve_files_create_from_hash(const char *prefix, .directory_list_template = hash_find(hash, "directory_list_template"), .read_ahead = (size_t)parse_long("read_ahead", SERVE_FILES_READ_AHEAD_BYTES), + .auto_index_readme = + parse_bool(hash_find(hash, "auto_index_readme"), true), }; return serve_files_create(prefix, &settings); diff --git a/src/lib/lwan-mod-serve-files.h b/src/lib/lwan-mod-serve-files.h index d8866cfa9..515a8fa7d 100644 --- a/src/lib/lwan-mod-serve-files.h +++ b/src/lib/lwan-mod-serve-files.h @@ -34,6 +34,7 @@ struct lwan_serve_files_settings { size_t read_ahead; bool serve_precompressed_files; bool auto_index; + bool auto_index_readme; }; LWAN_MODULE_FORWARD_DECL(serve_files); @@ -46,7 +47,8 @@ LWAN_MODULE_FORWARD_DECL(serve_files); .index_html = index_html_, \ .serve_precompressed_files = serve_precompressed_files_, \ .directory_list_template = NULL, \ - .auto_index = true \ + .auto_index = true, \ + .auto_index_readme = true, \ }}), \ .flags = (enum lwan_handler_flags)0 From ff15ae06968740cc4a878abc8507b82685e393b1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 26 May 2019 19:39:07 -0700 Subject: [PATCH 1066/2505] Add sanity checks in template post-processing --- src/lib/lwan-template.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index fc759a63a..3aeab6851 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -988,9 +988,11 @@ static bool post_process_template(struct parser *parser) LWAN_ARRAY_FOREACH(&parser->chunks, chunk) { if (chunk->action == ACTION_IF_VARIABLE_NOT_EMPTY) { - for (prev_chunk = chunk; ; chunk++) { - if (chunk->action == ACTION_LAST) - break; + for (prev_chunk = chunk;; chunk++) { + if (chunk->action == ACTION_LAST) { + lwan_status_error("Internal error: Could not find the end var not empty chunk"); + return false; + } if (chunk->action == ACTION_END_IF_VARIABLE_NOT_EMPTY && chunk->data == prev_chunk->data) break; @@ -1009,9 +1011,11 @@ static bool post_process_template(struct parser *parser) } else if (chunk->action == ACTION_START_ITER) { enum flags flags = chunk->flags; - for (prev_chunk = chunk; ; chunk++) { - if (chunk->action == ACTION_LAST) - break; + for (prev_chunk = chunk;; chunk++) { + if (chunk->action == ACTION_LAST) { + lwan_status_error("Internal error: Could not find the end iter chunk"); + return false; + } if (chunk->action == ACTION_END_ITER) { size_t start_index = (size_t)chunk->data; size_t prev_index = From 9293c6133b7f502203da05657d13b8e2dbc83c05 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 26 May 2019 19:39:26 -0700 Subject: [PATCH 1067/2505] Print chunk index when debugging templates --- src/lib/lwan-template.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index 3aeab6851..5edc5bf7a 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -1181,6 +1181,8 @@ static void dump_program(const struct lwan_tpl *tpl) LWAN_ARRAY_FOREACH(&tpl->chunks, iter) { char instr_buf[32]; + printf("%8zu ", iter - (struct chunk *)tpl->chunks.base.base); + switch (iter->action) { default: for (int i = 0; i < indent; i++) { @@ -1222,11 +1224,10 @@ static void dump_program(const struct lwan_tpl *tpl) indent++; break; } - case ACTION_END_ITER: { - printf("%s ", instr("END_ITER", instr_buf)); + case ACTION_END_ITER: + printf("%s [%zu]", instr("END_ITER", instr_buf), (size_t)iter->data); indent--; break; - } case ACTION_IF_VARIABLE_NOT_EMPTY: { struct chunk_descriptor *cd = iter->data; From bb8147577bf48a6b355a1edca7f145da15233108 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 26 May 2019 19:45:01 -0700 Subject: [PATCH 1068/2505] Cache result of gettid() while logging --- src/lib/lwan-status.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-status.c b/src/lib/lwan-status.c index 93cca008e..f1ddf13a9 100644 --- a/src/lib/lwan-status.c +++ b/src/lib/lwan-status.c @@ -109,6 +109,18 @@ static inline char *strerror_thunk_r(int error_number, char *buffer, size_t len) #endif } +#ifndef NDEBUG +static long gettid_cached(void) +{ + static __thread long tid; + + if (!tid) + tid = gettid(); + + return tid; +} +#endif + static void #ifdef NDEBUG status_out(enum lwan_status_type type, const char *fmt, va_list values) @@ -130,11 +142,11 @@ status_out(const char *file, #ifndef NDEBUG char *base_name = basename(strdupa(file)); if (use_colors) { - printf("\033[32;1m%ld\033[0m", gettid()); + printf("\033[32;1m%ld\033[0m", gettid_cached()); printf(" \033[3m%s:%d\033[0m", base_name, line); printf(" \033[33m%s()\033[0m ", func); } else { - printf("%ld %s:%d %s() ", gettid(), base_name, line, func); + printf("%ld %s:%d %s() ", gettid_cached(), base_name, line, func); } #endif From 45134304e1fb9213be9e13a258154055ca55738d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 26 May 2019 19:54:02 -0700 Subject: [PATCH 1069/2505] Mention OSS-Fuzz in README.md --- README.md | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index bc169ae9a..6ee08c3bb 100644 --- a/README.md +++ b/README.md @@ -531,27 +531,15 @@ Some tests will only work on Linux, and won't be executed on other platforms. ### Fuzz-testing -To enable, pass `-DBUILD_FUZZER=ON` in the CMake command line, and set the `CC` -environment variable to `clang` (this requires clang >= 6.0). No samples will -be built, `liblwan` will be instrumented, and the driver will be available as -`src/bin/fuzz/fuzz`. - -Right now this fuzzes only with address sanitizer, but there are plans to build -a fuzzing binary for each sanitizer that makes sense. +Lwan is automatically fuzz-tested by +[OSS-Fuzz](https://github.com/google/oss-fuzz/). To fuzz-test locally, +though, one can [follow the instructions to test +locally](https://github.com/google/oss-fuzz/blob/master/docs/new_project_guide.md#testing-locally). This fuzzes only the request parsing code. There are plans to add fuzzing drivers for other parts of the code, including the rewriting engine, configuration file reader, template parser, and URL routing. -A good dictionary and corpus to use are from the [H2O web -server](https://github.com/h2o/h2o), available in its repository, `fuzz' -directory. A corpus specific to Lwan with requests obtained from its test -suite should be added in the future, as it contains tests to exercise corner -cases that have been fixed in the past. - -To learn how to use a binary instrumented with `libFuzzer`, please [read its -documentation](https://llvm.org/docs/LibFuzzer.html). - ### Exporting APIs The shared object version of `liblwan` on ELF targets (e.g. Linux) will use From 2c00f053c3c44f8607409c95cac3151448e3cc78 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 26 May 2019 22:10:21 -0700 Subject: [PATCH 1070/2505] Use small icons for auto index --- wwwroot/icons/README.TXT | 15 +++++++++++++++ wwwroot/icons/back.gif | Bin 122 -> 126 bytes wwwroot/icons/file.gif | Bin 127 -> 116 bytes wwwroot/icons/folder.gif | Bin 131 -> 132 bytes 4 files changed, 15 insertions(+) create mode 100644 wwwroot/icons/README.TXT diff --git a/wwwroot/icons/README.TXT b/wwwroot/icons/README.TXT new file mode 100644 index 000000000..04b4a1daf --- /dev/null +++ b/wwwroot/icons/README.TXT @@ -0,0 +1,15 @@ +Public Domain Icons +------------------- + +These icons were originally made for Mosaic for X and have been included in +the NCSA httpd and Apache server distributions in the past. They are in the +public domain and may be freely included in any application. The originals +were done by Kevin Hughes (kevinh@kevcom.com). Andy Polyakov tuned the icon +colors and added few new images. + +If you'd like to contribute additions to this set, contact the httpd +documentation project http://httpd.apache.org/docs-project/. + +Almost all of these icons are 20x22 pixels in size. There are alternative +icons in the "small" directory that are 16x16 in size, provided by Mike +Brown (mike@hyperreal.org). diff --git a/wwwroot/icons/back.gif b/wwwroot/icons/back.gif index 7d7a12a2bb0f8e55977c6f0be4524f36e9c75854..4268366043b1e8706a56c856c9d51521b4d1907d 100644 GIT binary patch literal 126 zcmZ?wbhEHb6krfw_{7GbsHkXYXc!e0)zZ?^)6>Jiz;NKe0U)XPlZBOmfsH{2$Oftu zU|?W&&e(N__kd@B*OczC?%oTc3MEIv1m*_jX=H9Yyx_!UOQDSlTf7pg8WR$>2<&Nb cY|rc{X>f76mlUeV(#faHt$naknv=mA00k~5{r~^~ literal 122 zcmZ?wbhEHb6k!l!_{hQl1jfe3X=!OQXUIOz`~#d5(UXKFnfFKy7RB8<%5=_ zaq#X%=k9#XoMY}MR@OA3M2Nv$%RweDB3J+Sq}Qfy0Uw!PRUBI~aaj(NhTn6ATRjI> Y>^k5eaW!nxy$hj#UQ9gS&B9;}01aO*AOHXW diff --git a/wwwroot/icons/file.gif b/wwwroot/icons/file.gif index 46694ad85274c4a7206553cc118a2c8a027c85de..f8da6ff92c3103d440aa34c842efce51ddd2d55c 100644 GIT binary patch literal 116 zcmV-)0E_=eNk%w1VGsZi0HXf@|Ns9vIXP=#Us`0y{o*207$1fAZnn)8K98{r)%?UybGCi7VD9G@ebp^h1Yj db)e>!&fc<}KKh0iv}aeYKaqR(pDF``H2{fqFf#xE diff --git a/wwwroot/icons/folder.gif b/wwwroot/icons/folder.gif index 1f9618db65a7318b9dc1649e77b813740baa7095..7b37b099177d12b3f6ee7056c03d992b09e7fee1 100644 GIT binary patch literal 132 zcmZ?wbhEHb6krfwSoELa|Gl;U@2op;;J}Urwc*~@+N!b)V4(Pug^_`Qi9rX%1F2&GNR2Uen0VOOkQ~&?~ literal 131 zcmZ?wbhEHb6k!l!_{hQl1jfe3Gt-R!pP8xnlZBOmfrUW_BnpyeU=H!vb?2YKDNo5A zKl}`Ky>~cLo3c!^!J#Uvlg~-#xVB8+S1I=JdE7R0`dQ!bdb&O=Ot;z8 Date: Sun, 26 May 2019 22:17:05 -0700 Subject: [PATCH 1071/2505] Add test for README files in auto indexes --- src/scripts/testsuite.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index 9fbdc826b..fc4ac5fd9 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -294,6 +294,14 @@ def assertHasImage(name): assertHasImage('file') assertHasImage('folder') + with open('wwwroot/icons/README.TXT', 'r') as readme: + readme = readme.read() + readme = readme.replace('"', """) + readme = readme.replace('/', "/") + readme = readme.replace("'", "'") + + self.assertTrue(readme in r.text) + self.assertTrue('' in r.text) From fb53ee4bf3a31c15aa4be8a91d570d723a1aaadf Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 27 May 2019 08:20:51 -0700 Subject: [PATCH 1072/2505] Only parse /proc/sys/net/core/somaxconn on Linux systems --- src/lib/lwan-socket.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/lwan-socket.c b/src/lib/lwan-socket.c index fa7a06cc1..bd2973304 100644 --- a/src/lib/lwan-socket.c +++ b/src/lib/lwan-socket.c @@ -38,6 +38,8 @@ static int get_backlog_size(void) { int backlog = SOMAXCONN; + +#ifdef __linux__ FILE *somaxconn; somaxconn = fopen("/proc/sys/net/core/somaxconn", "re"); @@ -47,6 +49,7 @@ static int get_backlog_size(void) backlog = tmp; fclose(somaxconn); } +#endif return backlog; } From e2ff89e21db313b896d53076d8c2f4e79d4b7a3f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 27 May 2019 10:33:02 -0700 Subject: [PATCH 1073/2505] Document `auto_index_readme` --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6ee08c3bb..b79891b47 100644 --- a/README.md +++ b/README.md @@ -334,6 +334,7 @@ best to serve files in the fastest way possible according to some heuristics. | `index_path` | `str` | `index.html` | File name to serve as an index for a directory | | `serve_precompressed_path` | `bool` | `true` | If $FILE.gz exists, is smaller and newer than $FILE, and the client accepts `gzip` encoding, transfer it | | `auto_index` | `bool` | `true` | Generate a directory list automatically if no `index_path` file present. Otherwise, yields 404 | +| `auto_index_readme` | `bool` | `true` | Includes the contents of README files as part of the automatically generated directory index | | `directory_list_template` | `str` | `NULL` | Path to a Mustache template for the directory list; by default, use an internal template | | `read_ahead` | `int` | `131702` | Maximum amount of bytes to read ahead when caching open files. A value of `0` disables readahead. Readahead is performed by a low priority thread to not block the I/O threads while file extents are being read from the filesystem. | From bb47b46b9e85da747203658eb3ca9bbdb06e81f4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 27 May 2019 10:37:28 -0700 Subject: [PATCH 1074/2505] Document WebSocket metamethods --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index b79891b47..30484bd39 100644 --- a/README.md +++ b/README.md @@ -373,6 +373,9 @@ information from the request, or to set the response, as seen below: - `req:cookie(param)` returns the cookie named `param`, or `nil` is not found - `req:set_headers(tbl)` sets the response headers from the table `tbl`; a header may be specified multiple times by using a table, rather than a string, in the table value (`{'foo'={'bar', 'baz'}}`); must be called before sending any response with `say()` or `send_event()` - `req:sleep(ms)` pauses the current handler for the specified amount of milliseconds + - `req:ws_upgrade()` returns `1` if the connection could be upgraded to a WebSocket; `0` otherwise + - `req:ws_write(str)` sends `str` through the WebSocket-upgraded connection + - `req:ws_read()` returns a string obtained from the WebSocket, or `nil` on error Handler functions may return either `nil` (in which case, a `200 OK` response is generated), or a number matching an HTTP status code. Attempting to return From 7f696529f785f3ab38de3ee8292efe08410a27fd Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 27 May 2019 10:56:46 -0700 Subject: [PATCH 1075/2505] Initialize hash table odd constant at a higher priority --- src/lib/hash.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/hash.c b/src/lib/hash.c index 593c87848..b97366b2c 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -165,7 +165,7 @@ static inline unsigned int hash_int_crc32(const void *keyptr) } #endif -__attribute__((constructor)) static void initialize_odd_constant(void) +__attribute__((constructor(65535))) static void initialize_odd_constant(void) { /* This constant is randomized in order to mitigate the DDoS attack * described by Crosby and Wallach in UsenixSec2003. */ From 749272b3fcc95f1ebfd42c7688f2aac30061bd3e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 28 May 2019 06:48:50 -0700 Subject: [PATCH 1076/2505] Reindent lwan-template.[ch] --- src/lib/lwan-template.c | 18 ++++++++++------ src/lib/lwan-template.h | 47 +++++++++++++++++++++++------------------ 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index 5edc5bf7a..b9c7718fc 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -44,10 +44,10 @@ #include "hash.h" #include "int-to-str.h" #include "list.h" +#include "ringbuffer.h" #include "lwan-array.h" #include "lwan-strbuf.h" #include "lwan-template.h" -#include "ringbuffer.h" /* Define this and build a debug version to have the template * chunks printed out after compilation. */ @@ -81,8 +81,8 @@ enum flags { X(SLASH) X(QUESTION_MARK) X(HAT) X(GREATER_THAN) X(OPEN_CURLY_BRACE) \ X(CLOSE_CURLY_BRACE) -#define GENERATE_ENUM(id) LEXEME_ ##id, -#define GENERATE_ARRAY_ITEM(id) [LEXEME_ ##id] = #id, +#define GENERATE_ENUM(id) LEXEME_##id, +#define GENERATE_ARRAY_ITEM(id) [LEXEME_##id] = #id, enum lexeme_type { FOR_EACH_LEXEME(GENERATE_ENUM) @@ -990,7 +990,8 @@ static bool post_process_template(struct parser *parser) if (chunk->action == ACTION_IF_VARIABLE_NOT_EMPTY) { for (prev_chunk = chunk;; chunk++) { if (chunk->action == ACTION_LAST) { - lwan_status_error("Internal error: Could not find the end var not empty chunk"); + lwan_status_error("Internal error: Could not find the end " + "var not empty chunk"); return false; } if (chunk->action == ACTION_END_IF_VARIABLE_NOT_EMPTY && @@ -1013,7 +1014,8 @@ static bool post_process_template(struct parser *parser) for (prev_chunk = chunk;; chunk++) { if (chunk->action == ACTION_LAST) { - lwan_status_error("Internal error: Could not find the end iter chunk"); + lwan_status_error( + "Internal error: Could not find the end iter chunk"); return false; } if (chunk->action == ACTION_END_ITER) { @@ -1023,7 +1025,8 @@ static bool post_process_template(struct parser *parser) if (prev_index == start_index) { chunk->flags |= flags; - chunk->data = chunk_array_get_elem(&parser->chunks, start_index); + chunk->data = + chunk_array_get_elem(&parser->chunks, start_index); break; } } @@ -1225,7 +1228,8 @@ static void dump_program(const struct lwan_tpl *tpl) break; } case ACTION_END_ITER: - printf("%s [%zu]", instr("END_ITER", instr_buf), (size_t)iter->data); + printf("%s [%zu]", instr("END_ITER", instr_buf), + (size_t)iter->data); indent--; break; case ACTION_IF_VARIABLE_NOT_EMPTY: { diff --git a/src/lib/lwan-template.h b/src/lib/lwan-template.h index 5cc698a76..3fdd081fb 100644 --- a/src/lib/lwan-template.h +++ b/src/lib/lwan-template.h @@ -14,17 +14,16 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #pragma once -#include -#include "lwan-strbuf.h" #include "lwan-coro.h" +#include "lwan-strbuf.h" +#include -enum lwan_tpl_flag { - LWAN_TPL_FLAG_CONST_TEMPLATE = 1<<0 -}; +enum lwan_tpl_flag { LWAN_TPL_FLAG_CONST_TEMPLATE = 1 << 0 }; struct lwan_var_descriptor { const char *name; @@ -73,18 +72,26 @@ struct lwan_var_descriptor { * them, though, that's why they're exported. Eventually this will move to * something more opaque. */ -void lwan_append_int_to_strbuf(struct lwan_strbuf *buf, void *ptr); -bool lwan_tpl_int_is_empty(void *ptr); -void lwan_append_str_to_strbuf(struct lwan_strbuf *buf, void *ptr); -void lwan_append_str_escaped_to_strbuf(struct lwan_strbuf *buf, void *ptr); -bool lwan_tpl_str_is_empty(void *ptr); -void lwan_append_double_to_strbuf(struct lwan_strbuf *buf, void *ptr); -bool lwan_tpl_double_is_empty(void *ptr); - -struct lwan_tpl *lwan_tpl_compile_string_full(const char *string, const struct lwan_var_descriptor *descriptor, enum lwan_tpl_flag flags); -struct lwan_tpl *lwan_tpl_compile_string(const char *string, const struct lwan_var_descriptor *descriptor); -struct lwan_tpl *lwan_tpl_compile_file(const char *filename, const struct lwan_var_descriptor *descriptor); -struct lwan_strbuf *lwan_tpl_apply(struct lwan_tpl *tpl, void *variables); -bool lwan_tpl_apply_with_buffer(struct lwan_tpl *tpl, struct lwan_strbuf *buf, void *variables); -void lwan_tpl_free(struct lwan_tpl *tpl); +void lwan_append_int_to_strbuf(struct lwan_strbuf *buf, void *ptr); +bool lwan_tpl_int_is_empty(void *ptr); +void lwan_append_str_to_strbuf(struct lwan_strbuf *buf, void *ptr); +void lwan_append_str_escaped_to_strbuf(struct lwan_strbuf *buf, void *ptr); +bool lwan_tpl_str_is_empty(void *ptr); +void lwan_append_double_to_strbuf(struct lwan_strbuf *buf, void *ptr); +bool lwan_tpl_double_is_empty(void *ptr); +struct lwan_tpl * +lwan_tpl_compile_string_full(const char *string, + const struct lwan_var_descriptor *descriptor, + enum lwan_tpl_flag flags); +struct lwan_tpl * +lwan_tpl_compile_string(const char *string, + const struct lwan_var_descriptor *descriptor); +struct lwan_tpl * +lwan_tpl_compile_file(const char *filename, + const struct lwan_var_descriptor *descriptor); +struct lwan_strbuf *lwan_tpl_apply(struct lwan_tpl *tpl, void *variables); +bool lwan_tpl_apply_with_buffer(struct lwan_tpl *tpl, + struct lwan_strbuf *buf, + void *variables); +void lwan_tpl_free(struct lwan_tpl *tpl); From c9c2d5a259427a7f8505bc498570b312c85a6ef3 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 30 May 2019 20:32:12 -0700 Subject: [PATCH 1077/2505] Don't let readme contents stick around after rendering template --- src/lib/lwan-mod-serve-files.c | 42 ++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 9e2d6c77d..5c82242b0 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -100,7 +100,6 @@ struct sendfile_cache_data { struct dir_list_cache_data { struct lwan_strbuf rendered; - struct lwan_strbuf readme; }; struct redir_cache_data { @@ -528,7 +527,8 @@ static const char *get_rel_path(const char *full_path, return priv->prefix; } -static const char *dirlist_find_readme(struct dir_list_cache_data *dd, +static const char *dirlist_find_readme(struct lwan_strbuf *readme, + struct dir_list_cache_data *dd, struct serve_files_priv *priv, const char *full_path) { @@ -536,8 +536,6 @@ static const char *dirlist_find_readme(struct dir_list_cache_data *dd, "README.TXT", "README"}; int fd = -1; - lwan_strbuf_init(&dd->readme); - if (!priv->auto_index_readme) return NULL; @@ -564,16 +562,15 @@ static const char *dirlist_find_readme(struct dir_list_cache_data *dd, if (!n) break; - if (!lwan_strbuf_append_str(&dd->readme, buffer, (size_t)n)) + if (!lwan_strbuf_append_str(readme, buffer, (size_t)n)) goto error; } close(fd); - return lwan_strbuf_get_buffer(&dd->readme); + return lwan_strbuf_get_buffer(readme); } error: - lwan_strbuf_reset(&dd->readme); if (fd >= 0) close(fd); return NULL; @@ -585,22 +582,34 @@ static bool dirlist_init(struct file_cache_entry *ce, struct stat *st __attribute__((unused))) { struct dir_list_cache_data *dd = &ce->dir_list_cache_data; - struct file_list vars = {.full_path = full_path, - .rel_path = get_rel_path(full_path, priv), - .readme = dirlist_find_readme(dd, priv, full_path)}; + struct lwan_strbuf readme; + bool ret = false; - if (!lwan_strbuf_init(&dd->rendered)) + if (!lwan_strbuf_init(&readme)) return false; + if (!lwan_strbuf_init(&dd->rendered)) + goto out_free_readme; + + struct file_list vars = { + .full_path = full_path, + .rel_path = get_rel_path(full_path, priv), + .readme = dirlist_find_readme(&readme, dd, priv, full_path), + }; if (!lwan_tpl_apply_with_buffer(priv->directory_list_tpl, &dd->rendered, - &vars)) { - lwan_strbuf_free(&dd->rendered); - return false; - } + &vars)) + goto out_free_rendered; ce->mime_type = "text/html"; - return true; + ret = true; + goto out_free_readme; + +out_free_rendered: + lwan_strbuf_free(&dd->rendered); +out_free_readme: + lwan_strbuf_free(&readme); + return ret; } static bool redir_init(struct file_cache_entry *ce, @@ -781,7 +790,6 @@ static void dirlist_free(struct file_cache_entry *fce) struct dir_list_cache_data *dd = &fce->dir_list_cache_data; lwan_strbuf_free(&dd->rendered); - lwan_strbuf_free(&dd->readme); } static void redir_free(struct file_cache_entry *fce) From 0b8c2ac894963b7a5e4121552275257f3eba54e3 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 1 Jun 2019 13:43:40 -0700 Subject: [PATCH 1078/2505] Stop reading auth config if no section end line could be found --- src/lib/lwan.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 0ff888e33..e07ca7214 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -171,12 +171,11 @@ static void parse_listener_prefix_authorization(struct config *c, url_map->authorization.password_file = strdup("htpasswd"); url_map->flags |= HANDLER_MUST_AUTHORIZE; - goto out; + return; } } -out: - return; + config_error(c, "Could not find end of authorization section"); error: free(url_map->authorization.realm); From e8f60602e12962dc3c3b770a16c1f0469e701dc3 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 2 Jun 2019 13:53:28 -0700 Subject: [PATCH 1079/2505] Change 'no_http_version_fails' test to GET a longer path Lwan now waits until MIN_REQUEST_SIZE bytes is received from the socket before attempting to parse it, so this was causing timeout issues. --- src/scripts/testsuite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index fc4ac5fd9..f40410e04 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -418,7 +418,7 @@ def test_cat_sleeping_on_keyboard(self): def test_no_http_version_fails(self): with self.connect() as sock: - sock.send('GET /\r\n\r\n') + sock.send('GET /some-long-url-that-is-longer-than-version-string\r\n\r\n') self.assertHttpCode(sock, 400) From 6eabfeba325427fbd2828e47d7a03f38531a6251 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 2 Jun 2019 12:57:52 -0700 Subject: [PATCH 1080/2505] Streamline connection coroutines intent when yielding This took a while to get right; hopefully, this is right now. Connections never tracked the events tracked by epoll. It assumed certain things, that happened to be correct most of the time. This change simplifies a bunch of things, by getting rid of a few of the flags, and introducing a few new values to enum lwan_connection_coro_yield. Each value clearly states that the coroutine may want to abort, yield (without changing the flags), read, write, read and write, suspend by a timer, or resume by a timer. Hopefully I got all the corner cases correctly. With this change, two flags were removed from the connection flags enum, that no longer make sense: - CONN_FLIP_FLAGS: ambiguous. Never really understood how to use it intuitively. - CONN_WRITE_EVENTS: not really write events most of the time. Really confusing. In their place, two new flags (with convenience values) have been added: - CONN_EVENTS_READ - CONN_EVENTS_WRITE - CONN_EVENTS_READ_WRITE (READ and WRITE ORed together) - CONN_EVENTS_MASK (READ and WRITE ORed together) --- src/lib/lwan-cache.c | 2 +- src/lib/lwan-io-wrappers.c | 57 +++++++++--------- src/lib/lwan-mod-lua.c | 2 +- src/lib/lwan-request.c | 25 +++----- src/lib/lwan-response.c | 9 +-- src/lib/lwan-thread.c | 117 ++++++++++++++++++++----------------- src/lib/lwan.h | 36 +++++++----- src/lib/missing.c | 7 +-- 8 files changed, 132 insertions(+), 123 deletions(-) diff --git a/src/lib/lwan-cache.c b/src/lib/lwan-cache.c index eba3e60ff..a7a6e62fd 100644 --- a/src/lib/lwan-cache.c +++ b/src/lib/lwan-cache.c @@ -374,7 +374,7 @@ struct cache_entry *cache_coro_get_and_ref_entry(struct cache *cache, * try again. On any other error, just return NULL. */ if (error == EWOULDBLOCK) { - coro_yield(coro, CONN_CORO_MAY_RESUME); + coro_yield(coro, CONN_CORO_YIELD); } else { break; } diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index df7dc35af..2195a0c5a 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -44,8 +44,8 @@ lwan_writev(struct lwan_request *request, struct iovec *iov, int iov_count) switch (errno) { case EAGAIN: - request->conn->flags |= CONN_FLIP_FLAGS; - /* fallthrough */ + coro_yield(request->conn->coro, CONN_CORO_WANT_WRITE); + continue; case EINTR: goto try_again; default: @@ -67,7 +67,7 @@ lwan_writev(struct lwan_request *request, struct iovec *iov, int iov_count) iov[curr_iov].iov_len -= (size_t)written; try_again: - coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); + coro_yield(request->conn->coro, CONN_CORO_YIELD); } out: @@ -89,8 +89,8 @@ lwan_readv(struct lwan_request *request, struct iovec *iov, int iov_count) switch (errno) { case EAGAIN: - request->conn->flags |= CONN_FLIP_FLAGS | CONN_MUST_READ; - /* fallthrough */ + coro_yield(request->conn->coro, CONN_CORO_WANT_READ); + continue; case EINTR: goto try_again; default: @@ -112,7 +112,7 @@ lwan_readv(struct lwan_request *request, struct iovec *iov, int iov_count) iov[curr_iov].iov_len -= (size_t)bytes_read; try_again: - coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); + coro_yield(request->conn->coro, CONN_CORO_YIELD); } out: @@ -132,8 +132,8 @@ lwan_send(struct lwan_request *request, const void *buf, size_t count, int flags switch (errno) { case EAGAIN: - request->conn->flags |= CONN_FLIP_FLAGS; - /* fallthrough */ + coro_yield(request->conn->coro, CONN_CORO_WANT_WRITE); + continue; case EINTR: goto try_again; default: @@ -148,7 +148,7 @@ lwan_send(struct lwan_request *request, const void *buf, size_t count, int flags buf = (char *)buf + written; try_again: - coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); + coro_yield(request->conn->coro, CONN_CORO_YIELD); } out: @@ -168,8 +168,8 @@ lwan_recv(struct lwan_request *request, void *buf, size_t count, int flags) switch (errno) { case EAGAIN: - request->conn->flags |= CONN_FLIP_FLAGS | CONN_MUST_READ; - /* fallthrough */ + coro_yield(request->conn->coro, CONN_CORO_WANT_READ); + continue; case EINTR: goto try_again; default: @@ -184,7 +184,7 @@ lwan_recv(struct lwan_request *request, void *buf, size_t count, int flags) buf = (char *)buf + recvd; try_again: - coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); + coro_yield(request->conn->coro, CONN_CORO_YIELD); } out: @@ -212,12 +212,10 @@ lwan_sendfile(struct lwan_request *request, int in_fd, off_t offset, size_t coun if (written < 0) { switch (errno) { case EAGAIN: - request->conn->flags |= CONN_FLIP_FLAGS; - /* fallthrough */ - case EINTR: - coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); + coro_yield(request->conn->coro, CONN_CORO_WANT_WRITE); continue; - + case EINTR: + goto try_again; default: coro_yield(request->conn->coro, CONN_CORO_ABORT); __builtin_unreachable(); @@ -230,8 +228,9 @@ lwan_sendfile(struct lwan_request *request, int in_fd, off_t offset, size_t coun chunk_size = min_size(to_be_written, 1<<19); lwan_readahead_queue(in_fd, offset, chunk_size); - request->conn->flags |= CONN_FLIP_FLAGS; - coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); + +try_again: + coro_yield(request->conn->coro, CONN_CORO_YIELD); } } #elif defined(__FreeBSD__) || defined(__APPLE__) @@ -263,13 +262,11 @@ lwan_sendfile(struct lwan_request *request, int in_fd, off_t offset, size_t coun if (UNLIKELY(r < 0)) { switch (errno) { case EAGAIN: - request->conn->flags |= CONN_FLIP_FLAGS; - /* fallthrough */ + coro_yield(request->conn->coro, CONN_CORO_WANT_WRITE); + continue; case EBUSY: case EINTR: - coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); - continue; - + goto try_again; default: coro_yield(request->conn->coro, CONN_CORO_ABORT); __builtin_unreachable(); @@ -278,7 +275,8 @@ lwan_sendfile(struct lwan_request *request, int in_fd, off_t offset, size_t coun total_written += (size_t)sbytes; - coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); +try_again: + coro_yield(request->conn->coro, CONN_CORO_YIELD); } while (total_written < count); } #else @@ -300,8 +298,8 @@ static off_t try_pread(struct lwan_request *request, switch (errno) { case EAGAIN: - request->conn->flags |= CONN_FLIP_FLAGS; - /* fallthrough */ + coro_yield(request->conn->coro, CONN_CORO_WANT_READ); + continue; case EINTR: goto try_again; default: @@ -313,8 +311,9 @@ static off_t try_pread(struct lwan_request *request, offset += r; if ((size_t)total_read == len) return offset; - try_again: - coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); + +try_again: + coro_yield(request->conn->coro, CONN_CORO_YIELD); } out: diff --git a/src/lib/lwan-mod-lua.c b/src/lib/lwan-mod-lua.c index 924771e48..9ef6b99c4 100644 --- a/src/lib/lwan-mod-lua.c +++ b/src/lib/lwan-mod-lua.c @@ -202,7 +202,7 @@ static enum lwan_http_status lua_handle_request(struct lwan_request *request, while (true) { switch (lua_resume(L, n_arguments)) { case LUA_YIELD: - coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); + coro_yield(request->conn->coro, CONN_CORO_YIELD); n_arguments = 0; break; case 0: diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index d199461c2..5d0170152 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -825,13 +825,11 @@ read_from_request_socket(struct lwan_request *request, if (UNLIKELY(n < 0)) { switch (errno) { case EAGAIN: - request->conn->flags |= CONN_FLIP_FLAGS; - coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); - /* fallthrough */ + coro_yield(request->conn->coro, CONN_CORO_WANT_READ); + continue; case EINTR: yield_and_read_again: - request->conn->flags |= CONN_MUST_READ; - coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); + coro_yield(request->conn->coro, CONN_CORO_YIELD); continue; } @@ -850,7 +848,6 @@ read_from_request_socket(struct lwan_request *request, try_to_finalize: switch (finalizer(total_read, buffer_size, request, n_packets)) { case FINALIZER_DONE: - request->conn->flags &= ~CONN_MUST_READ; buffer->value[buffer->len] = '\0'; #if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) @@ -1285,11 +1282,11 @@ lwan_request_websocket_upgrade(struct lwan_request *request) {}, }); if (LIKELY(header_buf_len)) { - request->conn->flags |= (CONN_FLIP_FLAGS | CONN_IS_WEBSOCKET); + request->conn->flags |= CONN_IS_WEBSOCKET; lwan_send(request, header_buf, header_buf_len, 0); - coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); + coro_yield(request->conn->coro, CONN_CORO_WANT_READ_WRITE); return HTTP_SWITCHING_PROTOCOLS; } @@ -1552,8 +1549,10 @@ static void remove_sleep(void *data1, void *data2) struct lwan_request *request = container_of(timeout, struct lwan_request, timeout); - if (request->conn->flags & CONN_SUSPENDED_BY_TIMER) + if (request->conn->flags & CONN_SUSPENDED_TIMER) { timeouts_del(wheel, timeout); + request->conn->flags &= ~CONN_SUSPENDED_TIMER; + } } void lwan_request_sleep(struct lwan_request *request, uint64_t ms) @@ -1561,16 +1560,10 @@ void lwan_request_sleep(struct lwan_request *request, uint64_t ms) struct lwan_connection *conn = request->conn; struct timeouts *wheel = conn->thread->wheel; - assert(!(conn->flags & CONN_SUSPENDED_BY_TIMER)); - conn->flags |= CONN_SUSPENDED_BY_TIMER; - request->timeout = (struct timeout) {}; timeouts_add(wheel, &request->timeout, ms); coro_defer2(conn->coro, remove_sleep, wheel, &request->timeout); - coro_yield(conn->coro, CONN_CORO_MAY_RESUME); - - assert(!(conn->flags & CONN_SUSPENDED_BY_TIMER)); - assert(!(conn->flags & CONN_RESUMED_FROM_TIMER)); + coro_yield(conn->coro, CONN_CORO_SUSPEND_TIMER); } ALWAYS_INLINE int diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index a7618c8f7..bd5e7ad42 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -396,7 +396,6 @@ bool lwan_response_set_chunked(struct lwan_request *request, return false; request->flags |= RESPONSE_SENT_HEADERS; - request->conn->flags |= CONN_FLIP_FLAGS; lwan_send(request, buffer, buffer_len, MSG_MORE); return true; @@ -436,7 +435,7 @@ void lwan_response_send_chunk(struct lwan_request *request) lwan_writev(request, chunk_vec, N_ELEMENTS(chunk_vec)); lwan_strbuf_reset(request->response.buffer); - coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); + coro_yield(request->conn->coro, CONN_CORO_WANT_WRITE); } bool lwan_response_set_event_stream(struct lwan_request *request, @@ -456,7 +455,6 @@ bool lwan_response_set_event_stream(struct lwan_request *request, return false; request->flags |= RESPONSE_SENT_HEADERS; - request->conn->flags |= CONN_FLIP_FLAGS; lwan_send(request, buffer, buffer_len, MSG_MORE); return true; @@ -507,8 +505,7 @@ void lwan_response_send_event(struct lwan_request *request, const char *event) lwan_writev(request, vec, last); lwan_strbuf_reset(request->response.buffer); - - coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); + coro_yield(request->conn->coro, CONN_CORO_WANT_WRITE); } enum ws_opcode { @@ -686,7 +683,7 @@ bool lwan_response_websocket_read(struct lwan_request *request) } if (continuation && !fin) { - coro_yield(request->conn->coro, CONN_CORO_MAY_RESUME); + coro_yield(request->conn->coro, CONN_CORO_WANT_READ); continuation = false; goto next_frame; diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index a075222e4..86819126f 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -38,11 +38,6 @@ #include "lwan-dq.h" #include "list.h" -static const uint32_t events_by_write_flag[] = { - EPOLLOUT | EPOLLRDHUP | EPOLLERR, - EPOLLIN | EPOLLRDHUP | EPOLLERR -}; - static ALWAYS_INLINE int min(const int a, const int b) { return a < b ? a : b; } #define REQUEST_FLAG(bool_, name_) \ @@ -98,10 +93,11 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, coro_deferred_run(coro, generation); if (LIKELY(conn->flags & CONN_KEEP_ALIVE)) { - if (next_request && *next_request) - conn->flags |= CONN_FLIP_FLAGS; - - coro_yield(coro, CONN_CORO_MAY_RESUME); + if (next_request && *next_request) { + coro_yield(coro, CONN_CORO_WANT_WRITE); + } else { + coro_yield(coro, CONN_CORO_WANT_READ); + } } else { shutdown(fd, SHUT_RDWR); coro_yield(coro, CONN_CORO_ABORT); @@ -114,43 +110,61 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, #undef REQUEST_FLAG -static void update_epoll_flags(struct death_queue *dq, +static ALWAYS_INLINE uint32_t +conn_flags_to_epoll_events(enum lwan_connection_flags flags) +{ + static const uint32_t map[CONN_EVENTS_MASK + 1] = { + [0 /* Suspended by timer */] = EPOLLRDHUP, + [CONN_EVENTS_WRITE] = EPOLLOUT | EPOLLRDHUP, + [CONN_EVENTS_READ] = EPOLLIN | EPOLLRDHUP, + [CONN_EVENTS_READ_WRITE] = EPOLLIN | EPOLLOUT | EPOLLRDHUP, + }; + + return map[flags & CONN_EVENTS_MASK]; +} + +static void update_epoll_flags(int fd, struct lwan_connection *conn, int epoll_fd, enum lwan_connection_coro_yield yield_result) { - uint32_t events = 0; - bool write_events; - - if (UNLIKELY(conn->flags & CONN_RESUMED_FROM_TIMER)) { - conn->flags &= ~(CONN_RESUMED_FROM_TIMER | CONN_WRITE_EVENTS); - write_events = false; - } else if (UNLIKELY(conn->flags & CONN_SUSPENDED_BY_TIMER)) { - /* CONN_WRITE_EVENTS shouldn't be flipped in this case. */ - events = EPOLLERR | EPOLLRDHUP; - } else if (conn->flags & CONN_MUST_READ) { - write_events = true; - } else { - bool should_resume_coro = (yield_result == CONN_CORO_MAY_RESUME); - - if (should_resume_coro) - conn->flags |= CONN_SHOULD_RESUME_CORO; - else - conn->flags &= ~CONN_SHOULD_RESUME_CORO; + static const enum lwan_connection_flags set_flags[CONN_CORO_MAX] = { + [CONN_CORO_WANT_READ_WRITE] = CONN_EVENTS_READ_WRITE, + [CONN_CORO_WANT_READ] = CONN_EVENTS_READ, + [CONN_CORO_WANT_WRITE] = CONN_EVENTS_WRITE, + + /* While the coro is suspended, we're not interested in either EPOLLIN + * or EPOLLOUT events. We still want to track this fd in epoll, though, + * so unset both so that only EPOLLRDHUP (plus the implicitly-set ones) + * are set. */ + [CONN_CORO_SUSPEND_TIMER] = CONN_SUSPENDED_TIMER, + + /* Either EPOLLIN or EPOLLOUT have to be set here. There's no need to + * know which event, because they were both cleared when the coro was + * suspended. So set both flags here. This works because EPOLLET isn't + * used. */ + [CONN_CORO_RESUME_TIMER] = CONN_EVENTS_READ_WRITE, + }; + static const enum lwan_connection_flags reset_flags[CONN_CORO_MAX] = { + [CONN_CORO_WANT_READ_WRITE] = ~0, + [CONN_CORO_WANT_READ] = ~CONN_EVENTS_WRITE, + [CONN_CORO_WANT_WRITE] = ~CONN_EVENTS_READ, + [CONN_CORO_SUSPEND_TIMER] = ~CONN_EVENTS_READ_WRITE, + [CONN_CORO_RESUME_TIMER] = ~CONN_SUSPENDED_TIMER, + }; + enum lwan_connection_flags prev_flags = conn->flags; - write_events = (conn->flags & CONN_WRITE_EVENTS); - if (should_resume_coro == write_events) - return; - } + conn->flags |= set_flags[yield_result]; + conn->flags &= reset_flags[yield_result]; - if (LIKELY(!events)) { - events = events_by_write_flag[write_events]; - conn->flags ^= CONN_WRITE_EVENTS; - } + if (conn->flags == prev_flags) + return; - struct epoll_event event = {.events = events, .data.ptr = conn}; + struct epoll_event event = { + .events = conn_flags_to_epoll_events(conn->flags), + .data.ptr = conn, + }; - int fd = lwan_connection_get_fd(dq->lwan, conn); if (UNLIKELY(epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &event) < 0)) lwan_status_perror("epoll_ctl"); } @@ -159,24 +173,22 @@ static ALWAYS_INLINE void resume_coro_if_needed(struct death_queue *dq, struct lwan_connection *conn, int epoll_fd) { - const enum lwan_connection_flags update_mask = - CONN_FLIP_FLAGS | CONN_RESUMED_FROM_TIMER | CONN_SUSPENDED_BY_TIMER; - assert(conn->coro); if (!(conn->flags & CONN_SHOULD_RESUME_CORO)) return; enum lwan_connection_coro_yield yield_result = coro_resume(conn->coro); - /* CONN_CORO_ABORT is -1, but comparing with 0 is cheaper */ - if (UNLIKELY(yield_result < CONN_CORO_MAY_RESUME)) { + + switch (yield_result) { + case CONN_CORO_ABORT: death_queue_kill(dq, conn); + /* fallthrough */ + case CONN_CORO_YIELD: return; - } - - if (conn->flags & update_mask) { - conn->flags &= ~CONN_FLIP_FLAGS; - update_epoll_flags(dq, conn, epoll_fd, yield_result); + default: + update_epoll_flags(lwan_connection_get_fd(dq->lwan, conn), conn, + epoll_fd, yield_result); } } @@ -204,7 +216,7 @@ static ALWAYS_INLINE void spawn_coro(struct lwan_connection *conn, *conn = (struct lwan_connection) { .coro = coro_new(switcher, process_request_coro, conn), - .flags = CONN_IS_ALIVE | CONN_SHOULD_RESUME_CORO, + .flags = CONN_IS_ALIVE | CONN_SHOULD_RESUME_CORO | CONN_EVENTS_READ, .time_to_die = dq->time + dq->keep_alive_timeout, .thread = t, }; @@ -234,7 +246,7 @@ static void accept_nudge(int pipe_fd, while (spsc_queue_pop(&t->pending_fds, &new_fd)) { struct lwan_connection *conn = &conns[new_fd]; - struct epoll_event ev = {.events = events_by_write_flag[1], + struct epoll_event ev = {.events = EPOLLIN | EPOLLRDHUP, .data.ptr = conn}; if (LIKELY(!epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_fd, &ev))) @@ -262,9 +274,8 @@ static bool process_pending_timers(struct death_queue *dq, request = container_of(timeout, struct lwan_request, timeout); - request->conn->flags &= ~CONN_SUSPENDED_BY_TIMER; - request->conn->flags |= CONN_RESUMED_FROM_TIMER; - update_epoll_flags(dq, request->conn, epoll_fd, CONN_CORO_MAY_RESUME); + update_epoll_flags(request->fd, request->conn, epoll_fd, + CONN_CORO_RESUME_TIMER); } if (processed_dq_timeout) { diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 6e25d3723..81997676d 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -275,22 +275,32 @@ static_assert(REQUEST_ACCEPT_DEFLATE > REQUEST_METHOD_MASK, enum lwan_connection_flags { CONN_MASK = -1, - CONN_KEEP_ALIVE = 1 << 0, - CONN_IS_ALIVE = 1 << 1, - CONN_SHOULD_RESUME_CORO = 1 << 2, - CONN_WRITE_EVENTS = 1 << 3, - CONN_MUST_READ = 1 << 4, - CONN_SUSPENDED_BY_TIMER = 1 << 5, - CONN_RESUMED_FROM_TIMER = 1 << 6, - CONN_FLIP_FLAGS = 1 << 7, - CONN_IS_UPGRADE = 1 << 8, - CONN_IS_WEBSOCKET = 1 << 9, + + /* These flags have smaller numbers so that the table to convert + * them to epoll events is smaller. See conn_flags_to_epoll_events(). */ + CONN_EVENTS_READ = 1 << 0, + CONN_EVENTS_WRITE = 1 << 1, + CONN_EVENTS_READ_WRITE = 1 << 0 | 1 << 1, + CONN_EVENTS_MASK = 1 << 0 | 1 << 1, + + CONN_KEEP_ALIVE = 1 << 2, + CONN_IS_ALIVE = 1 << 3, + CONN_SHOULD_RESUME_CORO = 1 << 4, + CONN_IS_UPGRADE = 1 << 5, + CONN_IS_WEBSOCKET = 1 << 6, + + CONN_SUSPENDED_TIMER = 1 << 7, }; enum lwan_connection_coro_yield { - CONN_CORO_ABORT = -1, - CONN_CORO_MAY_RESUME = 0, - CONN_CORO_FINISHED = 1 + CONN_CORO_ABORT, + CONN_CORO_YIELD, + CONN_CORO_WANT_READ, + CONN_CORO_WANT_WRITE, + CONN_CORO_WANT_READ_WRITE, + CONN_CORO_SUSPEND_TIMER, + CONN_CORO_RESUME_TIMER, + CONN_CORO_MAX, }; struct lwan_key_value { diff --git a/src/lib/missing.c b/src/lib/missing.c index fee52bfbc..0b4a2a449 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -164,10 +164,9 @@ int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) if (event->events & EPOLLONESHOT) flags |= EV_ONESHOT; - if (event->events & EPOLLRDHUP) - flags |= EV_EOF; - if (event->events & EPOLLERR) - flags |= EV_ERROR; + + flags |= EV_ERROR; /* EPOLLERR is always set. */ + flags |= EV_EOF; /* EPOLLHUP is always set. */ EV_SET(&ev, fd, events, flags, 0, 0, (void *)udata); break; From a9be192f8c12b4b940cb2013674908120770be73 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 2 Jun 2019 21:19:07 -0700 Subject: [PATCH 1081/2505] Add forgotten __builtin_unreachable() after yield(CONN_CORO_ABORT) --- src/lib/lwan-thread.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 86819126f..55aeefd76 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -101,6 +101,7 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, } else { shutdown(fd, SHUT_RDWR); coro_yield(coro, CONN_CORO_ABORT); + __builtin_unreachable(); } lwan_strbuf_reset(&strbuf); From 61e6f6d5f01ed394979ee914643577585ff1ac9d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 3 Jun 2019 17:32:29 -0700 Subject: [PATCH 1082/2505] Centralize new connection epoll flags calculation Not only this makes it easier to change the epoll flags for any given set of CONN_EVENTS flags (however unlikely), it also makes the intent clear that the epoll events mask should be in sync with the connection flags from the get go. --- src/lib/lwan-thread.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 55aeefd76..da5a5ec3e 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -247,8 +247,13 @@ static void accept_nudge(int pipe_fd, while (spsc_queue_pop(&t->pending_fds, &new_fd)) { struct lwan_connection *conn = &conns[new_fd]; - struct epoll_event ev = {.events = EPOLLIN | EPOLLRDHUP, - .data.ptr = conn}; + struct epoll_event ev = { + .data.ptr = conn, + + /* Actual connection flags will be set by spawn_coro(), but + * CONN_EVENTS_READ is the only thing needed here. */ + .events = conn_flags_to_epoll_events(CONN_EVENTS_READ), + }; if (LIKELY(!epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_fd, &ev))) spawn_coro(conn, switcher, dq); From af52ef7b37caf8adda8c33993ceede1775b9d51b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 3 Jun 2019 17:39:06 -0700 Subject: [PATCH 1083/2505] Clarify intent of CONN_SUSPENDED_TIMER --- src/lib/lwan-request.c | 4 +--- src/lib/lwan.h | 2 ++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 5d0170152..3587fc94d 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1549,10 +1549,8 @@ static void remove_sleep(void *data1, void *data2) struct lwan_request *request = container_of(timeout, struct lwan_request, timeout); - if (request->conn->flags & CONN_SUSPENDED_TIMER) { + if (request->conn->flags & CONN_SUSPENDED_TIMER) timeouts_del(wheel, timeout); - request->conn->flags &= ~CONN_SUSPENDED_TIMER; - } } void lwan_request_sleep(struct lwan_request *request, uint64_t ms) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 81997676d..20c2b5c80 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -289,6 +289,8 @@ enum lwan_connection_flags { CONN_IS_UPGRADE = 1 << 5, CONN_IS_WEBSOCKET = 1 << 6, + /* This is only used to determine if timeout_del() is necessary when + * the connection coro ends. */ CONN_SUSPENDED_TIMER = 1 << 7, }; From 02f60dafa93f350acab64e36b8d1126fa0676838 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 3 Jun 2019 17:39:19 -0700 Subject: [PATCH 1084/2505] Sename set_flags/reset_flags to or_mask/and_mask --- src/lib/lwan-thread.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index da5a5ec3e..3a19ecc36 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -129,7 +129,7 @@ static void update_epoll_flags(int fd, int epoll_fd, enum lwan_connection_coro_yield yield_result) { - static const enum lwan_connection_flags set_flags[CONN_CORO_MAX] = { + static const enum lwan_connection_flags or_mask[CONN_CORO_MAX] = { [CONN_CORO_WANT_READ_WRITE] = CONN_EVENTS_READ_WRITE, [CONN_CORO_WANT_READ] = CONN_EVENTS_READ, [CONN_CORO_WANT_WRITE] = CONN_EVENTS_WRITE, @@ -146,7 +146,7 @@ static void update_epoll_flags(int fd, * used. */ [CONN_CORO_RESUME_TIMER] = CONN_EVENTS_READ_WRITE, }; - static const enum lwan_connection_flags reset_flags[CONN_CORO_MAX] = { + static const enum lwan_connection_flags and_mask[CONN_CORO_MAX] = { [CONN_CORO_WANT_READ_WRITE] = ~0, [CONN_CORO_WANT_READ] = ~CONN_EVENTS_WRITE, [CONN_CORO_WANT_WRITE] = ~CONN_EVENTS_READ, @@ -155,8 +155,8 @@ static void update_epoll_flags(int fd, }; enum lwan_connection_flags prev_flags = conn->flags; - conn->flags |= set_flags[yield_result]; - conn->flags &= reset_flags[yield_result]; + conn->flags |= or_mask[yield_result]; + conn->flags &= and_mask[yield_result]; if (conn->flags == prev_flags) return; From e0b4549482e7d8bf3bca5a821e4f43c3d370bfa6 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 3 Jun 2019 17:49:09 -0700 Subject: [PATCH 1085/2505] Remove one branch from resume_coro_if_needed() Leave only the comparison with 0 (CONN_CORO_ABORT), which will happen with every request. The early return in update_epoll_flags() will make sure no flags get changed when a conneciton coro just yields. --- src/lib/lwan-thread.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 3a19ecc36..0ede52cf8 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -130,6 +130,7 @@ static void update_epoll_flags(int fd, enum lwan_connection_coro_yield yield_result) { static const enum lwan_connection_flags or_mask[CONN_CORO_MAX] = { + [CONN_CORO_YIELD] = 0, [CONN_CORO_WANT_READ_WRITE] = CONN_EVENTS_READ_WRITE, [CONN_CORO_WANT_READ] = CONN_EVENTS_READ, [CONN_CORO_WANT_WRITE] = CONN_EVENTS_WRITE, @@ -147,6 +148,7 @@ static void update_epoll_flags(int fd, [CONN_CORO_RESUME_TIMER] = CONN_EVENTS_READ_WRITE, }; static const enum lwan_connection_flags and_mask[CONN_CORO_MAX] = { + [CONN_CORO_YIELD] = ~0, [CONN_CORO_WANT_READ_WRITE] = ~0, [CONN_CORO_WANT_READ] = ~CONN_EVENTS_WRITE, [CONN_CORO_WANT_WRITE] = ~CONN_EVENTS_READ, @@ -180,17 +182,13 @@ static ALWAYS_INLINE void resume_coro_if_needed(struct death_queue *dq, return; enum lwan_connection_coro_yield yield_result = coro_resume(conn->coro); - - switch (yield_result) { - case CONN_CORO_ABORT: + if (yield_result == CONN_CORO_ABORT) { death_queue_kill(dq, conn); - /* fallthrough */ - case CONN_CORO_YIELD: return; - default: - update_epoll_flags(lwan_connection_get_fd(dq->lwan, conn), conn, - epoll_fd, yield_result); } + + update_epoll_flags(lwan_connection_get_fd(dq->lwan, conn), conn, epoll_fd, + yield_result); } static void update_date_cache(struct lwan_thread *thread) From 867e4830df3dc1ed580a5bf3c9625f5bc5395531 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 4 Jun 2019 21:58:04 -0700 Subject: [PATCH 1086/2505] Simplify I/O wrapper yield values Since it's now the same cost to YIELD or WANT_WRITE/WANT_READ, just yield with whatever's most appropriate for that particular wrapper regardless of the occasion: handling EINTR, EAGAIN, or one of the tentative steps to transmit/receive the data. --- src/lib/lwan-io-wrappers.c | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index 2195a0c5a..97c63c84f 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -44,8 +44,6 @@ lwan_writev(struct lwan_request *request, struct iovec *iov, int iov_count) switch (errno) { case EAGAIN: - coro_yield(request->conn->coro, CONN_CORO_WANT_WRITE); - continue; case EINTR: goto try_again; default: @@ -67,7 +65,7 @@ lwan_writev(struct lwan_request *request, struct iovec *iov, int iov_count) iov[curr_iov].iov_len -= (size_t)written; try_again: - coro_yield(request->conn->coro, CONN_CORO_YIELD); + coro_yield(request->conn->coro, CONN_CORO_WANT_WRITE); } out: @@ -89,8 +87,6 @@ lwan_readv(struct lwan_request *request, struct iovec *iov, int iov_count) switch (errno) { case EAGAIN: - coro_yield(request->conn->coro, CONN_CORO_WANT_READ); - continue; case EINTR: goto try_again; default: @@ -112,7 +108,7 @@ lwan_readv(struct lwan_request *request, struct iovec *iov, int iov_count) iov[curr_iov].iov_len -= (size_t)bytes_read; try_again: - coro_yield(request->conn->coro, CONN_CORO_YIELD); + coro_yield(request->conn->coro, CONN_CORO_WANT_READ); } out: @@ -132,8 +128,6 @@ lwan_send(struct lwan_request *request, const void *buf, size_t count, int flags switch (errno) { case EAGAIN: - coro_yield(request->conn->coro, CONN_CORO_WANT_WRITE); - continue; case EINTR: goto try_again; default: @@ -148,7 +142,7 @@ lwan_send(struct lwan_request *request, const void *buf, size_t count, int flags buf = (char *)buf + written; try_again: - coro_yield(request->conn->coro, CONN_CORO_YIELD); + coro_yield(request->conn->coro, CONN_CORO_WANT_WRITE); } out: @@ -168,8 +162,6 @@ lwan_recv(struct lwan_request *request, void *buf, size_t count, int flags) switch (errno) { case EAGAIN: - coro_yield(request->conn->coro, CONN_CORO_WANT_READ); - continue; case EINTR: goto try_again; default: @@ -184,7 +176,7 @@ lwan_recv(struct lwan_request *request, void *buf, size_t count, int flags) buf = (char *)buf + recvd; try_again: - coro_yield(request->conn->coro, CONN_CORO_YIELD); + coro_yield(request->conn->coro, CONN_CORO_WANT_READ); } out: @@ -212,8 +204,6 @@ lwan_sendfile(struct lwan_request *request, int in_fd, off_t offset, size_t coun if (written < 0) { switch (errno) { case EAGAIN: - coro_yield(request->conn->coro, CONN_CORO_WANT_WRITE); - continue; case EINTR: goto try_again; default: @@ -230,7 +220,7 @@ lwan_sendfile(struct lwan_request *request, int in_fd, off_t offset, size_t coun lwan_readahead_queue(in_fd, offset, chunk_size); try_again: - coro_yield(request->conn->coro, CONN_CORO_YIELD); + coro_yield(request->conn->coro, CONN_CORO_WANT_WRITE); } } #elif defined(__FreeBSD__) || defined(__APPLE__) @@ -262,8 +252,6 @@ lwan_sendfile(struct lwan_request *request, int in_fd, off_t offset, size_t coun if (UNLIKELY(r < 0)) { switch (errno) { case EAGAIN: - coro_yield(request->conn->coro, CONN_CORO_WANT_WRITE); - continue; case EBUSY: case EINTR: goto try_again; @@ -276,7 +264,7 @@ lwan_sendfile(struct lwan_request *request, int in_fd, off_t offset, size_t coun total_written += (size_t)sbytes; try_again: - coro_yield(request->conn->coro, CONN_CORO_YIELD); + coro_yield(request->conn->coro, CONN_CORO_WANT_WRITE); } while (total_written < count); } #else @@ -298,8 +286,6 @@ static off_t try_pread(struct lwan_request *request, switch (errno) { case EAGAIN: - coro_yield(request->conn->coro, CONN_CORO_WANT_READ); - continue; case EINTR: goto try_again; default: @@ -313,7 +299,10 @@ static off_t try_pread(struct lwan_request *request, return offset; try_again: - coro_yield(request->conn->coro, CONN_CORO_YIELD); + /* FIXME: is this correct? fd being read here is a file, not + * a socket, so a WANT_READ may actually lead to a coro never + * being woken up */ + coro_yield(request->conn->coro, CONN_CORO_WANT_READ); } out: From 67c7f48214bc52e3bdef9df54d8f22f7a7f94b29 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 4 Jun 2019 22:02:27 -0700 Subject: [PATCH 1087/2505] Reindent lwan-io-wrappers.c --- src/lib/lwan-io-wrappers.c | 86 ++++++++++++++++++++------------------ 1 file changed, 46 insertions(+), 40 deletions(-) diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index 97c63c84f..69f8f0401 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -14,19 +14,20 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include -#include -#include -#include #include +#include +#include +#include -#include "lwan-private.h" #include "lwan-io-wrappers.h" +#include "lwan-private.h" static const int MAX_FAILED_TRIES = 5; @@ -37,7 +38,8 @@ lwan_writev(struct lwan_request *request, struct iovec *iov, int iov_count) int curr_iov = 0; for (int tries = MAX_FAILED_TRIES; tries;) { - ssize_t written = writev(request->fd, iov + curr_iov, iov_count - curr_iov); + ssize_t written = + writev(request->fd, iov + curr_iov, iov_count - curr_iov); if (UNLIKELY(written < 0)) { /* FIXME: Consider short writes as another try as well? */ tries--; @@ -53,7 +55,8 @@ lwan_writev(struct lwan_request *request, struct iovec *iov, int iov_count) total_written += written; - while (curr_iov < iov_count && written >= (ssize_t)iov[curr_iov].iov_len) { + while (curr_iov < iov_count && + written >= (ssize_t)iov[curr_iov].iov_len) { written -= (ssize_t)iov[curr_iov].iov_len; curr_iov++; } @@ -64,7 +67,7 @@ lwan_writev(struct lwan_request *request, struct iovec *iov, int iov_count) iov[curr_iov].iov_base = (char *)iov[curr_iov].iov_base + written; iov[curr_iov].iov_len -= (size_t)written; -try_again: + try_again: coro_yield(request->conn->coro, CONN_CORO_WANT_WRITE); } @@ -80,7 +83,8 @@ lwan_readv(struct lwan_request *request, struct iovec *iov, int iov_count) int curr_iov = 0; for (int tries = MAX_FAILED_TRIES; tries;) { - ssize_t bytes_read = readv(request->fd, iov + curr_iov, iov_count - curr_iov); + ssize_t bytes_read = + readv(request->fd, iov + curr_iov, iov_count - curr_iov); if (UNLIKELY(bytes_read < 0)) { /* FIXME: Consider short writes as another try as well? */ tries--; @@ -96,7 +100,8 @@ lwan_readv(struct lwan_request *request, struct iovec *iov, int iov_count) total_bytes_read += bytes_read; - while (curr_iov < iov_count && bytes_read >= (ssize_t)iov[curr_iov].iov_len) { + while (curr_iov < iov_count && + bytes_read >= (ssize_t)iov[curr_iov].iov_len) { bytes_read -= (ssize_t)iov[curr_iov].iov_len; curr_iov++; } @@ -107,7 +112,7 @@ lwan_readv(struct lwan_request *request, struct iovec *iov, int iov_count) iov[curr_iov].iov_base = (char *)iov[curr_iov].iov_base + bytes_read; iov[curr_iov].iov_len -= (size_t)bytes_read; -try_again: + try_again: coro_yield(request->conn->coro, CONN_CORO_WANT_READ); } @@ -116,8 +121,10 @@ lwan_readv(struct lwan_request *request, struct iovec *iov, int iov_count) __builtin_unreachable(); } -ssize_t -lwan_send(struct lwan_request *request, const void *buf, size_t count, int flags) +ssize_t lwan_send(struct lwan_request *request, + const void *buf, + size_t count, + int flags) { ssize_t total_sent = 0; @@ -141,7 +148,7 @@ lwan_send(struct lwan_request *request, const void *buf, size_t count, int flags if ((size_t)total_sent < count) buf = (char *)buf + written; -try_again: + try_again: coro_yield(request->conn->coro, CONN_CORO_WANT_WRITE); } @@ -175,7 +182,7 @@ lwan_recv(struct lwan_request *request, void *buf, size_t count, int flags) if ((size_t)total_recv < count) buf = (char *)buf + recvd; -try_again: + try_again: coro_yield(request->conn->coro, CONN_CORO_WANT_READ); } @@ -185,16 +192,16 @@ lwan_recv(struct lwan_request *request, void *buf, size_t count, int flags) } #if defined(__linux__) -static inline size_t min_size(size_t a, size_t b) -{ - return (a > b) ? b : a; -} +static inline size_t min_size(size_t a, size_t b) { return (a > b) ? b : a; } -void -lwan_sendfile(struct lwan_request *request, int in_fd, off_t offset, size_t count, - const char *header, size_t header_len) +void lwan_sendfile(struct lwan_request *request, + int in_fd, + off_t offset, + size_t count, + const char *header, + size_t header_len) { - size_t chunk_size = min_size(count, 1<<17); + size_t chunk_size = min_size(count, 1 << 17); size_t to_be_written = count; lwan_send(request, header, header_len, MSG_MORE); @@ -216,27 +223,25 @@ lwan_sendfile(struct lwan_request *request, int in_fd, off_t offset, size_t coun if (!to_be_written) break; - chunk_size = min_size(to_be_written, 1<<19); + chunk_size = min_size(to_be_written, 1 << 19); lwan_readahead_queue(in_fd, offset, chunk_size); -try_again: + try_again: coro_yield(request->conn->coro, CONN_CORO_WANT_WRITE); } } #elif defined(__FreeBSD__) || defined(__APPLE__) -void -lwan_sendfile(struct lwan_request *request, int in_fd, off_t offset, size_t count, - const char *header, size_t header_len) +void lwan_sendfile(struct lwan_request *request, + int in_fd, + off_t offset, + size_t count, + const char *header, + size_t header_len) { - struct sf_hdtr headers = { - .headers = (struct iovec[]) { - { - .iov_base = (void *)header, - .iov_len = header_len - } - }, - .hdr_cnt = 1 - }; + struct sf_hdtr headers = {.headers = + (struct iovec[]){{.iov_base = (void *)header, + .iov_len = header_len}}, + .hdr_cnt = 1}; size_t total_written = 0; off_t sbytes = (off_t)count; @@ -246,7 +251,8 @@ lwan_sendfile(struct lwan_request *request, int in_fd, off_t offset, size_t coun #ifdef __APPLE__ r = sendfile(in_fd, request->fd, offset, &sbytes, &headers, 0); #else - r = sendfile(in_fd, request->fd, offset, count, &headers, &sbytes, SF_MNOWAIT); + r = sendfile(in_fd, request->fd, offset, count, &headers, &sbytes, + SF_MNOWAIT); #endif if (UNLIKELY(r < 0)) { @@ -263,7 +269,7 @@ lwan_sendfile(struct lwan_request *request, int in_fd, off_t offset, size_t coun total_written += (size_t)sbytes; -try_again: + try_again: coro_yield(request->conn->coro, CONN_CORO_WANT_WRITE); } while (total_written < count); } @@ -298,7 +304,7 @@ static off_t try_pread(struct lwan_request *request, if ((size_t)total_read == len) return offset; -try_again: + try_again: /* FIXME: is this correct? fd being read here is a file, not * a socket, so a WANT_READ may actually lead to a coro never * being woken up */ From 11a3605aaf89797b6eb02444861fea2e0c7b0479 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 6 Jun 2019 21:25:55 -0700 Subject: [PATCH 1088/2505] Simplify functions depending on lazy-parsing before value_lookup() Lazy-parsing can be performed by lwan_request_get_{query_params,post_params,cookies}() already, so just use them. --- src/lib/lwan-request.c | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 3587fc94d..6814c801e 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1447,34 +1447,19 @@ value_lookup(const struct lwan_key_value_array *array, const char *key) const char *lwan_request_get_query_param(struct lwan_request *request, const char *key) { - if (!(request->flags & REQUEST_PARSED_QUERY_STRING)) { - parse_query_string(request); - request->flags |= REQUEST_PARSED_QUERY_STRING; - } - - return value_lookup(&request->query_params, key); + return value_lookup(lwan_request_get_query_params(request), key); } const char *lwan_request_get_post_param(struct lwan_request *request, const char *key) { - if (!(request->flags & REQUEST_PARSED_POST_DATA)) { - parse_post_data(request); - request->flags |= REQUEST_PARSED_POST_DATA; - } - - return value_lookup(&request->post_params, key); + return value_lookup(lwan_request_get_post_params(request), key); } const char *lwan_request_get_cookie(struct lwan_request *request, const char *key) { - if (!(request->flags & REQUEST_PARSED_COOKIES)) { - parse_cookies(request); - request->flags |= REQUEST_PARSED_COOKIES; - } - - return value_lookup(&request->cookies, key); + return value_lookup(lwan_request_get_cookies(request), key); } const char *lwan_request_get_header(struct lwan_request *request, From 11aec62e4706686ccd1be5cd33858216e8b3b610 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 9 Jun 2019 18:55:22 -0700 Subject: [PATCH 1089/2505] Get rid of CONN_SHOULD_RESUME_CORO flag This was a refactoring leftover; nothing was unsetting this flag, so consider if a coroutine should be resumed if it's alive. --- src/lib/lwan-dq.c | 2 +- src/lib/lwan-thread.c | 4 ++-- src/lib/lwan.h | 7 +++---- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/lib/lwan-dq.c b/src/lib/lwan-dq.c index 8aa810a06..003d234f3 100644 --- a/src/lib/lwan-dq.c +++ b/src/lib/lwan-dq.c @@ -70,7 +70,7 @@ void death_queue_move_to_last(struct death_queue *dq, * resumed -- then just mark it to be reaped right away. */ conn->time_to_die = dq->time; - if (conn->flags & (CONN_KEEP_ALIVE | CONN_SHOULD_RESUME_CORO)) + if (conn->flags & (CONN_KEEP_ALIVE | CONN_IS_ALIVE)) conn->time_to_die += dq->keep_alive_timeout; death_queue_remove(dq, conn); diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 0ede52cf8..3c3abd770 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -178,7 +178,7 @@ static ALWAYS_INLINE void resume_coro_if_needed(struct death_queue *dq, { assert(conn->coro); - if (!(conn->flags & CONN_SHOULD_RESUME_CORO)) + if (!(conn->flags & CONN_IS_ALIVE)) return; enum lwan_connection_coro_yield yield_result = coro_resume(conn->coro); @@ -215,7 +215,7 @@ static ALWAYS_INLINE void spawn_coro(struct lwan_connection *conn, *conn = (struct lwan_connection) { .coro = coro_new(switcher, process_request_coro, conn), - .flags = CONN_IS_ALIVE | CONN_SHOULD_RESUME_CORO | CONN_EVENTS_READ, + .flags = CONN_IS_ALIVE | CONN_EVENTS_READ, .time_to_die = dq->time + dq->keep_alive_timeout, .thread = t, }; diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 20c2b5c80..0bc9388a4 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -285,13 +285,12 @@ enum lwan_connection_flags { CONN_KEEP_ALIVE = 1 << 2, CONN_IS_ALIVE = 1 << 3, - CONN_SHOULD_RESUME_CORO = 1 << 4, - CONN_IS_UPGRADE = 1 << 5, - CONN_IS_WEBSOCKET = 1 << 6, + CONN_IS_UPGRADE = 1 << 4, + CONN_IS_WEBSOCKET = 1 << 5, /* This is only used to determine if timeout_del() is necessary when * the connection coro ends. */ - CONN_SUSPENDED_TIMER = 1 << 7, + CONN_SUSPENDED_TIMER = 1 << 6, }; enum lwan_connection_coro_yield { From 1b5073a6d58c185574e2fe009e294bdd54911f07 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 10 Jun 2019 06:45:22 -0700 Subject: [PATCH 1090/2505] Consider a connection alive if it has an associated coroutine Get rid of CONN_IS_ALIVE, and rename CONN_KEEP_ALIVE to CONN_IS_KEEP_ALIVE. --- src/lib/lwan-dq.c | 20 ++++++-------------- src/lib/lwan-request.c | 4 ++-- src/lib/lwan-response.c | 2 +- src/lib/lwan-thread.c | 20 +++++--------------- src/lib/lwan.h | 9 ++++----- 5 files changed, 18 insertions(+), 37 deletions(-) diff --git a/src/lib/lwan-dq.c b/src/lib/lwan-dq.c index 003d234f3..48455eac6 100644 --- a/src/lib/lwan-dq.c +++ b/src/lib/lwan-dq.c @@ -61,17 +61,10 @@ bool death_queue_empty(struct death_queue *dq) { return dq->head.next < 0; } void death_queue_move_to_last(struct death_queue *dq, struct lwan_connection *conn) { - /* - * If the connection isn't keep alive, it might have a coroutine that - * should be resumed. If that's the case, schedule for this request to - * die according to the keep alive timeout. - * - * If it's not a keep alive connection, or the coroutine shouldn't be - * resumed -- then just mark it to be reaped right away. - */ - conn->time_to_die = dq->time; - if (conn->flags & (CONN_KEEP_ALIVE | CONN_IS_ALIVE)) - conn->time_to_die += dq->keep_alive_timeout; + /* CONN_IS_KEEP_ALIVE isn't checked here because non-keep-alive connections + * are closed in the request processing coroutine after they have been + * served. In practice, if this is called, it's a keep-alive connection. */ + conn->time_to_die = dq->time + dq->keep_alive_timeout; death_queue_remove(dq, conn); death_queue_insert(dq, conn); @@ -90,12 +83,11 @@ void death_queue_init(struct death_queue *dq, const struct lwan *lwan) void death_queue_kill(struct death_queue *dq, struct lwan_connection *conn) { death_queue_remove(dq, conn); + if (LIKELY(conn->coro)) { coro_free(conn->coro); conn->coro = NULL; - } - if (conn->flags & CONN_IS_ALIVE) { - conn->flags &= ~CONN_IS_ALIVE; + close(lwan_connection_get_fd(dq->lwan, conn)); } } diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 6814c801e..2ee875cde 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -737,9 +737,9 @@ static ALWAYS_INLINE void parse_connection_header(struct lwan_request *request) has_keep_alive = !has_close; if (has_keep_alive) - request->conn->flags |= CONN_KEEP_ALIVE; + request->conn->flags |= CONN_IS_KEEP_ALIVE; else - request->conn->flags &= ~CONN_KEEP_ALIVE; + request->conn->flags &= ~CONN_IS_KEEP_ALIVE; } #if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index bd5e7ad42..5a12cdb04 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -328,7 +328,7 @@ size_t lwan_prepare_response_header_full( if (request->conn->flags & CONN_IS_UPGRADE) { APPEND_CONSTANT("\r\nConnection: Upgrade"); } else { - if (request->conn->flags & CONN_KEEP_ALIVE) { + if (request->conn->flags & CONN_IS_KEEP_ALIVE) { APPEND_CONSTANT("\r\nConnection: keep-alive"); } else { APPEND_CONSTANT("\r\nConnection: close"); diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 3c3abd770..7975d5bdc 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -85,14 +85,12 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, .flags = flags, .proxy = &proxy}; - assert(conn->flags & CONN_IS_ALIVE); - size_t generation = coro_deferred_get_generation(coro); next_request = lwan_process_request(lwan, &request, &buffer, next_request); coro_deferred_run(coro, generation); - if (LIKELY(conn->flags & CONN_KEEP_ALIVE)) { + if (LIKELY(conn->flags & CONN_IS_KEEP_ALIVE)) { if (next_request && *next_request) { coro_yield(coro, CONN_CORO_WANT_WRITE); } else { @@ -172,15 +170,11 @@ static void update_epoll_flags(int fd, lwan_status_perror("epoll_ctl"); } -static ALWAYS_INLINE void resume_coro_if_needed(struct death_queue *dq, - struct lwan_connection *conn, - int epoll_fd) +static ALWAYS_INLINE void +resume_coro(struct death_queue *dq, struct lwan_connection *conn, int epoll_fd) { assert(conn->coro); - if (!(conn->flags & CONN_IS_ALIVE)) - return; - enum lwan_connection_coro_yield yield_result = coro_resume(conn->coro); if (yield_result == CONN_CORO_ABORT) { death_queue_kill(dq, conn); @@ -207,7 +201,6 @@ static ALWAYS_INLINE void spawn_coro(struct lwan_connection *conn, struct lwan_thread *t = conn->thread; assert(!conn->coro); - assert(!(conn->flags & CONN_IS_ALIVE)); assert(t); assert((uintptr_t)t >= (uintptr_t)dq->lwan->thread.threads); assert((uintptr_t)t < @@ -215,7 +208,7 @@ static ALWAYS_INLINE void spawn_coro(struct lwan_connection *conn, *conn = (struct lwan_connection) { .coro = coro_new(switcher, process_request_coro, conn), - .flags = CONN_IS_ALIVE | CONN_EVENTS_READ, + .flags = CONN_EVENTS_READ, .time_to_die = dq->time + dq->keep_alive_timeout, .thread = t, }; @@ -247,9 +240,6 @@ static void accept_nudge(int pipe_fd, struct lwan_connection *conn = &conns[new_fd]; struct epoll_event ev = { .data.ptr = conn, - - /* Actual connection flags will be set by spawn_coro(), but - * CONN_EVENTS_READ is the only thing needed here. */ .events = conn_flags_to_epoll_events(CONN_EVENTS_READ), }; @@ -381,7 +371,7 @@ static void *thread_io_loop(void *data) continue; } - resume_coro_if_needed(&dq, conn, epoll_fd); + resume_coro(&dq, conn, epoll_fd); death_queue_move_to_last(&dq, conn); } } diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 0bc9388a4..b24b43e26 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -283,14 +283,13 @@ enum lwan_connection_flags { CONN_EVENTS_READ_WRITE = 1 << 0 | 1 << 1, CONN_EVENTS_MASK = 1 << 0 | 1 << 1, - CONN_KEEP_ALIVE = 1 << 2, - CONN_IS_ALIVE = 1 << 3, - CONN_IS_UPGRADE = 1 << 4, - CONN_IS_WEBSOCKET = 1 << 5, + CONN_IS_KEEP_ALIVE = 1 << 2, + CONN_IS_UPGRADE = 1 << 3, + CONN_IS_WEBSOCKET = 1 << 4, /* This is only used to determine if timeout_del() is necessary when * the connection coro ends. */ - CONN_SUSPENDED_TIMER = 1 << 6, + CONN_SUSPENDED_TIMER = 1 << 5, }; enum lwan_connection_coro_yield { From 72648531e50ae2e5e6fa2353727cdf55888db7c4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 12 Jun 2019 20:02:52 -0700 Subject: [PATCH 1091/2505] Ensure only one coro_defer() will be scheduled when sleeping --- src/lib/lwan-request.c | 7 ++++++- src/lib/lwan.h | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 2ee875cde..a7bb25461 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1545,7 +1545,12 @@ void lwan_request_sleep(struct lwan_request *request, uint64_t ms) request->timeout = (struct timeout) {}; timeouts_add(wheel, &request->timeout, ms); - coro_defer2(conn->coro, remove_sleep, wheel, &request->timeout); + + if (!(conn->flags & CONN_HAS_REMOVE_SLEEP_DEFER)) { + coro_defer2(conn->coro, remove_sleep, wheel, &request->timeout); + conn->flags |= CONN_HAS_REMOVE_SLEEP_DEFER; + } + coro_yield(conn->coro, CONN_CORO_SUSPEND_TIMER); } diff --git a/src/lib/lwan.h b/src/lib/lwan.h index b24b43e26..3bc08f3b4 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -290,6 +290,7 @@ enum lwan_connection_flags { /* This is only used to determine if timeout_del() is necessary when * the connection coro ends. */ CONN_SUSPENDED_TIMER = 1 << 5, + CONN_HAS_REMOVE_SLEEP_DEFER = 1 << 6, }; enum lwan_connection_coro_yield { From 901515906d13d05e3c96b369ce7809c6978b2bc2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 13 Jun 2019 18:08:45 -0700 Subject: [PATCH 1092/2505] Clear "has remove sleep defer" flag after it has been executed Otherwise, if the same connection services two or more requests that end up being suspended by a timer, the deferred callback won't be scheduled after the first time. --- src/lib/lwan-request.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index a7bb25461..ebdda1938 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1536,6 +1536,8 @@ static void remove_sleep(void *data1, void *data2) if (request->conn->flags & CONN_SUSPENDED_TIMER) timeouts_del(wheel, timeout); + + request->conn->flags &= ~CONN_HAS_REMOVE_SLEEP_DEFER; } void lwan_request_sleep(struct lwan_request *request, uint64_t ms) From f693433d2c34483817f101f662128e8d51c32a6b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 15 Jun 2019 09:54:24 -0700 Subject: [PATCH 1093/2505] Get rid of FINALIZER_YIELD_TRY_AGAIN: always yield coroutine --- src/lib/lwan-request.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index ebdda1938..dbd8237a2 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -50,7 +50,6 @@ enum lwan_read_finalizer { FINALIZER_DONE, FINALIZER_TRY_AGAIN, - FINALIZER_YIELD_TRY_AGAIN, FINALIZER_ERROR_TOO_LARGE, FINALIZER_ERROR_TIMEOUT }; @@ -857,9 +856,6 @@ read_from_request_socket(struct lwan_request *request, return HTTP_OK; case FINALIZER_TRY_AGAIN: - continue; - - case FINALIZER_YIELD_TRY_AGAIN: goto yield_and_read_again; case FINALIZER_ERROR_TOO_LARGE: @@ -898,7 +894,7 @@ read_request_finalizer(size_t total_read, return FINALIZER_ERROR_TIMEOUT; if (UNLIKELY(total_read < MIN_REQUEST_SIZE)) - return FINALIZER_YIELD_TRY_AGAIN; + return FINALIZER_TRY_AGAIN; char *crlfcrlf = has_crlfcrlf(helper->buffer); if (LIKELY(crlfcrlf)) { From 1f5d8907b554fc5a74d21291d911838b72b76c2b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 15 Jun 2019 09:56:28 -0700 Subject: [PATCH 1094/2505] Remove one branch from read_request_finalizer() The location for the request terminator can be used to check if there's a minimum request waiting in the buffer. --- src/lib/lwan-request.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index dbd8237a2..73a3f234c 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -893,17 +893,16 @@ read_request_finalizer(size_t total_read, if (UNLIKELY(n_packets > helper->error_when_n_packets)) return FINALIZER_ERROR_TIMEOUT; - if (UNLIKELY(total_read < MIN_REQUEST_SIZE)) - return FINALIZER_TRY_AGAIN; - char *crlfcrlf = has_crlfcrlf(helper->buffer); if (LIKELY(crlfcrlf)) { + const size_t crlfcrlf_to_base = (size_t)(crlfcrlf - helper->buffer->value); + if (LIKELY(helper->next_request)) { helper->next_request = NULL; return FINALIZER_DONE; } - if (crlfcrlf != helper->buffer->value) + if (crlfcrlf_to_base >= MIN_REQUEST_SIZE - 4) return FINALIZER_DONE; if (total_read > min_proxied_request_size && From 43434a2937ddf36bed3c4e2d622f262502327302 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 16 Jun 2019 10:34:52 -0700 Subject: [PATCH 1095/2505] Slightly reduce code size of process_request_coro() --- src/lib/lwan-thread.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 7975d5bdc..94cb3c512 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -69,10 +69,8 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, enum lwan_request_flags flags = 0; struct lwan_proxy proxy; - if (UNLIKELY(!lwan_strbuf_init(&strbuf))) { - coro_yield(coro, CONN_CORO_ABORT); - __builtin_unreachable(); - } + if (UNLIKELY(!lwan_strbuf_init(&strbuf))) + goto abort; coro_defer(coro, lwan_strbuf_free_defer, &strbuf); flags |= REQUEST_FLAG(proxy_protocol, ALLOW_PROXY_REQS) | @@ -97,14 +95,17 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, coro_yield(coro, CONN_CORO_WANT_READ); } } else { - shutdown(fd, SHUT_RDWR); - coro_yield(coro, CONN_CORO_ABORT); - __builtin_unreachable(); + goto abort; } lwan_strbuf_reset(&strbuf); flags = request.flags & flags_filter; } + +abort: + shutdown(fd, SHUT_RDWR); + coro_yield(coro, CONN_CORO_ABORT); + __builtin_unreachable(); } #undef REQUEST_FLAG From e4537204a3bfad3d16982b22fc0f213b4a5b8c08 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 16 Jun 2019 12:28:30 -0700 Subject: [PATCH 1096/2505] Workaround build issues in OSS-Fuzz --- src/lib/lwan-status.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib/lwan-status.c b/src/lib/lwan-status.c index f1ddf13a9..0e4fc95eb 100644 --- a/src/lib/lwan-status.c +++ b/src/lib/lwan-status.c @@ -112,12 +112,18 @@ static inline char *strerror_thunk_r(int error_number, char *buffer, size_t len) #ifndef NDEBUG static long gettid_cached(void) { +#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) static __thread long tid; if (!tid) tid = gettid(); return tid; +#else + /* Workaround for: + * https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=15216 */ + return gettid(); +#endif } #endif From 566aa6cd7ea14da32ac6593b1319d78c5c4a2758 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 18 Jun 2019 20:23:06 -0700 Subject: [PATCH 1097/2505] Use a more efficient way to loop through threads-to-nudge --- src/lib/lwan.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index e07ca7214..09b409534 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -696,13 +696,10 @@ accept_connection_coro(struct coro *coro, void *data) if (UNLIKELY(ha > HERD_MORE)) break; - if (LIKELY(cores)) { - for (unsigned short t = 0; t < l->thread.count; t++) { - if (cores & UINT64_C(1)<thread.threads[t]); - } - - cores = 0; + while (cores) { + int core = __builtin_ctzl(cores); + lwan_thread_nudge(&l->thread.threads[core]); + cores ^= cores & -cores; } } From 3efd31c7b6c30429d3edde6127c9f60ade34370b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 18 Jun 2019 21:57:28 -0700 Subject: [PATCH 1098/2505] Support more than 64 threads --- src/lib/lwan.c | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 09b409534..499c70149 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -573,10 +573,10 @@ void lwan_init_with_config(struct lwan *l, const struct lwan_config *config) lwan_status_warning("%d threads requested, but only %d online CPUs; " "capping to %d threads", l->config.n_threads, l->n_cpus, 3 * l->n_cpus); - } else if (l->config.n_threads > 63) { - l->thread.count = 64; + } else if (l->config.n_threads > 255) { + l->thread.count = 256; - lwan_status_warning("%d threads requested, but max 64 supported", + lwan_status_warning("%d threads requested, but max 256 supported", l->config.n_threads); } else { l->thread.count = l->config.n_threads; @@ -622,13 +622,13 @@ void lwan_shutdown(struct lwan *l) lwan_fd_watch_shutdown(l); } -static ALWAYS_INLINE unsigned int schedule_client(struct lwan *l, int fd) +static ALWAYS_INLINE int schedule_client(struct lwan *l, int fd) { struct lwan_thread *thread = l->conns[fd].thread; lwan_thread_add_client(thread, fd); - return (unsigned int)(thread - l->thread.threads); + return (int)(thread - l->thread.threads); } static volatile sig_atomic_t main_socket = -1; @@ -649,13 +649,19 @@ static void sigint_handler(int signal_number __attribute__((unused))) enum herd_accept { HERD_MORE = 0, HERD_GONE = -1, HERD_SHUTDOWN = 1 }; +struct core_bitmap { + uint64_t bitmap[4]; +}; + static ALWAYS_INLINE enum herd_accept -accept_one(struct lwan *l, uint64_t *cores) +accept_one(struct lwan *l, struct core_bitmap *cores) { int fd = accept4((int)main_socket, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC); if (LIKELY(fd >= 0)) { - *cores |= UINT64_C(1)<bitmap[core / 64] |= core % 64; return HERD_MORE; } @@ -680,11 +686,10 @@ accept_one(struct lwan *l, uint64_t *cores) } } -static int -accept_connection_coro(struct coro *coro, void *data) +static int accept_connection_coro(struct coro *coro, void *data) { struct lwan *l = data; - uint64_t cores = 0; + struct core_bitmap cores = {}; while (coro_yield(coro, 1) & ~(EPOLLHUP | EPOLLRDHUP | EPOLLERR)) { enum herd_accept ha; @@ -696,11 +701,18 @@ accept_connection_coro(struct coro *coro, void *data) if (UNLIKELY(ha > HERD_MORE)) break; - while (cores) { - int core = __builtin_ctzl(cores); - lwan_thread_nudge(&l->thread.threads[core]); - cores ^= cores & -cores; + for (size_t i = 0; i < N_ELEMENTS(cores.bitmap); i++) { + uint64_t c = cores.bitmap[i]; + + while (c) { + size_t core = (size_t)__builtin_ctzl(c); + + lwan_thread_nudge(&l->thread.threads[i * 64 + core]); + + c ^= c & -c; + } } + memset(&cores, 0, sizeof(cores)); } return 0; From 36516f77e05ca5f799466703c45d6c818b3d779b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 18 Jun 2019 21:58:59 -0700 Subject: [PATCH 1099/2505] Print error message if epoll_wait() in main loop errors out --- src/lib/lwan.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 499c70149..ab2a59ee3 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -785,6 +785,7 @@ void lwan_main_loop(struct lwan *l) break; if (errno == EINTR || errno == EAGAIN) continue; + lwan_status_perror("epoll_wait"); break; } From 8d5ea7b3097d63cefd8baee961151c11bdf998b8 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 19 Jun 2019 06:46:39 -0700 Subject: [PATCH 1100/2505] Actually fix build issue in oss-fuzz#15216 --- src/lib/lwan-status.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-status.c b/src/lib/lwan-status.c index 0e4fc95eb..b2e58f2ee 100644 --- a/src/lib/lwan-status.c +++ b/src/lib/lwan-status.c @@ -113,16 +113,16 @@ static inline char *strerror_thunk_r(int error_number, char *buffer, size_t len) static long gettid_cached(void) { #if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) + /* Workaround for: + * https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=15216 */ + return gettid(); +#else static __thread long tid; if (!tid) tid = gettid(); return tid; -#else - /* Workaround for: - * https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=15216 */ - return gettid(); #endif } #endif From 069900e90c76feca8998bf66bda866977bf76608 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 19 Jun 2019 06:50:47 -0700 Subject: [PATCH 1101/2505] `dd` parameter is unused, remove it from dirlist_find_readme() --- src/lib/lwan-mod-serve-files.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 5c82242b0..7b1b794ba 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -528,7 +528,6 @@ static const char *get_rel_path(const char *full_path, } static const char *dirlist_find_readme(struct lwan_strbuf *readme, - struct dir_list_cache_data *dd, struct serve_files_priv *priv, const char *full_path) { @@ -593,7 +592,7 @@ static bool dirlist_init(struct file_cache_entry *ce, struct file_list vars = { .full_path = full_path, .rel_path = get_rel_path(full_path, priv), - .readme = dirlist_find_readme(&readme, dd, priv, full_path), + .readme = dirlist_find_readme(&readme, priv, full_path), }; if (!lwan_tpl_apply_with_buffer(priv->directory_list_tpl, &dd->rendered, From f3caad206f56bfd1c33b4c5a45acd7d9dd493fc7 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 19 Jun 2019 07:36:48 -0700 Subject: [PATCH 1102/2505] Slightly shorter code to loop over set bits in core_bitmap struct Generated assembly should be the same; this is just more compact C. --- src/lib/lwan.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index ab2a59ee3..ccf478d18 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -702,14 +702,9 @@ static int accept_connection_coro(struct coro *coro, void *data) break; for (size_t i = 0; i < N_ELEMENTS(cores.bitmap); i++) { - uint64_t c = cores.bitmap[i]; - - while (c) { + for (uint64_t c = cores.bitmap[i]; c; c ^= c & -c) { size_t core = (size_t)__builtin_ctzl(c); - lwan_thread_nudge(&l->thread.threads[i * 64 + core]); - - c ^= c & -c; } } memset(&cores, 0, sizeof(cores)); From 206b69df3bea347c836158966c08c1f97b41d965 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 20 Jun 2019 19:24:56 -0700 Subject: [PATCH 1103/2505] alloc_post_buffer() should mmap() with MAP_SHARED It need to go to the underlying file. --- src/lib/lwan-request.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 73a3f234c..0aaf37bd3 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1103,10 +1103,10 @@ alloc_post_buffer(struct coro *coro, size_t size, bool allow_file) if (MAP_HUGETLB) { ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_HUGETLB, fd, 0); + MAP_SHARED | MAP_HUGETLB, fd, 0); } if (UNLIKELY(ptr == MAP_FAILED)) - ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); + ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); close(fd); if (UNLIKELY(ptr == MAP_FAILED)) return NULL; From 406c15bb706c173d7595c9343a1dfa9cb24c29e4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 20 Jun 2019 19:38:09 -0700 Subject: [PATCH 1104/2505] Actually schedule clients (fix regression in 3efd31c7) --- src/lib/lwan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index ccf478d18..4f943429f 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -661,7 +661,7 @@ accept_one(struct lwan *l, struct core_bitmap *cores) if (LIKELY(fd >= 0)) { int core = schedule_client(l, fd); - cores->bitmap[core / 64] |= core % 64; + cores->bitmap[core / 64] |= UINT64_C(1)<<(core % 64); return HERD_MORE; } From 1fb5b0b916980a83a49a182adb2dc3a2290514b2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 21 Jun 2019 07:00:37 -0700 Subject: [PATCH 1105/2505] coro_malloc_full() should take a void(*)(void *) The function pointer prototype without a parameter was a remnant from the time where coro_defer() and coro_defer2() took the same prototype as arguments. --- src/lib/lwan-coro.c | 2 +- src/lib/lwan-coro.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 69407b224..0effd4cc7 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -368,7 +368,7 @@ coro_defer2(struct coro *coro, defer2_func func, void *data1, void *data2) } void * -coro_malloc_full(struct coro *coro, size_t size, void (*destroy_func)()) +coro_malloc_full(struct coro *coro, size_t size, void (*destroy_func)(void *data)) { void *ptr = malloc(size); diff --git a/src/lib/lwan-coro.h b/src/lib/lwan-coro.h index 420b76364..b428e55e1 100644 --- a/src/lib/lwan-coro.h +++ b/src/lib/lwan-coro.h @@ -59,7 +59,7 @@ size_t coro_deferred_get_generation(const struct coro *coro) void *coro_malloc(struct coro *coro, size_t sz) __attribute__((malloc)); -void *coro_malloc_full(struct coro *coro, size_t size, void (*destroy_func)()) +void *coro_malloc_full(struct coro *coro, size_t size, void (*destroy_func)(void *data)) __attribute__((malloc)); char *coro_strdup(struct coro *coro, const char *str); char *coro_strndup(struct coro *coro, const char *str, size_t len); From 3d2c9ad45b22d31d51aecc902cf8b287c2bf82dd Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 21 Jun 2019 07:02:33 -0700 Subject: [PATCH 1106/2505] Add tests for minimal requests These are -- surprisingly -- failing, so adding these tests here so I don't forget to fix them. --- src/scripts/testsuite.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index f40410e04..67df299a4 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -402,6 +402,25 @@ def _connect(host, port): self.assertNotEqual(sock, None) return SocketTest.WrappedSock(sock) + +class TestMinimalRequests(SocketTest): + def assertHttpCode(self, sock, code): + contents = sock.recv(128) + + self.assertRegex(contents, r'^HTTP/1\.[01] ' + str(code) + r' ') + + def test_http1_0_request(self): + with self.connect() as sock: + sock.send("GET / HTTP/1.0\r\n\r\n") + self.assertHttpCode(sock, 200) + + + def test_http1_1_request(self): + with self.connect() as sock: + sock.send("GET / HTTP/1.1\r\n\r\n") + self.assertHttpCode(sock, 200) + + class TestMalformedRequests(SocketTest): def assertHttpCode(self, sock, code): contents = sock.recv(128) From a432e97ea4fbdc950c958c6be52d7882843d6165 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 21 Jun 2019 07:26:07 -0700 Subject: [PATCH 1107/2505] Print some information about the MIME type table when uncompressing --- src/lib/lwan-tables.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index a40c339ba..3a41724b8 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -37,7 +37,8 @@ lwan_tables_init(void) if (mime_entries_initialized) return; - lwan_status_debug("Uncompressing MIME type table"); + lwan_status_debug("Uncompressing MIME type table: %u->%u bytes, %d entries", + MIME_COMPRESSED_LEN, MIME_UNCOMPRESSED_LEN, MIME_ENTRIES); uLongf uncompressed_length = MIME_UNCOMPRESSED_LEN; int ret = uncompress((Bytef*)uncompressed_mime_entries, &uncompressed_length, (const Bytef*)mime_entries_compressed, From 3947414fd46eb866b10023df26741b111647b9b0 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 22 Jun 2019 09:26:40 -0700 Subject: [PATCH 1108/2505] Fix timeout when processing POST requests in some machines I couldn't reproduce this locally, but this happens very frequently in the machine that runs the unit tests, so the TestHelloWorld.test_post_data always fails there. --- src/lib/lwan-request.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 0aaf37bd3..d96e7b935 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -814,7 +814,7 @@ read_from_request_socket(struct lwan_request *request, for (;; n_packets++) { n = read(request->fd, buffer->value + total_read, - (size_t)(buffer_size - total_read - 1)); + (size_t)(buffer_size - total_read)); /* Client has shutdown orderly, nothing else to do; kill coro */ if (UNLIKELY(n == 0)) { coro_yield(request->conn->coro, CONN_CORO_ABORT); @@ -1170,7 +1170,7 @@ static enum lwan_http_status read_post_data(struct lwan_request *request) helper->error_when_n_packets = calculate_n_packets(post_data_size); struct lwan_value buffer = {.value = new_buffer, - .len = post_data_size - have}; + .len = post_data_size}; return read_from_request_socket(request, &buffer, buffer.len, post_data_finalizer); } From 9d12d2c2b8547902fb1182489de14b06af61fc9f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 22 Jun 2019 09:28:06 -0700 Subject: [PATCH 1109/2505] Optimistically try at least 5 packets while reading from req. socket --- src/lib/lwan-request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index d96e7b935..df404730d 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -965,7 +965,7 @@ static ALWAYS_INLINE int calculate_n_packets(size_t total) { /* 740 = 1480 (a common MTU) / 2, so that Lwan'll optimistically error out * after ~2x number of expected packets to fully read the request body.*/ - return max(1, (int)(total / 740)); + return max(5, (int)(total / 740)); } static const char *is_dir(const char *v) From e59fbdb76fa5485c751b6f8ae4b3f15ce32dc299 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 22 Jun 2019 09:32:08 -0700 Subject: [PATCH 1110/2505] Try to gracefully close connection if request has Connection: close --- src/lib/lwan-thread.c | 59 ++++++++++++++++++++++++++++++++++--- src/lib/missing/sys/ioctl.h | 37 +++++++++++++++++++++++ 2 files changed, 92 insertions(+), 4 deletions(-) create mode 100644 src/lib/missing/sys/ioctl.h diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 94cb3c512..1740cf27c 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -51,6 +52,56 @@ static void lwan_strbuf_free_defer(void *data) lwan_strbuf_free((struct lwan_strbuf *)data); } +static void graceful_close(struct lwan *l, + struct lwan_connection *conn, + char buffer[static DEFAULT_BUFFER_SIZE]) +{ + int fd = lwan_connection_get_fd(l, conn); + + while (TIOCOUTQ) { + /* This ioctl isn't probably doing what it says on the tin; the details + * are subtle, but it seems to do the trick to allow gracefully closing + * the connection in some cases with minimal system calls. */ + int bytes_waiting; + int r = ioctl(fd, TIOCOUTQ, &bytes_waiting); + + if (!r && !bytes_waiting) /* See note about close(2) below. */ + return; + if (r < 0 && errno == EINTR) + continue; + + break; + } + + if (UNLIKELY(shutdown(fd, SHUT_WR) < 0)) { + if (UNLIKELY(errno == ENOTCONN)) + return; + } + + for (int tries = 0; tries < 20; tries++) { + ssize_t r = read(fd, buffer, DEFAULT_BUFFER_SIZE); + + if (!r) + break; + + if (r < 0) { + switch (errno) { + case EINTR: + continue; + case EAGAIN: + coro_yield(conn->coro, CONN_CORO_WANT_READ); + continue; + default: + return; + } + } + + coro_yield(conn->coro, CONN_CORO_YIELD); + } + + /* close(2) will be called when the coroutine yields with CONN_CORO_ABORT */ +} + __attribute__((noreturn)) static int process_request_coro(struct coro *coro, void *data) { @@ -70,7 +121,7 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, struct lwan_proxy proxy; if (UNLIKELY(!lwan_strbuf_init(&strbuf))) - goto abort; + goto out; coro_defer(coro, lwan_strbuf_free_defer, &strbuf); flags |= REQUEST_FLAG(proxy_protocol, ALLOW_PROXY_REQS) | @@ -95,15 +146,15 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, coro_yield(coro, CONN_CORO_WANT_READ); } } else { - goto abort; + graceful_close(lwan, conn, request_buffer); + goto out; } lwan_strbuf_reset(&strbuf); flags = request.flags & flags_filter; } -abort: - shutdown(fd, SHUT_RDWR); +out: coro_yield(coro, CONN_CORO_ABORT); __builtin_unreachable(); } diff --git a/src/lib/missing/sys/ioctl.h b/src/lib/missing/sys/ioctl.h new file mode 100644 index 000000000..c7d61f50f --- /dev/null +++ b/src/lib/missing/sys/ioctl.h @@ -0,0 +1,37 @@ +/* + * lwan - simple web server + * Copyright (c) 2019 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include_next + +#ifndef MISSING_SYS_IOCTL_H +#define MISSING_SYS_IOCTL_H + +#ifndef TIOCOUTQ + +#ifdef FIONWRITE /* FreeBSD, ... */ +#define TIOCOUTQ FIONWRITE +#else +#define TIOCOUTQ 0 +#endif /* FIONWRITE */ + +#endif /* TIOCOUTQ */ + +#endif /* MISSING_SYS_IOCTL_H */ + From 76c2768fd4fdaae38433ae2e47a215d5fac2bc05 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 22 Jun 2019 09:47:57 -0700 Subject: [PATCH 1111/2505] Error out with 413 if read_from_request_socket() would read 0 bytes --- src/lib/lwan-request.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index df404730d..8f1d2f42a 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -813,8 +813,12 @@ read_from_request_socket(struct lwan_request *request, } for (;; n_packets++) { - n = read(request->fd, buffer->value + total_read, - (size_t)(buffer_size - total_read)); + size_t to_read = (size_t)(buffer_size - total_read); + + if (UNLIKELY(to_read == 0)) + return HTTP_TOO_LARGE; + + n = read(request->fd, buffer->value + total_read, to_read); /* Client has shutdown orderly, nothing else to do; kill coro */ if (UNLIKELY(n == 0)) { coro_yield(request->conn->coro, CONN_CORO_ABORT); From 30b15f74231f409f7ac0a866fb52e5af34726211 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 22 Jun 2019 11:12:45 -0700 Subject: [PATCH 1112/2505] Fix parsing of minimal requests --- src/lib/lwan-request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 8f1d2f42a..7b4095bfd 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1207,7 +1207,7 @@ static enum lwan_http_status parse_http_request(struct lwan_request *request) buffer = ignore_leading_whitespace(buffer); - if (UNLIKELY(buffer >= helper->buffer->value + helper->buffer->len - + if (UNLIKELY(buffer > helper->buffer->value + helper->buffer->len - MIN_REQUEST_SIZE)) return HTTP_BAD_REQUEST; From bf52ee6e241493b5f7b9505e10b93340abe0caf6 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 23 Jun 2019 11:44:19 -0700 Subject: [PATCH 1113/2505] Fix some compiler warnings after building with GCC 9.1 --- CMakeLists.txt | 3 +++ src/lib/lwan-coro.c | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 74f4378bf..569b37175 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -229,6 +229,9 @@ enable_warning_if_supported(-Wno-unused-parameter) enable_warning_if_supported(-Wstringop-truncation) enable_warning_if_supported(-Wvla) +# While a useful warning, this is giving false positives. +enable_warning_if_supported(-Wno-free-nonheap-object) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wshadow -Wconversion -std=gnu99") set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${C_FLAGS_REL}") set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} ${C_FLAGS_REL}") diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 0effd4cc7..1b04ddfcd 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -400,7 +400,7 @@ coro_strndup(struct coro *coro, const char *str, size_t max_len) char * coro_strdup(struct coro *coro, const char *str) { - return coro_strndup(coro, str, SIZE_MAX - 1); + return coro_strndup(coro, str, SSIZE_MAX - 1); } char * From 6d3c92f62ef705e9c4d45eec8ddc53be7c01b7ec Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 12 Jul 2019 07:20:56 -0700 Subject: [PATCH 1114/2505] Clean up some macros in lwan.h --- src/lib/lwan.h | 30 ++++++------------------------ 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 3bc08f3b4..dd29737e3 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -45,29 +45,24 @@ extern "C" { #define N_ELEMENTS(array) (sizeof(array) / sizeof(array[0])) -#define LWAN_MODULE_REF(name_) lwan_module_info_##name_.module - -#define LWAN_MODULE_FORWARD_DECL(name_) \ - extern const struct lwan_module_info lwan_module_info_##name_; - #ifdef __APPLE__ #define LWAN_SECTION_NAME(name_) "__DATA," #name_ #else #define LWAN_SECTION_NAME(name_) #name_ #endif +#define LWAN_MODULE_REF(name_) lwan_module_info_##name_.module +#define LWAN_MODULE_FORWARD_DECL(name_) \ + extern const struct lwan_module_info lwan_module_info_##name_; #define LWAN_REGISTER_MODULE(name_, module_) \ const struct lwan_module_info \ __attribute__((used, section(LWAN_SECTION_NAME(lwan_module)))) \ lwan_module_info_##name_ = {.name = #name_, .module = module_} #define LWAN_HANDLER_REF(name_) lwan_handler_##name_ - -#define LWAN_HANDLER_DECLARE(name_) \ +#define LWAN_HANDLER(name_) \ static enum lwan_http_status lwan_handler_##name_( \ - struct lwan_request *, struct lwan_response *, void *) - -#define LWAN_HANDLER_DEFINE(name_) \ + struct lwan_request *, struct lwan_response *, void *); \ static const struct lwan_handler_info \ __attribute__((used, section(LWAN_SECTION_NAME(lwan_handler)))) \ lwan_handler_info_##name_ = {.name = #name_, \ @@ -77,10 +72,6 @@ extern "C" { struct lwan_response *response __attribute__((unused)), \ void *data __attribute__((unused))) -#define LWAN_HANDLER(name_) \ - LWAN_HANDLER_DECLARE(name_); \ - LWAN_HANDLER_DEFINE(name_) - #define LWAN_LUA_METHOD(name_) \ static int lwan_lua_method_##name_(lua_State *L); \ static const struct lwan_lua_method_info \ @@ -89,17 +80,7 @@ extern "C" { .func = lwan_lua_method_##name_}; \ static int lwan_lua_method_##name_(lua_State *L) -#ifdef DISABLE_INLINE_FUNCTIONS -#define ALWAYS_INLINE -#else #define ALWAYS_INLINE inline __attribute__((always_inline)) -#endif - -#ifdef DISABLE_BRANCH_PREDICTION -#define LIKELY_IS(x, y) (x) -#else -#define LIKELY_IS(x, y) __builtin_expect((x), (y)) -#endif #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ #define MULTICHAR_CONSTANT(a, b, c, d) \ @@ -157,6 +138,7 @@ static ALWAYS_INLINE int16_t string_as_int16(const char *s) #define STRING_SWITCH_LARGE_L(s) \ switch (string_as_int64(s) | 0x2020202020202020) +#define LIKELY_IS(x, y) __builtin_expect((x), (y)) #define LIKELY(x) LIKELY_IS(!!(x), 1) #define UNLIKELY(x) LIKELY_IS((x), 0) From 3c43a72dbfae4fbaa455695230ec4d1a47efd6ff Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 12 Jul 2019 07:21:08 -0700 Subject: [PATCH 1115/2505] thread_siblings_list may use '-' or ',' as separator --- src/lib/lwan-thread.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 1740cf27c..cbd7e4297 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -523,6 +523,7 @@ static bool read_cpu_topology(struct lwan *l, uint32_t siblings[]) for (unsigned short i = 0; i < l->n_cpus; i++) { FILE *sib; uint32_t id, sibling; + char separator; snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%hd/topology/thread_siblings_list", @@ -535,11 +536,16 @@ static bool read_cpu_topology(struct lwan *l, uint32_t siblings[]) return false; } - switch (fscanf(sib, "%u-%u", &id, &sibling)) { - case 1: /* No SMT */ + switch (fscanf(sib, "%u%c%u", &id, &separator, &sibling)) { + case 2: /* No SMT */ siblings[i] = id; break; - case 2: /* SMT */ + case 3: /* SMT */ + if (!(separator == ',' || separator == '-')) { + lwan_status_critical("Expecting either ',' or '-' for sibling separator"); + __builtin_unreachable(); + } + siblings[i] = sibling; break; default: @@ -547,6 +553,7 @@ static bool read_cpu_topology(struct lwan *l, uint32_t siblings[]) __builtin_unreachable(); } + fclose(sib); } From 1800a7285c9a79b93e9038eb86c3a022c003637d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 12 Jul 2019 22:12:47 -0700 Subject: [PATCH 1116/2505] Reduce number of writes to header_start array by half --- src/lib/lwan-request.c | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 7b4095bfd..1a4e9ac68 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -44,6 +44,7 @@ #include "lwan-io-wrappers.h" #include "sha1.h" +#define HEADER_TERMINATOR_LEN (sizeof("\r\n") - 1) #define MIN_REQUEST_SIZE (sizeof("GET / HTTP/1.1\r\n\r\n") - 1) #define N_HEADER_START 64 @@ -534,20 +535,22 @@ static bool parse_headers(struct lwan_request_parser_helper *helper, char *buffer_end = helper->buffer->value + helper->buffer->len; char **header_start = helper->header_start; size_t n_headers = 0; + char *next_header; char *p; for (p = buffer + 1;;) { char *next_chr = p; - char *next_hdr = memchr(next_chr, '\r', (size_t)(buffer_end - p)); - if (!next_hdr) + next_header = memchr(next_chr, '\r', (size_t)(buffer_end - p)); + + if (!next_header) break; - if (next_chr == next_hdr) { - if (buffer_end - next_chr > 2) { - STRING_SWITCH_SMALL (next_hdr) { + if (next_chr == next_header) { + if (buffer_end - next_chr > (ptrdiff_t)HEADER_TERMINATOR_LEN) { + STRING_SWITCH_SMALL (next_header) { case MULTICHAR_CONSTANT_SMALL('\r', '\n'): - helper->next_request = next_hdr + 2; + helper->next_request = next_header + HEADER_TERMINATOR_LEN; } } break; @@ -556,24 +559,26 @@ static bool parse_headers(struct lwan_request_parser_helper *helper, /* Is there at least a space for a minimal (H)eader and a (V)alue? */ if (LIKELY(next_hdr - next_chr > (ptrdiff_t)(sizeof("H: V") - 1))) { header_start[n_headers++] = next_chr; - header_start[n_headers++] = next_hdr; } else { /* Better to abort early if there's no space. */ return false; } - p = next_hdr + 2; + p = next_header + HEADER_TERMINATOR_LEN; - if (n_headers >= N_HEADER_START || p >= buffer_end) { + if (UNLIKELY(n_headers >= (N_HEADER_START - 1) || p >= buffer_end)) { helper->n_header_start = 0; return false; } } - for (size_t i = 0; i < n_headers; i += 2) { - char *end = header_start[i + 1]; + header_start[n_headers] = next_header; + + for (size_t i = 0; i < n_headers; i++) { + char *end; p = header_start[i]; + end = header_start[i + 1] - HEADER_TERMINATOR_LEN; STRING_SWITCH_L (p) { case MULTICHAR_CONSTANT_L('A', 'c', 'c', 'e'): @@ -1471,9 +1476,9 @@ const char *lwan_request_get_header(struct lwan_request *request, if (UNLIKELY(r < 0 || r >= (int)sizeof(name))) return NULL; - for (size_t i = 0; i < request->helper->n_header_start; i += 2) { + for (size_t i = 0; i < request->helper->n_header_start; i++) { const char *start = request->helper->header_start[i]; - char *end = request->helper->header_start[i + 1]; + char *end = request->helper->header_start[i + 1] - HEADER_TERMINATOR_LEN; if (UNLIKELY(end - start < r)) continue; From 9c51443d57c415adf889add968f54e49d5948217 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 12 Jul 2019 22:13:04 -0700 Subject: [PATCH 1117/2505] Fix parsing of short headers --- src/lib/lwan-request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 1a4e9ac68..cb08ef913 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -557,7 +557,7 @@ static bool parse_headers(struct lwan_request_parser_helper *helper, } /* Is there at least a space for a minimal (H)eader and a (V)alue? */ - if (LIKELY(next_hdr - next_chr > (ptrdiff_t)(sizeof("H: V") - 1))) { + if (LIKELY(next_header - next_chr >= (ptrdiff_t)(sizeof("H: V") - 1))) { header_start[n_headers++] = next_chr; } else { /* Better to abort early if there's no space. */ From e4e0cf8adca4bfe19385a4da8c71efcaed0a3bfb Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 12 Jul 2019 22:16:05 -0700 Subject: [PATCH 1118/2505] Slightly reduce size of HEADER() macro by outlining some code text data bss dec hex filename 173900 12816 43480 230196 38334 Before 173804 12816 43480 230100 382d4 After A reduction of 96 bytes in a hot function. (Have not benchmarked yet.) --- src/lib/lwan-request.c | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index cb08ef913..309e492e0 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -511,6 +511,18 @@ identify_http_path(struct lwan_request *request, char *buffer) return end_of_line + 1; } +__attribute__((noinline)) static void +set_header_value(struct lwan_value *header, char *end, char *p) +{ + if (LIKELY(string_as_int16(p) == MULTICHAR_CONSTANT_SMALL(':', ' '))) { + *end = '\0'; + char *value = p + sizeof(": ") - 1; + + header->value = value; + header->len = (size_t)(end - value); + } +} + #define HEADER_LENGTH(hdr) \ ({ \ if (UNLIKELY(end - sizeof(hdr) + 1 < p)) \ @@ -518,16 +530,8 @@ identify_http_path(struct lwan_request *request, char *buffer) sizeof(hdr) - 1; \ }) -#define HEADER(hdr) \ - ({ \ - p += HEADER_LENGTH(hdr); \ - if (UNLIKELY(string_as_int16(p) != \ - MULTICHAR_CONSTANT_SMALL(':', ' '))) \ - continue; \ - *end = '\0'; \ - char *value = p + sizeof(": ") - 1; \ - (struct lwan_value){.value = value, .len = (size_t)(end - value)}; \ - }) +#define SET_HEADER_VALUE(dest, hdr) \ + set_header_value(&(helper->dest), end, p += HEADER_LENGTH(hdr)); static bool parse_headers(struct lwan_request_parser_helper *helper, char *buffer) @@ -586,30 +590,30 @@ static bool parse_headers(struct lwan_request_parser_helper *helper, STRING_SWITCH_L (p) { case MULTICHAR_CONSTANT_L('-', 'E', 'n', 'c'): - helper->accept_encoding = HEADER("-Encoding"); + SET_HEADER_VALUE(accept_encoding, "-Encoding"); break; } break; case MULTICHAR_CONSTANT_L('C', 'o', 'n', 'n'): - helper->connection = HEADER("Connection"); + SET_HEADER_VALUE(connection, "Connection"); break; case MULTICHAR_CONSTANT_L('C', 'o', 'n', 't'): p += HEADER_LENGTH("Content"); STRING_SWITCH_L (p) { case MULTICHAR_CONSTANT_L('-', 'T', 'y', 'p'): - helper->content_type = HEADER("-Type"); + SET_HEADER_VALUE(content_type, "-Type"); break; case MULTICHAR_CONSTANT_L('-', 'L', 'e', 'n'): - helper->content_length = HEADER("-Length"); + SET_HEADER_VALUE(content_length, "-Length"); break; } break; case MULTICHAR_CONSTANT_L('I', 'f', '-', 'M'): - helper->if_modified_since.raw = HEADER("If-Modified-Since"); + SET_HEADER_VALUE(if_modified_since.raw, "If-Modified-Since"); break; case MULTICHAR_CONSTANT_L('R', 'a', 'n', 'g'): - helper->range.raw = HEADER("Range"); + SET_HEADER_VALUE(range.raw, "Range"); break; } } @@ -618,7 +622,7 @@ static bool parse_headers(struct lwan_request_parser_helper *helper, return true; } #undef HEADER_LENGTH -#undef HEADER +#undef SET_HEADER_VALUE static void parse_if_modified_since(struct lwan_request_parser_helper *helper) { From 657e378b0c5adf8f6c503135f0714d75b9390794 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 13 Jul 2019 10:13:35 -0700 Subject: [PATCH 1119/2505] Ignore spaces while parsing Accept-Encoding headers Instead of matching " def" for "deflate", or " gzi" for "gzip", skip spaces between commas and only match "defl" or "gzip". --- src/lib/lwan-request.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 309e492e0..b26a32cb0 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -686,20 +686,20 @@ parse_accept_encoding(struct lwan_request *request) if (!helper->accept_encoding.len) return; - for (const char *p = helper->accept_encoding.value; *p; p++) { + for (const char *p = helper->accept_encoding.value; *p;) { STRING_SWITCH(p) { case MULTICHAR_CONSTANT('d','e','f','l'): - case MULTICHAR_CONSTANT(' ','d','e','f'): request->flags |= REQUEST_ACCEPT_DEFLATE; break; case MULTICHAR_CONSTANT('g','z','i','p'): - case MULTICHAR_CONSTANT(' ','g','z','i'): request->flags |= REQUEST_ACCEPT_GZIP; break; } if (!(p = strchr(p, ','))) break; + + for (p++; lwan_char_isspace(*p); p++); } } From 9983c1b8570a8cfd3d42cdf68e42246094e07ac3 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 13 Jul 2019 10:15:25 -0700 Subject: [PATCH 1120/2505] Add Brotli compression support for file serving --- CMakeLists.txt | 6 ++ README.md | 1 + src/cmake/lwan-build-config.h.cmake | 1 + src/lib/lwan-mod-serve-files.c | 91 ++++++++++++++++++++++++----- src/lib/lwan-request.c | 8 +++ src/lib/lwan.h | 31 +++++----- 6 files changed, 109 insertions(+), 29 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 569b37175..6a13d48c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,12 @@ else () set(HAVE_LUA 1) endif () +pkg_check_modules(BROTLI libbrotlienc libbrotlicommon) +if (BROTLI_FOUND) + list(APPEND ADDITIONAL_LIBRARIES "${BROTLI_LDFLAGS}") + set(HAVE_BROTLI 1) +endif () + option(USE_ALTERNATIVE_MALLOC "Use alternative malloc implementations" OFF) if (USE_ALTERNATIVE_MALLOC) find_library(TCMALLOC_LIBRARY NAMES tcmalloc_minimal tcmalloc) diff --git a/README.md b/README.md index 30484bd39..e618beef4 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ The build system will look for these libraries and enable/link if available. - [Lua 5.1](http://www.lua.org) or [LuaJIT 2.0](http://luajit.org) - [Valgrind](http://valgrind.org) + - [Brotli](https://github.com/google/brotli) - Alternative memory allocators can be used by passing `-DUSE_ALTERNATIVE_MALLOC=ON` to CMake: - [TCMalloc](https://github.com/gperftools/gperftools) - [jemalloc](http://jemalloc.net/) diff --git a/src/cmake/lwan-build-config.h.cmake b/src/cmake/lwan-build-config.h.cmake index 4cd1ab79d..f4e31846c 100644 --- a/src/cmake/lwan-build-config.h.cmake +++ b/src/cmake/lwan-build-config.h.cmake @@ -55,6 +55,7 @@ /* Libraries */ #cmakedefine HAVE_LUA +#cmakedefine HAVE_BROTLI /* Valgrind support for coroutines */ #cmakedefine HAVE_VALGRIND diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 7b1b794ba..6fc065f99 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -42,6 +42,10 @@ #include "auto-index-icons.h" +#if defined(HAVE_BROTLI) +#include +#endif + static const struct lwan_key_value deflate_compression_hdr = { .key = "Content-Encoding", .value = "deflate", @@ -50,6 +54,12 @@ static const struct lwan_key_value gzip_compression_hdr = { .key = "Content-Encoding", .value = "gzip", }; +#if defined(HAVE_BROTLI) +static const struct lwan_key_value br_compression_hdr = { + .key = "Content-Encoding", + .value = "br", +}; +#endif static const int open_mode = O_RDONLY | O_NONBLOCK | O_CLOEXEC; @@ -88,7 +98,12 @@ struct mmap_cache_data { void *contents; /* zlib expects unsigned longs instead of size_t */ unsigned long size; - } compressed, uncompressed; + } uncompressed + , deflate_compressed +#if defined(HAVE_BROTLI) + , br_compressed +#endif + ; }; struct sendfile_cache_data { @@ -343,28 +358,61 @@ static ALWAYS_INLINE bool is_compression_worthy(const size_t compressed_sz, return ((compressed_sz + deflated_header_size) < uncompressed_sz); } -static void compress_cached_entry(struct mmap_cache_data *md) +static void deflate_compress_cached_entry(struct mmap_cache_data *md) { - md->compressed.size = compressBound(md->uncompressed.size); + md->deflate_compressed.size = compressBound(md->uncompressed.size); - if (UNLIKELY(!(md->compressed.contents = malloc(md->compressed.size)))) + if (UNLIKELY(!(md->deflate_compressed.contents = + malloc(md->deflate_compressed.size)))) goto error_zero_out; - if (UNLIKELY(compress(md->compressed.contents, &md->compressed.size, + if (UNLIKELY(compress(md->deflate_compressed.contents, + &md->deflate_compressed.size, md->uncompressed.contents, md->uncompressed.size) != Z_OK)) goto error_free_compressed; - if (is_compression_worthy(md->compressed.size, md->uncompressed.size)) + if (is_compression_worthy(md->deflate_compressed.size, + md->uncompressed.size)) return; error_free_compressed: - free(md->compressed.contents); - md->compressed.contents = NULL; + free(md->deflate_compressed.contents); + md->deflate_compressed.contents = NULL; error_zero_out: - md->compressed.size = 0; + md->deflate_compressed.size = 0; } +#if defined(HAVE_BROTLI) +static void br_compress_cached_entry(struct mmap_cache_data *md) +{ + md->br_compressed.size = + BrotliEncoderMaxCompressedSize(md->uncompressed.size); + + if (UNLIKELY( + !(md->br_compressed.contents = malloc(md->br_compressed.size)))) + goto error_zero_out; + + if (UNLIKELY(BrotliEncoderCompress( + BROTLI_DEFAULT_QUALITY, BROTLI_DEFAULT_WINDOW, + BROTLI_DEFAULT_MODE, md->uncompressed.size, + md->uncompressed.contents, &md->br_compressed.size, + md->br_compressed.contents) != BROTLI_TRUE)) + goto error_free_compressed; + + /* is_compression_worthy() is already called for deflate-compressed data, + * so only consider brotli-compressed data if it's worth it WRT deflate */ + if (LIKELY(md->br_compressed.size < md->deflate_compressed.size)) + return; + +error_free_compressed: + free(md->br_compressed.contents); + md->br_compressed.contents = NULL; +error_zero_out: + md->br_compressed.size = 0; +} +#endif + static bool mmap_init(struct file_cache_entry *ce, struct serve_files_priv *priv, const char *full_path, @@ -391,7 +439,10 @@ static bool mmap_init(struct file_cache_entry *ce, lwan_madvise_queue(md->uncompressed.contents, (size_t)st->st_size); md->uncompressed.size = (size_t)st->st_size; - compress_cached_entry(md); + deflate_compress_cached_entry(md); +#if defined(HAVE_BROTLI) + br_compress_cached_entry(md); +#endif ce->mime_type = lwan_determine_mime_type_for_file_name(full_path + priv->root_path_len); @@ -771,7 +822,10 @@ static void mmap_free(struct file_cache_entry *fce) struct mmap_cache_data *md = &fce->mmap_cache_data; munmap(md->uncompressed.contents, md->uncompressed.size); - free(md->compressed.contents); + free(md->deflate_compressed.contents); +#if defined(HAVE_BROTLI) + free(md->br_compressed.contents); +#endif } static void sendfile_free(struct file_cache_entry *fce) @@ -1091,9 +1145,18 @@ static enum lwan_http_status mmap_serve(struct lwan_request *request, size_t size; enum lwan_http_status status; - if (md->compressed.size && (request->flags & REQUEST_ACCEPT_DEFLATE)) { - contents = md->compressed.contents; - size = md->compressed.size; +#if defined(HAVE_BROTLI) + if (md->br_compressed.size && (request->flags & REQUEST_ACCEPT_BROTLI)) { + contents = md->br_compressed.contents; + size = md->br_compressed.size; + compressed = &br_compression_hdr; + + status = HTTP_OK; + } else +#endif + if (md->deflate_compressed.size && (request->flags & REQUEST_ACCEPT_DEFLATE)) { + contents = md->deflate_compressed.contents; + size = md->deflate_compressed.size; compressed = &deflate_compression_hdr; status = HTTP_OK; diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index b26a32cb0..26520c367 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -694,6 +694,14 @@ parse_accept_encoding(struct lwan_request *request) case MULTICHAR_CONSTANT('g','z','i','p'): request->flags |= REQUEST_ACCEPT_GZIP; break; +#if defined(HAVE_BROTLI) + default: + STRING_SWITCH_SMALL(p) { + case MULTICHAR_CONSTANT_SMALL('b', 'r'): + request->flags |= REQUEST_ACCEPT_BROTLI; + break; + } +#endif } if (!(p = strchr(p, ','))) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index dd29737e3..0c77bf99f 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -218,32 +218,33 @@ enum lwan_request_flags { * Allows this: if (some_boolean) flags |= SOME_FLAG; * To turn into: flags |= some_boolean << SOME_FLAG_SHIFT; */ - REQUEST_ALLOW_PROXY_REQS_SHIFT = 6, - REQUEST_ALLOW_CORS_SHIFT = 8, + REQUEST_ALLOW_PROXY_REQS_SHIFT = 7, + REQUEST_ALLOW_CORS_SHIFT = 9, REQUEST_METHOD_MASK = FOR_EACH_REQUEST_METHOD(SELECT_MASK) 0, FOR_EACH_REQUEST_METHOD(GENERATE_ENUM_ITEM) REQUEST_ACCEPT_DEFLATE = 1 << 3, REQUEST_ACCEPT_GZIP = 1 << 4, - REQUEST_IS_HTTP_1_0 = 1 << 5, + REQUEST_ACCEPT_BROTLI = 1 << 5, + REQUEST_IS_HTTP_1_0 = 1 << 6, REQUEST_ALLOW_PROXY_REQS = 1 << REQUEST_ALLOW_PROXY_REQS_SHIFT, - REQUEST_PROXIED = 1 << 7, + REQUEST_PROXIED = 1 << 8, REQUEST_ALLOW_CORS = 1 << REQUEST_ALLOW_CORS_SHIFT, - RESPONSE_SENT_HEADERS = 1 << 9, - RESPONSE_CHUNKED_ENCODING = 1 << 10, - RESPONSE_NO_CONTENT_LENGTH = 1 << 11, - RESPONSE_URL_REWRITTEN = 1 << 12, + RESPONSE_SENT_HEADERS = 1 << 10, + RESPONSE_CHUNKED_ENCODING = 1 << 11, + RESPONSE_NO_CONTENT_LENGTH = 1 << 12, + RESPONSE_URL_REWRITTEN = 1 << 13, - RESPONSE_STREAM = 1 << 13, + RESPONSE_STREAM = 1 << 14, - REQUEST_PARSED_QUERY_STRING = 1 << 14, - REQUEST_PARSED_IF_MODIFIED_SINCE = 1 << 15, - REQUEST_PARSED_RANGE = 1 << 16, - REQUEST_PARSED_POST_DATA = 1 << 17, - REQUEST_PARSED_COOKIES = 1 << 18, - REQUEST_PARSED_ACCEPT_ENCODING = 1 << 19, + REQUEST_PARSED_QUERY_STRING = 1 << 15, + REQUEST_PARSED_IF_MODIFIED_SINCE = 1 << 16, + REQUEST_PARSED_RANGE = 1 << 17, + REQUEST_PARSED_POST_DATA = 1 << 18, + REQUEST_PARSED_COOKIES = 1 << 19, + REQUEST_PARSED_ACCEPT_ENCODING = 1 << 20, }; #undef SELECT_MASK From 905cefb3b9bad1740174b09c4703919ff0c08125 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 13 Jul 2019 10:16:39 -0700 Subject: [PATCH 1121/2505] Clean up read_request_finalizer() slightly --- src/lib/lwan-request.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 26520c367..606578245 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -893,11 +893,6 @@ read_from_request_socket(struct lwan_request *request, return HTTP_INTERNAL_ERROR; } -static inline char *has_crlfcrlf(struct lwan_value *buffer) -{ - return memmem(buffer->value, buffer->len, "\r\n\r\n", 4); -} - static enum lwan_read_finalizer read_request_finalizer(size_t total_read, size_t buffer_size, @@ -914,9 +909,11 @@ read_request_finalizer(size_t total_read, if (UNLIKELY(n_packets > helper->error_when_n_packets)) return FINALIZER_ERROR_TIMEOUT; - char *crlfcrlf = has_crlfcrlf(helper->buffer); + char *crlfcrlf = + memmem(helper->buffer->value, helper->buffer->len, "\r\n\r\n", 4); if (LIKELY(crlfcrlf)) { - const size_t crlfcrlf_to_base = (size_t)(crlfcrlf - helper->buffer->value); + const size_t crlfcrlf_to_base = + (size_t)(crlfcrlf - helper->buffer->value); if (LIKELY(helper->next_request)) { helper->next_request = NULL; From 448279005a2df2a532801674ad57f8e62bf96323 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 13 Jul 2019 10:26:40 -0700 Subject: [PATCH 1122/2505] Reallocate compressed buffer if necessary --- src/lib/lwan-mod-serve-files.c | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 6fc065f99..ad3249357 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -360,7 +360,9 @@ static ALWAYS_INLINE bool is_compression_worthy(const size_t compressed_sz, static void deflate_compress_cached_entry(struct mmap_cache_data *md) { - md->deflate_compressed.size = compressBound(md->uncompressed.size); + const unsigned long bound = compressBound(md->uncompressed.size); + + md->deflate_compressed.size = bound; if (UNLIKELY(!(md->deflate_compressed.contents = malloc(md->deflate_compressed.size)))) @@ -373,8 +375,17 @@ static void deflate_compress_cached_entry(struct mmap_cache_data *md) goto error_free_compressed; if (is_compression_worthy(md->deflate_compressed.size, - md->uncompressed.size)) + md->uncompressed.size)) { + if (bound > md->deflate_compressed.size) { + void *tmp; + + tmp = realloc(md->deflate_compressed.contents, + md->deflate_compressed.size); + if (tmp) + md->deflate_compressed.contents = tmp; + } return; + } error_free_compressed: free(md->deflate_compressed.contents); @@ -386,8 +397,9 @@ static void deflate_compress_cached_entry(struct mmap_cache_data *md) #if defined(HAVE_BROTLI) static void br_compress_cached_entry(struct mmap_cache_data *md) { - md->br_compressed.size = - BrotliEncoderMaxCompressedSize(md->uncompressed.size); + const unsigned long bound = BrotliEncoderMaxCompressedSize(md->uncompressed.size); + + md->br_compressed.size = bound; if (UNLIKELY( !(md->br_compressed.contents = malloc(md->br_compressed.size)))) @@ -402,8 +414,17 @@ static void br_compress_cached_entry(struct mmap_cache_data *md) /* is_compression_worthy() is already called for deflate-compressed data, * so only consider brotli-compressed data if it's worth it WRT deflate */ - if (LIKELY(md->br_compressed.size < md->deflate_compressed.size)) + if (LIKELY(md->br_compressed.size < md->deflate_compressed.size)) { + if (bound > md->br_compressed.size) { + void *tmp; + + tmp = realloc(md->br_compressed.contents, + md->br_compressed.size); + if (tmp) + md->br_compressed.contents = tmp; + } return; + } error_free_compressed: free(md->br_compressed.contents); From bd3c7d37fb7ef3c51151d4ecf92c3798c2c284fa Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 13 Jul 2019 13:35:45 -0700 Subject: [PATCH 1123/2505] Compress MIME Type table with Brotli if available Doesn't shave a whole lot (-641 bytes in the default build, compared to compression with Zopfli), though. --- CMakeLists.txt | 2 +- src/bin/tools/CMakeLists.txt | 19 ++++++++++------ src/bin/tools/mimegen.c | 31 ++++++++++++++++++++----- src/lib/lwan-tables.c | 44 +++++++++++++++++++++++++----------- 4 files changed, 69 insertions(+), 27 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6a13d48c6..33963ad57 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,7 +54,7 @@ else () set(HAVE_LUA 1) endif () -pkg_check_modules(BROTLI libbrotlienc libbrotlicommon) +pkg_check_modules(BROTLI libbrotlienc libbrotlidec libbrotlicommon) if (BROTLI_FOUND) list(APPEND ADDITIONAL_LIBRARIES "${BROTLI_LDFLAGS}") set(HAVE_BROTLI 1) diff --git a/src/bin/tools/CMakeLists.txt b/src/bin/tools/CMakeLists.txt index ef91abd2d..c9dc34354 100644 --- a/src/bin/tools/CMakeLists.txt +++ b/src/bin/tools/CMakeLists.txt @@ -8,14 +8,19 @@ else () ${CMAKE_SOURCE_DIR}/src/lib/murmur3.c ${CMAKE_SOURCE_DIR}/src/lib/missing.c ) - find_library(ZOPFLI_LIBRARY NAMES zopfli PATHS /usr/lib /usr/local/lib) - if (ZOPFLI_LIBRARY) - message(STATUS "Using Zopfli (${ZOPFLI_LIBRARY}) for mimegen") - target_link_libraries(mimegen ${ZOPFLI_LIBRARY}) - target_compile_definitions(mimegen PUBLIC -DHAVE_ZOPFLI=1) + if (HAVE_BROTLI) + message(STATUS "Using Brotli for mimegen") + target_link_libraries(mimegen ${BROTLI_LDFLAGS}) else () - message(STATUS "Using zlib (${ZLIB_LIBRARIES}) for mimegen") - target_link_libraries(mimegen ${ZLIB_LIBRARIES}) + find_library(ZOPFLI_LIBRARY NAMES zopfli PATHS /usr/lib /usr/local/lib) + if (ZOPFLI_LIBRARY) + message(STATUS "Using Zopfli (${ZOPFLI_LIBRARY}) for mimegen") + target_link_libraries(mimegen ${ZOPFLI_LIBRARY}) + target_compile_definitions(mimegen PUBLIC -DHAVE_ZOPFLI=1) + else () + message(STATUS "Using zlib (${ZLIB_LIBRARIES}) for mimegen") + target_link_libraries(mimegen ${ZLIB_LIBRARIES}) + endif () endif () add_executable(bin2hex diff --git a/src/bin/tools/mimegen.c b/src/bin/tools/mimegen.c index d6fc11337..71e115a9e 100644 --- a/src/bin/tools/mimegen.c +++ b/src/bin/tools/mimegen.c @@ -25,7 +25,9 @@ #include #include -#ifdef HAVE_ZOPFLI +#if defined(HAVE_BROTLI) +#include +#elif defined(HAVE_ZOPFLI) #include #else #include @@ -84,15 +86,31 @@ static char *compress_output(const struct output *output, size_t *outlen) { char *compressed; -#ifdef HAVE_ZOPFLI +#if defined(HAVE_BROTLI) + *outlen = BrotliEncoderMaxCompressedSize(output->used); + + compressed = malloc(*outlen); + if (!compressed) { + fprintf(stderr, "Could not allocate memory for compressed data\n"); + exit(1); + } + + if (BrotliEncoderCompress(BROTLI_MAX_QUALITY, BROTLI_MAX_WINDOW_BITS, + BROTLI_MODE_TEXT, output->used, + (const unsigned char *)output->ptr, outlen, + (unsigned char *)compressed) != BROTLI_TRUE) { + fprintf(stderr, "Could not compress mime type table with Brotli\n"); + exit(1); + } +#elif defined(HAVE_ZOPFLI) ZopfliOptions opts; *outlen = 0; ZopfliInitOptions(&opts); ZopfliCompress(&opts, ZOPFLI_FORMAT_ZLIB, - (const unsigned char *)output->ptr, output->used, - (unsigned char **)&compressed, outlen); + (const unsigned char *)output->ptr, output->used, + (unsigned char **)&compressed, outlen); #else *outlen = compressBound((uLong)output->used); compressed = malloc(*outlen); @@ -100,8 +118,9 @@ static char *compress_output(const struct output *output, size_t *outlen) fprintf(stderr, "Could not allocate memory for compressed data\n"); exit(1); } - if (compress2((Bytef *)compressed, outlen, (const Bytef *)output->ptr, output->used, 9) != Z_OK) { - fprintf(stderr, "Could not compress data\n"); + if (compress2((Bytef *)compressed, outlen, (const Bytef *)output->ptr, + output->used, 9) != Z_OK) { + fprintf(stderr, "Could not compress data with zlib\n"); exit(1); } #endif diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index 3a41724b8..4e541d2d9 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -21,7 +21,12 @@ #include #include #include + +#if defined(HAVE_BROTLI) +#include +#else #include +#endif #include "lwan-private.h" @@ -31,31 +36,44 @@ static unsigned char uncompressed_mime_entries[MIME_UNCOMPRESSED_LEN]; static struct mime_entry mime_entries[MIME_ENTRIES]; static bool mime_entries_initialized = false; -void -lwan_tables_init(void) +void lwan_tables_init(void) { if (mime_entries_initialized) return; lwan_status_debug("Uncompressing MIME type table: %u->%u bytes, %d entries", MIME_COMPRESSED_LEN, MIME_UNCOMPRESSED_LEN, MIME_ENTRIES); + +#if defined(HAVE_BROTLI) + size_t uncompressed_length = MIME_UNCOMPRESSED_LEN; + BrotliDecoderResult ret; + + ret = BrotliDecoderDecompress(MIME_COMPRESSED_LEN, mime_entries_compressed, + &uncompressed_length, + uncompressed_mime_entries); + if (ret != BROTLI_DECODER_RESULT_SUCCESS) + lwan_status_critical("Error while uncompressing table with Brotli"); +#else uLongf uncompressed_length = MIME_UNCOMPRESSED_LEN; - int ret = uncompress((Bytef*)uncompressed_mime_entries, - &uncompressed_length, (const Bytef*)mime_entries_compressed, - MIME_COMPRESSED_LEN); - if (ret != Z_OK) - lwan_status_critical( - "Error while uncompressing table: zlib error %d", ret); - - if (uncompressed_length != MIME_UNCOMPRESSED_LEN) + int ret = + uncompress((Bytef *)uncompressed_mime_entries, &uncompressed_length, + (const Bytef *)mime_entries_compressed, MIME_COMPRESSED_LEN); + if (ret != Z_OK) { + lwan_status_critical("Error while uncompressing table: zlib error %d", + ret); + } +#endif + + if (uncompressed_length != MIME_UNCOMPRESSED_LEN) { lwan_status_critical("Expected uncompressed length %d, got %ld", - MIME_UNCOMPRESSED_LEN, uncompressed_length); + MIME_UNCOMPRESSED_LEN, uncompressed_length); + } unsigned char *ptr = uncompressed_mime_entries; for (size_t i = 0; i < MIME_ENTRIES; i++) { - mime_entries[i].extension = (char*)ptr; + mime_entries[i].extension = (char *)ptr; ptr = rawmemchr(ptr + 1, '\0') + 1; - mime_entries[i].type = (char*)ptr; + mime_entries[i].type = (char *)ptr; ptr = rawmemchr(ptr + 1, '\0') + 1; } From 19124ec55a149534e8193caad1408e158a3088d4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 13 Jul 2019 21:21:30 -0700 Subject: [PATCH 1124/2505] No need to send body for 304 Not Modified responses Sometimes the body is larger than the content itself... --- src/lib/lwan-response.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 5a12cdb04..dc43c2c33 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -128,10 +128,17 @@ static void log_request(struct lwan_request *request, #define log_request(...) #endif -static const bool has_response_body[REQUEST_METHOD_MASK] = { - [REQUEST_METHOD_GET] = true, - [REQUEST_METHOD_POST] = true, -}; + +static inline bool has_response_body(enum lwan_request_flags method, + enum lwan_http_status status) +{ + static const bool method_has_body[REQUEST_METHOD_MASK] = { + [REQUEST_METHOD_GET] = true, + [REQUEST_METHOD_POST] = true, + }; + + return method_has_body[method] || status != HTTP_NOT_MODIFIED; +} void lwan_response(struct lwan_request *request, enum lwan_http_status status) { @@ -182,7 +189,7 @@ void lwan_response(struct lwan_request *request, enum lwan_http_status status) return; } - if (!has_response_body[lwan_request_get_method(request)]) { + if (!has_response_body(lwan_request_get_method(request), status)) { lwan_send(request, headers, header_len, 0); return; } From 8e1d2b71bf194fda69c86af5afebc05789538bc4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 13 Jul 2019 21:24:50 -0700 Subject: [PATCH 1125/2505] Use fwrite_unlocked() directly to print status messages No need to call printf() here. Use the unlocked variant as stdout has been locked. --- src/lib/lwan-status.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-status.c b/src/lib/lwan-status.c index b2e58f2ee..65e1b35d0 100644 --- a/src/lib/lwan-status.c +++ b/src/lib/lwan-status.c @@ -156,7 +156,7 @@ status_out(const char *file, } #endif - printf("%.*s", (int)start.len, start.value); + fwrite_unlocked(start.value, start.len, 1, stdout); vprintf(fmt, values); if (type & STATUS_PERROR) { @@ -167,7 +167,7 @@ status_out(const char *file, printf(": %s (error number %d)", errmsg, saved_errno); } - printf("%.*s", (int)end.len, end.value); + fwrite_unlocked(end.value, end.len, 1, stdout); funlockfile(stdout); From 789d3e9608f1d651fa82864edf551409c70dde20 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 14 Jul 2019 07:46:21 -0700 Subject: [PATCH 1126/2505] Remove HANDLER_REMOVE_LEADING_SLASH from enum lwan_handler_flags Shaves off a branch from prepare_for_response(). --- src/lib/lwan-mod-lua.c | 1 - src/lib/lwan-mod-serve-files.c | 2 +- src/lib/lwan-request.c | 8 +++----- src/lib/lwan.h | 7 +++---- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/lib/lwan-mod-lua.c b/src/lib/lwan-mod-lua.c index 9ef6b99c4..dd252cdaf 100644 --- a/src/lib/lwan-mod-lua.c +++ b/src/lib/lwan-mod-lua.c @@ -307,7 +307,6 @@ static const struct lwan_module module = { .create_from_hash = lua_create_from_hash, .destroy = lua_destroy, .handle_request = lua_handle_request, - .flags = HANDLER_REMOVE_LEADING_SLASH, }; LWAN_REGISTER_MODULE(lua, &module); diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index ad3249357..caa022777 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -1287,7 +1287,7 @@ static const struct lwan_module module = { .create_from_hash = serve_files_create_from_hash, .destroy = serve_files_destroy, .handle_request = serve_files_handle_request, - .flags = HANDLER_REMOVE_LEADING_SLASH | HANDLER_PARSE_ACCEPT_ENCODING, + .flags = HANDLER_PARSE_ACCEPT_ENCODING, }; LWAN_REGISTER_MODULE(serve_files, &module); diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 606578245..3f12c8ba0 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1327,11 +1327,9 @@ static enum lwan_http_status prepare_for_response(struct lwan_url_map *url_map, return HTTP_NOT_AUTHORIZED; } - if (url_map->flags & HANDLER_REMOVE_LEADING_SLASH) { - while (*request->url.value == '/' && request->url.len > 0) { - ++request->url.value; - --request->url.len; - } + while (*request->url.value == '/' && request->url.len > 0) { + request->url.value++; + request->url.len--; } if (url_map->flags & HANDLER_PARSE_ACCEPT_ENCODING) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 0c77bf99f..52e3b95ef 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -191,10 +191,9 @@ enum lwan_http_status { enum lwan_handler_flags { HANDLER_HAS_POST_DATA = 1 << 0, HANDLER_MUST_AUTHORIZE = 1 << 1, - HANDLER_REMOVE_LEADING_SLASH = 1 << 2, - HANDLER_CAN_REWRITE_URL = 1 << 3, - HANDLER_DATA_IS_HASH_TABLE = 1 << 4, - HANDLER_PARSE_ACCEPT_ENCODING = 1 << 5, + HANDLER_CAN_REWRITE_URL = 1 << 2, + HANDLER_DATA_IS_HASH_TABLE = 1 << 3, + HANDLER_PARSE_ACCEPT_ENCODING = 1 << 4, HANDLER_PARSE_MASK = HANDLER_HAS_POST_DATA, }; From 0356cf1c4ed1012a9db177cc092288ac0122e0c2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 14 Jul 2019 09:35:47 -0700 Subject: [PATCH 1127/2505] Use NSS_BUFLEN_PASSWD if sysconf(_SC_GETPW_R_SIZE_MAX) fails --- src/lib/lwan-straitjacket.c | 10 +++++++--- src/lib/missing/pwd.h | 30 ++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 src/lib/missing/pwd.h diff --git a/src/lib/lwan-straitjacket.c b/src/lib/lwan-straitjacket.c index 08c4ddb04..eeb652540 100644 --- a/src/lib/lwan-straitjacket.c +++ b/src/lib/lwan-straitjacket.c @@ -37,14 +37,18 @@ static bool get_user_uid_gid(const char *user, uid_t *uid, gid_t *gid) { - struct passwd pwd = { }; + struct passwd pwd = {}; struct passwd *result; char *buf; long pw_size_max = sysconf(_SC_GETPW_R_SIZE_MAX); int r; - if (pw_size_max < 0) - pw_size_max = 16384; + if (pw_size_max < 0) { + /* This constant is returned for sysconf(_SC_GETPW_R_SIZE_MAX) in glibc, + * and it seems to be a reasonable size (1024). Use it as a fallback in + * the (very unlikely) case where sysconf() fails. */ + pw_size_max = NSS_BUFLEN_PASSWD; + } buf = malloc((size_t)pw_size_max); if (!buf) { diff --git a/src/lib/missing/pwd.h b/src/lib/missing/pwd.h new file mode 100644 index 000000000..d1955b33a --- /dev/null +++ b/src/lib/missing/pwd.h @@ -0,0 +1,30 @@ +/* + * lwan - simple web server + * Copyright (c) 2019 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include_next + +#ifndef MISSING_PWD_H +#define MISSING_PWD_H + +#ifndef NSS_BUFLEN_PASSWD +#define NSS_BUFLEN_PASSWD 1024 +#endif + +#endif /* MISSING_PWD_H */ From e2da5d13b8d48d23785ccc29b24834e316d14bb5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 14 Jul 2019 10:50:48 -0700 Subject: [PATCH 1128/2505] Reduce cache pressure while looking up MIME type from extension By splitting an array of {extension, mime_type} into two arrays, each containing extensions and mime_types respectively, this reduces the cache line pressure while looking up by quite a bit. With 954 extensions at 8 bytes/pointer, the extensions array uses 120 cache lines. This is just for the pointer to the actual data, which still uses the same layout as it used to use. --- src/bin/tools/mimegen.c | 1 - src/lib/lwan-tables.c | 24 +++++++++++++----------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/bin/tools/mimegen.c b/src/bin/tools/mimegen.c index 71e115a9e..a8b9a4f31 100644 --- a/src/bin/tools/mimegen.c +++ b/src/bin/tools/mimegen.c @@ -273,7 +273,6 @@ int main(int argc, char *argv[]) printf("#define MIME_UNCOMPRESSED_LEN %zu\n", output.used); printf("#define MIME_COMPRESSED_LEN %lu\n", compressed_size); printf("#define MIME_ENTRIES %d\n", hash_get_count(ext_mime)); - printf("struct mime_entry { const char *extension; const char *type; };\n"); printf("static const unsigned char mime_entries_compressed[] = {\n"); for (i = 1; compressed_size; compressed_size--, i++) printf("0x%x,%c", compressed[i - 1] & 0xff, " \n"[i % 13 == 0]); diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index 4e541d2d9..b16056fc3 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -33,7 +33,8 @@ #include "mime-types.h" static unsigned char uncompressed_mime_entries[MIME_UNCOMPRESSED_LEN]; -static struct mime_entry mime_entries[MIME_ENTRIES]; +static char *mime_extensions[MIME_ENTRIES]; +static char *mime_types[MIME_ENTRIES]; static bool mime_entries_initialized = false; void lwan_tables_init(void) @@ -71,9 +72,9 @@ void lwan_tables_init(void) unsigned char *ptr = uncompressed_mime_entries; for (size_t i = 0; i < MIME_ENTRIES; i++) { - mime_entries[i].extension = (char *)ptr; + mime_extensions[i] = (char *)ptr; ptr = rawmemchr(ptr + 1, '\0') + 1; - mime_entries[i].type = (char *)ptr; + mime_types[i] = (char *)ptr; ptr = rawmemchr(ptr + 1, '\0') + 1; } @@ -88,9 +89,10 @@ lwan_tables_shutdown(void) static int compare_mime_entry(const void *a, const void *b) { - const struct mime_entry *me1 = a; - const struct mime_entry *me2 = b; - return strcmp(me1->extension, me2->extension); + const char **exta = (const char **)a; + const char **extb = (const char **)b; + + return strcmp(*exta, *extb); } const char * @@ -116,12 +118,12 @@ lwan_determine_mime_type_for_file_name(const char *file_name) } if (LIKELY(*last_dot)) { - struct mime_entry *entry, key = { .extension = last_dot + 1 }; + char **extension, *key = last_dot + 1; - entry = bsearch(&key, mime_entries, MIME_ENTRIES, - sizeof(struct mime_entry), compare_mime_entry); - if (LIKELY(entry)) - return entry->type; + extension = bsearch(&key, mime_extensions, MIME_ENTRIES, sizeof(char *), + compare_mime_entry); + if (LIKELY(extension)) + return mime_types[extension - mime_extensions]; } fallback: From a4adb08f77474521d96463413142038bcf14002b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 14 Jul 2019 11:23:22 -0700 Subject: [PATCH 1129/2505] Reduce size of parse_headers() a little bit further Increment the `p' pointer inside set_header_value(). --- src/lib/lwan-request.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 3f12c8ba0..e16565fe7 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -511,9 +511,11 @@ identify_http_path(struct lwan_request *request, char *buffer) return end_of_line + 1; } -__attribute__((noinline)) static void -set_header_value(struct lwan_value *header, char *end, char *p) +__attribute__((noinline)) static void set_header_value( + struct lwan_value *header, char *end, char *p, size_t header_len) { + p += header_len; + if (LIKELY(string_as_int16(p) == MULTICHAR_CONSTANT_SMALL(':', ' '))) { *end = '\0'; char *value = p + sizeof(": ") - 1; @@ -531,7 +533,10 @@ set_header_value(struct lwan_value *header, char *end, char *p) }) #define SET_HEADER_VALUE(dest, hdr) \ - set_header_value(&(helper->dest), end, p += HEADER_LENGTH(hdr)); + do { \ + const size_t header_len = HEADER_LENGTH(hdr); \ + set_header_value(&(helper->dest), end, p, header_len); \ + } while (0) static bool parse_headers(struct lwan_request_parser_helper *helper, char *buffer) From b8e7dfec0607255ccdc883daecf69230f3cc5996 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 14 Jul 2019 11:24:53 -0700 Subject: [PATCH 1130/2505] Minor cleanups in parse_headers() --- src/lib/lwan-request.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index e16565fe7..de8c98f2f 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -545,9 +545,8 @@ static bool parse_headers(struct lwan_request_parser_helper *helper, char **header_start = helper->header_start; size_t n_headers = 0; char *next_header; - char *p; - for (p = buffer + 1;;) { + for (char *p = buffer + 1;;) { char *next_chr = p; next_header = memchr(next_chr, '\r', (size_t)(buffer_end - p)); @@ -575,19 +574,15 @@ static bool parse_headers(struct lwan_request_parser_helper *helper, p = next_header + HEADER_TERMINATOR_LEN; - if (UNLIKELY(n_headers >= (N_HEADER_START - 1) || p >= buffer_end)) { - helper->n_header_start = 0; + if (UNLIKELY(n_headers >= (N_HEADER_START - 1) || p >= buffer_end)) return false; - } } header_start[n_headers] = next_header; for (size_t i = 0; i < n_headers; i++) { - char *end; - - p = header_start[i]; - end = header_start[i + 1] - HEADER_TERMINATOR_LEN; + char *p = header_start[i]; + char *end = header_start[i + 1] - HEADER_TERMINATOR_LEN; STRING_SWITCH_L (p) { case MULTICHAR_CONSTANT_L('A', 'c', 'c', 'e'): From 0b7458a0dd3cacacf03acd38d35c3f7d424ec2e6 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 14 Jul 2019 11:25:03 -0700 Subject: [PATCH 1131/2505] Fail early if '\r' is not found while scanning for headers --- src/lib/lwan-request.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index de8c98f2f..614a273e2 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -551,8 +551,8 @@ static bool parse_headers(struct lwan_request_parser_helper *helper, next_header = memchr(next_chr, '\r', (size_t)(buffer_end - p)); - if (!next_header) - break; + if (UNLIKELY(!next_header)) + return false; if (next_chr == next_header) { if (buffer_end - next_chr > (ptrdiff_t)HEADER_TERMINATOR_LEN) { From 147608edfae39fadcba956e444a4bcc66ca019bd Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 14 Jul 2019 12:35:01 -0700 Subject: [PATCH 1132/2505] Add test case for oss-fuzz issue 15809 https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=15809 --- ...sterfuzz-testcase-minimized-request_fuzzer-5652010562486272 | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 fuzz/clusterfuzz-testcase-minimized-request_fuzzer-5652010562486272 diff --git a/fuzz/clusterfuzz-testcase-minimized-request_fuzzer-5652010562486272 b/fuzz/clusterfuzz-testcase-minimized-request_fuzzer-5652010562486272 new file mode 100644 index 000000000..03e0bd4d3 --- /dev/null +++ b/fuzz/clusterfuzz-testcase-minimized-request_fuzzer-5652010562486272 @@ -0,0 +1,3 @@ + + +GET / HTTP/1.0 Rang : \ No newline at end of file From 77ae44c2c00843ecb14f04a66d4c349ac8c51aa2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 14 Jul 2019 12:38:10 -0700 Subject: [PATCH 1133/2505] Fuzz regression tests might issue a HTTP/1.0 request --- src/scripts/testsuite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index 67df299a4..ce9a45c8d 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -835,7 +835,7 @@ def run_test(self, contents): with self.connect() as sock: sock.send(contents) first_8 = sock.recv(8) - self.assertTrue(first_8 in ("HTTP/1.1", "")) + self.assertTrue(first_8 in ("HTTP/1.1", "HTTP/1.0", "")) @staticmethod def wrap(name): From d7dcff4ea828fe267a2381afa560600eefd2f870 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 14 Jul 2019 12:43:51 -0700 Subject: [PATCH 1134/2505] Fix regression found by re-running fuzz-tests with an asan build clusterfuzz-testcase-minimized-request_fuzzer-5729298679332864 has regressed; fix it. --- src/lib/lwan-request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 614a273e2..a39e9b7c5 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -945,7 +945,7 @@ static ALWAYS_INLINE enum lwan_http_status read_request(struct lwan_request *request) { return read_from_request_socket(request, request->helper->buffer, - DEFAULT_BUFFER_SIZE, + DEFAULT_BUFFER_SIZE - 1 /* -1 for NUL byte */, read_request_finalizer); } From 43340f519385cfc28096d59a2a4aeb98f8c7e845 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 14 Jul 2019 13:35:16 -0700 Subject: [PATCH 1135/2505] Send random strings for test_will_it_blend() --- src/scripts/testsuite.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index ce9a45c8d..de4b6eb55 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -4,6 +4,7 @@ # considerably and make it possible to perform more low-level tests. import os +import random import re import requests import signal @@ -12,6 +13,7 @@ import sys import time import unittest +import string LWAN_PATH = './build/src/bin/testrunner/testrunner' for arg in sys.argv[1:]: @@ -72,7 +74,9 @@ def test_will_it_blend(self): self.assertEqual(r.json(), {'did-it-blend': 'oh-hell-yeah'}) def make_request_with_size(self, size): - data = "tro" + "lo" * size + random.seed(size) + + data = "".join(random.choice(string.printable) for c in range(size * 2)) r = requests.post('http://127.0.0.1:8080/post/big', data=data, headers={'Content-Type': 'x-test/trololo'}) From ee40bc295bf1c7ea0e88e5ec6635a75b28364a0e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 14 Jul 2019 21:33:28 -0700 Subject: [PATCH 1136/2505] Outline compressed blob reallocation code --- src/lib/lwan-mod-serve-files.c | 36 ++++++++++++++++------------------ 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index caa022777..96933211a 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -358,6 +358,17 @@ static ALWAYS_INLINE bool is_compression_worthy(const size_t compressed_sz, return ((compressed_sz + deflated_header_size) < uncompressed_sz); } +static void +realloc_if_needed(size_t current_alloc_size, size_t needed_size, void **ptr) +{ + if (needed_size < current_alloc_size) { + void *tmp = realloc(*ptr, needed_size); + + if (tmp) + *ptr = tmp; + } +} + static void deflate_compress_cached_entry(struct mmap_cache_data *md) { const unsigned long bound = compressBound(md->uncompressed.size); @@ -376,15 +387,8 @@ static void deflate_compress_cached_entry(struct mmap_cache_data *md) if (is_compression_worthy(md->deflate_compressed.size, md->uncompressed.size)) { - if (bound > md->deflate_compressed.size) { - void *tmp; - - tmp = realloc(md->deflate_compressed.contents, - md->deflate_compressed.size); - if (tmp) - md->deflate_compressed.contents = tmp; - } - return; + return realloc_if_needed(bound, md->deflate_compressed.size, + &md->deflate_compressed.contents); } error_free_compressed: @@ -397,7 +401,8 @@ static void deflate_compress_cached_entry(struct mmap_cache_data *md) #if defined(HAVE_BROTLI) static void br_compress_cached_entry(struct mmap_cache_data *md) { - const unsigned long bound = BrotliEncoderMaxCompressedSize(md->uncompressed.size); + const unsigned long bound = + BrotliEncoderMaxCompressedSize(md->uncompressed.size); md->br_compressed.size = bound; @@ -415,15 +420,8 @@ static void br_compress_cached_entry(struct mmap_cache_data *md) /* is_compression_worthy() is already called for deflate-compressed data, * so only consider brotli-compressed data if it's worth it WRT deflate */ if (LIKELY(md->br_compressed.size < md->deflate_compressed.size)) { - if (bound > md->br_compressed.size) { - void *tmp; - - tmp = realloc(md->br_compressed.contents, - md->br_compressed.size); - if (tmp) - md->br_compressed.contents = tmp; - } - return; + return realloc_if_needed(bound, md->br_compressed.size, + &md->br_compressed.contents); } error_free_compressed: From 113cda6bac9b9d25a487c2a1cdeb9f5207e0328f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 14 Jul 2019 23:37:23 -0700 Subject: [PATCH 1137/2505] Use struct lwan_value to implement struct mmap_cache_data --- src/lib/lwan-mod-serve-files.c | 97 +++++++++++++++------------------- 1 file changed, 42 insertions(+), 55 deletions(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 96933211a..2e989d584 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -94,16 +94,11 @@ struct cache_funcs { }; struct mmap_cache_data { - struct { - void *contents; - /* zlib expects unsigned longs instead of size_t */ - unsigned long size; - } uncompressed - , deflate_compressed + struct lwan_value uncompressed; + struct lwan_value deflated; #if defined(HAVE_BROTLI) - , br_compressed + struct lwan_value brotli; #endif - ; }; struct sendfile_cache_data { @@ -359,10 +354,10 @@ static ALWAYS_INLINE bool is_compression_worthy(const size_t compressed_sz, } static void -realloc_if_needed(size_t current_alloc_size, size_t needed_size, void **ptr) +realloc_if_needed(size_t current_alloc_size, size_t needed_size, char **ptr) { if (needed_size < current_alloc_size) { - void *tmp = realloc(*ptr, needed_size); + char *tmp = realloc(*ptr, needed_size); if (tmp) *ptr = tmp; @@ -371,64 +366,56 @@ realloc_if_needed(size_t current_alloc_size, size_t needed_size, void **ptr) static void deflate_compress_cached_entry(struct mmap_cache_data *md) { - const unsigned long bound = compressBound(md->uncompressed.size); + const unsigned long bound = compressBound(md->uncompressed.len); - md->deflate_compressed.size = bound; + md->deflated.len = bound; - if (UNLIKELY(!(md->deflate_compressed.contents = - malloc(md->deflate_compressed.size)))) + if (UNLIKELY(!(md->deflated.value = malloc(md->deflated.len)))) goto error_zero_out; - if (UNLIKELY(compress(md->deflate_compressed.contents, - &md->deflate_compressed.size, - md->uncompressed.contents, - md->uncompressed.size) != Z_OK)) + if (UNLIKELY(compress((Bytef *)md->deflated.value, &md->deflated.len, + (Bytef *)md->uncompressed.value, + md->uncompressed.len) != Z_OK)) goto error_free_compressed; - if (is_compression_worthy(md->deflate_compressed.size, - md->uncompressed.size)) { - return realloc_if_needed(bound, md->deflate_compressed.size, - &md->deflate_compressed.contents); - } + if (is_compression_worthy(md->deflated.len, md->uncompressed.len)) + return realloc_if_needed(bound, md->deflated.len, &md->deflated.value); error_free_compressed: - free(md->deflate_compressed.contents); - md->deflate_compressed.contents = NULL; + free(md->deflated.value); + md->deflated.value = NULL; error_zero_out: - md->deflate_compressed.size = 0; + md->deflated.len = 0; } #if defined(HAVE_BROTLI) static void br_compress_cached_entry(struct mmap_cache_data *md) { const unsigned long bound = - BrotliEncoderMaxCompressedSize(md->uncompressed.size); + BrotliEncoderMaxCompressedSize(md->uncompressed.len); - md->br_compressed.size = bound; + md->brotli.len = bound; - if (UNLIKELY( - !(md->br_compressed.contents = malloc(md->br_compressed.size)))) + if (UNLIKELY(!(md->brotli.value = malloc(md->brotli.len)))) goto error_zero_out; if (UNLIKELY(BrotliEncoderCompress( BROTLI_DEFAULT_QUALITY, BROTLI_DEFAULT_WINDOW, - BROTLI_DEFAULT_MODE, md->uncompressed.size, - md->uncompressed.contents, &md->br_compressed.size, - md->br_compressed.contents) != BROTLI_TRUE)) + BROTLI_DEFAULT_MODE, md->uncompressed.len, + (uint8_t *)md->uncompressed.value, &md->brotli.len, + (uint8_t *)md->brotli.value) != BROTLI_TRUE)) goto error_free_compressed; /* is_compression_worthy() is already called for deflate-compressed data, * so only consider brotli-compressed data if it's worth it WRT deflate */ - if (LIKELY(md->br_compressed.size < md->deflate_compressed.size)) { - return realloc_if_needed(bound, md->br_compressed.size, - &md->br_compressed.contents); - } + if (LIKELY(md->brotli.len < md->deflated.len)) + return realloc_if_needed(bound, md->brotli.len, &md->brotli.value); error_free_compressed: - free(md->br_compressed.contents); - md->br_compressed.contents = NULL; + free(md->brotli.value); + md->brotli.value = NULL; error_zero_out: - md->br_compressed.size = 0; + md->brotli.len = 0; } #endif @@ -448,16 +435,16 @@ static bool mmap_init(struct file_cache_entry *ce, if (UNLIKELY(file_fd < 0)) return false; - md->uncompressed.contents = + md->uncompressed.value = mmap(NULL, (size_t)st->st_size, PROT_READ, MAP_SHARED, file_fd, 0); - if (UNLIKELY(md->uncompressed.contents == MAP_FAILED)) { + if (UNLIKELY(md->uncompressed.value == MAP_FAILED)) { success = false; goto close_file; } - lwan_madvise_queue(md->uncompressed.contents, (size_t)st->st_size); + lwan_madvise_queue(md->uncompressed.value, (size_t)st->st_size); - md->uncompressed.size = (size_t)st->st_size; + md->uncompressed.len = (size_t)st->st_size; deflate_compress_cached_entry(md); #if defined(HAVE_BROTLI) br_compress_cached_entry(md); @@ -840,10 +827,10 @@ static void mmap_free(struct file_cache_entry *fce) { struct mmap_cache_data *md = &fce->mmap_cache_data; - munmap(md->uncompressed.contents, md->uncompressed.size); - free(md->deflate_compressed.contents); + munmap(md->uncompressed.value, md->uncompressed.len); + free(md->deflated.value); #if defined(HAVE_BROTLI) - free(md->br_compressed.contents); + free(md->brotli.value); #endif } @@ -1165,17 +1152,17 @@ static enum lwan_http_status mmap_serve(struct lwan_request *request, enum lwan_http_status status; #if defined(HAVE_BROTLI) - if (md->br_compressed.size && (request->flags & REQUEST_ACCEPT_BROTLI)) { - contents = md->br_compressed.contents; - size = md->br_compressed.size; + if (md->brotli.len && (request->flags & REQUEST_ACCEPT_BROTLI)) { + contents = md->brotli.value; + size = md->brotli.len; compressed = &br_compression_hdr; status = HTTP_OK; } else #endif - if (md->deflate_compressed.size && (request->flags & REQUEST_ACCEPT_DEFLATE)) { - contents = md->deflate_compressed.contents; - size = md->deflate_compressed.size; + if (md->deflated.len && (request->flags & REQUEST_ACCEPT_DEFLATE)) { + contents = md->deflated.value; + size = md->deflated.len; compressed = &deflate_compression_hdr; status = HTTP_OK; @@ -1183,11 +1170,11 @@ static enum lwan_http_status mmap_serve(struct lwan_request *request, off_t from, to; status = - compute_range(request, &from, &to, (off_t)md->uncompressed.size); + compute_range(request, &from, &to, (off_t)md->uncompressed.len); switch (status) { case HTTP_PARTIAL_CONTENT: case HTTP_OK: - contents = (char *)md->uncompressed.contents + from; + contents = (char *)md->uncompressed.value + from; size = (size_t)(to - from); compressed = NULL; break; From 31873ebe552a7586c6121dbdabea9e6677f23d05 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 14 Jul 2019 23:43:04 -0700 Subject: [PATCH 1138/2505] Rename ENFORCE_STATIC_BUFFER_LENGTH to LWAN_ARRAY_PARAM --- src/lib/lwan.h | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 52e3b95ef..a24f69d86 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -149,9 +149,9 @@ static ALWAYS_INLINE int16_t string_as_int16(const char *s) #define ATOMIC_BITWISE(P, O, V) (__sync_##O##_and_fetch((P), (V))) #if defined(__cplusplus) -#define ENFORCE_STATIC_BUFFER_LENGTH +#define LWAN_ARRAY_PARAM(length) [length] #else -#define ENFORCE_STATIC_BUFFER_LENGTH static +#define LWAN_ARRAY_PARAM(length) [static length] #endif #define FOR_EACH_HTTP_STATUS(X) \ @@ -518,15 +518,13 @@ int lwan_connection_get_fd(const struct lwan *lwan, const struct lwan_connection *conn) __attribute__((pure)) __attribute__((warn_unused_result)); -const char *lwan_request_get_remote_address( - struct lwan_request *request, - char buffer[ENFORCE_STATIC_BUFFER_LENGTH INET6_ADDRSTRLEN]) +const char * +lwan_request_get_remote_address(struct lwan_request *request, + char buffer LWAN_ARRAY_PARAM(INET6_ADDRSTRLEN)) __attribute__((warn_unused_result)); -int lwan_format_rfc_time(const time_t in, - char out[ENFORCE_STATIC_BUFFER_LENGTH 30]); -int lwan_parse_rfc_time(const char in[ENFORCE_STATIC_BUFFER_LENGTH 30], - time_t *out); +int lwan_format_rfc_time(const time_t in, char out LWAN_ARRAY_PARAM(30)); +int lwan_parse_rfc_time(const char in LWAN_ARRAY_PARAM(30), time_t *out); static inline enum lwan_request_flags lwan_request_get_method(const struct lwan_request *request) From 7e9e0fc4f13d73b837ffc9ee0da0931169ecf401 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 15 Jul 2019 00:04:48 -0700 Subject: [PATCH 1139/2505] Deflate automatic index in file serving module File listings with thousands of files can be hundreds of kilobytes long, so compression makes sense. --- src/lib/lwan-mod-serve-files.c | 89 ++++++++++++++++++++-------------- 1 file changed, 53 insertions(+), 36 deletions(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 2e989d584..a4b26c445 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -110,6 +110,7 @@ struct sendfile_cache_data { struct dir_list_cache_data { struct lwan_strbuf rendered; + struct lwan_value deflated; }; struct redir_cache_data { @@ -353,69 +354,71 @@ static ALWAYS_INLINE bool is_compression_worthy(const size_t compressed_sz, return ((compressed_sz + deflated_header_size) < uncompressed_sz); } -static void -realloc_if_needed(size_t current_alloc_size, size_t needed_size, char **ptr) +static void realloc_if_needed(struct lwan_value *value, size_t needed_size) { - if (needed_size < current_alloc_size) { - char *tmp = realloc(*ptr, needed_size); + if (needed_size < value->len) { + char *tmp = realloc(value->value, needed_size); if (tmp) - *ptr = tmp; + value->value = tmp; } } -static void deflate_compress_cached_entry(struct mmap_cache_data *md) +static void deflate_value(const struct lwan_value *uncompressed, + struct lwan_value *compressed) { - const unsigned long bound = compressBound(md->uncompressed.len); + const unsigned long bound = compressBound(uncompressed->len); - md->deflated.len = bound; + compressed->len = bound; - if (UNLIKELY(!(md->deflated.value = malloc(md->deflated.len)))) + if (UNLIKELY(!(compressed->value = malloc(bound)))) goto error_zero_out; - if (UNLIKELY(compress((Bytef *)md->deflated.value, &md->deflated.len, - (Bytef *)md->uncompressed.value, - md->uncompressed.len) != Z_OK)) + if (UNLIKELY(compress((Bytef *)compressed->value, &compressed->len, + (Bytef *)uncompressed->value, + uncompressed->len) != Z_OK)) goto error_free_compressed; - if (is_compression_worthy(md->deflated.len, md->uncompressed.len)) - return realloc_if_needed(bound, md->deflated.len, &md->deflated.value); + if (is_compression_worthy(compressed->len, uncompressed->len)) + return realloc_if_needed(compressed, bound); error_free_compressed: - free(md->deflated.value); - md->deflated.value = NULL; + free(compressed->value); + compressed->value = NULL; error_zero_out: - md->deflated.len = 0; + compressed->len = 0; } #if defined(HAVE_BROTLI) -static void br_compress_cached_entry(struct mmap_cache_data *md) +static void brotli_value(const struct lwan_value *uncompressed, + struct lwan_value *brotli, + const struct lwan_value *deflated) { const unsigned long bound = - BrotliEncoderMaxCompressedSize(md->uncompressed.len); + BrotliEncoderMaxCompressedSize(uncompressed->len); - md->brotli.len = bound; + brotli->len = bound; - if (UNLIKELY(!(md->brotli.value = malloc(md->brotli.len)))) + if (UNLIKELY(!(brotli->value = malloc(brotli->len)))) goto error_zero_out; - if (UNLIKELY(BrotliEncoderCompress( - BROTLI_DEFAULT_QUALITY, BROTLI_DEFAULT_WINDOW, - BROTLI_DEFAULT_MODE, md->uncompressed.len, - (uint8_t *)md->uncompressed.value, &md->brotli.len, - (uint8_t *)md->brotli.value) != BROTLI_TRUE)) + if (UNLIKELY( + BrotliEncoderCompress(BROTLI_DEFAULT_QUALITY, BROTLI_DEFAULT_WINDOW, + BROTLI_DEFAULT_MODE, uncompressed->len, + (uint8_t *)uncompressed->value, &brotli->len, + (uint8_t *)brotli->value) != BROTLI_TRUE)) goto error_free_compressed; /* is_compression_worthy() is already called for deflate-compressed data, * so only consider brotli-compressed data if it's worth it WRT deflate */ - if (LIKELY(md->brotli.len < md->deflated.len)) - return realloc_if_needed(bound, md->brotli.len, &md->brotli.value); + if (LIKELY(brotli->len < deflated->len)) + return realloc_if_needed(brotli, bound); error_free_compressed: - free(md->brotli.value); - md->brotli.value = NULL; + free(brotli->value); + brotli->value = NULL; error_zero_out: - md->brotli.len = 0; + brotli->len = 0; } #endif @@ -445,9 +448,9 @@ static bool mmap_init(struct file_cache_entry *ce, lwan_madvise_queue(md->uncompressed.value, (size_t)st->st_size); md->uncompressed.len = (size_t)st->st_size; - deflate_compress_cached_entry(md); + deflate_value(&md->uncompressed, &md->deflated); #if defined(HAVE_BROTLI) - br_compress_cached_entry(md); + brotli_value(&md->uncompressed, &md->brotli, &md->deflated); #endif ce->mime_type = @@ -658,6 +661,12 @@ static bool dirlist_init(struct file_cache_entry *ce, ce->mime_type = "text/html"; + struct lwan_value rendered = { + .value = lwan_strbuf_get_buffer(&dd->rendered), + .len = lwan_strbuf_get_length(&dd->rendered), + }; + deflate_value(&rendered, &dd->deflated); + ret = true; goto out_free_readme; @@ -849,6 +858,7 @@ static void dirlist_free(struct file_cache_entry *fce) struct dir_list_cache_data *dd = &fce->dir_list_cache_data; lwan_strbuf_free(&dd->rendered); + free(dd->deflated.value); } static void redir_free(struct file_cache_entry *fce) @@ -1190,6 +1200,7 @@ static enum lwan_http_status mmap_serve(struct lwan_request *request, static enum lwan_http_status dirlist_serve(struct lwan_request *request, void *data) { + const struct lwan_key_value *compressed = NULL; struct file_cache_entry *fce = data; struct dir_list_cache_data *dd = &fce->dir_list_cache_data; const char *icon; @@ -1198,8 +1209,14 @@ static enum lwan_http_status dirlist_serve(struct lwan_request *request, icon = lwan_request_get_query_param(request, "icon"); if (!icon) { - contents = lwan_strbuf_get_buffer(&dd->rendered); - size = lwan_strbuf_get_length(&dd->rendered); + if (dd->deflated.len && (request->flags & REQUEST_ACCEPT_DEFLATE)) { + compressed = &deflate_compression_hdr; + contents = dd->deflated.value; + size = dd->deflated.len; + } else { + contents = lwan_strbuf_get_buffer(&dd->rendered); + size = lwan_strbuf_get_length(&dd->rendered); + } } else if (streq(icon, "back")) { contents = back_gif; size = sizeof(back_gif); @@ -1216,7 +1233,7 @@ static enum lwan_http_status dirlist_serve(struct lwan_request *request, return HTTP_NOT_FOUND; } - return serve_buffer(request, fce, NULL, contents, size, HTTP_OK); + return serve_buffer(request, fce, compressed, contents, size, HTTP_OK); } static enum lwan_http_status redir_serve(struct lwan_request *request, From 7e9c3fb43e6210efd50ce9f7d881b76e965d6b88 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 15 Jul 2019 18:53:47 -0700 Subject: [PATCH 1140/2505] Simplify accept-encoding parsing --- src/lib/lwan-request.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index a39e9b7c5..6eea901ae 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -686,16 +686,21 @@ parse_accept_encoding(struct lwan_request *request) if (!helper->accept_encoding.len) return; - for (const char *p = helper->accept_encoding.value; *p;) { + for (const char *p = helper->accept_encoding.value; *p; p++) { STRING_SWITCH(p) { case MULTICHAR_CONSTANT('d','e','f','l'): + case MULTICHAR_CONSTANT(' ','d','e','f'): request->flags |= REQUEST_ACCEPT_DEFLATE; break; case MULTICHAR_CONSTANT('g','z','i','p'): + case MULTICHAR_CONSTANT(' ','g','z','i'): request->flags |= REQUEST_ACCEPT_GZIP; break; #if defined(HAVE_BROTLI) default: + while (lwan_char_isspace(*p)) + p++; + STRING_SWITCH_SMALL(p) { case MULTICHAR_CONSTANT_SMALL('b', 'r'): request->flags |= REQUEST_ACCEPT_BROTLI; @@ -706,8 +711,6 @@ parse_accept_encoding(struct lwan_request *request) if (!(p = strchr(p, ','))) break; - - for (p++; lwan_char_isspace(*p); p++); } } From 4cae6b374ffc60d6a5310707adcdde4086874302 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 17 Jul 2019 08:06:54 -0700 Subject: [PATCH 1141/2505] Brotli needs more stack space in release build --- src/lib/lwan-coro.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 1b04ddfcd..4cea2340e 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -39,7 +39,11 @@ # define SIGSTKSZ 16384 #endif -#define CORO_STACK_MIN (4 * SIGSTKSZ) +#ifdef HAVE_BROTLI +# define CORO_STACK_MIN (8 * SIGSTKSZ) +#else +# define CORO_STACK_MIN (4 * SIGSTKSZ) +#endif static_assert(DEFAULT_BUFFER_SIZE < (CORO_STACK_MIN + SIGSTKSZ), "Request buffer fits inside coroutine stack"); From 205f889d08084bd231d9721562ad8cc7d075ef65 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 17 Jul 2019 08:14:25 -0700 Subject: [PATCH 1142/2505] Try to coalesce pipelined responses into smaller TCP packets Sort of an "auto-corking" feature for lwan_send(). Improves pipelined response rate by ~20%. --- src/lib/lwan-io-wrappers.c | 18 ++++++++++++++---- src/lib/lwan-io-wrappers.h | 2 +- src/lib/lwan-response.c | 4 ++-- src/lib/lwan-thread.c | 2 ++ src/lib/lwan.h | 2 ++ 5 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index 69f8f0401..7214a4585 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -32,14 +32,21 @@ static const int MAX_FAILED_TRIES = 5; ssize_t -lwan_writev(struct lwan_request *request, struct iovec *iov, int iov_count) +lwan_writev(struct lwan_request *request, struct iovec *iov, size_t iov_count) { ssize_t total_written = 0; - int curr_iov = 0; + size_t curr_iov = 0; + int flags = 0; + + if (request->conn->flags & CONN_CORK) + flags |= MSG_MORE; for (int tries = MAX_FAILED_TRIES; tries;) { - ssize_t written = - writev(request->fd, iov + curr_iov, iov_count - curr_iov); + struct msghdr hdr = { + .msg_iov = iov + curr_iov, + .msg_iovlen = iov_count - curr_iov, + }; + ssize_t written = sendmsg(request->fd, &hdr, flags); if (UNLIKELY(written < 0)) { /* FIXME: Consider short writes as another try as well? */ tries--; @@ -128,6 +135,9 @@ ssize_t lwan_send(struct lwan_request *request, { ssize_t total_sent = 0; + if (request->conn->flags & CONN_CORK) + flags |= MSG_MORE; + for (int tries = MAX_FAILED_TRIES; tries;) { ssize_t written = send(request->fd, buf, count, flags); if (UNLIKELY(written < 0)) { diff --git a/src/lib/lwan-io-wrappers.h b/src/lib/lwan-io-wrappers.h index de60ab025..41b9a241d 100644 --- a/src/lib/lwan-io-wrappers.h +++ b/src/lib/lwan-io-wrappers.h @@ -25,7 +25,7 @@ #include "lwan.h" ssize_t lwan_writev(struct lwan_request *request, struct iovec *iov, - int iovcnt); + size_t iovcnt); ssize_t lwan_send(struct lwan_request *request, const void *buf, size_t count, int flags); void lwan_sendfile(struct lwan_request *request, int in_fd, diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index dc43c2c33..b94dddf22 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -470,7 +470,7 @@ bool lwan_response_set_event_stream(struct lwan_request *request, void lwan_response_send_event(struct lwan_request *request, const char *event) { struct iovec vec[6]; - int last = 0; + size_t last = 0; if (!(request->flags & RESPONSE_SENT_HEADERS)) { if (UNLIKELY(!lwan_response_set_event_stream(request, HTTP_OK))) @@ -533,7 +533,7 @@ static void write_websocket_frame(struct lwan_request *request, uint8_t net_len_byte; uint16_t net_len_short; uint64_t net_len_long; - int last = 0; + size_t last = 0; vec[last++] = (struct iovec){.iov_base = &header_byte, .iov_len = 1}; diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index cbd7e4297..980671dc8 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -141,8 +141,10 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, if (LIKELY(conn->flags & CONN_IS_KEEP_ALIVE)) { if (next_request && *next_request) { + conn->flags |= CONN_CORK; coro_yield(coro, CONN_CORO_WANT_WRITE); } else { + conn->flags &= ~CONN_CORK; coro_yield(coro, CONN_CORO_WANT_READ); } } else { diff --git a/src/lib/lwan.h b/src/lib/lwan.h index a24f69d86..40f451c0b 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -273,6 +273,8 @@ enum lwan_connection_flags { * the connection coro ends. */ CONN_SUSPENDED_TIMER = 1 << 5, CONN_HAS_REMOVE_SLEEP_DEFER = 1 << 6, + + CONN_CORK = 1 << 7, }; enum lwan_connection_coro_yield { From 3e5bad291ad64edb8430b7470b86e2394cd0ddaf Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 18 Jul 2019 08:16:12 -0700 Subject: [PATCH 1143/2505] Fix background color in auto index template --- src/lib/lwan-mod-serve-files.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index a4b26c445..65c665af9 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -241,7 +241,7 @@ static const char *directory_list_tpl_str = "{{rel_path?}} Index of {{rel_path}}{{/rel_path?}}\n" "{{^rel_path?}} Index of /{{/rel_path?}}\n" "\n" From 4e489bcf19b25023eb1738ae3e1914ca83ec1e96 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 27 Jul 2019 16:00:27 -0700 Subject: [PATCH 1144/2505] Include "CheckSymbolExists" to define check_symbol_exists() --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 33963ad57..43fab472e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,7 @@ set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/src/cmake") include(CheckCCompilerFlag) include(CheckCSourceCompiles) include(CheckFunctionExists) +include(CheckSymbolExists) include(CheckIncludeFile) include(CheckIncludeFiles) include(CodeCoverage) From 80988702ca536d50d2ac9dbf2740f8cfcba46589 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 27 Jul 2019 18:48:04 -0700 Subject: [PATCH 1145/2505] Provide a specialized 2-digit int-to-str function --- src/lib/int-to-str.c | 18 +++++++++++++----- src/lib/int-to-str.h | 1 + 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/lib/int-to-str.c b/src/lib/int-to-str.c index bfd1f3b57..7425b6f29 100644 --- a/src/lib/int-to-str.c +++ b/src/lib/int-to-str.c @@ -24,6 +24,19 @@ #include "int-to-str.h" +static const char digits[201] = "0001020304050607080910111213141516171819" + "2021222324252627282930313233343536373839" + "4041424344454647484950515253545556575859" + "6061626364656667686970717273747576777879" + "8081828384858687888990919293949596979899"; + +ALWAYS_INLINE __attribute__((pure)) const char * +uint_to_string_2_digits(size_t value) +{ + assert(value <= 99); + return &digits[2 * value]; +} + ALWAYS_INLINE char *uint_to_string(size_t value, char dst[static INT_TO_STR_BUFFER_SIZE], size_t *length_out) @@ -34,11 +47,6 @@ ALWAYS_INLINE char *uint_to_string(size_t value, */ static const size_t length = INT_TO_STR_BUFFER_SIZE; size_t next = length - 1; - static const char digits[201] = "0001020304050607080910111213141516171819" - "2021222324252627282930313233343536373839" - "4041424344454647484950515253545556575859" - "6061626364656667686970717273747576777879" - "8081828384858687888990919293949596979899"; dst[next--] = '\0'; while (value >= 100) { const uint32_t i = (uint32_t)((value % 100) * 2); diff --git a/src/lib/int-to-str.h b/src/lib/int-to-str.h index 98793611c..2bafc358b 100644 --- a/src/lib/int-to-str.h +++ b/src/lib/int-to-str.h @@ -30,3 +30,4 @@ char *uint_to_string(size_t value, char buffer[static INT_TO_STR_BUFFER_SIZE], size_t *len); +const char *uint_to_string_2_digits(size_t value) __attribute__((pure)); From 0ef60f06034336353ebe97924d22f99c174f4f86 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 27 Jul 2019 18:48:42 -0700 Subject: [PATCH 1146/2505] Build date header without snprintf() --- src/lib/lwan-response.c | 2 +- src/lib/lwan-time.c | 34 +++++++++++++++++++++++----------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index b94dddf22..6ceb4cd59 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -127,7 +127,7 @@ static void log_request(struct lwan_request *request, #else #define log_request(...) #endif - +#include static inline bool has_response_body(enum lwan_request_flags method, enum lwan_http_status status) diff --git a/src/lib/lwan-time.c b/src/lib/lwan-time.c index 86eb61a0b..d1d25747c 100644 --- a/src/lib/lwan-time.c +++ b/src/lib/lwan-time.c @@ -23,6 +23,7 @@ #include #include "lwan-private.h" +#include "int-to-str.h" static int parse_2_digit_num(const char *str, const char end_chr, int min, int max) { @@ -119,23 +120,34 @@ int lwan_parse_rfc_time(const char in[static 30], time_t *out) int lwan_format_rfc_time(const time_t in, char out[static 30]) { - static const char *weekdays = "SunMonTueWedThuFriSat"; - static const char *months = "JanFebMarAprMayJunJulAugSepOctNovDec"; - const char *weekday, *month; + static const char *weekdays = "Sun,Mon,Tue,Wed,Thu,Fri,Sat,"; + static const char *months = "Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec "; struct tm tm; - int r; + char *p; if (UNLIKELY(!gmtime_r(&in, &tm))) return -errno; - weekday = weekdays + tm.tm_wday * 3; - month = months + tm.tm_mon * 3; + p = mempcpy(out, weekdays + tm.tm_wday * 4, 4); + *p++ = ' '; - r = snprintf(out, 30, "%.3s, %02d %.3s %04d %02d:%02d:%02d GMT", weekday, - tm.tm_mday, month, tm.tm_year + 1900, tm.tm_hour, tm.tm_min, - tm.tm_sec); - if (UNLIKELY(r < 0 || r > 30)) - return -EINVAL; + p = mempcpy(p, uint_to_string_2_digits((unsigned int)tm.tm_mday), 2); + *p++ = ' '; + p = mempcpy(p, months + tm.tm_mon * 4, 4); + + tm.tm_year += 1900; + p = mempcpy(p, uint_to_string_2_digits((unsigned int)tm.tm_year / 100), 2); + p = mempcpy(p, uint_to_string_2_digits((unsigned int)tm.tm_year % 100), 2); + + *p++ = ' '; + + p = mempcpy(p, uint_to_string_2_digits((unsigned int)tm.tm_hour), 2); + *p++ = ':'; + p = mempcpy(p, uint_to_string_2_digits((unsigned int)tm.tm_min), 2); + *p++ = ':'; + p = mempcpy(p, uint_to_string_2_digits((unsigned int)tm.tm_sec), 2); + + p = mempcpy(p, " GMT", 4); return 0; } From 480ecfeb38062c18768149090417857e6016e072 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 28 Jul 2019 11:16:25 -0700 Subject: [PATCH 1147/2505] Try compressing auto index with Brotli as well --- src/lib/lwan-mod-serve-files.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 65c665af9..dbf102c8e 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -111,6 +111,9 @@ struct sendfile_cache_data { struct dir_list_cache_data { struct lwan_strbuf rendered; struct lwan_value deflated; +#if defined(HAVE_BROTLI) + struct lwan_value brotli; +#endif }; struct redir_cache_data { @@ -666,6 +669,9 @@ static bool dirlist_init(struct file_cache_entry *ce, .len = lwan_strbuf_get_length(&dd->rendered), }; deflate_value(&rendered, &dd->deflated); +#if defined(HAVE_BROTLI) + brotli_value(&rendered, &dd->brotli, &dd->deflated); +#endif ret = true; goto out_free_readme; @@ -859,6 +865,9 @@ static void dirlist_free(struct file_cache_entry *fce) lwan_strbuf_free(&dd->rendered); free(dd->deflated.value); +#if defined(HAVE_BROTLI) + free(dd->brotli.value); +#endif } static void redir_free(struct file_cache_entry *fce) @@ -1209,6 +1218,13 @@ static enum lwan_http_status dirlist_serve(struct lwan_request *request, icon = lwan_request_get_query_param(request, "icon"); if (!icon) { +#if defined(HAVE_BROTLI) + if (dd->brotli.len && (request->flags & REQUEST_ACCEPT_BROTLI)) { + compressed = &br_compression_hdr; + contents = dd->brotli.value; + size = dd->brotli.len; + } else +#endif if (dd->deflated.len && (request->flags & REQUEST_ACCEPT_DEFLATE)) { compressed = &deflate_compression_hdr; contents = dd->deflated.value; From d9ee3cc70c71c568bc7ff23d13bf525fca1cf63f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 30 Jul 2019 19:11:57 -0700 Subject: [PATCH 1148/2505] Actually zero-terminate RFC822-formatted time --- src/lib/lwan-time.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-time.c b/src/lib/lwan-time.c index d1d25747c..99c8687c3 100644 --- a/src/lib/lwan-time.c +++ b/src/lib/lwan-time.c @@ -147,7 +147,7 @@ int lwan_format_rfc_time(const time_t in, char out[static 30]) *p++ = ':'; p = mempcpy(p, uint_to_string_2_digits((unsigned int)tm.tm_sec), 2); - p = mempcpy(p, " GMT", 4); + p = mempcpy(p, " GMT", 5); return 0; } From 6111a73967abd5a6d36dd823707b78a68ab10c34 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 2 Aug 2019 21:44:45 -0700 Subject: [PATCH 1149/2505] Only set max number of open file descriptors if < OPEN_MAX --- src/lib/lwan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 4f943429f..a01ff7287 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -466,7 +466,7 @@ static rlim_t setup_open_file_count_limits(void) if (r.rlim_max != r.rlim_cur) { const rlim_t current = r.rlim_cur; - if (r.rlim_max == RLIM_INFINITY) { + if (r.rlim_max == RLIM_INFINITY && r.rlim_cur < OPEN_MAX) { r.rlim_cur = OPEN_MAX; } else if (r.rlim_cur < r.rlim_max) { r.rlim_cur = r.rlim_max; From 2eb442f5b8a7ec986832f7ee82b076fc3787b836 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 3 Aug 2019 19:04:23 -0700 Subject: [PATCH 1150/2505] Rename MULTICHAR_CONSTANT_*() macros to shorter STRn_INT[_L]() Easier to read/type. --- src/lib/lwan-request.c | 56 ++++++++++++++++++++--------------------- src/lib/lwan-response.c | 6 ++--- src/lib/lwan-tables.c | 12 ++++----- src/lib/lwan-time.c | 40 ++++++++++++++--------------- src/lib/lwan.h | 40 +++++++++++++---------------- 5 files changed, 74 insertions(+), 80 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 6eea901ae..05b29038e 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -178,7 +178,7 @@ parse_proxy_protocol_v1(struct lwan_request *request, char *buffer) return NULL; STRING_SWITCH(protocol) { - case MULTICHAR_CONSTANT('T', 'C', 'P', '4'): { + case STR4_INT('T', 'C', 'P', '4'): { struct sockaddr_in *from = &proxy->from.ipv4; struct sockaddr_in *to = &proxy->to.ipv4; @@ -195,7 +195,7 @@ parse_proxy_protocol_v1(struct lwan_request *request, char *buffer) break; } - case MULTICHAR_CONSTANT('T', 'C', 'P', '6'): { + case STR4_INT('T', 'C', 'P', '6'): { struct sockaddr_in6 *from = &proxy->from.ipv6; struct sockaddr_in6 *to = &proxy->to.ipv6; @@ -499,10 +499,10 @@ identify_http_path(struct lwan_request *request, char *buffer) *space++ = '\0'; STRING_SWITCH_LARGE(space) { - case MULTICHAR_CONSTANT_LARGE('H','T','T','P','/','1','.','0'): + case STR8_INT('H','T','T','P','/','1','.','0'): request->flags |= REQUEST_IS_HTTP_1_0; break; - case MULTICHAR_CONSTANT_LARGE('H','T','T','P','/','1','.','1'): + case STR8_INT('H','T','T','P','/','1','.','1'): break; default: return NULL; @@ -516,7 +516,7 @@ __attribute__((noinline)) static void set_header_value( { p += header_len; - if (LIKELY(string_as_int16(p) == MULTICHAR_CONSTANT_SMALL(':', ' '))) { + if (LIKELY(string_as_int16(p) == STR2_INT(':', ' '))) { *end = '\0'; char *value = p + sizeof(": ") - 1; @@ -557,7 +557,7 @@ static bool parse_headers(struct lwan_request_parser_helper *helper, if (next_chr == next_header) { if (buffer_end - next_chr > (ptrdiff_t)HEADER_TERMINATOR_LEN) { STRING_SWITCH_SMALL (next_header) { - case MULTICHAR_CONSTANT_SMALL('\r', '\n'): + case STR2_INT('\r', '\n'): helper->next_request = next_header + HEADER_TERMINATOR_LEN; } } @@ -585,34 +585,34 @@ static bool parse_headers(struct lwan_request_parser_helper *helper, char *end = header_start[i + 1] - HEADER_TERMINATOR_LEN; STRING_SWITCH_L (p) { - case MULTICHAR_CONSTANT_L('A', 'c', 'c', 'e'): + case STR4_INT_L('A', 'c', 'c', 'e'): p += HEADER_LENGTH("Accept"); STRING_SWITCH_L (p) { - case MULTICHAR_CONSTANT_L('-', 'E', 'n', 'c'): + case STR4_INT_L('-', 'E', 'n', 'c'): SET_HEADER_VALUE(accept_encoding, "-Encoding"); break; } break; - case MULTICHAR_CONSTANT_L('C', 'o', 'n', 'n'): + case STR4_INT_L('C', 'o', 'n', 'n'): SET_HEADER_VALUE(connection, "Connection"); break; - case MULTICHAR_CONSTANT_L('C', 'o', 'n', 't'): + case STR4_INT_L('C', 'o', 'n', 't'): p += HEADER_LENGTH("Content"); STRING_SWITCH_L (p) { - case MULTICHAR_CONSTANT_L('-', 'T', 'y', 'p'): + case STR4_INT_L('-', 'T', 'y', 'p'): SET_HEADER_VALUE(content_type, "-Type"); break; - case MULTICHAR_CONSTANT_L('-', 'L', 'e', 'n'): + case STR4_INT_L('-', 'L', 'e', 'n'): SET_HEADER_VALUE(content_length, "-Length"); break; } break; - case MULTICHAR_CONSTANT_L('I', 'f', '-', 'M'): + case STR4_INT_L('I', 'f', '-', 'M'): SET_HEADER_VALUE(if_modified_since.raw, "If-Modified-Since"); break; - case MULTICHAR_CONSTANT_L('R', 'a', 'n', 'g'): + case STR4_INT_L('R', 'a', 'n', 'g'): SET_HEADER_VALUE(range.raw, "Range"); break; } @@ -688,12 +688,12 @@ parse_accept_encoding(struct lwan_request *request) for (const char *p = helper->accept_encoding.value; *p; p++) { STRING_SWITCH(p) { - case MULTICHAR_CONSTANT('d','e','f','l'): - case MULTICHAR_CONSTANT(' ','d','e','f'): + case STR4_INT('d','e','f','l'): + case STR4_INT(' ','d','e','f'): request->flags |= REQUEST_ACCEPT_DEFLATE; break; - case MULTICHAR_CONSTANT('g','z','i','p'): - case MULTICHAR_CONSTANT(' ','g','z','i'): + case STR4_INT('g','z','i','p'): + case STR4_INT(' ','g','z','i'): request->flags |= REQUEST_ACCEPT_GZIP; break; #if defined(HAVE_BROTLI) @@ -702,7 +702,7 @@ parse_accept_encoding(struct lwan_request *request) p++; STRING_SWITCH_SMALL(p) { - case MULTICHAR_CONSTANT_SMALL('b', 'r'): + case STR2_INT('b', 'r'): request->flags |= REQUEST_ACCEPT_BROTLI; break; } @@ -733,16 +733,16 @@ static ALWAYS_INLINE void parse_connection_header(struct lwan_request *request) for (const char *p = helper->connection.value; *p; p++) { STRING_SWITCH_L(p) { - case MULTICHAR_CONSTANT_L('k','e','e','p'): - case MULTICHAR_CONSTANT_L(' ', 'k','e','e'): + case STR4_INT_L('k','e','e','p'): + case STR4_INT_L(' ', 'k','e','e'): has_keep_alive = true; break; - case MULTICHAR_CONSTANT_L('c','l','o','s'): - case MULTICHAR_CONSTANT_L(' ', 'c','l','o'): + case STR4_INT_L('c','l','o','s'): + case STR4_INT_L(' ', 'c','l','o'): has_close = true; break; - case MULTICHAR_CONSTANT_L('u','p','g','r'): - case MULTICHAR_CONSTANT_L(' ', 'u','p','g'): + case STR4_INT_L('u','p','g','r'): + case STR4_INT_L(' ', 'u','p','g'): request->conn->flags |= CONN_IS_UPGRADE; break; } @@ -931,7 +931,7 @@ read_request_finalizer(size_t total_read, /* FIXME: Checking for PROXYv2 protocol header here is a layering * violation. */ STRING_SWITCH_LARGE (crlfcrlf + 4) { - case MULTICHAR_CONSTANT_LARGE(0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, + case STR8_INT(0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, 0x54, 0x0a): return FINALIZER_DONE; } @@ -1200,9 +1200,9 @@ static char * parse_proxy_protocol(struct lwan_request *request, char *buffer) { STRING_SWITCH(buffer) { - case MULTICHAR_CONSTANT('P','R','O','X'): + case STR4_INT('P','R','O','X'): return parse_proxy_protocol_v1(request, buffer); - case MULTICHAR_CONSTANT('\x0D','\x0A','\x0D','\x0A'): + case STR4_INT('\x0D','\x0A','\x0D','\x0A'): return parse_proxy_protocol_v2(request, buffer); } diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 6ceb4cd59..73960a6b0 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -298,15 +298,15 @@ size_t lwan_prepare_response_header_full( for (header = additional_headers; header->key; header++) { STRING_SWITCH_L(header->key) { - case MULTICHAR_CONSTANT_L('S', 'e', 'r', 'v'): + case STR4_INT_L('S', 'e', 'r', 'v'): if (LIKELY(streq(header->key + 4, "er"))) continue; break; - case MULTICHAR_CONSTANT_L('D', 'a', 't', 'e'): + case STR4_INT_L('D', 'a', 't', 'e'): if (LIKELY(*(header->key + 4) == '\0')) date_overridden = true; break; - case MULTICHAR_CONSTANT_L('E', 'x', 'p', 'i'): + case STR4_INT_L('E', 'x', 'p', 'i'): if (LIKELY(streq(header->key + 4, "res"))) expires_overridden = true; break; diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index b16056fc3..5f9e96290 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -103,17 +103,17 @@ lwan_determine_mime_type_for_file_name(const char *file_name) goto fallback; STRING_SWITCH_L(last_dot) { - case MULTICHAR_CONSTANT_L('.','j','p','g'): + case STR4_INT_L('.','j','p','g'): return "image/jpeg"; - case MULTICHAR_CONSTANT_L('.','p','n','g'): + case STR4_INT_L('.','p','n','g'): return "image/png"; - case MULTICHAR_CONSTANT_L('.','h','t','m'): + case STR4_INT_L('.','h','t','m'): return "text/html"; - case MULTICHAR_CONSTANT_L('.','c','s','s'): + case STR4_INT_L('.','c','s','s'): return "text/css"; - case MULTICHAR_CONSTANT_L('.','t','x','t'): + case STR4_INT_L('.','t','x','t'): return "text/plain"; - case MULTICHAR_CONSTANT_L('.','j','s',0x20): + case STR4_INT_L('.','j','s',0x20): return "application/javascript"; } diff --git a/src/lib/lwan-time.c b/src/lib/lwan-time.c index 99c8687c3..ec520e951 100644 --- a/src/lib/lwan-time.c +++ b/src/lib/lwan-time.c @@ -54,13 +54,13 @@ int lwan_parse_rfc_time(const char in[static 30], time_t *out) const char *str = in; STRING_SWITCH(str) { - case MULTICHAR_CONSTANT('S','u','n',','): tm.tm_wday = 0; break; - case MULTICHAR_CONSTANT('M','o','n',','): tm.tm_wday = 1; break; - case MULTICHAR_CONSTANT('T','u','e',','): tm.tm_wday = 2; break; - case MULTICHAR_CONSTANT('W','e','d',','): tm.tm_wday = 3; break; - case MULTICHAR_CONSTANT('T','h','u',','): tm.tm_wday = 4; break; - case MULTICHAR_CONSTANT('F','r','i',','): tm.tm_wday = 5; break; - case MULTICHAR_CONSTANT('S','a','t',','): tm.tm_wday = 6; break; + case STR4_INT('S','u','n',','): tm.tm_wday = 0; break; + case STR4_INT('M','o','n',','): tm.tm_wday = 1; break; + case STR4_INT('T','u','e',','): tm.tm_wday = 2; break; + case STR4_INT('W','e','d',','): tm.tm_wday = 3; break; + case STR4_INT('T','h','u',','): tm.tm_wday = 4; break; + case STR4_INT('F','r','i',','): tm.tm_wday = 5; break; + case STR4_INT('S','a','t',','): tm.tm_wday = 6; break; default: return -EINVAL; } str += 5; @@ -71,18 +71,18 @@ int lwan_parse_rfc_time(const char in[static 30], time_t *out) str += 3; STRING_SWITCH(str) { - case MULTICHAR_CONSTANT('J','a','n',' '): tm.tm_mon = 0; break; - case MULTICHAR_CONSTANT('F','e','b',' '): tm.tm_mon = 1; break; - case MULTICHAR_CONSTANT('M','a','r',' '): tm.tm_mon = 2; break; - case MULTICHAR_CONSTANT('A','p','r',' '): tm.tm_mon = 3; break; - case MULTICHAR_CONSTANT('M','a','y',' '): tm.tm_mon = 4; break; - case MULTICHAR_CONSTANT('J','u','n',' '): tm.tm_mon = 5; break; - case MULTICHAR_CONSTANT('J','u','l',' '): tm.tm_mon = 6; break; - case MULTICHAR_CONSTANT('A','u','g',' '): tm.tm_mon = 7; break; - case MULTICHAR_CONSTANT('S','e','p',' '): tm.tm_mon = 8; break; - case MULTICHAR_CONSTANT('O','c','t',' '): tm.tm_mon = 9; break; - case MULTICHAR_CONSTANT('N','o','v',' '): tm.tm_mon = 10; break; - case MULTICHAR_CONSTANT('D','e','c',' '): tm.tm_mon = 11; break; + case STR4_INT('J','a','n',' '): tm.tm_mon = 0; break; + case STR4_INT('F','e','b',' '): tm.tm_mon = 1; break; + case STR4_INT('M','a','r',' '): tm.tm_mon = 2; break; + case STR4_INT('A','p','r',' '): tm.tm_mon = 3; break; + case STR4_INT('M','a','y',' '): tm.tm_mon = 4; break; + case STR4_INT('J','u','n',' '): tm.tm_mon = 5; break; + case STR4_INT('J','u','l',' '): tm.tm_mon = 6; break; + case STR4_INT('A','u','g',' '): tm.tm_mon = 7; break; + case STR4_INT('S','e','p',' '): tm.tm_mon = 8; break; + case STR4_INT('O','c','t',' '): tm.tm_mon = 9; break; + case STR4_INT('N','o','v',' '): tm.tm_mon = 10; break; + case STR4_INT('D','e','c',' '): tm.tm_mon = 11; break; default: return -EINVAL; } str += 4; @@ -103,7 +103,7 @@ int lwan_parse_rfc_time(const char in[static 30], time_t *out) str += 3; STRING_SWITCH(str) { - case MULTICHAR_CONSTANT('G','M','T','\0'): + case STR4_INT('G','M','T','\0'): tm.tm_isdst = -1; *out = timegm(&tm); diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 40f451c0b..bcd156b4d 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -83,29 +83,23 @@ extern "C" { #define ALWAYS_INLINE inline __attribute__((always_inline)) #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ -#define MULTICHAR_CONSTANT(a, b, c, d) \ - ((int32_t)((a) | (b) << 8 | (c) << 16 | (d) << 24)) -#define MULTICHAR_CONSTANT_SMALL(a, b) ((int16_t)((a) | (b) << 8)) -#define MULTICHAR_CONSTANT_LARGE(a, b, c, d, e, f, g, h) \ - ((int64_t)MULTICHAR_CONSTANT(a, b, c, d) | \ - (int64_t)MULTICHAR_CONSTANT(e, f, g, h) << 32) +#define STR4_INT(a, b, c, d) ((int32_t)((a) | (b) << 8 | (c) << 16 | (d) << 24)) +#define STR2_INT(a, b) ((int16_t)((a) | (b) << 8)) +#define STR8_INT(a, b, c, d, e, f, g, h) \ + ((int64_t)STR4_INT(a, b, c, d) | (int64_t)STR4_INT(e, f, g, h) << 32) #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ -#define MULTICHAR_CONSTANT(d, c, b, a) \ - ((int32_t)((a) | (b) << 8 | (c) << 16 | (d) << 24)) -#define MULTICHAR_CONSTANT_SMALL(b, a) ((int16_t)((a) | (b) << 8)) -#define MULTICHAR_CONSTANT_LARGE(a, b, c, d, e, f, g, h) \ - ((int64_t)MULTICHAR_CONSTANT(a, b, c, d) << 32 | \ - (int64_t)MULTICHAR_CONSTANT(e, f, g, h)) +#define STR4_INT(d, c, b, a) ((int32_t)((a) | (b) << 8 | (c) << 16 | (d) << 24)) +#define STR2_INT(b, a) ((int16_t)((a) | (b) << 8)) +#define STR8_INT(a, b, c, d, e, f, g, h) \ + ((int64_t)STR4_INT(a, b, c, d) << 32 | (int64_t)STR4_INT(e, f, g, h)) #elif __BYTE_ORDER__ == __ORDER_PDP_ENDIAN__ #error A PDP? Seriously? #endif -#define MULTICHAR_CONSTANT_L(a, b, c, d) \ - (MULTICHAR_CONSTANT(a, b, c, d) | 0x20202020) -#define MULTICHAR_CONSTANT_SMALL_L(a, b) \ - (MULTICHAR_CONSTANT_SMALL(a, b) | 0x2020) -#define MULTICHAR_CONSTANT_LARGE_L(a, b, c, d, e, f, g, h) \ - (MULTICHAR_CONSTANT_LARGE(a, b, c, d, e, f, g, h) | 0x2020202020202020) +#define STR4_INT_L(a, b, c, d) (STR4_INT(a, b, c, d) | 0x20202020) +#define STR2_INT_L(a, b) (STR2_INT(a, b) | 0x2020) +#define STR8_INT_L(a, b, c, d, e, f, g, h) \ + (STR8_INT(a, b, c, d, e, f, g, h) | 0x2020202020202020) static ALWAYS_INLINE int64_t string_as_int64(const char *s) { @@ -199,11 +193,11 @@ enum lwan_handler_flags { }; #define FOR_EACH_REQUEST_METHOD(X) \ - X(GET, get, (1 << 0), MULTICHAR_CONSTANT('G', 'E', 'T', ' ')) \ - X(POST, post, (1 << 1), MULTICHAR_CONSTANT('P', 'O', 'S', 'T')) \ - X(HEAD, head, (1 << 0 | 1 << 1), MULTICHAR_CONSTANT('H', 'E', 'A', 'D')) \ - X(OPTIONS, options, (1 << 2), MULTICHAR_CONSTANT('O', 'P', 'T', 'I')) \ - X(DELETE, delete, (1 << 2 | 1 << 0), MULTICHAR_CONSTANT('D', 'E', 'L', 'E')) + X(GET, get, (1 << 0), STR4_INT('G', 'E', 'T', ' ')) \ + X(POST, post, (1 << 1), STR4_INT('P', 'O', 'S', 'T')) \ + X(HEAD, head, (1 << 0 | 1 << 1), STR4_INT('H', 'E', 'A', 'D')) \ + X(OPTIONS, options, (1 << 2), STR4_INT('O', 'P', 'T', 'I')) \ + X(DELETE, delete, (1 << 2 | 1 << 0), STR4_INT('D', 'E', 'L', 'E')) #define SELECT_MASK(upper, lower, mask, constant) mask | #define GENERATE_ENUM_ITEM(upper, lower, mask, constant) REQUEST_METHOD_##upper = mask, From c93b7d2f8399bac33b9d29e39899461060469dfb Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 3 Aug 2019 19:58:14 -0700 Subject: [PATCH 1151/2505] Simplify how a method declares it has a response body Methods with the least significant bit set will have a response body. With this, the `method_has_body` array in has_response_body() has been removed, and the check is now more efficient. --- src/lib/lwan-response.c | 8 ++------ src/lib/lwan.h | 7 ++++--- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 73960a6b0..6a8eb44f7 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -132,12 +132,8 @@ static void log_request(struct lwan_request *request, static inline bool has_response_body(enum lwan_request_flags method, enum lwan_http_status status) { - static const bool method_has_body[REQUEST_METHOD_MASK] = { - [REQUEST_METHOD_GET] = true, - [REQUEST_METHOD_POST] = true, - }; - - return method_has_body[method] || status != HTTP_NOT_MODIFIED; + /* See FOR_EACH_REQUEST_METHOD() in lwan.h */ + return (method & 1 << 0) || status != HTTP_NOT_MODIFIED; } void lwan_response(struct lwan_request *request, enum lwan_http_status status) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index bcd156b4d..5e9e3d3c5 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -192,12 +192,13 @@ enum lwan_handler_flags { HANDLER_PARSE_MASK = HANDLER_HAS_POST_DATA, }; +/* 1<<0 set: response has body; see has_response_body() in lwan-response.c */ #define FOR_EACH_REQUEST_METHOD(X) \ X(GET, get, (1 << 0), STR4_INT('G', 'E', 'T', ' ')) \ - X(POST, post, (1 << 1), STR4_INT('P', 'O', 'S', 'T')) \ - X(HEAD, head, (1 << 0 | 1 << 1), STR4_INT('H', 'E', 'A', 'D')) \ + X(POST, post, (1 << 1 | 1 << 0), STR4_INT('P', 'O', 'S', 'T')) \ + X(HEAD, head, (1 << 1), STR4_INT('H', 'E', 'A', 'D')) \ X(OPTIONS, options, (1 << 2), STR4_INT('O', 'P', 'T', 'I')) \ - X(DELETE, delete, (1 << 2 | 1 << 0), STR4_INT('D', 'E', 'L', 'E')) + X(DELETE, delete, (1 << 1 | 1 << 2), STR4_INT('D', 'E', 'L', 'E')) #define SELECT_MASK(upper, lower, mask, constant) mask | #define GENERATE_ENUM_ITEM(upper, lower, mask, constant) REQUEST_METHOD_##upper = mask, From cdfa3d5c0825a4e8bbd315670dbc1a17ff81db2a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 3 Aug 2019 20:04:01 -0700 Subject: [PATCH 1152/2505] Make unlikely check for RESPONSE_SENT_HEADERS an assertion --- src/lib/lwan-response.c | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 6a8eb44f7..481f32b1b 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -141,6 +141,8 @@ void lwan_response(struct lwan_request *request, enum lwan_http_status status) const struct lwan_response *response = &request->response; char headers[DEFAULT_HEADERS_SIZE]; + assert(!(request->flags & RESPONSE_SENT_HEADERS)); + if (UNLIKELY(request->flags & RESPONSE_CHUNKED_ENCODING)) { /* Send last, 0-sized chunk */ lwan_strbuf_reset(response->buffer); @@ -149,14 +151,9 @@ void lwan_response(struct lwan_request *request, enum lwan_http_status status) return; } - if (UNLIKELY(request->flags & RESPONSE_SENT_HEADERS)) { - lwan_status_debug("Headers already sent, ignoring call"); - return; - } - if (UNLIKELY(!response->mime_type)) { - /* Requests without a MIME Type are errors from handlers that - should just be handled by lwan_default_response(). */ + /* Requests without a MIME Type are errors from handlers that should + just be handled by lwan_default_response(). */ lwan_default_response(request, status); return; } @@ -193,10 +190,9 @@ void lwan_response(struct lwan_request *request, enum lwan_http_status status) char *resp_buf = lwan_strbuf_get_buffer(response->buffer); const size_t resp_len = lwan_strbuf_get_length(response->buffer); if (sizeof(headers) - header_len > resp_len) { - /* writev() has to allocate, copy, and validate the - * response vector, so use send() for responses small - * enough to fit the headers buffer. On Linux, this - * is ~10% faster. */ + /* writev() has to allocate, copy, and validate the response vector, + * so use send() for responses small enough to fit the headers + * buffer. On Linux, this is ~10% faster. */ memcpy(headers + header_len, resp_buf, resp_len); lwan_send(request, headers, header_len + resp_len, 0); } else { From 72231f0a2d8627f8cc1fe2fad5deb840e75f032f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 4 Aug 2019 22:31:43 -0700 Subject: [PATCH 1153/2505] Revert "Make unlikely check for RESPONSE_SENT_HEADERS an assertion" This reverts commit cdfa3d5c. Apparently some things require this behavior and the assert was being hit. While the behavior isn't fixed, revert this instead. --- src/lib/lwan-response.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 481f32b1b..468c5bb6a 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -141,8 +141,6 @@ void lwan_response(struct lwan_request *request, enum lwan_http_status status) const struct lwan_response *response = &request->response; char headers[DEFAULT_HEADERS_SIZE]; - assert(!(request->flags & RESPONSE_SENT_HEADERS)); - if (UNLIKELY(request->flags & RESPONSE_CHUNKED_ENCODING)) { /* Send last, 0-sized chunk */ lwan_strbuf_reset(response->buffer); @@ -151,6 +149,11 @@ void lwan_response(struct lwan_request *request, enum lwan_http_status status) return; } + if (UNLIKELY(request->flags & RESPONSE_SENT_HEADERS)) { + lwan_status_debug("Headers already sent, ignoring call"); + return; + } + if (UNLIKELY(!response->mime_type)) { /* Requests without a MIME Type are errors from handlers that should just be handled by lwan_default_response(). */ From 20507592979d2dbba41846bd8b5a8ecd36ef1b69 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 4 Aug 2019 20:47:31 -0700 Subject: [PATCH 1154/2505] Reduce indirection when looking up extension in mime_type table Instead of processing data so that there's an array of pointers pointing to the decompressed buffer, store it differently. Previously, data was stored as a string of extension-NUL-mime-type-NUL; after decompression, two arrays were populated, containing pointers to the data. It now contains all extensions, padded with NUL bytes (from 0 to 7 NULs), then followed by all the MIME types, separated by NUL; after decompression, Lwan spends time populating only an array of pointers to MIME types. With this change, the cache lines that would be used for the extension pointers are now used for the data itself. This is 120 fewer cache lines ((sizeof(char*) * 954) / 64) that need to be, in the worst case, populated in order to find an extension. As a side-effect, all 9 extensions from mime.types that are over 8 characters long (which aren't common), are not included in the MIME type table any longer. Not a bit deal. As such, the binary size decreased a few hundred bytes due to having fewer elements, and the compressor liking all the NUL padding. When compressing with Brotli, compressed table now sits at 6480 bytes, from 6739 before this change. Uncompressed data is larger due to padding, but is offset from not needing the pointer array, so it's a win-win scenario. This could probably be improved further by using something like gperf, though, but, considering that this information is cached by the file serving module, it's not really a hot path. --- src/bin/tools/mimegen.c | 31 ++++++++++++++++++++++++++++--- src/lib/lwan-tables.c | 17 +++++++---------- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/bin/tools/mimegen.c b/src/bin/tools/mimegen.c index a8b9a4f31..b2b42e961 100644 --- a/src/bin/tools/mimegen.c +++ b/src/bin/tools/mimegen.c @@ -40,9 +40,9 @@ struct output { size_t used, capacity; }; -static int output_append(struct output *output, const char *str) +static int +output_append_full(struct output *output, const char *str, size_t str_len) { - size_t str_len = strlen(str) + 1; size_t total_size = output->used + str_len; if (total_size >= output->capacity) { @@ -64,6 +64,26 @@ static int output_append(struct output *output, const char *str) return 0; } +static int output_append_padded(struct output *output, const char *str) +{ + size_t str_len = strlen(str); + + if (str_len <= 8) { + int r = output_append_full(output, str, str_len); + if (r < 0) + return r; + + return output_append_full(output, "\0\0\0\0\0\0\0\0", 8 - str_len); + } + + return -EINVAL; +} + +static int output_append(struct output *output, const char *str) +{ + return output_append_full(output, str, strlen(str) + 1); +} + static int compare_ext(const void *a, const void *b) { const char **exta = (const char **)a; @@ -212,6 +232,9 @@ int main(int argc, char *argv[]) end = strchr(ext, '\0'); /* If not found, find last extension. */ *end = '\0'; + if (end - ext > 8) + continue; + k = strdup(ext); v = strdup(mime_type); @@ -251,10 +274,12 @@ int main(int argc, char *argv[]) return 1; } for (i = 0; i < hash_get_count(ext_mime); i++) { - if (output_append(&output, exts[i]) < 0) { + if (output_append_padded(&output, exts[i]) < 0) { fprintf(stderr, "Could not append to output\n"); return 1; } + } + for (i = 0; i < hash_get_count(ext_mime); i++) { if (output_append(&output, hash_find(ext_mime, exts[i])) < 0) { fprintf(stderr, "Could not append to output\n"); return 1; diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index 5f9e96290..d7ba9aa1c 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -33,7 +33,6 @@ #include "mime-types.h" static unsigned char uncompressed_mime_entries[MIME_UNCOMPRESSED_LEN]; -static char *mime_extensions[MIME_ENTRIES]; static char *mime_types[MIME_ENTRIES]; static bool mime_entries_initialized = false; @@ -70,10 +69,8 @@ void lwan_tables_init(void) MIME_UNCOMPRESSED_LEN, uncompressed_length); } - unsigned char *ptr = uncompressed_mime_entries; + unsigned char *ptr = uncompressed_mime_entries + 8 * MIME_ENTRIES; for (size_t i = 0; i < MIME_ENTRIES; i++) { - mime_extensions[i] = (char *)ptr; - ptr = rawmemchr(ptr + 1, '\0') + 1; mime_types[i] = (char *)ptr; ptr = rawmemchr(ptr + 1, '\0') + 1; } @@ -89,10 +86,10 @@ lwan_tables_shutdown(void) static int compare_mime_entry(const void *a, const void *b) { - const char **exta = (const char **)a; - const char **extb = (const char **)b; + const char *exta = (const char *)a; + const char *extb = (const char *)b; - return strcmp(*exta, *extb); + return strncmp(exta, extb, 8); } const char * @@ -118,12 +115,12 @@ lwan_determine_mime_type_for_file_name(const char *file_name) } if (LIKELY(*last_dot)) { - char **extension, *key = last_dot + 1; + char *extension, *key = last_dot + 1; - extension = bsearch(&key, mime_extensions, MIME_ENTRIES, sizeof(char *), + extension = bsearch(key, uncompressed_mime_entries, MIME_ENTRIES, 8, compare_mime_entry); if (LIKELY(extension)) - return mime_types[extension - mime_extensions]; + return mime_types[(extension - (char*)uncompressed_mime_entries) / 8]; } fallback: From 5721de0036576da20c418e33ef6d5cdf8f6e8791 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 7 Aug 2019 18:48:51 -0700 Subject: [PATCH 1155/2505] Optimize hash_del() by not defragmenting bucket Instead of compacting the bucket array by moving elements, just copy over the last element on top of the element being removed. This changes the ordering inside the bucket array, but it's much more efficient, as it always has to copy exactly at most 1 element instead of potentially bucket->used elements. --- src/lib/hash.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/lib/hash.c b/src/lib/hash.c index b97366b2c..ac96c9b91 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -453,7 +453,7 @@ int hash_del(struct hash *hash, const void *key) unsigned int hashval = hash->hash_value(key); unsigned int pos = hashval & hash->n_buckets_mask; struct hash_bucket *bucket = hash->buckets + pos; - struct hash_entry *entry, *entry_end; + struct hash_entry *entry; entry = hash_find_entry(hash, key, hashval); if (entry == NULL) @@ -462,9 +462,17 @@ int hash_del(struct hash *hash, const void *key) hash->free_value((void *)entry->value); hash->free_key((void *)entry->key); - entry_end = bucket->entries + bucket->used; - memmove(entry, entry + 1, - (size_t)(entry_end - entry) * sizeof(struct hash_entry)); + if (bucket->used > 1) { + /* Instead of compacting the bucket array by moving elements, just copy + * over the last element on top of the element being removed. This + * changes the ordering inside the bucket array, but it's much more + * efficient, as it always has to copy exactly at most 1 element instead + * of potentially bucket->used elements. */ + struct hash_entry *entry_last = bucket->entries + bucket->used - 1; + + if (entry != entry_last) + memcpy(entry, entry_last, sizeof(*entry)); + } bucket->used--; hash->count--; From be3286fbe59fbbcceda76295bf0881d3fd3bb6f1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 10 Aug 2019 10:54:55 -0700 Subject: [PATCH 1156/2505] Finalizers don't need to return FINALIZER_ERROR_TOO_LARGE anymore The HTTP_TOO_LARGE error code is now always generated prior to calling read(2) on the socket, so finalizers don't need to check for this condition anymore. --- src/lib/lwan-request.c | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 05b29038e..0cfe9b724 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -51,7 +51,6 @@ enum lwan_read_finalizer { FINALIZER_DONE, FINALIZER_TRY_AGAIN, - FINALIZER_ERROR_TOO_LARGE, FINALIZER_ERROR_TIMEOUT }; @@ -882,9 +881,6 @@ read_from_request_socket(struct lwan_request *request, case FINALIZER_TRY_AGAIN: goto yield_and_read_again; - case FINALIZER_ERROR_TOO_LARGE: - return HTTP_TOO_LARGE; - case FINALIZER_ERROR_TIMEOUT: return HTTP_TIMEOUT; } @@ -931,16 +927,12 @@ read_request_finalizer(size_t total_read, /* FIXME: Checking for PROXYv2 protocol header here is a layering * violation. */ STRING_SWITCH_LARGE (crlfcrlf + 4) { - case STR8_INT(0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, - 0x54, 0x0a): + case STR8_INT(0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, 0x54, 0x0a): return FINALIZER_DONE; } } } - if (UNLIKELY(total_read == buffer_size - 1)) - return FINALIZER_ERROR_TOO_LARGE; - return FINALIZER_TRY_AGAIN; } From e4dd53a1332e3a9d17c97d731dd27394bbd85192 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 10 Aug 2019 16:59:49 -0700 Subject: [PATCH 1157/2505] Fuzzer is built outside; revert back to declaring Lwan as a C project --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 43fab472e..5b37f9e89 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -project(lwan) +project(lwan C) cmake_minimum_required(VERSION 2.8.10) set(PROJECT_DESCRIPTION "Scalable, high performance, experimental web server") message(STATUS "Running CMake for ${PROJECT_NAME} (${PROJECT_DESCRIPTION})") From cba0cfa7693d85a66120f4b6eea0742677730154 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 13 Aug 2019 08:00:26 -0700 Subject: [PATCH 1158/2505] lwan_tpl_apply_with_buffer() should return false if applying fails --- src/lib/lwan-template.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index b9c7718fc..05e05c262 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -1415,7 +1415,7 @@ action_apply_tpl: { action_start_iter: if (UNLIKELY(coro != NULL)) { lwan_status_warning("Coroutine is not NULL when starting iteration"); - DISPATCH_NEXT_ACTION(); + return NULL; } struct chunk_descriptor *cd = chunk->data; @@ -1447,8 +1447,10 @@ action_apply_tpl: { goto finalize; if (UNLIKELY(!coro)) { - if (!chunk->flags) + if (!chunk->flags) { lwan_status_warning("Coroutine is NULL when finishing iteration"); + return NULL; + } DISPATCH_NEXT_ACTION(); } @@ -1477,7 +1479,8 @@ bool lwan_tpl_apply_with_buffer(struct lwan_tpl *tpl, if (UNLIKELY(!lwan_strbuf_grow_to(buf, tpl->minimum_size))) return false; - apply(tpl, tpl->chunks.base.base, buf, variables, NULL); + if (!apply(tpl, tpl->chunks.base.base, buf, variables, NULL)) + return false; return true; } From c157068c8d08f8476e2853a5dd8a78531a978c2f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 13 Aug 2019 08:00:45 -0700 Subject: [PATCH 1159/2505] Optimize ACTION_APPLY_TPL by not creating a temporary strbuf --- src/lib/lwan-template.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index 05e05c262..a45a37ef5 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -1405,10 +1405,19 @@ action_if_variable_not_empty: { DISPATCH_NEXT_ACTION(); action_apply_tpl: { - struct lwan_strbuf *tmp = lwan_tpl_apply(chunk->data, variables); - lwan_strbuf_append_str(buf, lwan_strbuf_get_buffer(tmp), - lwan_strbuf_get_length(tmp)); - lwan_strbuf_free(tmp); + struct lwan_tpl *inner_tpl = chunk->data; + + if (LIKELY(lwan_strbuf_grow_by(buf, inner_tpl->minimum_size))) { + if (!apply(inner_tpl, inner_tpl->chunks.base.base, buf, variables, NULL)) { + lwan_status_warning("Could not apply subtemplate"); + return NULL; + } + } else { + lwan_status_warning("Could not grow template by %zu bytes", + inner_tpl->minimum_size); + return NULL; + } + DISPATCH_NEXT_ACTION(); } From efa42256d7291835a7489812cf828253ef1c8e83 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 13 Aug 2019 08:10:47 -0700 Subject: [PATCH 1160/2505] Truncate extensions over 8 chars in MIME type table Instead of ignoring these extensions, truncate them so they're at most at 8 characters. Lookup will will work because Lwan uses strncmp(..., ..., 8). Table is back at 954 elements, with compressed len of 6599 bytes (with Brotli). --- src/bin/tools/mimegen.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/bin/tools/mimegen.c b/src/bin/tools/mimegen.c index b2b42e961..4e47a032f 100644 --- a/src/bin/tools/mimegen.c +++ b/src/bin/tools/mimegen.c @@ -232,8 +232,10 @@ int main(int argc, char *argv[]) end = strchr(ext, '\0'); /* If not found, find last extension. */ *end = '\0'; - if (end - ext > 8) - continue; + if (end - ext > 8) { + /* Truncate extensions over 8 characters. See commit 2050759297. */ + ext[8] = '\0'; + } k = strdup(ext); v = strdup(mime_type); From 71512985b55ce7bc76e0fa04b7052c9e3ef42d63 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 14 Aug 2019 20:09:17 -0700 Subject: [PATCH 1161/2505] Provide fwrite_unlocked() for non-glibc systems Fixes #269. --- CMakeLists.txt | 1 + src/cmake/lwan-build-config.h.cmake | 1 + src/lib/missing.c | 26 +++++++++++++++++++++++++ src/lib/missing/stdio.h | 30 +++++++++++++++++++++++++++++ 4 files changed, 58 insertions(+) create mode 100644 src/lib/missing/stdio.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b37f9e89..3b6939d98 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -129,6 +129,7 @@ check_function_exists(pthread_set_name_np HAVE_PTHREAD_SET_NAME_NP) check_function_exists(eventfd HAVE_EVENTFD) check_function_exists(posix_fadvise HAVE_POSIX_FADVISE) check_function_exists(getentropy HAVE_GETENTROPY) +check_function_exists(fwrite_unlocked HAVE_FWRITE_UNLOCKED) # This is available on -ldl in glibc, but some systems (such as OpenBSD) # will bundle these in the C library. This isn't required for glibc anyway, diff --git a/src/cmake/lwan-build-config.h.cmake b/src/cmake/lwan-build-config.h.cmake index f4e31846c..dc70f1f83 100644 --- a/src/cmake/lwan-build-config.h.cmake +++ b/src/cmake/lwan-build-config.h.cmake @@ -41,6 +41,7 @@ #cmakedefine HAVE_LINUX_CAPABILITY #cmakedefine HAVE_PTHREAD_SET_NAME_NP #cmakedefine HAVE_GETENTROPY +#cmakedefine HAVE_FWRITE_UNLOCKED /* Compiler builtins for specific CPU instruction support */ #cmakedefine HAVE_BUILTIN_CLZLL diff --git a/src/lib/missing.c b/src/lib/missing.c index 0b4a2a449..48e783d90 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -563,3 +563,29 @@ int capset(struct __user_cap_header_struct *header, return 0; } #endif + +#if !defined(HAVE_FWRITE_UNLOCKED) +size_t fwrite_unlocked(const void *ptr, size_t size, size_t n, FILE *stream) +{ + size_t to_write = size * n; + const size_t total_to_write = to_write; + + if (!to_write) + return 0; + + fflush/* _unlocked? */(stream); + + while (to_write) { + ssize_t r = write(fileno(stream), ptr, to_write); + if (r < 0) { + if (errno == EINTR) + continue; + break; + } + + to_write -= (size_t)r; + } + + return (total_to_write - to_write) / size; +} +#endif diff --git a/src/lib/missing/stdio.h b/src/lib/missing/stdio.h new file mode 100644 index 000000000..6657163af --- /dev/null +++ b/src/lib/missing/stdio.h @@ -0,0 +1,30 @@ +/* + * lwan - simple web server + * Copyright (c) 2019 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include_next + +#ifndef MISSING_STDIO_H +#define MISSING_STDIO_H + +#if !defined(HAVE_FWRITE_UNLOCKED) +size_t fwrite_unlocked(const void *ptr, size_t size, size_t n, FILE *stream); +#endif + +#endif /* MISSING_STDIO_H */ From 4451569863e5bd933050fb31225242d69c16fd67 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 14 Aug 2019 20:10:44 -0700 Subject: [PATCH 1162/2505] expand() function doesn't need to take a request parameter --- src/lib/lwan-mod-rewrite.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index 3bef07ffd..e97f6e023 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -131,8 +131,7 @@ static __attribute__((noinline)) int parse_int_len(const char *s, size_t len, return parse_int(strndupa(s, len), default_value); } -static const char *expand(struct lwan_request *request __attribute__((unused)), - struct pattern *pattern, const char *orig, +static const char *expand(struct pattern *pattern, const char *orig, char buffer[static PATH_MAX], const struct str_find *sf, int captures) { @@ -269,7 +268,7 @@ rewrite_handle_request(struct lwan_request *request, break; #endif case PATTERN_EXPAND_LWAN: - expanded = expand(request, p, url, final_url, sf, captures); + expanded = expand(p, url, final_url, sf, captures); break; } From 65b1f39e3a82471a68ce6e35736c9efbfd4d13c6 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 14 Aug 2019 20:11:06 -0700 Subject: [PATCH 1163/2505] Cleanup expand() function by caching strlen() output --- src/lib/lwan-mod-rewrite.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index e97f6e023..e86f5f7d4 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -174,8 +174,8 @@ static const char *expand(struct pattern *pattern, const char *orig, expand_pattern++; } while ((ptr = strchr(expand_pattern, '%'))); - if (*expand_pattern && - !append_str(&builder, expand_pattern, strlen(expand_pattern))) + const size_t remaining_len = strlen(expand_pattern); + if (remaining_len && !append_str(&builder, expand_pattern, remaining_len)) return NULL; if (UNLIKELY(!builder.len)) From 6ecc05c9b47c45db394adf20d3945fa3a738dfd1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 14 Aug 2019 20:11:56 -0700 Subject: [PATCH 1164/2505] Implement small string optimization in template chunks Use the space that was destined for the pointer to store strings up to sizeof(void*) instead of wasting most of it for a single character. Avoids allocating a strbuf just for those small strings. --- src/lib/lwan-template.c | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index a45a37ef5..41b79e673 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -57,7 +57,7 @@ enum action { ACTION_APPEND, - ACTION_APPEND_CHAR, + ACTION_APPEND_SMALL, ACTION_VARIABLE, ACTION_VARIABLE_STR, ACTION_VARIABLE_STR_ESCAPE, @@ -841,9 +841,11 @@ static void *parser_text(struct parser *parser, struct lexeme *lexeme) return parser_meta; if (lexeme->type == LEXEME_TEXT) { - if (lexeme->value.len == 1) { - emit_chunk(parser, ACTION_APPEND_CHAR, 0, - (void *)(uintptr_t)*lexeme->value.value); + if (lexeme->value.len <= sizeof(void *)) { + uintptr_t tmp = 0; + + memcpy(&tmp, lexeme->value.value, lexeme->value.len); + emit_chunk(parser, ACTION_APPEND_SMALL, 0, (void*)tmp); } else { struct lwan_strbuf *buf = lwan_strbuf_from_lexeme(parser, lexeme); if (!buf) @@ -943,7 +945,7 @@ static void free_chunk(struct chunk *chunk) switch (chunk->action) { case ACTION_LAST: - case ACTION_APPEND_CHAR: + case ACTION_APPEND_SMALL: case ACTION_VARIABLE: case ACTION_VARIABLE_STR: case ACTION_VARIABLE_STR_ESCAPE: @@ -1203,10 +1205,13 @@ static void dump_program(const struct lwan_tpl *tpl) (int)lwan_strbuf_get_length(iter->data), lwan_strbuf_get_buffer(iter->data)); break; - case ACTION_APPEND_CHAR: - printf("%s [%d]", instr("APPEND_CHAR", instr_buf), - (char)(uintptr_t)iter->data); + case ACTION_APPEND_SMALL: { + uintptr_t val = (uintptr_t)iter->data; + size_t len = strnlen((char *)&val, 8); + + printf("%s (%zu) [%.*s]", instr("APPEND_SMALL", instr_buf), len, (int)len, (char *)&val); break; + } case ACTION_VARIABLE: { struct lwan_var_descriptor *descriptor = iter->data; @@ -1331,7 +1336,7 @@ static const struct chunk *apply(struct lwan_tpl *tpl, { static const void *const dispatch_table[] = { [ACTION_APPEND] = &&action_append, - [ACTION_APPEND_CHAR] = &&action_append_char, + [ACTION_APPEND_SMALL] = &&action_append_small, [ACTION_VARIABLE] = &&action_variable, [ACTION_VARIABLE_STR] = &&action_variable_str, [ACTION_VARIABLE_STR_ESCAPE] = &&action_variable_str_escape, @@ -1366,9 +1371,14 @@ static const struct chunk *apply(struct lwan_tpl *tpl, lwan_strbuf_get_length(chunk->data)); DISPATCH_NEXT_ACTION(); -action_append_char: - lwan_strbuf_append_char(buf, (char)(uintptr_t)chunk->data); - DISPATCH_NEXT_ACTION(); +action_append_small: { + uintptr_t val = (uintptr_t)chunk->data; + size_t len = strnlen((char *)&val, sizeof(val)); + + lwan_strbuf_append_str(buf, (char*)&val, len); + + DISPATCH_NEXT_ACTION(); + } action_variable: { struct lwan_var_descriptor *descriptor = chunk->data; From 1268e91737a79dd2555a38ee5befc3ca29ee4a02 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 14 Aug 2019 20:12:59 -0700 Subject: [PATCH 1165/2505] Cleanup DISPATCH_ACTION() macro --- src/lib/lwan-template.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index 41b79e673..8de369121 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -1354,10 +1354,8 @@ static const struct chunk *apply(struct lwan_tpl *tpl, if (UNLIKELY(!chunk)) return NULL; -#define DISPATCH_ACTION() \ - do { \ - goto *dispatch_table[chunk->action]; \ - } while (false) +#define DISPATCH_ACTION() goto *dispatch_table[chunk->action] + #define DISPATCH_NEXT_ACTION() \ do { \ chunk++; \ From b4670c9c0b55a3c54560cfd21323f6efe64e13b2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 15 Aug 2019 09:39:15 -0700 Subject: [PATCH 1166/2505] Fragments should never be received by the server --- src/lib/lwan-request.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 0cfe9b724..14938c915 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -64,7 +64,6 @@ struct lwan_request_parser_helper { struct lwan_value accept_encoding; /* Accept-Encoding: */ struct lwan_value query_string; /* Stuff after ? and before # */ - struct lwan_value fragment; /* Stuff after # */ struct lwan_value post_data; /* Request body for POST */ struct lwan_value content_type; /* Content-Type: for POST */ @@ -445,23 +444,20 @@ static void parse_fragment_and_query(struct lwan_request *request, { struct lwan_request_parser_helper *helper = request->helper; - /* Most of the time, fragments are small -- so search backwards */ + /* Fragments shouldn't be received by the server, but look for them anyway + * just in case. */ char *fragment = memrchr(request->url.value, '#', request->url.len); - if (fragment) { + if (UNLIKELY(fragment != NULL)) { *fragment = '\0'; - helper->fragment.value = fragment + 1; - helper->fragment.len = (size_t)(space - fragment - 1); - request->url.len -= helper->fragment.len + 1; + request->url.len = (size_t)(fragment - request->url.value); + space = fragment; } - /* Most of the time, query string values are larger than the URL, so - search from the beginning */ char *query_string = memchr(request->url.value, '?', request->url.len); if (query_string) { *query_string = '\0'; helper->query_string.value = query_string + 1; - helper->query_string.len = - (size_t)((fragment ? fragment : space) - query_string - 1); + helper->query_string.len = (size_t)(space - query_string - 1); request->url.len -= helper->query_string.len + 1; } } From b4c77f9110fc6ae5fd7c3d9b7449729480d0b45c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 15 Aug 2019 09:39:46 -0700 Subject: [PATCH 1167/2505] read_request_finalizer() doesn't use buffer_size --- src/lib/lwan-request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 14938c915..cb42f6945 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -890,7 +890,7 @@ read_from_request_socket(struct lwan_request *request, static enum lwan_read_finalizer read_request_finalizer(size_t total_read, - size_t buffer_size, + size_t buffer_size __attribute__((unused)), struct lwan_request *request, int n_packets) { From f899d11dc549d5bdfebfbbe028fcc7cd7934f41d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 17 Aug 2019 00:57:27 -0700 Subject: [PATCH 1168/2505] Clean up header slicing step of parse_headers() --- src/lib/lwan-request.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index cb42f6945..a2d924f6f 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -562,14 +562,16 @@ static bool parse_headers(struct lwan_request_parser_helper *helper, /* Is there at least a space for a minimal (H)eader and a (V)alue? */ if (LIKELY(next_header - next_chr >= (ptrdiff_t)(sizeof("H: V") - 1))) { header_start[n_headers++] = next_chr; + + if (UNLIKELY(n_headers >= N_HEADER_START - 1)) + return false; } else { /* Better to abort early if there's no space. */ return false; } p = next_header + HEADER_TERMINATOR_LEN; - - if (UNLIKELY(n_headers >= (N_HEADER_START - 1) || p >= buffer_end)) + if (UNLIKELY(p >= buffer_end)) return false; } From 2cfc552a3fefc04a7faf03ad6a062de0857a5ab6 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 18 Aug 2019 16:51:12 -0700 Subject: [PATCH 1169/2505] Add support for mimalloc when specifying an alternative malloc --- CMakeLists.txt | 29 +++++++++++++++++------------ README.md | 8 +++++--- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b6939d98..3c2e8f720 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,21 +61,26 @@ if (BROTLI_FOUND) set(HAVE_BROTLI 1) endif () -option(USE_ALTERNATIVE_MALLOC "Use alternative malloc implementations" OFF) +option(USE_ALTERNATIVE_MALLOC "Use alternative malloc implementations" "OFF") if (USE_ALTERNATIVE_MALLOC) - find_library(TCMALLOC_LIBRARY NAMES tcmalloc_minimal tcmalloc) - if (TCMALLOC_LIBRARY) - message(STATUS "tcmalloc found: ${TCMALLOC_LIBRARY}") - list(APPEND ADDITIONAL_LIBRARIES ${TCMALLOC_LIBRARY}) + unset(ALTMALLOC_LIBS CACHE) + unset(ALTMALLOC_LIBRARY CACHE) + + if (${USE_ALTERNATIVE_MALLOC} STREQUAL "mimalloc") + set(ALTMALLOC_LIBS mimalloc) + elseif (${USE_ALTERNATIVE_MALLOC} STREQUAL "tcmalloc") + set(ALTMALLOC_LIBS tcmalloc_minimal tcmalloc) + elseif (${USE_ALTERNATIVE_MALLOC} STREQUAL "jemalloc") + set(ALTMALLOC_LIBS jemalloc) else () - find_library(JEMALLOC_LIBRARY NAMES jemalloc) - if (JEMALLOC_LIBRARY) - message(STATUS "jemalloc found: ${JEMALLOC_LIBRARY}") - list(APPEND ADDITIONAL_LIBRARIES ${JEMALLOC_LIBRARY}) - else () - message(STATUS "jemalloc and tcmalloc were not found, using system malloc") - endif() + set(ALTMALLOC_LIBS "mimalloc tcmalloc_minimal tcmalloc jemalloc") endif() + + find_library(ALTMALLOC_LIBRARY NAMES ${ALTMALLOC_LIBS}) + if (ALTMALLOC_LIBRARY) + message(STATUS "Using alternative malloc (${USE_ALTERNATIVE_MALLOC}): ${ALTMALLOC_LIBRARY}") + list(PREPEND ADDITIONAL_LIBRARIES ${ALTMALLOC_LIBRARY}) + endif () endif () # diff --git a/README.md b/README.md index e618beef4..046bcb113 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,11 @@ The build system will look for these libraries and enable/link if available. - [Lua 5.1](http://www.lua.org) or [LuaJIT 2.0](http://luajit.org) - [Valgrind](http://valgrind.org) - [Brotli](https://github.com/google/brotli) - - Alternative memory allocators can be used by passing `-DUSE_ALTERNATIVE_MALLOC=ON` to CMake: - - [TCMalloc](https://github.com/gperftools/gperftools) - - [jemalloc](http://jemalloc.net/) + - Alternative memory allocators can be used by passing `-DUSE_ALTERNATIVE_MALLOC` to CMake with the following values: + - ["tcmalloc" ](https://github.com/gperftools/gperftools) + - ["jemalloc"](http://jemalloc.net/) + - ["mimalloc"](https://github.com/microsoft/mimalloc) + - "auto": Autodetect between these tcmalloc, jemalloc, and mimalloc - To run test suite: - [Python](https://www.python.org/) (2.6+) with Requests - [Lua 5.1](http://www.lua.org) From 86b60b846b5c039392500dc7a901b53935bede04 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 21 Aug 2019 23:20:20 +0200 Subject: [PATCH 1170/2505] It's UB to memcpy() 0-byte quantities --- src/bin/tools/mimegen.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/bin/tools/mimegen.c b/src/bin/tools/mimegen.c index 4e47a032f..3ac11d9d7 100644 --- a/src/bin/tools/mimegen.c +++ b/src/bin/tools/mimegen.c @@ -73,7 +73,10 @@ static int output_append_padded(struct output *output, const char *str) if (r < 0) return r; - return output_append_full(output, "\0\0\0\0\0\0\0\0", 8 - str_len); + if (str_len != 8) + return output_append_full(output, "\0\0\0\0\0\0\0\0", 8 - str_len); + + return 0; } return -EINVAL; From 4165f532f28c8c57e66bf95ddc514fa7b01c77a2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 25 Aug 2019 09:21:14 +0200 Subject: [PATCH 1171/2505] Reindent lwan-coro.[ch] --- src/lib/lwan-coro.c | 155 ++++++++++++++++++++------------------------ src/lib/lwan-coro.h | 49 +++++++------- 2 files changed, 99 insertions(+), 105 deletions(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 4cea2340e..5278d6869 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -14,7 +14,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #define _GNU_SOURCE @@ -32,21 +33,21 @@ #include "lwan-coro.h" #ifdef HAVE_VALGRIND -# include +#include #endif #if !defined(SIGSTKSZ) -# define SIGSTKSZ 16384 +#define SIGSTKSZ 16384 #endif #ifdef HAVE_BROTLI -# define CORO_STACK_MIN (8 * SIGSTKSZ) +#define CORO_STACK_MIN (8 * SIGSTKSZ) #else -# define CORO_STACK_MIN (4 * SIGSTKSZ) +#define CORO_STACK_MIN (4 * SIGSTKSZ) #endif static_assert(DEFAULT_BUFFER_SIZE < (CORO_STACK_MIN + SIGSTKSZ), - "Request buffer fits inside coroutine stack"); + "Request buffer fits inside coroutine stack"); typedef void (*defer1_func)(void *data); typedef void (*defer2_func)(void *data1, void *data2); @@ -88,7 +89,8 @@ struct coro { #define ASM_SYMBOL(name_) #name_ #endif -#define ASM_ROUTINE(name_) ".globl " ASM_SYMBOL(name_) "\n\t" ASM_SYMBOL(name_) ":\n\t" +#define ASM_ROUTINE(name_) \ + ".globl " ASM_SYMBOL(name_) "\n\t" ASM_SYMBOL(name_) ":\n\t" /* * This swapcontext() implementation was obtained from glibc and modified @@ -103,38 +105,36 @@ struct coro { #if defined(__x86_64__) void __attribute__((noinline, visibility("internal"))) coro_swapcontext(coro_context *current, coro_context *other); - asm( - ".text\n\t" +asm(".text\n\t" ".p2align 4\n\t" ASM_ROUTINE(coro_swapcontext) - "mov %rbx,0(%rdi)\n\t" - "mov %rbp,8(%rdi)\n\t" - "mov %r12,16(%rdi)\n\t" - "mov %r13,24(%rdi)\n\t" - "mov %r14,32(%rdi)\n\t" - "mov %r15,40(%rdi)\n\t" - "mov %rdi,48(%rdi)\n\t" - "mov %rsi,56(%rdi)\n\t" - "mov (%rsp),%rcx\n\t" - "mov %rcx,64(%rdi)\n\t" - "lea 0x8(%rsp),%rcx\n\t" - "mov %rcx,72(%rdi)\n\t" - "mov 72(%rsi),%rsp\n\t" - "mov 0(%rsi),%rbx\n\t" - "mov 8(%rsi),%rbp\n\t" - "mov 16(%rsi),%r12\n\t" - "mov 24(%rsi),%r13\n\t" - "mov 32(%rsi),%r14\n\t" - "mov 40(%rsi),%r15\n\t" - "mov 48(%rsi),%rdi\n\t" - "mov 64(%rsi),%rcx\n\t" - "mov 56(%rsi),%rsi\n\t" - "jmp *%rcx\n\t"); + "movq %rbx,0(%rdi)\n\t" + "movq %rbp,8(%rdi)\n\t" + "movq %r12,16(%rdi)\n\t" + "movq %r13,24(%rdi)\n\t" + "movq %r14,32(%rdi)\n\t" + "movq %r15,40(%rdi)\n\t" + "movq %rdi,48(%rdi)\n\t" + "movq %rsi,56(%rdi)\n\t" + "movq (%rsp),%rcx\n\t" + "movq %rcx,64(%rdi)\n\t" + "leaq 0x8(%rsp),%rcx\n\t" + "movq %rcx,72(%rdi)\n\t" + "movq 72(%rsi),%rsp\n\t" + "movq 0(%rsi),%rbx\n\t" + "movq 8(%rsi),%rbp\n\t" + "movq 16(%rsi),%r12\n\t" + "movq 24(%rsi),%r13\n\t" + "movq 32(%rsi),%r14\n\t" + "movq 40(%rsi),%r15\n\t" + "movq 48(%rsi),%rdi\n\t" + "movq 64(%rsi),%rcx\n\t" + "movq 56(%rsi),%rsi\n\t" + "jmpq *%rcx\n\t"); #elif defined(__i386__) void __attribute__((noinline, visibility("internal"))) coro_swapcontext(coro_context *current, coro_context *other); - asm( - ".text\n\t" +asm(".text\n\t" ".p2align 16\n\t" ASM_ROUTINE(coro_swapcontext) "movl 0x4(%esp),%eax\n\t" @@ -158,7 +158,7 @@ coro_swapcontext(coro_context *current, coro_context *other); "movl 0x1c(%eax),%ecx\n\t" /* ECX */ "ret\n\t"); #else -#define coro_swapcontext(cur,oth) swapcontext(cur, oth) +#define coro_swapcontext(cur, oth) swapcontext(cur, oth) #endif #ifndef __x86_64__ @@ -171,24 +171,22 @@ coro_entry_point(struct coro *coro, coro_function_t func, void *data) #else void __attribute__((noinline, visibility("internal"))) coro_entry_point(struct coro *coro, coro_function_t func, void *data); - asm( - ".text\n\t" +asm(".text\n\t" ".p2align 4\n\t" ASM_ROUTINE(coro_entry_point) "pushq %rbx\n\t" - "movq %rdi, %rbx\n\t" /* coro = rdi */ - "movq %rsi, %rdx\n\t" /* func = rsi */ - "movq %r15, %rsi\n\t" /* data = r15 */ - "call *%rdx\n\t" /* eax = func(coro, data) */ + "movq %rdi, %rbx\n\t" /* coro = rdi */ + "movq %rsi, %rdx\n\t" /* func = rsi */ + "movq %r15, %rsi\n\t" /* data = r15 */ + "call *%rdx\n\t" /* eax = func(coro, data) */ "movq (%rbx), %rsi\n\t" - "movl %eax, 0x68(%rbx)\n\t" /* coro->yield_value = eax */ + "movl %eax, 0x68(%rbx)\n\t" /* coro->yield_value eax */ "popq %rbx\n\t" - "leaq 0x50(%rsi), %rdi\n\t" /* get coro context from coro */ + "leaq 0x50(%rsi), %rdi\n\t" /* get coro context from coro */ "jmp " ASM_SYMBOL(coro_swapcontext) "\n\t"); #endif -void -coro_deferred_run(struct coro *coro, size_t generation) +void coro_deferred_run(struct coro *coro, size_t generation) { struct lwan_array *array = (struct lwan_array *)&coro->defer; struct coro_defer *defers = array->base; @@ -205,16 +203,14 @@ coro_deferred_run(struct coro *coro, size_t generation) array->elements = generation; } -size_t -coro_deferred_get_generation(const struct coro *coro) +size_t coro_deferred_get_generation(const struct coro *coro) { const struct lwan_array *array = (struct lwan_array *)&coro->defer; return array->elements; } -void -coro_reset(struct coro *coro, coro_function_t func, void *data) +void coro_reset(struct coro *coro, coro_function_t func, void *data) { unsigned char *stack = coro->stack; @@ -226,15 +222,15 @@ coro_reset(struct coro *coro, coro_function_t func, void *data) * stored. Use R15 instead, and implement the trampoline * function in assembly in order to use this register when * calling the user function. */ - coro->context[5 /* R15 */] = (uintptr_t) data; - coro->context[6 /* RDI */] = (uintptr_t) coro; - coro->context[7 /* RSI */] = (uintptr_t) func; - coro->context[8 /* RIP */] = (uintptr_t) coro_entry_point; + coro->context[5 /* R15 */] = (uintptr_t)data; + coro->context[6 /* RDI */] = (uintptr_t)coro; + coro->context[7 /* RSI */] = (uintptr_t)func; + coro->context[8 /* RIP */] = (uintptr_t)coro_entry_point; /* Ensure stack is properly aligned: it should be aligned to a * 16-bytes boundary so SSE will work properly, but should be * aligned on an 8-byte boundary right after calling a function. */ - uintptr_t rsp = (uintptr_t) stack + CORO_STACK_MIN; + uintptr_t rsp = (uintptr_t)stack + CORO_STACK_MIN; #define STACK_PTR 9 coro->context[STACK_PTR] = (rsp & ~0xful) - 0x8ul; @@ -244,7 +240,7 @@ coro_reset(struct coro *coro, coro_function_t func, void *data) /* Make room for 3 args */ stack -= sizeof(uintptr_t) * 3; /* Ensure 4-byte alignment */ - stack = (unsigned char*)((uintptr_t)stack & (uintptr_t)~0x3); + stack = (unsigned char *)((uintptr_t)stack & (uintptr_t)~0x3); uintptr_t *argp = (uintptr_t *)stack; *argp++ = 0; @@ -252,10 +248,10 @@ coro_reset(struct coro *coro, coro_function_t func, void *data) *argp++ = (uintptr_t)func; *argp++ = (uintptr_t)data; - coro->context[5 /* EIP */] = (uintptr_t) coro_entry_point; + coro->context[5 /* EIP */] = (uintptr_t)coro_entry_point; #define STACK_PTR 6 - coro->context[STACK_PTR] = (uintptr_t) stack; + coro->context[STACK_PTR] = (uintptr_t)stack; #else getcontext(&coro->context); @@ -264,8 +260,8 @@ coro_reset(struct coro *coro, coro_function_t func, void *data) coro->context.uc_stack.ss_flags = 0; coro->context.uc_link = NULL; - makecontext(&coro->context, (void (*)())coro_entry_point, 3, - coro, func, data); + makecontext(&coro->context, (void (*)())coro_entry_point, 3, coro, func, + data); #endif } @@ -292,8 +288,7 @@ coro_new(struct coro_switcher *switcher, coro_function_t function, void *data) return coro; } -ALWAYS_INLINE int -coro_resume(struct coro *coro) +ALWAYS_INLINE int coro_resume(struct coro *coro) { assert(coro); @@ -304,14 +299,12 @@ coro_resume(struct coro *coro) #endif coro_swapcontext(&coro->switcher->caller, &coro->context); - memcpy(&coro->context, &coro->switcher->callee, - sizeof(coro->context)); + memcpy(&coro->context, &coro->switcher->callee, sizeof(coro->context)); return coro->yield_value; } -ALWAYS_INLINE int -coro_resume_value(struct coro *coro, int value) +ALWAYS_INLINE int coro_resume_value(struct coro *coro, int value) { assert(coro); @@ -319,8 +312,7 @@ coro_resume_value(struct coro *coro, int value) return coro_resume(coro); } -ALWAYS_INLINE int -coro_yield(struct coro *coro, int value) +ALWAYS_INLINE int coro_yield(struct coro *coro, int value) { assert(coro); coro->yield_value = value; @@ -328,8 +320,7 @@ coro_yield(struct coro *coro, int value) return coro->yield_value; } -void -coro_free(struct coro *coro) +void coro_free(struct coro *coro) { assert(coro); #if !defined(NDEBUG) && defined(HAVE_VALGRIND) @@ -340,13 +331,13 @@ coro_free(struct coro *coro) free(coro); } -ALWAYS_INLINE void -coro_defer(struct coro *coro, defer1_func func, void *data) +ALWAYS_INLINE void coro_defer(struct coro *coro, defer1_func func, void *data) { struct coro_defer *defer = coro_defer_array_append(&coro->defer); if (UNLIKELY(!defer)) { - lwan_status_error("Could not add new deferred function for coro %p", coro); + lwan_status_error("Could not add new deferred function for coro %p", + coro); return; } @@ -361,7 +352,8 @@ coro_defer2(struct coro *coro, defer2_func func, void *data1, void *data2) struct coro_defer *defer = coro_defer_array_append(&coro->defer); if (UNLIKELY(!defer)) { - lwan_status_error("Could not add new deferred function for coro %p", coro); + lwan_status_error("Could not add new deferred function for coro %p", + coro); return; } @@ -371,8 +363,9 @@ coro_defer2(struct coro *coro, defer2_func func, void *data1, void *data2) defer->has_two_args = true; } -void * -coro_malloc_full(struct coro *coro, size_t size, void (*destroy_func)(void *data)) +void *coro_malloc_full(struct coro *coro, + size_t size, + void (*destroy_func)(void *data)) { void *ptr = malloc(size); @@ -382,14 +375,12 @@ coro_malloc_full(struct coro *coro, size_t size, void (*destroy_func)(void *data return ptr; } -inline void * -coro_malloc(struct coro *coro, size_t size) +inline void *coro_malloc(struct coro *coro, size_t size) { return coro_malloc_full(coro, size, free); } -char * -coro_strndup(struct coro *coro, const char *str, size_t max_len) +char *coro_strndup(struct coro *coro, const char *str, size_t max_len) { const size_t len = strnlen(str, max_len) + 1; char *dup = coro_malloc(coro, len); @@ -401,14 +392,12 @@ coro_strndup(struct coro *coro, const char *str, size_t max_len) return dup; } -char * -coro_strdup(struct coro *coro, const char *str) +char *coro_strdup(struct coro *coro, const char *str) { return coro_strndup(coro, str, SSIZE_MAX - 1); } -char * -coro_printf(struct coro *coro, const char *fmt, ...) +char *coro_printf(struct coro *coro, const char *fmt, ...) { va_list values; int len; diff --git a/src/lib/lwan-coro.h b/src/lib/lwan-coro.h index b428e55e1..74590d44b 100644 --- a/src/lib/lwan-coro.h +++ b/src/lib/lwan-coro.h @@ -14,7 +14,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #pragma once @@ -33,35 +34,39 @@ typedef ucontext_t coro_context; struct coro; -typedef int (*coro_function_t) (struct coro *coro, void *data); +typedef int (*coro_function_t)(struct coro *coro, void *data); struct coro_switcher { coro_context caller; coro_context callee; }; -struct coro *coro_new(struct coro_switcher *switcher, coro_function_t function, void *data); -void coro_free(struct coro *coro); +struct coro * +coro_new(struct coro_switcher *switcher, coro_function_t function, void *data); +void coro_free(struct coro *coro); -void coro_reset(struct coro *coro, coro_function_t func, void *data); +void coro_reset(struct coro *coro, coro_function_t func, void *data); -int coro_resume(struct coro *coro); -int coro_resume_value(struct coro *coro, int value); -int coro_yield(struct coro *coro, int value); +int coro_resume(struct coro *coro); +int coro_resume_value(struct coro *coro, int value); +int coro_yield(struct coro *coro, int value); -void coro_defer(struct coro *coro, void (*func)(void *data), void *data); -void coro_defer2(struct coro *coro, void (*func)(void *data1, void *data2), - void *data1, void *data2); +void coro_defer(struct coro *coro, void (*func)(void *data), void *data); +void coro_defer2(struct coro *coro, + void (*func)(void *data1, void *data2), + void *data1, + void *data2); -void coro_deferred_run(struct coro *coro, size_t generation); -size_t coro_deferred_get_generation(const struct coro *coro) - __attribute__((pure)); +void coro_deferred_run(struct coro *coro, size_t generation); +size_t coro_deferred_get_generation(const struct coro *coro) + __attribute__((pure)); -void *coro_malloc(struct coro *coro, size_t sz) - __attribute__((malloc)); -void *coro_malloc_full(struct coro *coro, size_t size, void (*destroy_func)(void *data)) - __attribute__((malloc)); -char *coro_strdup(struct coro *coro, const char *str); -char *coro_strndup(struct coro *coro, const char *str, size_t len); -char *coro_printf(struct coro *coro, const char *fmt, ...) - __attribute__((format(printf, 2, 3))); +void *coro_malloc(struct coro *coro, size_t sz) __attribute__((malloc)); +void *coro_malloc_full(struct coro *coro, + size_t size, + void (*destroy_func)(void *data)) + __attribute__((malloc)); +char *coro_strdup(struct coro *coro, const char *str); +char *coro_strndup(struct coro *coro, const char *str, size_t len); +char *coro_printf(struct coro *coro, const char *fmt, ...) + __attribute__((format(printf, 2, 3))); From 1226f8826769acf7f2a0cf5f2a344885bead687f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 8 Sep 2019 19:30:20 -0700 Subject: [PATCH 1172/2505] Use &array->base instead of downcasting It's clearer which type should be passed to lwan_array_*() functions this way. --- src/lib/lwan-array.h | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/lib/lwan-array.h b/src/lib/lwan-array.h index 81ab6e671..ac5cea09a 100644 --- a/src/lib/lwan-array.h +++ b/src/lib/lwan-array.h @@ -56,17 +56,17 @@ struct lwan_array *coro_lwan_array_new(struct coro *coro); __attribute__((unused)) static inline int array_type_##_init( \ struct array_type_ *array) \ { \ - return lwan_array_init((struct lwan_array *)array); \ + return lwan_array_init(&array->base); \ } \ __attribute__((unused)) static inline int array_type_##_reset( \ struct array_type_ *array) \ { \ - return lwan_array_reset((struct lwan_array *)array); \ + return lwan_array_reset(&array->base); \ } \ __attribute__((unused)) static inline element_type_ *array_type_##_append( \ struct array_type_ *array) \ { \ - return (element_type_ *)lwan_array_append((struct lwan_array *)array, \ + return (element_type_ *)lwan_array_append(&array->base, \ sizeof(element_type_)); \ } \ __attribute__((unused)) static inline element_type_ \ @@ -82,8 +82,7 @@ struct lwan_array *coro_lwan_array_new(struct coro *coro); __attribute__((unused)) static inline void array_type_##_sort( \ struct array_type_ *array, int (*cmp)(const void *a, const void *b)) \ { \ - lwan_array_sort((struct lwan_array *)array, sizeof(element_type_), \ - cmp); \ + lwan_array_sort(&array->base, sizeof(element_type_), cmp); \ } \ __attribute__((unused)) static inline struct array_type_ \ *coro_##array_type_##_new(struct coro *coro) \ From 6bf3a9a48c71ab10404999f0bbc5576c256c2baf Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 8 Sep 2019 19:31:01 -0700 Subject: [PATCH 1173/2505] Clarify the intent of error_when_n_packets when reading request --- src/lib/lwan-request.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index a2d924f6f..b22e6ca0a 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -900,9 +900,12 @@ read_request_finalizer(size_t total_read, MIN_REQUEST_SIZE + sizeof(struct proxy_header_v2); struct lwan_request_parser_helper *helper = request->helper; - /* 16 packets should be enough to read a request (without the body, as - * is the case for POST requests). This yields a timeout error to avoid - * clients being intentionally slow and hogging the server. */ + /* Yield a timeout error to avoid clients being intentionally slow and + * hogging the server. (Clients can't only connect and do nothing, they + * need to send data, otherwise the DQ timer will kick in and close the + * connection. Limit the number of packets to avoid them sending just + * a byte at a time.) + * See calculate_n_packets() to see how this is calculated. */ if (UNLIKELY(n_packets > helper->error_when_n_packets)) return FINALIZER_ERROR_TIMEOUT; From 07b4e99f1fe9b63cfe94554fd5bc87eca78884c1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 8 Sep 2019 19:31:23 -0700 Subject: [PATCH 1174/2505] Use memcpy() to tack on " GMT" to RFC822-formatted date No need to use mempcpy() here; the return value is useless. --- src/lib/lwan-time.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-time.c b/src/lib/lwan-time.c index ec520e951..8f70c5dc5 100644 --- a/src/lib/lwan-time.c +++ b/src/lib/lwan-time.c @@ -147,7 +147,7 @@ int lwan_format_rfc_time(const time_t in, char out[static 30]) *p++ = ':'; p = mempcpy(p, uint_to_string_2_digits((unsigned int)tm.tm_sec), 2); - p = mempcpy(p, " GMT", 5); + memcpy(p, " GMT", 5); return 0; } From 61bca99566db1882d189b52aebdc40cc2feb56c3 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 11 Sep 2019 20:00:58 -0700 Subject: [PATCH 1175/2505] Move cookies, query_params, and post_params arrays to helper struct Make them opaque so that it's not possible to modify these arrays from user code. They can still query as usual using the getters (which are preferrable since they're filled in lazily). --- src/lib/lwan-request.c | 19 ++++++++++++------- src/lib/lwan.h | 1 - 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index b22e6ca0a..78d4d90ff 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -71,6 +71,8 @@ struct lwan_request_parser_helper { struct lwan_value connection; /* Connection: */ + struct lwan_key_value_array cookies, query_params, post_params; + struct { /* If-Modified-Since: */ struct lwan_value raw; time_t parsed; @@ -415,13 +417,16 @@ static void parse_cookies(struct lwan_request *request) struct lwan_value header = {.value = (char *)cookies, .len = strlen(cookies)}; - parse_key_values(request, &header, &request->cookies, identity_decode, ';'); + parse_key_values(request, &header, &request->helper->cookies, + identity_decode, ';'); } static void parse_query_string(struct lwan_request *request) { - parse_key_values(request, &request->helper->query_string, - &request->query_params, url_decode, '&'); + struct lwan_request_parser_helper *helper = request->helper; + + parse_key_values(request, &helper->query_string, &helper->query_params, + url_decode, '&'); } static void parse_post_data(struct lwan_request *request) @@ -435,7 +440,7 @@ static void parse_post_data(struct lwan_request *request) sizeof(content_type) - 1))) return; - parse_key_values(request, &helper->post_data, &request->post_params, + parse_key_values(request, &helper->post_data, &helper->post_params, url_decode, '&'); } @@ -1620,7 +1625,7 @@ lwan_request_get_cookies(struct lwan_request *request) request->flags |= REQUEST_PARSED_COOKIES; } - return &request->cookies; + return &request->helper->cookies; } ALWAYS_INLINE const struct lwan_key_value_array * @@ -1631,7 +1636,7 @@ lwan_request_get_query_params(struct lwan_request *request) request->flags |= REQUEST_PARSED_QUERY_STRING; } - return &request->query_params; + return &request->helper->query_params; } ALWAYS_INLINE const struct lwan_key_value_array * @@ -1642,7 +1647,7 @@ lwan_request_get_post_params(struct lwan_request *request) request->flags |= REQUEST_PARSED_POST_DATA; } - return &request->post_params; + return &request->helper->post_params; } #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 5e9e3d3c5..6cc524584 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -344,7 +344,6 @@ struct lwan_request { struct timeout timeout; struct lwan_request_parser_helper *helper; - struct lwan_key_value_array cookies, query_params, post_params; struct lwan_response response; }; From 0957a41546ec9b92cb63814a19c81a5f28c84c8c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 12 Sep 2019 07:30:32 -0700 Subject: [PATCH 1176/2505] Simplify worker thread startup message --- src/lib/lwan-thread.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 980671dc8..b91f10a4b 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -384,9 +384,8 @@ static void *thread_io_loop(void *data) struct coro_switcher switcher; struct death_queue dq; - lwan_status_debug("Starting IO loop on thread #%d", - (unsigned short)(ptrdiff_t)(t - t->lwan->thread.threads) + - 1); + lwan_status_debug("Worker thread #%zd starting", + t - t->lwan->thread.threads + 1); lwan_set_thread_name("worker"); events = calloc((size_t)max_events, sizeof(*events)); From 330c03c66c3b8d93d4147fa1ef91626a7149805e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 12 Sep 2019 08:22:02 -0700 Subject: [PATCH 1177/2505] Use sizeof(val) to strlen(small strings) in template debugging output --- src/lib/lwan-template.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index 8de369121..d4e6e459f 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -1207,7 +1207,7 @@ static void dump_program(const struct lwan_tpl *tpl) break; case ACTION_APPEND_SMALL: { uintptr_t val = (uintptr_t)iter->data; - size_t len = strnlen((char *)&val, 8); + size_t len = strnlen((char *)&val, sizeof(val)); printf("%s (%zu) [%.*s]", instr("APPEND_SMALL", instr_buf), len, (int)len, (char *)&val); break; From 755d9ac2cacbd795fb57fbf06898f069504e2e15 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 19 Sep 2019 19:38:49 -0700 Subject: [PATCH 1178/2505] Use O_DIRECT flag in the pipe for lwan_readahead on Linux Simplifies slightly the readahead thread prio loop if supported --- src/lib/lwan-readahead.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-readahead.c b/src/lib/lwan-readahead.c index 5d983ed21..fda61696c 100644 --- a/src/lib/lwan-readahead.c +++ b/src/lib/lwan-readahead.c @@ -28,6 +28,12 @@ #include "lwan-private.h" +#if defined(__linux__) && defined(O_DIRECT) && O_DIRECT +#define PIPE_DIRECT_FLAG O_DIRECT +#else +#define PIPE_DIRECT_FLAG 0 +#endif + enum readahead_cmd { READAHEAD, MADVISE, @@ -131,10 +137,12 @@ static void *lwan_readahead_loop(void *data __attribute__((unused))) lwan_status_perror("Ignoring error while reading from pipe (%d)", readahead_pipe_fd[0]); continue; +#if PIPE_DIRECT_FLAG } else if (UNLIKELY(n_bytes % (ssize_t)sizeof(cmd[0]))) { lwan_status_warning("Ignoring readahead packet read of %zd bytes", n_bytes); continue; +#endif } cmds = n_bytes / (ssize_t)sizeof(struct lwan_readahead_cmd); @@ -168,7 +176,7 @@ void lwan_readahead_init(void) lwan_status_debug("Initializing low priority readahead thread"); - if (pipe2(readahead_pipe_fd, O_CLOEXEC) < 0) + if (pipe2(readahead_pipe_fd, O_CLOEXEC | PIPE_DIRECT_FLAG) < 0) lwan_status_critical_perror("pipe2"); /* Only write side should be non-blocking. */ From b2ef5bb7a16abd9e9223e5386c386e3b80359547 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 19 Sep 2019 19:39:51 -0700 Subject: [PATCH 1179/2505] Coalesce stream-closed and read-error when reading request --- src/lib/lwan-request.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 78d4d90ff..dafa20942 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -841,13 +841,11 @@ read_from_request_socket(struct lwan_request *request, return HTTP_TOO_LARGE; n = read(request->fd, buffer->value + total_read, to_read); - /* Client has shutdown orderly, nothing else to do; kill coro */ - if (UNLIKELY(n == 0)) { - coro_yield(request->conn->coro, CONN_CORO_ABORT); - __builtin_unreachable(); - } + if (UNLIKELY(n <= 0)) { + /* Client has shutdown orderly, nothing else to do; kill coro */ + if (UNLIKELY(n == 0)) + break; - if (UNLIKELY(n < 0)) { switch (errno) { case EAGAIN: coro_yield(request->conn->coro, CONN_CORO_WANT_READ); @@ -863,8 +861,7 @@ read_from_request_socket(struct lwan_request *request, return HTTP_BAD_REQUEST; /* Unexpected error, kill coro */ - coro_yield(request->conn->coro, CONN_CORO_ABORT); - __builtin_unreachable(); + break; } total_read += (size_t)n; @@ -889,7 +886,6 @@ read_from_request_socket(struct lwan_request *request, } } - /* Shouldn't reach here. */ coro_yield(request->conn->coro, CONN_CORO_ABORT); __builtin_unreachable(); return HTTP_INTERNAL_ERROR; From c8a82c75b230d39527fec5481ba75633d925e153 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 19 Sep 2019 19:40:36 -0700 Subject: [PATCH 1180/2505] Only try defining TCP_FASTOPEN on Linux --- src/lib/lwan-socket.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-socket.c b/src/lib/lwan-socket.c index bd2973304..f9a97e938 100644 --- a/src/lib/lwan-socket.c +++ b/src/lib/lwan-socket.c @@ -232,10 +232,6 @@ static int setup_socket_normally(struct lwan *l) return fd; } -#ifndef TCP_FASTOPEN -#define TCP_FASTOPEN 23 -#endif - void lwan_socket_init(struct lwan *l) { int fd, n; @@ -255,6 +251,11 @@ void lwan_socket_init(struct lwan *l) (&(struct linger){.l_onoff = 1, .l_linger = 1})); #ifdef __linux__ + +#ifndef TCP_FASTOPEN +#define TCP_FASTOPEN 23 +#endif + SET_SOCKET_OPTION_MAY_FAIL(SOL_TCP, TCP_FASTOPEN, (int[]){5}); SET_SOCKET_OPTION_MAY_FAIL(SOL_TCP, TCP_QUICKACK, (int[]){0}); #endif From 4bb64e2a7b46992249bff2afc8b582a5ce11b464 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 19 Sep 2019 21:23:25 -0700 Subject: [PATCH 1181/2505] Add OSS-Fuzz badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 046bcb113..c007338f3 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Build status | OS | Arch | Release | Debug | Static Analysis | Tests | |---------|--------|---------|-------|-----------------|------------| -| Linux | x86_64 | ![release](https://shield.lwan.ws/img/gycKbr/release "Release") | ![debug](https://shield.lwan.ws/img/gycKbr/debug "Debug") | ![static-analysis](https://shield.lwan.ws/img/gycKbr/clang-analyze "Static Analysis") ![coverity](https://scan.coverity.com/projects/375/badge.svg) [Report history](https://buildbot.lwan.ws/sa/) | ![tests](https://shield.lwan.ws/img/gycKbr/unit-tests "Test") | +| Linux | x86_64 | ![release](https://shield.lwan.ws/img/gycKbr/release "Release") | ![debug](https://shield.lwan.ws/img/gycKbr/debug "Debug") | ![static-analysis](https://shield.lwan.ws/img/gycKbr/clang-analyze "Static Analysis") ![coverity](https://scan.coverity.com/projects/375/badge.svg) [Report history](https://buildbot.lwan.ws/sa/) | ![tests](https://shield.lwan.ws/img/gycKbr/unit-tests "Test") [![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/lwan.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:lwan) | | Linux | armv7 | ![release-arm](https://shield.lwan.ws/img/gycKbr/release-arm "Release") | ![debug-arm](https://shield.lwan.ws/img/gycKbr/debug-arm "Debug") | | | | FreeBSD | x86_64 | ![freebsd-release](https://shield.lwan.ws/img/gycKbr/release-freebsd "Release FreeBSD") | ![freebsd-debug](https://shield.lwan.ws/img/gycKbr/debug-freebsd "Debug FreeBSD") | | | | macOS | x86_64 | ![osx-release](https://shield.lwan.ws/img/gycKbr/release-sierra "Release macOS") | ![osx-debug](https://shield.lwan.ws/img/gycKbr/debug-sierra "Debug macOS") | | | From e20c282f322cc7ed06985b18c5f53d739134cea5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 21 Sep 2019 09:45:40 -0700 Subject: [PATCH 1182/2505] Correctly calculate partial buffer size for POST bodies --- src/lib/lwan-request.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index dafa20942..73d8f880d 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1177,8 +1177,10 @@ static enum lwan_http_status read_post_data(struct lwan_request *request) helper->post_data.value = new_buffer; helper->post_data.len = post_data_size; - if (have) + if (have) { new_buffer = mempcpy(new_buffer, helper->next_request, have); + post_data_size -= have; + } helper->next_request = NULL; helper->error_when_time = time(NULL) + config->keep_alive_timeout; From 31e3cc992ee7ff0327cd58d041a1acf2b0748174 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 21 Sep 2019 09:48:47 -0700 Subject: [PATCH 1183/2505] Remove a few branches from trie lookup paths Trie lookups are always by prefix, never exact, so remove branches that would check for exact matches. --- src/lib/lwan-trie.c | 40 ++++++++++++++++------------------------ src/lib/lwan-trie.h | 13 +++++-------- 2 files changed, 21 insertions(+), 32 deletions(-) diff --git a/src/lib/lwan-trie.c b/src/lib/lwan-trie.c index 404fa7aa4..fac501242 100644 --- a/src/lib/lwan-trie.c +++ b/src/lib/lwan-trie.c @@ -102,7 +102,7 @@ lwan_trie_add(struct lwan_trie *trie, const char *key, void *data) #undef GET_NODE static ALWAYS_INLINE struct lwan_trie_node * -lookup_node(struct lwan_trie_node *root, const char *key, bool prefix, size_t *prefix_len) +lookup_node(struct lwan_trie_node *root, const char *key, size_t *prefix_len) { struct lwan_trie_node *node, *previous_node = NULL; const char *orig_key = key; @@ -113,38 +113,30 @@ lookup_node(struct lwan_trie_node *root, const char *key, bool prefix, size_t *p } *prefix_len = (size_t)(key - orig_key); + if (node && node->leaf) return node; - if (prefix && previous_node) - return previous_node; - return NULL; -} + return previous_node; +} -ALWAYS_INLINE void * -lwan_trie_lookup_full(struct lwan_trie *trie, const char *key, bool prefix) +ALWAYS_INLINE void *lwan_trie_lookup_prefix(struct lwan_trie *trie, + const char *key) { - if (UNLIKELY(!trie)) - return NULL; + assert(trie); + assert(key); size_t prefix_len; - struct lwan_trie_node *node = lookup_node(trie->root, key, prefix, &prefix_len); - if (!node) - return NULL; - struct lwan_trie_leaf *leaf = find_leaf_with_key(node, key, prefix_len); - return leaf ? leaf->data : NULL; -} + struct lwan_trie_node *node = lookup_node(trie->root, key, &prefix_len); -ALWAYS_INLINE void * -lwan_trie_lookup_prefix(struct lwan_trie *trie, const char *key) -{ - return lwan_trie_lookup_full(trie, key, true); -} + if (node) { + struct lwan_trie_leaf *leaf = find_leaf_with_key(node, key, prefix_len); -ALWAYS_INLINE void * -lwan_trie_lookup_exact(struct lwan_trie *trie, const char *key) -{ - return lwan_trie_lookup_full(trie, key, false); + if (leaf) + return leaf->data; + } + + return NULL; } ALWAYS_INLINE int32_t diff --git a/src/lib/lwan-trie.h b/src/lib/lwan-trie.h index 0230cdcfa..b88165316 100644 --- a/src/lib/lwan-trie.h +++ b/src/lib/lwan-trie.h @@ -39,11 +39,8 @@ struct lwan_trie { void (*free_node)(void *data); }; -bool lwan_trie_init(struct lwan_trie *trie, void (*free_node)(void *data)); -void lwan_trie_destroy(struct lwan_trie *trie); -void lwan_trie_add(struct lwan_trie *trie, const char *key, void *data); -void *lwan_trie_lookup_full(struct lwan_trie *trie, const char *key, bool prefix); -void *lwan_trie_lookup_prefix(struct lwan_trie *trie, const char *key); -void *lwan_trie_lookup_exact(struct lwan_trie *trie, const char *key); -int32_t lwan_trie_entry_count(struct lwan_trie *trie); - +bool lwan_trie_init(struct lwan_trie *trie, void (*free_node)(void *data)); +void lwan_trie_destroy(struct lwan_trie *trie); +void lwan_trie_add(struct lwan_trie *trie, const char *key, void *data); +void *lwan_trie_lookup_prefix(struct lwan_trie *trie, const char *key); +int32_t lwan_trie_entry_count(struct lwan_trie *trie); From 163c9166f9c3cdf731045e6c381b1b712ef2e4bd Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 21 Sep 2019 09:49:51 -0700 Subject: [PATCH 1184/2505] Reindent lwan-trie.[ch] --- src/lib/lwan-trie.c | 42 ++++++++++++++++++++---------------------- src/lib/lwan-trie.h | 3 ++- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/lib/lwan-trie.c b/src/lib/lwan-trie.c index fac501242..0c2ca6378 100644 --- a/src/lib/lwan-trie.c +++ b/src/lib/lwan-trie.c @@ -14,7 +14,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include @@ -22,8 +23,7 @@ #include "lwan.h" -bool -lwan_trie_init(struct lwan_trie *trie, void (*free_node)(void *data)) +bool lwan_trie_init(struct lwan_trie *trie, void (*free_node)(void *data)) { if (!trie) return false; @@ -51,18 +51,17 @@ find_leaf_with_key(struct lwan_trie_node *node, const char *key, size_t len) return NULL; } -#define GET_NODE() \ - do { \ - if (!(node = *knode)) { \ - *knode = node = calloc(1, sizeof(*node)); \ - if (!node) \ - goto oom; \ - } \ - ++node->ref_count; \ - } while(0) - -void -lwan_trie_add(struct lwan_trie *trie, const char *key, void *data) +#define GET_NODE() \ + do { \ + if (!(node = *knode)) { \ + *knode = node = calloc(1, sizeof(*node)); \ + if (!node) \ + goto oom; \ + } \ + ++node->ref_count; \ + } while (0) + +void lwan_trie_add(struct lwan_trie *trie, const char *key, void *data) { if (UNLIKELY(!trie || !key || !data)) return; @@ -77,7 +76,8 @@ lwan_trie_add(struct lwan_trie *trie, const char *key, void *data) /* Get the leaf node (allocate it if necessary) */ GET_NODE(); - struct lwan_trie_leaf *leaf = find_leaf_with_key(node, orig_key, (size_t)(key - orig_key)); + struct lwan_trie_leaf *leaf = + find_leaf_with_key(node, orig_key, (size_t)(key - orig_key)); bool had_key = leaf; if (!leaf) { leaf = malloc(sizeof(*leaf)); @@ -139,14 +139,13 @@ ALWAYS_INLINE void *lwan_trie_lookup_prefix(struct lwan_trie *trie, return NULL; } -ALWAYS_INLINE int32_t -lwan_trie_entry_count(struct lwan_trie *trie) +ALWAYS_INLINE int32_t lwan_trie_entry_count(struct lwan_trie *trie) { return (trie && trie->root) ? trie->root->ref_count : 0; } -static void -lwan_trie_node_destroy(struct lwan_trie *trie, struct lwan_trie_node *node) +static void lwan_trie_node_destroy(struct lwan_trie *trie, + struct lwan_trie_node *node) { if (!node) return; @@ -174,8 +173,7 @@ lwan_trie_node_destroy(struct lwan_trie *trie, struct lwan_trie_node *node) free(node); } -void -lwan_trie_destroy(struct lwan_trie *trie) +void lwan_trie_destroy(struct lwan_trie *trie) { if (!trie || !trie->root) return; diff --git a/src/lib/lwan-trie.h b/src/lib/lwan-trie.h index b88165316..c50f3770a 100644 --- a/src/lib/lwan-trie.h +++ b/src/lib/lwan-trie.h @@ -14,7 +14,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #pragma once From 186bff432283eb8f8fb27bf812c97acd03a04518 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 21 Sep 2019 09:50:02 -0700 Subject: [PATCH 1185/2505] Revisit definition of CLOCK_MONOTONIC_COARSE on FreeBSD and macOS --- src/lib/missing/time.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lib/missing/time.h b/src/lib/missing/time.h index e6e38d65a..31ce75b80 100644 --- a/src/lib/missing/time.h +++ b/src/lib/missing/time.h @@ -27,18 +27,18 @@ typedef int clockid_t; int clock_gettime(clockid_t clk_id, struct timespec *ts); -#ifndef CLOCK_MONOTONIC_COARSE -#define CLOCK_MONOTONIC_COARSE 0 -#endif - -#ifndef CLOCK_MONOTONIC -#define CLOCK_MONOTONIC 1 -#endif - #elif !defined(CLOCK_MONOTONIC_COARSE) && defined(CLOCK_MONOTONIC_FAST) #define CLOCK_MONOTONIC_COARSE CLOCK_MONOTONIC_FAST /* FreeBSD */ #elif !defined(CLOCK_MONOTONIC_COARSE) && defined(CLOCK_MONOTONIC_RAW_APPROX) #define CLOCK_MONOTONIC_COARSE CLOCK_MONOTONIC_RAW_APPROX /* macOS */ +#elif !defined(CLOCK_MONOTONIC_COARSE) && defined(CLOCK_MONOTONIC) +#define CLOCK_MONOTONIC_COARSE CLOCK_MONOTONIC +#elif !defined(CLOCK_MONOTONIC_COARSE) +#define CLOCK_MONOTONIC_COARSE 0 +#endif + +#if !defined(CLOCK_MONOTONIC) +#define CLOCK_MONOTONIC 1 #endif #endif /* MISSING_TIME_H */ From 3ea60d75e6797d92a6488034177500cdfc1dad11 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 21 Sep 2019 23:52:09 -0700 Subject: [PATCH 1186/2505] Remove branch from error condition in read_from_request_socket() --- src/lib/lwan-request.c | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 73d8f880d..3a9625851 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -842,25 +842,24 @@ read_from_request_socket(struct lwan_request *request, n = read(request->fd, buffer->value + total_read, to_read); if (UNLIKELY(n <= 0)) { - /* Client has shutdown orderly, nothing else to do; kill coro */ - if (UNLIKELY(n == 0)) - break; - - switch (errno) { - case EAGAIN: - coro_yield(request->conn->coro, CONN_CORO_WANT_READ); - continue; - case EINTR: + if (n < 0) { + switch (errno) { + case EAGAIN: + coro_yield(request->conn->coro, CONN_CORO_WANT_READ); + continue; + case EINTR: yield_and_read_again: - coro_yield(request->conn->coro, CONN_CORO_YIELD); - continue; - } + coro_yield(request->conn->coro, CONN_CORO_YIELD); + continue; + } - /* Unexpected error before reading anything */ - if (UNLIKELY(!total_read)) - return HTTP_BAD_REQUEST; + /* Unexpected error before reading anything */ + if (UNLIKELY(!total_read)) + return HTTP_BAD_REQUEST; + } - /* Unexpected error, kill coro */ + /* Client shut down orderly (n = 0), or unrecoverable error (n < 0); + * shut down coro. */ break; } From d4ace82fcbb2a5cd5c19236c390f0518ad502174 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 6 Oct 2019 13:38:06 -0700 Subject: [PATCH 1187/2505] Clarify alternate malloc section --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c007338f3..7606fbdcc 100644 --- a/README.md +++ b/README.md @@ -36,10 +36,10 @@ The build system will look for these libraries and enable/link if available. - [Valgrind](http://valgrind.org) - [Brotli](https://github.com/google/brotli) - Alternative memory allocators can be used by passing `-DUSE_ALTERNATIVE_MALLOC` to CMake with the following values: - - ["tcmalloc" ](https://github.com/gperftools/gperftools) - - ["jemalloc"](http://jemalloc.net/) - ["mimalloc"](https://github.com/microsoft/mimalloc) - - "auto": Autodetect between these tcmalloc, jemalloc, and mimalloc + - ["jemalloc"](http://jemalloc.net/) + - ["tcmalloc"](https://github.com/gperftools/gperftools) + - "auto": Autodetect from the list above, falling back to libc malloc if none found - To run test suite: - [Python](https://www.python.org/) (2.6+) with Requests - [Lua 5.1](http://www.lua.org) From 0025d4f0e17dbb35a0a5ac896134cd85162e9fd3 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 6 Oct 2019 13:38:44 -0700 Subject: [PATCH 1188/2505] Simplify set_headers() metamethod implementation --- src/lib/lwan-lua.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index ae939e3b5..9799e09b2 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -204,9 +204,7 @@ LWAN_LUA_METHOD(set_headers) if (!append_key_value(L, coro, headers, key, value_index)) goto out; } else if (lua_istable(L, value_index)) { - lua_pushnil(L); - - for (; lua_next(L, value_index) != 0; lua_pop(L, 1)) { + for (lua_pushnil(L); lua_next(L, value_index) != 0; lua_pop(L, 1)) { if (!lua_isstring(L, nested_value_index)) continue; if (!append_key_value(L, coro, headers, key, From ef569814941c162a1c1078ec7d098866746649b1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 6 Oct 2019 13:39:40 -0700 Subject: [PATCH 1189/2505] Declare variables in previous scope in write_websocket_frame() --- src/lib/lwan-response.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 468c5bb6a..0d92dc16b 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -525,7 +525,7 @@ static void write_websocket_frame(struct lwan_request *request, size_t len) { struct iovec vec[4]; - uint8_t net_len_byte; + uint8_t net_len_byte, opcode_byte; uint16_t net_len_short; uint64_t net_len_long; size_t last = 0; @@ -538,13 +538,15 @@ static void write_websocket_frame(struct lwan_request *request, vec[last++] = (struct iovec){.iov_base = &net_len_byte, .iov_len = 1}; } else if (len <= 65535) { net_len_short = htons((uint16_t)len); + opcode_byte = 0x7e; - vec[last++] = (struct iovec){.iov_base = (char[]){0x7e}, .iov_len = 1}; + vec[last++] = (struct iovec){.iov_base = &opcode_byte, .iov_len = 1}; vec[last++] = (struct iovec){.iov_base = &net_len_short, .iov_len = 2}; } else { net_len_long = htobe64((uint64_t)len); + opcode_byte = 0x7f; - vec[last++] = (struct iovec){.iov_base = (char[]){0x7f}, .iov_len = 1}; + vec[last++] = (struct iovec){.iov_base = &opcode_byte, .iov_len = 1}; vec[last++] = (struct iovec){.iov_base = &net_len_long, .iov_len = 8}; } From bc5e5203845124d10f9d6993663682f604e5c4c8 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 6 Oct 2019 13:40:46 -0700 Subject: [PATCH 1190/2505] Cleanup fuzz_parse_http_request() by using a NO_DISCARD macro --- src/lib/lwan-request.c | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 3a9625851..3e86f45ab 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1703,23 +1703,21 @@ __attribute__((used)) int fuzz_parse_http_request(const uint8_t *data, /* Requesting these items will force them to be parsed, and also exercise * the lookup function. */ - trash0 = lwan_request_get_header(&request, "Non-Existing-Header"); - __asm__ __volatile__("" :: "g"(trash0) : "memory"); - trash0 = lwan_request_get_header(&request, "Host"); /* Usually existing short header */ - __asm__ __volatile__("" :: "g"(trash0) : "memory"); - - trash0 = lwan_request_get_cookie(&request, "Non-Existing-Cookie"); - __asm__ __volatile__("" :: "g"(trash0) : "memory"); - - trash0 = lwan_request_get_cookie(&request, "FOO"); /* Set by some tests */ - __asm__ __volatile__("" :: "g"(trash0) : "memory"); +#define NO_DISCARD(...) \ + do { \ + const char *no_discard_ = __VA_ARGS__; \ + __asm__ __volatile__("" ::"g"(no_discard_) : "memory"); \ + } while (0) - trash0 = lwan_request_get_query_param(&request, "Non-Existing-Query-Param"); - __asm__ __volatile__("" :: "g"(trash0) : "memory"); + NO_DISCARD(lwan_request_get_header(&request, "Non-Existing-Header")); + NO_DISCARD(lwan_request_get_header(&request, "Host")); /* Usually existing short header */ + NO_DISCARD(lwan_request_get_cookie(&request, "Non-Existing-Cookie")); + NO_DISCARD(lwan_request_get_cookie(&request, "FOO")); /* Set by some tests */ + NO_DISCARD(lwan_request_get_query_param(&request, "Non-Existing-Query-Param")); + NO_DISCARD(lwan_request_get_post_param(&request, "Non-Existing-Post-Param")); - trash0 = lwan_request_get_post_param(&request, "Non-Existing-Post-Param"); - __asm__ __volatile__("" :: "g"(trash0) : "memory"); +#undef NO_DISCARD lwan_request_get_range(&request, &trash1, &trash1); lwan_request_get_if_modified_since(&request, &trash2); From 870a8aa013544b944db15abf484690b5fb0b88be Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 9 Oct 2019 22:03:06 -0700 Subject: [PATCH 1191/2505] Only provide gettid() implementation if not available in libc Newer glibc provides a wrapper for this syscall now. --- CMakeLists.txt | 1 + src/cmake/lwan-build-config.h.cmake | 1 + src/lib/missing.c | 6 +++++- src/lib/missing/sys/types.h | 4 +++- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c2e8f720..f303e6433 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -135,6 +135,7 @@ check_function_exists(eventfd HAVE_EVENTFD) check_function_exists(posix_fadvise HAVE_POSIX_FADVISE) check_function_exists(getentropy HAVE_GETENTROPY) check_function_exists(fwrite_unlocked HAVE_FWRITE_UNLOCKED) +check_function_exists(gettid HAVE_GETTID) # This is available on -ldl in glibc, but some systems (such as OpenBSD) # will bundle these in the C library. This isn't required for glibc anyway, diff --git a/src/cmake/lwan-build-config.h.cmake b/src/cmake/lwan-build-config.h.cmake index dc70f1f83..7716f9189 100644 --- a/src/cmake/lwan-build-config.h.cmake +++ b/src/cmake/lwan-build-config.h.cmake @@ -42,6 +42,7 @@ #cmakedefine HAVE_PTHREAD_SET_NAME_NP #cmakedefine HAVE_GETENTROPY #cmakedefine HAVE_FWRITE_UNLOCKED +#cmakedefine HAVE_GETTID /* Compiler builtins for specific CPU instruction support */ #cmakedefine HAVE_BUILTIN_CLZLL diff --git a/src/lib/missing.c b/src/lib/missing.c index 48e783d90..67069337b 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -351,9 +351,13 @@ int proc_pidpath(pid_t pid, void *buffer, size_t buffersize) #endif #if defined(__linux__) + +#if !defined(HAVE_GETTID) #include -long gettid(void) { return syscall(SYS_gettid); } +pid_t gettid(void) { return (pid_t)syscall(SYS_gettid); } +#endif + #elif defined(__FreeBSD__) #include diff --git a/src/lib/missing/sys/types.h b/src/lib/missing/sys/types.h index cfdad62c0..c2a22db29 100644 --- a/src/lib/missing/sys/types.h +++ b/src/lib/missing/sys/types.h @@ -23,6 +23,8 @@ #ifndef MISSING_SYS_TYPES_H #define MISSING_SYS_TYPES_H -long gettid(void); +#ifndef HAVE_GETTID +pid_t gettid(void); +#endif #endif From c96d7c2e67e30dc36467bcec4aabc7b7d73896ee Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 9 Oct 2019 22:03:50 -0700 Subject: [PATCH 1192/2505] Hash table rehashing function can be void --- src/lib/hash.c | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/lib/hash.c b/src/lib/hash.c index ac96c9b91..5f0d04880 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -307,7 +307,7 @@ static struct hash_entry *hash_add_entry_hashed(struct hash *hash, const void *k return entry; } -static int rehash(struct hash *hash, unsigned int new_bucket_size) +static void rehash(struct hash *hash, unsigned int new_bucket_size) { struct hash_bucket *buckets = calloc(new_bucket_size, sizeof(*buckets)); const struct hash_bucket *bucket_end = hash->buckets + hash_n_buckets(hash); @@ -318,7 +318,7 @@ static int rehash(struct hash *hash, unsigned int new_bucket_size) assert(hash_n_buckets(hash) != new_bucket_size); if (buckets == NULL) - return -errno; + return; hash_copy.count = 0; hash_copy.n_buckets_mask = new_bucket_size - 1; @@ -352,7 +352,7 @@ static int rehash(struct hash *hash, unsigned int new_bucket_size) assert(hash_copy.count == hash->count); - return 0; + return; fail: for (bucket_end = bucket, bucket = hash->buckets; bucket < bucket_end; @@ -360,7 +360,6 @@ static int rehash(struct hash *hash, unsigned int new_bucket_size) free(bucket->entries); free(buckets); - return -ENOMEM; } static struct hash_entry *hash_add_entry(struct hash *hash, const void *key) @@ -389,10 +388,8 @@ int hash_add(struct hash *hash, const void *key, const void *value) entry->key = key; entry->value = value; - if (hash->count > hash->n_buckets_mask) { - /* Not being able to resize the bucket array is not an error */ + if (hash->count > hash->n_buckets_mask) rehash(hash, hash_n_buckets(hash) * 2); - } return 0; } @@ -411,10 +408,8 @@ int hash_add_unique(struct hash *hash, const void *key, const void *value) entry->key = key; entry->value = value; - if (hash->count > hash->n_buckets_mask) { - /* Not being able to resize the bucket array is not an error */ + if (hash->count > hash->n_buckets_mask) rehash(hash, hash_n_buckets(hash) * 2); - } return 0; } @@ -478,7 +473,6 @@ int hash_del(struct hash *hash, const void *key) hash->count--; if (hash->n_buckets_mask > (MIN_BUCKETS - 1) && hash->count < hash->n_buckets_mask / 2) { - /* Not being able to trim the bucket array size isn't an error. */ rehash(hash, hash_n_buckets(hash) / 2); } else { unsigned int steps_used = bucket->used / STEPS; From 294d6b21f47813713ecf9711a0f8199d01ef78f0 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 9 Oct 2019 22:04:20 -0700 Subject: [PATCH 1193/2505] Clarify some comments in the cache implementation --- src/lib/lwan-cache.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/lib/lwan-cache.c b/src/lib/lwan-cache.c index a7a6e62fd..d61e12cc5 100644 --- a/src/lib/lwan-cache.c +++ b/src/lib/lwan-cache.c @@ -159,8 +159,6 @@ struct cache_entry *cache_get_and_ref_entry(struct cache *cache, *error = EWOULDBLOCK; return NULL; } - /* Find the item in the hash table. If it's there, increment the reference - * and return it. */ entry = hash_find(cache->hash.table, key); if (LIKELY(entry)) { ATOMIC_INC(entry->refs); @@ -171,7 +169,7 @@ struct cache_entry *cache_get_and_ref_entry(struct cache *cache, return entry; } - /* Unlock the cache so the item can be created. */ + /* No need to keep the hash table lock locked while the item is being created. */ pthread_rwlock_unlock(&cache->hash.lock); #ifndef NDEBUG @@ -193,11 +191,11 @@ struct cache_entry *cache_get_and_ref_entry(struct cache *cache, *entry = (struct cache_entry) { .key = key_copy, .refs = 1 }; if (pthread_rwlock_trywrlock(&cache->hash.lock) == EBUSY) { - /* Couldn't obtain hash lock: instead of waiting, just return - * the recently-created item as a temporary item. Might result - * in starvation, though, so this might be changed back to - * pthread_rwlock_wrlock() again someday if this proves to be - * a problem. */ + /* Couldn't obtain hash write lock: instead of waiting, just return + * the recently-created item as a temporary item. Might result in + * items not being added to the cache, though, so this might be + * changed back to pthread_rwlock_wrlock() again someday if this + * proves to be a problem. */ entry->flags = TEMPORARY; return entry; } From efb4376c222d53093f62508e5745a8f083d31b49 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 9 Oct 2019 22:04:34 -0700 Subject: [PATCH 1194/2505] Plug memory leak in cache while pruning entries Keys were not freed in some cases. --- src/lib/lwan-cache.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-cache.c b/src/lib/lwan-cache.c index d61e12cc5..4fb25f4e8 100644 --- a/src/lib/lwan-cache.c +++ b/src/lib/lwan-cache.c @@ -237,10 +237,8 @@ void cache_entry_unref(struct cache *cache, struct cache_entry *entry) { assert(entry); - if (entry->flags & TEMPORARY) { - free(entry->key); + if (entry->flags & TEMPORARY) goto destroy_entry; - } if (ATOMIC_DEC(entry->refs)) return; @@ -253,6 +251,7 @@ void cache_entry_unref(struct cache *cache, struct cache_entry *entry) * while there are cache items floating around, this will dereference * deallocated memory. */ cache->cb.destroy_entry(entry, cache->cb.context); + free(entry->key); } } From 4a8fbc617aaa04a4d55efe201e6af12c81fb2fbc Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 9 Oct 2019 22:06:55 -0700 Subject: [PATCH 1195/2505] Provide wrapper around posix_memalign() Use it to allocate memory aligned to cache line boundaries whenever it would be useful. --- src/lib/lwan-coro.c | 5 +++-- src/lib/lwan-private.h | 15 +++++++++++++++ src/lib/lwan-trie.c | 5 +++-- src/lib/lwan.c | 10 +++------- src/lib/timeout.c | 4 +++- 5 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 5278d6869..9e432121c 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -80,7 +80,7 @@ struct coro { unsigned int vg_stack_id; #endif - unsigned char stack[]; + unsigned char stack[] __attribute__((aligned(64))); }; #if defined(__APPLE__) @@ -268,7 +268,8 @@ void coro_reset(struct coro *coro, coro_function_t func, void *data) ALWAYS_INLINE struct coro * coro_new(struct coro_switcher *switcher, coro_function_t function, void *data) { - struct coro *coro = malloc(sizeof(*coro) + CORO_STACK_MIN); + struct coro *coro = lwan_aligned_alloc(sizeof(struct coro) + CORO_STACK_MIN, 64); + if (UNLIKELY(!coro)) return NULL; diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 2d2642f9e..462f43b74 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -19,6 +19,7 @@ #pragma once +#include #include #include "lwan.h" @@ -119,3 +120,17 @@ const char *lwan_lua_state_last_error(lua_State *L); #endif extern clockid_t monotonic_clock_id; + +static inline void * +lwan_aligned_alloc(size_t n, size_t alignment) +{ + void *ret; + + assert((alignment & (alignment - 1)) == 0); + + n = (n + alignment - 1) & ~(alignment - 1); + if (UNLIKELY(posix_memalign(&ret, alignment, n))) + return NULL; + + return ret; +} diff --git a/src/lib/lwan-trie.c b/src/lib/lwan-trie.c index 0c2ca6378..e46f5a974 100644 --- a/src/lib/lwan-trie.c +++ b/src/lib/lwan-trie.c @@ -54,9 +54,10 @@ find_leaf_with_key(struct lwan_trie_node *node, const char *key, size_t len) #define GET_NODE() \ do { \ if (!(node = *knode)) { \ - *knode = node = calloc(1, sizeof(*node)); \ + *knode = node = lwan_aligned_alloc(sizeof(*node), 64); \ if (!node) \ goto oom; \ + memset(node, 0, sizeof(*node)); \ } \ ++node->ref_count; \ } while (0) @@ -80,7 +81,7 @@ void lwan_trie_add(struct lwan_trie *trie, const char *key, void *data) find_leaf_with_key(node, orig_key, (size_t)(key - orig_key)); bool had_key = leaf; if (!leaf) { - leaf = malloc(sizeof(*leaf)); + leaf = lwan_aligned_alloc(sizeof(*leaf), 64); if (!leaf) lwan_status_critical_perror("malloc"); } else if (trie->free_node) { diff --git a/src/lib/lwan.c b/src/lib/lwan.c index a01ff7287..19f7c9a1b 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -487,17 +487,13 @@ static rlim_t setup_open_file_count_limits(void) return r.rlim_cur; } -static inline size_t align_to_size(size_t value, size_t alignment) -{ - return (value + alignment - 1) & ~(alignment - 1); -} - static void allocate_connections(struct lwan *l, size_t max_open_files) { const size_t sz = max_open_files * sizeof(struct lwan_connection); - if (posix_memalign((void **)&l->conns, 64, align_to_size(sz, 64))) - lwan_status_critical_perror("aligned_alloc"); + l->conns = lwan_aligned_alloc(sz, 64); + if (UNLIKELY(!l->conns)) + lwan_status_critical_perror("lwan_alloc_aligned"); memset(l->conns, 0, sz); } diff --git a/src/lib/timeout.c b/src/lib/timeout.c index 62f1e9738..121ef6b76 100644 --- a/src/lib/timeout.c +++ b/src/lib/timeout.c @@ -37,6 +37,8 @@ #include /* errno */ +#include "lwan-private.h" + #include "list.h" #include "timeout.h" @@ -182,7 +184,7 @@ struct timeouts *timeouts_open(timeout_error_t *error) { struct timeouts *T; - if ((T = malloc(sizeof *T))) + if ((T = lwan_aligned_alloc(sizeof *T, 64))) return timeouts_init(T); *error = errno; From 547f752796b966c2c8accd46d65655da9a6cc9c8 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 9 Oct 2019 22:07:55 -0700 Subject: [PATCH 1196/2505] Hide lwan_trie structs in .c file There's no need to expose them to the header file. --- src/lib/lwan-trie.c | 14 +++++++++++++- src/lib/lwan-trie.h | 12 +----------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/lib/lwan-trie.c b/src/lib/lwan-trie.c index e46f5a974..27a8a0d90 100644 --- a/src/lib/lwan-trie.c +++ b/src/lib/lwan-trie.c @@ -21,7 +21,19 @@ #include #include -#include "lwan.h" +#include "lwan-private.h" + +struct lwan_trie_node { + struct lwan_trie_node *next[8]; + struct lwan_trie_leaf *leaf; + int ref_count; +}; + +struct lwan_trie_leaf { + char *key; + void *data; + struct lwan_trie_leaf *next; +}; bool lwan_trie_init(struct lwan_trie *trie, void (*free_node)(void *data)) { diff --git a/src/lib/lwan-trie.h b/src/lib/lwan-trie.h index c50f3770a..20a04b4d8 100644 --- a/src/lib/lwan-trie.h +++ b/src/lib/lwan-trie.h @@ -23,17 +23,7 @@ #include #include -struct lwan_trie_node { - struct lwan_trie_node *next[8]; - struct lwan_trie_leaf *leaf; - int ref_count; -}; - -struct lwan_trie_leaf { - char *key; - void *data; - struct lwan_trie_leaf *next; -}; +struct lwan_trie_node; struct lwan_trie { struct lwan_trie_node *root; From 5b86898b2b54309aeaa672a10c1631c61ff7a194 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 9 Oct 2019 22:08:31 -0700 Subject: [PATCH 1197/2505] Remove unused lwan_trie_entry_count() --- src/lib/lwan-trie.c | 5 ----- src/lib/lwan-trie.h | 3 ++- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan-trie.c b/src/lib/lwan-trie.c index 27a8a0d90..360bc1e78 100644 --- a/src/lib/lwan-trie.c +++ b/src/lib/lwan-trie.c @@ -152,11 +152,6 @@ ALWAYS_INLINE void *lwan_trie_lookup_prefix(struct lwan_trie *trie, return NULL; } -ALWAYS_INLINE int32_t lwan_trie_entry_count(struct lwan_trie *trie) -{ - return (trie && trie->root) ? trie->root->ref_count : 0; -} - static void lwan_trie_node_destroy(struct lwan_trie *trie, struct lwan_trie_node *node) { diff --git a/src/lib/lwan-trie.h b/src/lib/lwan-trie.h index 20a04b4d8..76d3737a4 100644 --- a/src/lib/lwan-trie.h +++ b/src/lib/lwan-trie.h @@ -32,6 +32,7 @@ struct lwan_trie { bool lwan_trie_init(struct lwan_trie *trie, void (*free_node)(void *data)); void lwan_trie_destroy(struct lwan_trie *trie); + void lwan_trie_add(struct lwan_trie *trie, const char *key, void *data); + void *lwan_trie_lookup_prefix(struct lwan_trie *trie, const char *key); -int32_t lwan_trie_entry_count(struct lwan_trie *trie); From 5a3f0a2abacce77fec85bc06c387303fcfe9f8ae Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 9 Oct 2019 22:08:53 -0700 Subject: [PATCH 1198/2505] Correct behavior of reallocarray() when total_size is 0 Free the pointer but not return NULL: NULL is reserved for error cases. Free the original pointer instead and return a pointer suitable to be passed to free(). --- src/lib/missing.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/missing.c b/src/lib/missing.c index 67069337b..833c17a9d 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -515,6 +515,10 @@ void *reallocarray(void *optr, size_t nmemb, size_t size) errno = ENOMEM; return NULL; } + if (UNLIKELY(total_size == 0)) { + free(optr); + return malloc(1); + } return realloc(optr, total_size); } #endif /* HAVE_REALLOCARRAY */ From 577fe529b7657f25a028a6932b6ee514b88a4737 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 9 Oct 2019 22:12:42 -0700 Subject: [PATCH 1199/2505] Do not define CLOCK_MONOTONIC_COARSE unless similar id is available --- src/lib/missing/time.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/lib/missing/time.h b/src/lib/missing/time.h index 31ce75b80..a6118cbd9 100644 --- a/src/lib/missing/time.h +++ b/src/lib/missing/time.h @@ -26,19 +26,18 @@ #ifndef HAVE_CLOCK_GETTIME typedef int clockid_t; int clock_gettime(clockid_t clk_id, struct timespec *ts); +#endif -#elif !defined(CLOCK_MONOTONIC_COARSE) && defined(CLOCK_MONOTONIC_FAST) +#if !defined(CLOCK_MONOTONIC_COARSE) && defined(CLOCK_MONOTONIC_FAST) #define CLOCK_MONOTONIC_COARSE CLOCK_MONOTONIC_FAST /* FreeBSD */ #elif !defined(CLOCK_MONOTONIC_COARSE) && defined(CLOCK_MONOTONIC_RAW_APPROX) #define CLOCK_MONOTONIC_COARSE CLOCK_MONOTONIC_RAW_APPROX /* macOS */ #elif !defined(CLOCK_MONOTONIC_COARSE) && defined(CLOCK_MONOTONIC) #define CLOCK_MONOTONIC_COARSE CLOCK_MONOTONIC -#elif !defined(CLOCK_MONOTONIC_COARSE) -#define CLOCK_MONOTONIC_COARSE 0 #endif #if !defined(CLOCK_MONOTONIC) -#define CLOCK_MONOTONIC 1 +#define CLOCK_MONOTONIC 0xBebaCafe #endif #endif /* MISSING_TIME_H */ From 6fafde7024a3e204e9bda6ffb062af63bab81cf6 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 9 Oct 2019 22:13:22 -0700 Subject: [PATCH 1200/2505] Use N_ELEMENTS() macro in timeouts.c --- src/lib/timeout.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/lib/timeout.c b/src/lib/timeout.c index 121ef6b76..bfe859fb5 100644 --- a/src/lib/timeout.c +++ b/src/lib/timeout.c @@ -51,10 +51,6 @@ #define abstime_t timeout_t /* for documentation purposes */ #define reltime_t timeout_t /* "" */ -#if !defined countof -#define countof(a) (sizeof(a) / sizeof *(a)) -#endif - #if !defined MIN #define MIN(a, b) (((a) < (b)) ? (a) : (b)) #endif @@ -163,15 +159,15 @@ static struct timeouts *timeouts_init(struct timeouts *T) { unsigned i, j; - for (i = 0; i < countof(T->wheel); i++) { - for (j = 0; j < countof(T->wheel[i]); j++) { + for (i = 0; i < N_ELEMENTS(T->wheel); i++) { + for (j = 0; j < N_ELEMENTS(T->wheel[i]); j++) { list_head_init(&T->wheel[i][j]); } } list_head_init(&T->expired); - for (i = 0; i < countof(T->pending); i++) { + for (i = 0; i < N_ELEMENTS(T->pending); i++) { T->pending[i] = 0; } @@ -200,8 +196,8 @@ static void timeouts_reset(struct timeouts *T) list_head_init(&reset); - for (i = 0; i < countof(T->wheel); i++) { - for (j = 0; j < countof(T->wheel[i]); j++) { + for (i = 0; i < N_ELEMENTS(T->wheel); i++) { + for (j = 0; j < N_ELEMENTS(T->wheel[i]); j++) { list_append_list(&reset, &T->wheel[i][j]); list_head_init(&T->wheel[i][j]); } From 4cc28c6aaab9037de55682ace4e142c2a0e548c5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 9 Oct 2019 22:13:37 -0700 Subject: [PATCH 1201/2505] Remove unused `hertz` from timeouts struct --- src/lib/timeout.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/timeout.c b/src/lib/timeout.c index bfe859fb5..9363f83cf 100644 --- a/src/lib/timeout.c +++ b/src/lib/timeout.c @@ -152,7 +152,6 @@ struct timeouts { wheel_t pending[WHEEL_NUM]; timeout_t curtime; - timeout_t hertz; }; static struct timeouts *timeouts_init(struct timeouts *T) From 4fcd45cc1df40484ab5947c5bd82f87f62e2b5fb Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 9 Oct 2019 22:14:16 -0700 Subject: [PATCH 1202/2505] Improve first iteration of xdaliclock frame rendering function --- src/samples/clock/xdaliclock.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/samples/clock/xdaliclock.c b/src/samples/clock/xdaliclock.c index 4bf682f38..51a641081 100644 --- a/src/samples/clock/xdaliclock.c +++ b/src/samples/clock/xdaliclock.c @@ -254,19 +254,21 @@ static void draw_horizontal_line(struct xdaliclock *xdc, static void frame_render(struct xdaliclock *xdc, const struct frame *frame, int x) { - int px, py; - - for (py = 0; py < char_height; py++) { + for (int py = 0; py < char_height; py++) { const struct scanline *line = &frame->scanlines[py]; int last_right = 0; + int px = 0; + + goto first_iter; - for (px = 0; px < MAX_SEGS_PER_LINE; px++) { - if (px > 0 && (line->left[px] == line->right[px] || - (line->left[px] == line->left[px - 1] && - line->right[px] == line->right[px - 1]))) { + for (; px < MAX_SEGS_PER_LINE; px++) { + if ((line->left[px] == line->right[px] || + (line->left[px] == line->left[px - 1] && + line->right[px] == line->right[px - 1]))) { continue; } + first_iter: /* Erase the line between the last segment and this segment. */ draw_horizontal_line(xdc, x + last_right, x + line->left[px], py, From 10d5c6c8d36ed1565f2cbae20dc4b0e9adc9d4f4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 10 Oct 2019 21:29:24 -0700 Subject: [PATCH 1203/2505] Ensure pointer to key is kept around before destroying cache entry --- src/lib/lwan-cache.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-cache.c b/src/lib/lwan-cache.c index 4fb25f4e8..a8f61d151 100644 --- a/src/lib/lwan-cache.c +++ b/src/lib/lwan-cache.c @@ -233,6 +233,14 @@ struct cache_entry *cache_get_and_ref_entry(struct cache *cache, return entry; } +static void destroy_entry(struct cache *cache, struct cache_entry *entry) +{ + char *key = entry->key; + + cache->cb.destroy_entry(entry, cache->cb.context); + free(key); +} + void cache_entry_unref(struct cache *cache, struct cache_entry *entry) { assert(entry); @@ -250,8 +258,7 @@ void cache_entry_unref(struct cache *cache, struct cache_entry *entry) /* FIXME: There's a race condition here: if the cache is destroyed * while there are cache items floating around, this will dereference * deallocated memory. */ - cache->cb.destroy_entry(entry, cache->cb.context); - free(entry->key); + destroy_entry(cache, entry); } } @@ -315,7 +322,7 @@ static bool cache_pruner_job(void *data) /* Decrement the reference and see if we were genuinely the last one * holding it. If so, destroy the entry. */ if (!ATOMIC_DEC(node->refs)) - cache->cb.destroy_entry(node, cache->cb.context); + destroy_entry(cache, node); } evicted++; From 40acc5ed06cf6aae0877fe8e3e2748c94a1391d2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 10 Oct 2019 21:34:13 -0700 Subject: [PATCH 1204/2505] Revert "Ensure pointer to key is kept around before destroying cache entry" This reverts commit 10d5c6c8d36ed1565f2cbae20dc4b0e9adc9d4f4. The code was correct: the key is freed whenever the element is removed from the hash table. --- src/lib/lwan-cache.c | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/lib/lwan-cache.c b/src/lib/lwan-cache.c index a8f61d151..4fb25f4e8 100644 --- a/src/lib/lwan-cache.c +++ b/src/lib/lwan-cache.c @@ -233,14 +233,6 @@ struct cache_entry *cache_get_and_ref_entry(struct cache *cache, return entry; } -static void destroy_entry(struct cache *cache, struct cache_entry *entry) -{ - char *key = entry->key; - - cache->cb.destroy_entry(entry, cache->cb.context); - free(key); -} - void cache_entry_unref(struct cache *cache, struct cache_entry *entry) { assert(entry); @@ -258,7 +250,8 @@ void cache_entry_unref(struct cache *cache, struct cache_entry *entry) /* FIXME: There's a race condition here: if the cache is destroyed * while there are cache items floating around, this will dereference * deallocated memory. */ - destroy_entry(cache, entry); + cache->cb.destroy_entry(entry, cache->cb.context); + free(entry->key); } } @@ -322,7 +315,7 @@ static bool cache_pruner_job(void *data) /* Decrement the reference and see if we were genuinely the last one * holding it. If so, destroy the entry. */ if (!ATOMIC_DEC(node->refs)) - destroy_entry(cache, node); + cache->cb.destroy_entry(node, cache->cb.context); } evicted++; From e221ee9688a654f2ae6f3d505a090d365831055c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 10 Oct 2019 21:34:58 -0700 Subject: [PATCH 1205/2505] Do not free cache entry key when unreffing entry --- src/lib/lwan-cache.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/lwan-cache.c b/src/lib/lwan-cache.c index 4fb25f4e8..d4fcdbcb2 100644 --- a/src/lib/lwan-cache.c +++ b/src/lib/lwan-cache.c @@ -251,7 +251,6 @@ void cache_entry_unref(struct cache *cache, struct cache_entry *entry) * while there are cache items floating around, this will dereference * deallocated memory. */ cache->cb.destroy_entry(entry, cache->cb.context); - free(entry->key); } } From 2d06edc3780d82a76eb7ce87d0b09bd57a114df8 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 11 Oct 2019 08:30:53 -0700 Subject: [PATCH 1206/2505] Temporary cache entry keys need to be freed when unreffing The only exception is when they ended up being in the hash table but trying to acquire the queue lock failed for some reason. Otherwise, since entries are never added to the hash table, hash_del() will never call free() on the key, so do it in cache_entry_unref(). --- src/lib/lwan-cache.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-cache.c b/src/lib/lwan-cache.c index d4fcdbcb2..94424a1f2 100644 --- a/src/lib/lwan-cache.c +++ b/src/lib/lwan-cache.c @@ -36,6 +36,7 @@ enum { /* Entry flags */ FLOATING = 1 << 0, TEMPORARY = 1 << 1, + FREE_KEY_ON_DESTROY = 1 << 2, /* Cache flags */ SHUTTING_DOWN = 1 << 0 @@ -196,7 +197,7 @@ struct cache_entry *cache_get_and_ref_entry(struct cache *cache, * items not being added to the cache, though, so this might be * changed back to pthread_rwlock_wrlock() again someday if this * proves to be a problem. */ - entry->flags = TEMPORARY; + entry->flags = TEMPORARY | FREE_KEY_ON_DESTROY; return entry; } @@ -212,6 +213,8 @@ struct cache_entry *cache_get_and_ref_entry(struct cache *cache, list_add_tail(&cache->queue.list, &entry->entries); pthread_rwlock_unlock(&cache->queue.lock); } else { + /* Key is freed when this entry is removed from the hash + * table below. */ entry->flags = TEMPORARY; /* Ensure item is removed from the hash table; otherwise, @@ -226,7 +229,7 @@ struct cache_entry *cache_get_and_ref_entry(struct cache *cache, * time someone unrefs this entry. TEMPORARY entries are pretty much * like FLOATING entries, but unreffing them do not use atomic * operations. */ - entry->flags = TEMPORARY; + entry->flags = TEMPORARY | FREE_KEY_ON_DESTROY; } pthread_rwlock_unlock(&cache->hash.lock); @@ -237,8 +240,14 @@ void cache_entry_unref(struct cache *cache, struct cache_entry *entry) { assert(entry); - if (entry->flags & TEMPORARY) + if (entry->flags & TEMPORARY) { + /* FREE_KEY_ON_DESTROY is set on elements that never got into the + * hash table, so their keys are never destroyed automatically. */ + if (entry->flags & FREE_KEY_ON_DESTROY) + free(entry->key); + goto destroy_entry; + } if (ATOMIC_DEC(entry->refs)) return; From 0cfd5630f50a707286025939fcbdbdec72faa75d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 16 Oct 2019 19:55:21 -0700 Subject: [PATCH 1207/2505] Allow Lua metamethods to be defined outside lwan-lua.c --- src/lib/liblwan.sym | 1 + src/lib/lwan-lua.c | 20 ++++++++++---------- src/lib/lwan-lua.h | 4 ++++ 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/lib/liblwan.sym b/src/lib/liblwan.sym index 7c3e5a01c..adb504ce4 100644 --- a/src/lib/liblwan.sym +++ b/src/lib/liblwan.sym @@ -89,4 +89,5 @@ global: lwan_lua_create_state; lwan_lua_state_last_error; lwan_lua_state_push_request; + lwan_lua_get_request_from_userdata; } LIBLWAN_1; diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index 9799e09b2..e4db8d2fd 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -32,7 +32,7 @@ static const char *request_metatable_name = "Lwan.Request"; -static ALWAYS_INLINE struct lwan_request *userdata_as_request(lua_State *L) +ALWAYS_INLINE struct lwan_request *lwan_lua_get_request_from_userdata(lua_State *L) { struct lwan_request **r = luaL_checkudata(L, 1, request_metatable_name); @@ -41,7 +41,7 @@ static ALWAYS_INLINE struct lwan_request *userdata_as_request(lua_State *L) LWAN_LUA_METHOD(say) { - struct lwan_request *request = userdata_as_request(L); + struct lwan_request *request = lwan_lua_get_request_from_userdata(L); size_t response_str_len; const char *response_str = lua_tolstring(L, -1, &response_str_len); @@ -54,7 +54,7 @@ LWAN_LUA_METHOD(say) LWAN_LUA_METHOD(send_event) { - struct lwan_request *request = userdata_as_request(L); + struct lwan_request *request = lwan_lua_get_request_from_userdata(L); size_t event_str_len; const char *event_str = lua_tolstring(L, -1, &event_str_len); const char *event_name = lua_tostring(L, -2); @@ -67,7 +67,7 @@ LWAN_LUA_METHOD(send_event) LWAN_LUA_METHOD(set_response) { - struct lwan_request *request = userdata_as_request(L); + struct lwan_request *request = lwan_lua_get_request_from_userdata(L); size_t response_str_len; const char *response_str = lua_tolstring(L, -1, &response_str_len); @@ -80,7 +80,7 @@ static int request_param_getter(lua_State *L, const char *(*getter)(struct lwan_request *req, const char *key)) { - struct lwan_request *request = userdata_as_request(L); + struct lwan_request *request = lwan_lua_get_request_from_userdata(L); const char *key_str = lua_tostring(L, -1); const char *value = getter(request, key_str); @@ -114,7 +114,7 @@ LWAN_LUA_METHOD(cookie) LWAN_LUA_METHOD(ws_upgrade) { - struct lwan_request *request = userdata_as_request(L); + struct lwan_request *request = lwan_lua_get_request_from_userdata(L); enum lwan_http_status status = lwan_request_websocket_upgrade(request); lua_pushinteger(L, status); @@ -124,7 +124,7 @@ LWAN_LUA_METHOD(ws_upgrade) LWAN_LUA_METHOD(ws_write) { - struct lwan_request *request = userdata_as_request(L); + struct lwan_request *request = lwan_lua_get_request_from_userdata(L); size_t data_len; const char *data_str = lua_tolstring(L, -1, &data_len); @@ -136,7 +136,7 @@ LWAN_LUA_METHOD(ws_write) LWAN_LUA_METHOD(ws_read) { - struct lwan_request *request = userdata_as_request(L); + struct lwan_request *request = lwan_lua_get_request_from_userdata(L); if (lwan_response_websocket_read(request)) { lua_pushlstring(L, lwan_strbuf_get_buffer(request->response.buffer), @@ -173,7 +173,7 @@ LWAN_LUA_METHOD(set_headers) const int value_index = 2 + table_index; const int nested_value_index = value_index * 2 - table_index; struct lwan_key_value_array *headers; - struct lwan_request *request = userdata_as_request(L); + struct lwan_request *request = lwan_lua_get_request_from_userdata(L); struct coro *coro = request->conn->coro; struct lwan_key_value *kv; @@ -232,7 +232,7 @@ LWAN_LUA_METHOD(set_headers) LWAN_LUA_METHOD(sleep) { - struct lwan_request *request = userdata_as_request(L); + struct lwan_request *request = lwan_lua_get_request_from_userdata(L); lua_Integer ms = lua_tointeger(L, -1); lwan_request_sleep(request, (uint64_t)ms); diff --git a/src/lib/lwan-lua.h b/src/lib/lwan-lua.h index a31c924d0..041afeeb1 100644 --- a/src/lib/lwan-lua.h +++ b/src/lib/lwan-lua.h @@ -25,3 +25,7 @@ const char *lwan_lua_state_last_error(lua_State *L); lua_State *lwan_lua_create_state(const char *script_file, const char *script); void lwan_lua_state_push_request(lua_State *L, struct lwan_request *request); + +struct lwan_request; +struct lwan_request *lwan_lua_get_request_from_userdata(lua_State *L); + From d58aa3209de75b74dba079cea7cd253918f2ac42 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 16 Oct 2019 20:02:12 -0700 Subject: [PATCH 1208/2505] Validate that either script or a file path is provided to lwan_lua_create_state() --- src/lib/lwan-lua.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index e4db8d2fd..523dec717 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -297,9 +297,14 @@ lua_State *lwan_lua_create_state(const char *script_file, const char *script) lua_tostring(L, -1)); goto close_lua_state; } - } else if (UNLIKELY(luaL_dostring(L, script) != 0)) { - lwan_status_error("Error evaluating Lua script %s", - lua_tostring(L, -1)); + } else if (script) { + if (UNLIKELY(luaL_dostring(L, script) != 0)) { + lwan_status_error("Error evaluating Lua script %s", + lua_tostring(L, -1)); + goto close_lua_state; + } + } else { + lwan_status_error("Either file or inline script has to be provided"); goto close_lua_state; } From 5ac13f7ce45253348042f8d55aae0e1cae7d51d3 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 17 Oct 2019 08:18:26 -0700 Subject: [PATCH 1209/2505] Remove one malloc roundtrip per connection This makes it possible to define array types that have a "inline first" allocation strategy, where data is initially allocated inline with the array type, and, if needed, move on to heap allocation. The size of this inline storage is 16 elements, the same number of elements that triggers a reallocation when using heap-allocated storage. This is now used for the deferred callbacks arrays. Most handlers defers at most around 5 callbacks, so there's plenty of room in the happy path. Since all connections will have at least one deferred callback (to free the strbuf), this removes a malloc roundtrip for all connections. --- src/lib/lwan-array.c | 42 +++++++++++++++++++++++++++++-------- src/lib/lwan-array.h | 49 +++++++++++++++++++++++++++++++------------- src/lib/lwan-coro.c | 2 +- 3 files changed, 70 insertions(+), 23 deletions(-) diff --git a/src/lib/lwan-array.c b/src/lib/lwan-array.c index c61a9e9d8..f59a430f6 100644 --- a/src/lib/lwan-array.c +++ b/src/lib/lwan-array.c @@ -27,8 +27,6 @@ #include "lwan.h" #include "lwan-array.h" -#define INCREMENT 16 - int lwan_array_init(struct lwan_array *a) { if (UNLIKELY(!a)) @@ -40,12 +38,14 @@ int lwan_array_init(struct lwan_array *a) return 0; } -int lwan_array_reset(struct lwan_array *a) +int lwan_array_reset(struct lwan_array *a, void *inline_storage) { if (UNLIKELY(!a)) return -EINVAL; - free(a->base); + if (a->base != inline_storage) + free(a->base); + a->base = NULL; a->elements = 0; @@ -65,13 +65,14 @@ static inline bool add_overflow(size_t a, size_t b, size_t *out) #define add_overflow __builtin_add_overflow #endif -void *lwan_array_append(struct lwan_array *a, size_t element_size) +void *lwan_array_append_heap(struct lwan_array *a, size_t element_size) { - if (!(a->elements % INCREMENT)) { + if (!(a->elements % LWAN_ARRAY_INCREMENT)) { void *new_base; size_t new_cap; - if (UNLIKELY(add_overflow(a->elements, INCREMENT, &new_cap))) { + if (UNLIKELY( + add_overflow(a->elements, LWAN_ARRAY_INCREMENT, &new_cap))) { errno = EOVERFLOW; return NULL; } @@ -86,6 +87,31 @@ void *lwan_array_append(struct lwan_array *a, size_t element_size) return ((unsigned char *)a->base) + a->elements++ * element_size; } +void *lwan_array_append_stack(struct lwan_array *a, + size_t element_size, + void *inline_storage) +{ + if (!a->elements) + a->base = inline_storage; + + if (a->elements < LWAN_ARRAY_INCREMENT) + return ((char *)a->base) + a->elements++ * element_size; + + if (a->elements == LWAN_ARRAY_INCREMENT) { + void *new_base; + + new_base = calloc(2 * LWAN_ARRAY_INCREMENT, element_size); + if (UNLIKELY(!new_base)) + return NULL; + + a->base = memcpy(new_base, inline_storage, + LWAN_ARRAY_INCREMENT * element_size); + return ((char *)a->base) + a->elements++ * element_size; + } + + return lwan_array_append_heap(a, element_size); +} + void lwan_array_sort(struct lwan_array *a, size_t element_size, int (*cmp)(const void *a, const void *b)) @@ -98,7 +124,7 @@ static void coro_lwan_array_free(void *data) { struct lwan_array *array = data; - lwan_array_reset(array); + lwan_array_reset(array, NULL); free(array); } diff --git a/src/lib/lwan-array.h b/src/lib/lwan-array.h index ac5cea09a..02ef9eb0c 100644 --- a/src/lib/lwan-array.h +++ b/src/lib/lwan-array.h @@ -24,14 +24,19 @@ #include "lwan-coro.h" +#define LWAN_ARRAY_INCREMENT 16 + struct lwan_array { void *base; size_t elements; }; int lwan_array_init(struct lwan_array *a); -int lwan_array_reset(struct lwan_array *a); -void *lwan_array_append(struct lwan_array *a, size_t element_size); +int lwan_array_reset(struct lwan_array *a, void *inline_storage); +void *lwan_array_append_heap(struct lwan_array *a, size_t element_size); +void *lwan_array_append_stack(struct lwan_array *a, + size_t element_size, + void *inline_storage); void lwan_array_sort(struct lwan_array *a, size_t element_size, int (*cmp)(const void *a, const void *b)); @@ -53,21 +58,42 @@ struct lwan_array *coro_lwan_array_new(struct coro *coro); struct array_type_ { \ struct lwan_array base; \ }; \ - __attribute__((unused)) static inline int array_type_##_init( \ + __attribute__((unused)) static inline element_type_ *array_type_##_append( \ struct array_type_ *array) \ { \ - return lwan_array_init(&array->base); \ + return (element_type_ *)lwan_array_append_heap(&array->base, \ + sizeof(element_type_)); \ } \ - __attribute__((unused)) static inline int array_type_##_reset( \ - struct array_type_ *array) \ + __attribute__((unused)) static inline struct array_type_ \ + *coro_##array_type_##_new(struct coro *coro) \ { \ - return lwan_array_reset(&array->base); \ + return (struct array_type_ *)coro_lwan_array_new(coro); \ } \ + DEFINE_ARRAY_TYPE_FUNCS(array_type_, element_type_, NULL) + +#define DEFINE_ARRAY_TYPE_INLINEFIRST(array_type_, element_type_) \ + struct array_type_ { \ + struct lwan_array base; \ + element_type_ storage[LWAN_ARRAY_INCREMENT]; \ + }; \ __attribute__((unused)) static inline element_type_ *array_type_##_append( \ struct array_type_ *array) \ { \ - return (element_type_ *)lwan_array_append(&array->base, \ - sizeof(element_type_)); \ + return (element_type_ *)lwan_array_append_stack( \ + &array->base, sizeof(element_type_), &array->storage); \ + } \ + DEFINE_ARRAY_TYPE_FUNCS(array_type_, element_type_, &array->storage) + +#define DEFINE_ARRAY_TYPE_FUNCS(array_type_, element_type_, inline_storage_) \ + __attribute__((unused)) static inline int array_type_##_init( \ + struct array_type_ *array) \ + { \ + return lwan_array_init(&array->base); \ + } \ + __attribute__((unused)) static inline int array_type_##_reset( \ + struct array_type_ *array) \ + { \ + return lwan_array_reset(&array->base, inline_storage_); \ } \ __attribute__((unused)) static inline element_type_ \ *array_type_##_append0(struct array_type_ *array) \ @@ -84,11 +110,6 @@ struct lwan_array *coro_lwan_array_new(struct coro *coro); { \ lwan_array_sort(&array->base, sizeof(element_type_), cmp); \ } \ - __attribute__((unused)) static inline struct array_type_ \ - *coro_##array_type_##_new(struct coro *coro) \ - { \ - return (struct array_type_ *)coro_lwan_array_new(coro); \ - } \ __attribute__((unused)) static inline size_t array_type_##_get_elem_index( \ struct array_type_ *array, element_type_ *elem) \ { \ diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 9e432121c..b0e669603 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -67,7 +67,7 @@ struct coro_defer { bool has_two_args; }; -DEFINE_ARRAY_TYPE(coro_defer_array, struct coro_defer) +DEFINE_ARRAY_TYPE_INLINEFIRST(coro_defer_array, struct coro_defer) struct coro { struct coro_switcher *switcher; From 4d865e2add447e381a4f8453c7ef8a9723286786 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 17 Oct 2019 08:50:11 -0700 Subject: [PATCH 1210/2505] Change last lwan_thread_init() debugging message --- src/lib/lwan-thread.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index b91f10a4b..1190c15d9 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -674,7 +674,7 @@ void lwan_thread_init(struct lwan *l) pthread_barrier_wait(&l->thread.barrier); - lwan_status_debug("IO threads created and ready to serve"); + lwan_status_debug("Worker threads created and ready to serve"); } void lwan_thread_shutdown(struct lwan *l) From 7587c1e5955d12e440e503a4bc1fc44ed3dc38aa Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 17 Oct 2019 08:50:38 -0700 Subject: [PATCH 1211/2505] Use inlinefirst array type for Fortunes in TWFB sample --- src/samples/techempower/techempower.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index d9df79dfd..15c55634a 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -39,7 +39,7 @@ struct Fortune { } item; }; -DEFINE_ARRAY_TYPE(fortune_array, struct Fortune) +DEFINE_ARRAY_TYPE_INLINEFIRST(fortune_array, struct Fortune) static const char fortunes_template_str[] = "" \ "" \ From f2f2dce2a9e7a2fa865c1075c0f1de3741e3bfe9 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 17 Oct 2019 18:04:55 -0700 Subject: [PATCH 1212/2505] Rename lwan_array_append_stack() to lwan_array_append_inline() --- src/lib/lwan-array.c | 6 +++--- src/lib/lwan-array.h | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/lib/lwan-array.c b/src/lib/lwan-array.c index f59a430f6..8f9208f4d 100644 --- a/src/lib/lwan-array.c +++ b/src/lib/lwan-array.c @@ -87,9 +87,9 @@ void *lwan_array_append_heap(struct lwan_array *a, size_t element_size) return ((unsigned char *)a->base) + a->elements++ * element_size; } -void *lwan_array_append_stack(struct lwan_array *a, - size_t element_size, - void *inline_storage) +void *lwan_array_append_inline(struct lwan_array *a, + size_t element_size, + void *inline_storage) { if (!a->elements) a->base = inline_storage; diff --git a/src/lib/lwan-array.h b/src/lib/lwan-array.h index 02ef9eb0c..a8a8e4396 100644 --- a/src/lib/lwan-array.h +++ b/src/lib/lwan-array.h @@ -34,9 +34,9 @@ struct lwan_array { int lwan_array_init(struct lwan_array *a); int lwan_array_reset(struct lwan_array *a, void *inline_storage); void *lwan_array_append_heap(struct lwan_array *a, size_t element_size); -void *lwan_array_append_stack(struct lwan_array *a, - size_t element_size, - void *inline_storage); +void *lwan_array_append_inline(struct lwan_array *a, + size_t element_size, + void *inline_storage); void lwan_array_sort(struct lwan_array *a, size_t element_size, int (*cmp)(const void *a, const void *b)); @@ -79,7 +79,7 @@ struct lwan_array *coro_lwan_array_new(struct coro *coro); __attribute__((unused)) static inline element_type_ *array_type_##_append( \ struct array_type_ *array) \ { \ - return (element_type_ *)lwan_array_append_stack( \ + return (element_type_ *)lwan_array_append_inline( \ &array->base, sizeof(element_type_), &array->storage); \ } \ DEFINE_ARRAY_TYPE_FUNCS(array_type_, element_type_, &array->storage) From e64c30d4f9b72d4738118b2d3a921c656e4841bf Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 17 Oct 2019 18:05:26 -0700 Subject: [PATCH 1213/2505] Properly check if an array has been demoted from inlinefirst Check the base pointer rather than the number of elements because the lwan_array struct isn't opaque and it can be changed from outside lwan-array.c. --- src/lib/lwan-array.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/lib/lwan-array.c b/src/lib/lwan-array.c index 8f9208f4d..0484d9add 100644 --- a/src/lib/lwan-array.c +++ b/src/lib/lwan-array.c @@ -91,25 +91,23 @@ void *lwan_array_append_inline(struct lwan_array *a, size_t element_size, void *inline_storage) { - if (!a->elements) + if (!a->base) a->base = inline_storage; + else if (UNLIKELY(a->base != inline_storage)) + return lwan_array_append_heap(a, element_size); - if (a->elements < LWAN_ARRAY_INCREMENT) - return ((char *)a->base) + a->elements++ * element_size; + assert(a->elements <= LWAN_ARRAY_INCREMENT); if (a->elements == LWAN_ARRAY_INCREMENT) { - void *new_base; - - new_base = calloc(2 * LWAN_ARRAY_INCREMENT, element_size); + void *new_base = calloc(2 * LWAN_ARRAY_INCREMENT, element_size); if (UNLIKELY(!new_base)) return NULL; a->base = memcpy(new_base, inline_storage, LWAN_ARRAY_INCREMENT * element_size); - return ((char *)a->base) + a->elements++ * element_size; } - return lwan_array_append_heap(a, element_size); + return ((char *)a->base) + a->elements++ * element_size; } void lwan_array_sort(struct lwan_array *a, From 2c4ea612279e1f94d7fa60bb71da58d82a938524 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 19 Oct 2019 09:33:46 -0700 Subject: [PATCH 1214/2505] Implement creation of lwan_array with coro-deferred destruction --- src/lib/lwan-array.c | 16 +++++++++++++--- src/lib/lwan-array.h | 9 +++++++-- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/lib/lwan-array.c b/src/lib/lwan-array.c index 0484d9add..296521cb6 100644 --- a/src/lib/lwan-array.c +++ b/src/lib/lwan-array.c @@ -118,7 +118,7 @@ void lwan_array_sort(struct lwan_array *a, qsort(a->base, a->elements, element_size, cmp); } -static void coro_lwan_array_free(void *data) +static void coro_lwan_array_free_heap(void *data) { struct lwan_array *array = data; @@ -126,11 +126,21 @@ static void coro_lwan_array_free(void *data) free(array); } -struct lwan_array *coro_lwan_array_new(struct coro *coro) +static void coro_lwan_array_free_inline(void *data) +{ + struct lwan_array *array = data; + + lwan_array_reset(array, array + 1); + free(array); +} + +struct lwan_array *coro_lwan_array_new(struct coro *coro, bool inline_first) { struct lwan_array *array; - array = coro_malloc_full(coro, sizeof(*array), coro_lwan_array_free); + array = coro_malloc_full(coro, sizeof(*array), + inline_first ? coro_lwan_array_free_inline + : coro_lwan_array_free_heap); if (LIKELY(array)) lwan_array_init(array); diff --git a/src/lib/lwan-array.h b/src/lib/lwan-array.h index a8a8e4396..1ebe964c2 100644 --- a/src/lib/lwan-array.h +++ b/src/lib/lwan-array.h @@ -40,7 +40,7 @@ void *lwan_array_append_inline(struct lwan_array *a, void lwan_array_sort(struct lwan_array *a, size_t element_size, int (*cmp)(const void *a, const void *b)); -struct lwan_array *coro_lwan_array_new(struct coro *coro); +struct lwan_array *coro_lwan_array_new(struct coro *coro, bool inline_first); #define LWAN_ARRAY_FOREACH(array_, iter_) \ for (iter_ = (array_)->base.base; \ @@ -67,7 +67,7 @@ struct lwan_array *coro_lwan_array_new(struct coro *coro); __attribute__((unused)) static inline struct array_type_ \ *coro_##array_type_##_new(struct coro *coro) \ { \ - return (struct array_type_ *)coro_lwan_array_new(coro); \ + return (struct array_type_ *)coro_lwan_array_new(coro, false); \ } \ DEFINE_ARRAY_TYPE_FUNCS(array_type_, element_type_, NULL) @@ -82,6 +82,11 @@ struct lwan_array *coro_lwan_array_new(struct coro *coro); return (element_type_ *)lwan_array_append_inline( \ &array->base, sizeof(element_type_), &array->storage); \ } \ + __attribute__((unused)) static inline struct array_type_ \ + *coro_##array_type_##_new(struct coro *coro) \ + { \ + return (struct array_type_ *)coro_lwan_array_new(coro, true); \ + } \ DEFINE_ARRAY_TYPE_FUNCS(array_type_, element_type_, &array->storage) #define DEFINE_ARRAY_TYPE_FUNCS(array_type_, element_type_, inline_storage_) \ From 3e0513d5c3966c49cf8daa374256b4aacf090888 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 19 Oct 2019 09:54:28 -0700 Subject: [PATCH 1215/2505] Fix build on BSD --- src/lib/missing.c | 8 ++++---- src/lib/missing/limits.h | 10 ++++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/lib/missing.c b/src/lib/missing.c index 833c17a9d..56ce17342 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -361,20 +361,20 @@ pid_t gettid(void) { return (pid_t)syscall(SYS_gettid); } #elif defined(__FreeBSD__) #include -long gettid(void) +pid_t gettid(void) { long ret; thr_self(&ret); - return ret; + return (pid_t)ret; } #elif defined(__APPLE__) && MAC_OS_X_VERSION_MAX_ALLOWED >= 101200 #include -long gettid(void) { return syscall(SYS_thread_selfid); } +pid_t gettid(void) { return syscall(SYS_thread_selfid); } #else -long gettid(void) { return (long)pthread_self(); } +pid_t gettid(void) { return (pid_t)pthread_self(); } #endif #if defined(__APPLE__) diff --git a/src/lib/missing/limits.h b/src/lib/missing/limits.h index 6112689fa..750f0416a 100644 --- a/src/lib/missing/limits.h +++ b/src/lib/missing/limits.h @@ -43,10 +43,12 @@ #ifndef PAGE_SIZE # include -# ifdef EXEC_PAGESIZE -# define PAGE_SIZE EXEC_PAGESIZE -# else -# define PAGE_SIZE 4096 +# ifndef PAGE_SIZE +# ifdef EXEC_PAGESIZE +# define PAGE_SIZE EXEC_PAGESIZE +# else +# define PAGE_SIZE 4096 +# endif # endif #endif From 40010f1d65595e3072e73207e7a0066f63f3c1c3 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 19 Oct 2019 16:08:05 -0700 Subject: [PATCH 1216/2505] Don't redefine [lb]e{16,32,64}toh() macros on BSD systems --- src/lib/missing/endian.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib/missing/endian.h b/src/lib/missing/endian.h index ec7697c69..210522ebf 100644 --- a/src/lib/missing/endian.h +++ b/src/lib/missing/endian.h @@ -56,14 +56,20 @@ #include +#ifndef be16toh #define be16toh(x) betoh16(x) #define le16toh(x) letoh16(x) +#endif +#ifndef be32toh #define be32toh(x) betoh32(x) #define le32toh(x) letoh32(x) +#endif +#ifndef be64toh #define be64toh(x) betoh64(x) #define le64toh(x) letoh64(x) +#endif #elif defined(__WINDOWS__) From e31cb7c58413a179962fb90876802e87b178df7b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 19 Oct 2019 16:08:56 -0700 Subject: [PATCH 1217/2505] Kqueue doesn't like filtering on both read and write This broke timers on FreeBSD. --- src/lib/lwan-thread.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 1190c15d9..56099b16e 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -176,6 +176,16 @@ conn_flags_to_epoll_events(enum lwan_connection_flags flags) return map[flags & CONN_EVENTS_MASK]; } +#if defined(__linux__) +# define CONN_EVENTS_RESUME_TIMER CONN_EVENTS_READ_WRITE +#else +/* Kqueue doesn't like when you filter on both read and write, so + * wait only on write when resuming a coro suspended by a timer. + * The I/O wrappers should yield if trying to read without anything + * in the buffer, changing the filter to only read, so this is OK. */ +# define CONN_EVENTS_RESUME_TIMER CONN_EVENTS_WRITE +#endif + static void update_epoll_flags(int fd, struct lwan_connection *conn, int epoll_fd, From 911c1691ab81532ad62fc905e6a23d43da5ba74a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 19 Oct 2019 16:10:22 -0700 Subject: [PATCH 1218/2505] Use global marker for no event filter in epoll-on-kqueue Instead of using a hardcoded 1, use a pointer to a global integer, which should be harder to be known by any epoll users. --- src/lib/missing.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/lib/missing.c b/src/lib/missing.c index 56ce17342..24c627741 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -142,6 +142,8 @@ int clock_gettime(clockid_t clk_id, struct timespec *ts) int epoll_create1(int flags __attribute__((unused))) { return kqueue(); } +static int epoll_no_event_marker; + int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) { struct kevent ev; @@ -150,7 +152,7 @@ int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) case EPOLL_CTL_ADD: case EPOLL_CTL_MOD: { int events = 0; - uintptr_t udata = (uintptr_t)event->data.ptr; + void *udata = event->data.ptr; int flags = EV_ADD; if (event->events & EPOLLIN) { @@ -159,7 +161,7 @@ int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) events = EVFILT_WRITE; } else { events = EVFILT_WRITE; - udata = 1; + udata = &epoll_no_event_marker; } if (event->events & EPOLLONESHOT) @@ -168,7 +170,7 @@ int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) flags |= EV_ERROR; /* EPOLLERR is always set. */ flags |= EV_EOF; /* EPOLLHUP is always set. */ - EV_SET(&ev, fd, events, flags, 0, 0, (void *)udata); + EV_SET(&ev, fd, events, flags, 0, 0, udata); break; } @@ -225,7 +227,7 @@ int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) if (kev->filter == EVFILT_READ) mask |= EPOLLIN; - else if (kev->filter == EVFILT_WRITE && (uintptr_t)evs[i].udata != 1) + else if (kev->filter == EVFILT_WRITE && evs[i].udata != &epoll_no_event_marker) mask |= EPOLLOUT; hash_add(coalesce, (void *)(intptr_t)evs[i].ident, @@ -239,6 +241,9 @@ int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) if (maskptr) { struct kevent *kev = &evs[i]; + if (kev->udata == &epoll_no_event_marker) + continue; + ev->data.ptr = kev->udata; ev->events = (uint32_t)(uintptr_t)maskptr; ev++; From 71268361cc2c094f8e233fdeeebc0fb34b9d999a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 20 Oct 2019 09:45:30 -0700 Subject: [PATCH 1219/2505] Actually use CONN_EVENTS_RESUME_TIMER when resuming from timer --- src/lib/lwan-thread.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 56099b16e..2b3e03426 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -207,7 +207,7 @@ static void update_epoll_flags(int fd, * know which event, because they were both cleared when the coro was * suspended. So set both flags here. This works because EPOLLET isn't * used. */ - [CONN_CORO_RESUME_TIMER] = CONN_EVENTS_READ_WRITE, + [CONN_CORO_RESUME_TIMER] = CONN_EVENTS_RESUME_TIMER, }; static const enum lwan_connection_flags and_mask[CONN_CORO_MAX] = { [CONN_CORO_YIELD] = ~0, From 643223e8d92a8796481117112304e1282587dd4b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 20 Oct 2019 12:56:24 -0700 Subject: [PATCH 1220/2505] Fuzz-test websockets handshaking --- fuzz/corpus/corpus-request-187309218 | 16 +++++++++ fuzz/request_fuzzer.dict | 2 ++ src/lib/lwan-request.c | 53 +++++++++++++++++++--------- 3 files changed, 54 insertions(+), 17 deletions(-) create mode 100644 fuzz/corpus/corpus-request-187309218 diff --git a/fuzz/corpus/corpus-request-187309218 b/fuzz/corpus/corpus-request-187309218 new file mode 100644 index 000000000..a8e57eed9 --- /dev/null +++ b/fuzz/corpus/corpus-request-187309218 @@ -0,0 +1,16 @@ +GET /ws HTTP/1.1 +Host: localhost:8080 +User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:69.0) Gecko/20100101 Firefox/69.0 +Accept: */* +Accept-Language: en-US,en;q=0.5 +Accept-Encoding: gzip, deflate +Sec-WebSocket-Version: 13 +Origin: http://localhost:8080 +Sec-WebSocket-Extensions: permessage-deflate +Sec-WebSocket-Key: YNDTJflH7V1X70XY2XHR0A== +DNT: 1 +Connection: keep-alive, Upgrade +Pragma: no-cache +Cache-Control: no-cache +Upgrade: websocket + diff --git a/fuzz/request_fuzzer.dict b/fuzz/request_fuzzer.dict index 1d76d71f5..284a82e5f 100644 --- a/fuzz/request_fuzzer.dict +++ b/fuzz/request_fuzzer.dict @@ -124,3 +124,5 @@ vdf="Proxy-Connection" vdf="X-UIDH" vdf="X-XSRF-TOKEN" vdf="X-Csrf-Token" +vdf="Sec-WebSocket-Key" +vdf="Upgrade" diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 3e86f45ab..fa0c9ce96 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1244,16 +1244,13 @@ static enum lwan_http_status parse_http_request(struct lwan_request *request) return HTTP_OK; } -enum lwan_http_status -lwan_request_websocket_upgrade(struct lwan_request *request) +static enum lwan_http_status +prepare_websocket_handshake(struct lwan_request *request, char **encoded) { static const unsigned char websocket_uuid[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - char header_buf[DEFAULT_HEADERS_SIZE]; - size_t header_buf_len; unsigned char digest[20]; sha1_context ctx; - char *encoded; if (UNLIKELY(request->flags & RESPONSE_SENT_HEADERS)) return HTTP_INTERNAL_ERROR; @@ -1278,10 +1275,24 @@ lwan_request_websocket_upgrade(struct lwan_request *request) sha1_update(&ctx, websocket_uuid, sizeof(websocket_uuid) - 1); sha1_finalize(&ctx, digest); - encoded = (char *)base64_encode(digest, sizeof(digest), NULL); - if (UNLIKELY(!encoded)) + *encoded = (char *)base64_encode(digest, sizeof(digest), NULL); + if (UNLIKELY(!*encoded)) return HTTP_INTERNAL_ERROR; - coro_defer(request->conn->coro, free, encoded); + coro_defer(request->conn->coro, free, *encoded); + + return HTTP_SWITCHING_PROTOCOLS; +} + +enum lwan_http_status +lwan_request_websocket_upgrade(struct lwan_request *request) +{ + char header_buf[DEFAULT_HEADERS_SIZE]; + size_t header_buf_len; + char *encoded; + + enum lwan_http_status r = prepare_websocket_handshake(request, &encoded); + if (r != HTTP_SWITCHING_PROTOCOLS) + return r; request->flags |= RESPONSE_NO_CONTENT_LENGTH; header_buf_len = lwan_prepare_response_header_full( @@ -1292,17 +1303,14 @@ lwan_request_websocket_upgrade(struct lwan_request *request) {.key = "Upgrade", .value = "websocket"}, {}, }); - if (LIKELY(header_buf_len)) { - request->conn->flags |= CONN_IS_WEBSOCKET; - - lwan_send(request, header_buf, header_buf_len, 0); - - coro_yield(request->conn->coro, CONN_CORO_WANT_READ_WRITE); + if (UNLIKELY(!header_buf_len)) + return HTTP_INTERNAL_ERROR; - return HTTP_SWITCHING_PROTOCOLS; - } + request->conn->flags |= CONN_IS_WEBSOCKET; + lwan_send(request, header_buf, header_buf_len, 0); + coro_yield(request->conn->coro, CONN_CORO_WANT_READ_WRITE); - return HTTP_INTERNAL_ERROR; + return HTTP_SWITCHING_PROTOCOLS; } static enum lwan_http_status prepare_for_response(struct lwan_url_map *url_map, @@ -1654,6 +1662,16 @@ static int useless_coro_for_fuzzing(struct coro *c __attribute__((unused)), return 0; } +static char *fuzz_websocket_handshake(struct lwan_request *r) +{ + char *encoded; + + if (prepare_websocket_handshake(r, &encoded) == HTTP_SWITCHING_PROTOCOLS) + return encoded; + + return NULL; +} + __attribute__((used)) int fuzz_parse_http_request(const uint8_t *data, size_t length) { @@ -1716,6 +1734,7 @@ __attribute__((used)) int fuzz_parse_http_request(const uint8_t *data, NO_DISCARD(lwan_request_get_cookie(&request, "FOO")); /* Set by some tests */ NO_DISCARD(lwan_request_get_query_param(&request, "Non-Existing-Query-Param")); NO_DISCARD(lwan_request_get_post_param(&request, "Non-Existing-Post-Param")); + NO_DISCARD(fuzz_websocket_handshake(&request)); #undef NO_DISCARD From 02a926c5e4b32aed216ab57f68fab309aaa3fb75 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 20 Oct 2019 22:20:48 -0700 Subject: [PATCH 1221/2505] Start strbuf as empty static They'll grow automatically if needed. This way, if they don't end up being used in request handlers (e.g. file serving), a malloc/free is saved for every request. --- src/lib/lwan-strbuf.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-strbuf.c b/src/lib/lwan-strbuf.c index 3f087c868..d9df51aa9 100644 --- a/src/lib/lwan-strbuf.c +++ b/src/lib/lwan-strbuf.c @@ -86,11 +86,9 @@ bool lwan_strbuf_init_with_size(struct lwan_strbuf *s, size_t size) memset(s, 0, sizeof(*s)); - if (UNLIKELY(!grow_buffer_if_needed(s, size))) - return false; - s->used = 0; - s->value.buffer[0] = '\0'; + s->value.buffer = ""; + s->flags = STATIC; return true; } From aa45d7a67e74271e17c50257ba0604f85718bfce Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 21 Oct 2019 18:44:38 -0700 Subject: [PATCH 1222/2505] Simplify strbuf initialization --- src/lib/lwan-strbuf.c | 45 ++++++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/src/lib/lwan-strbuf.c b/src/lib/lwan-strbuf.c index d9df51aa9..3fd004e09 100644 --- a/src/lib/lwan-strbuf.c +++ b/src/lib/lwan-strbuf.c @@ -28,7 +28,6 @@ static const unsigned int STATIC = 1 << 0; static const unsigned int DYNAMICALLY_ALLOCATED = 1 << 1; -static const size_t DEFAULT_BUF_SIZE = 64; static inline size_t align_size(size_t unaligned_size) { @@ -84,39 +83,47 @@ bool lwan_strbuf_init_with_size(struct lwan_strbuf *s, size_t size) if (UNLIKELY(!s)) return false; - memset(s, 0, sizeof(*s)); + if (!size) { + *s = (struct lwan_strbuf) { + .used = 0, + .value.static_buffer = "", + .flags = STATIC, + }; + } else { + memset(s, 0, sizeof(*s)); - s->used = 0; - s->value.buffer = ""; - s->flags = STATIC; + if (UNLIKELY(!grow_buffer_if_needed(s, size))) + return false; + + s->value.buffer[0] = '\0'; + } return true; } ALWAYS_INLINE bool lwan_strbuf_init(struct lwan_strbuf *s) { - return lwan_strbuf_init_with_size(s, DEFAULT_BUF_SIZE); + return lwan_strbuf_init_with_size(s, 0); } struct lwan_strbuf *lwan_strbuf_new_with_size(size_t size) { struct lwan_strbuf *s = malloc(sizeof(*s)); - if (UNLIKELY(!s)) - return NULL; - if (UNLIKELY(!lwan_strbuf_init_with_size(s, size))) { free(s); - s = NULL; - } else { - s->flags |= DYNAMICALLY_ALLOCATED; + + return NULL; } + + s->flags |= DYNAMICALLY_ALLOCATED; + return s; } ALWAYS_INLINE struct lwan_strbuf *lwan_strbuf_new(void) { - return lwan_strbuf_new_with_size(DEFAULT_BUF_SIZE); + return lwan_strbuf_new_with_size(0); } ALWAYS_INLINE struct lwan_strbuf *lwan_strbuf_new_static(const char *str, @@ -124,12 +131,14 @@ ALWAYS_INLINE struct lwan_strbuf *lwan_strbuf_new_static(const char *str, { struct lwan_strbuf *s = malloc(sizeof(*s)); - if (!s) + if (UNLIKELY(!s)) return NULL; - s->flags = STATIC | DYNAMICALLY_ALLOCATED; - s->value.static_buffer = str; - s->used = size; + *s = (struct lwan_strbuf) { + .flags = STATIC | DYNAMICALLY_ALLOCATED, + .value.static_buffer = str, + .used = size + }; return s; } @@ -260,7 +269,7 @@ bool lwan_strbuf_grow_by(struct lwan_strbuf *s, size_t offset) void lwan_strbuf_reset(struct lwan_strbuf *s) { if (s->flags & STATIC) { - s->value.buffer = ""; + s->value.static_buffer = ""; } else { s->value.buffer[0] = '\0'; } From 8d947d8773cdc35b5e0988557a162bc769b54eef Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 21 Oct 2019 18:50:37 -0700 Subject: [PATCH 1223/2505] Add tool to dump a configuration file using the config API --- README.md | 1 + src/bin/tools/CMakeLists.txt | 10 +++- src/bin/tools/configdump.c | 101 +++++++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 src/bin/tools/configdump.c diff --git a/README.md b/README.md index 7606fbdcc..323e49896 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,7 @@ This will generate a few binaries: - `src/samples/clock/clock`: [Clock sample](https://time.lwan.ws). Generates a GIF file that always shows the local time. - `src/bin/tools/mimegen`: Builds the extension-MIME type table. Used during build process. - `src/bin/tools/bin2hex`: Generates a C file from a binary file, suitable for use with #include. + - `src/bin/tools/configdump`: Dumps a configuration file using the configuration reader API. #### Remarks diff --git a/src/bin/tools/CMakeLists.txt b/src/bin/tools/CMakeLists.txt index c9dc34354..cf0ce69c9 100644 --- a/src/bin/tools/CMakeLists.txt +++ b/src/bin/tools/CMakeLists.txt @@ -27,5 +27,13 @@ else () bin2hex.c ) - export(TARGETS mimegen bin2hex FILE ${CMAKE_BINARY_DIR}/ImportExecutables.cmake) + add_executable(configdump + configdump.c + ${CMAKE_SOURCE_DIR}/src/lib/lwan-config.c + ${CMAKE_SOURCE_DIR}/src/lib/lwan-status.c + ${CMAKE_SOURCE_DIR}/src/lib/lwan-strbuf.c + ) + + export(TARGETS configdump mimegen bin2hex FILE ${CMAKE_BINARY_DIR}/ImportExecutables.cmake) + endif () diff --git a/src/bin/tools/configdump.c b/src/bin/tools/configdump.c new file mode 100644 index 000000000..84d3edea1 --- /dev/null +++ b/src/bin/tools/configdump.c @@ -0,0 +1,101 @@ +/* + * lwan - simple web server + * Copyright (c) 2019 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include + +#include "lwan-config.h" +#include "lwan-status.h" + +static void indent(int level) +{ + for (int i = 0; i < level; i++) { + putchar(' '); + putchar(' '); + } +} + +static void +dump(struct config *config, struct config_line *line, int indent_level) +{ + if (indent_level > 64) { + lwan_status_critical("Indent level %d above limit, aborting", + indent_level); + return; + } + + while (config_read_line(config, line)) { + switch (line->type) { + case CONFIG_LINE_TYPE_LINE: + indent(indent_level); + + if (strchr(line->value, '\n')) + printf("%s = '''%s'''\n", line->key, line->value); + else + printf("%s = %s\n", line->key, line->value); + break; + + case CONFIG_LINE_TYPE_SECTION_END: + if (indent_level == 0) + lwan_status_critical("Section ended before it started"); + return; + + case CONFIG_LINE_TYPE_SECTION: + indent(indent_level); + printf("%s %s {\n", line->key, line->value); + + dump(config, line, indent_level + 1); + + indent(indent_level); + printf("}\n"); + break; + } + } + + if (config_last_error(config)) { + lwan_status_critical("Error while reading configuration file (line %d): %s", + config_cur_line(config), + config_last_error(config)); + } +} + +int main(int argc, char *argv[]) +{ + struct config *config; + struct config_line line; + int indent_level = 0; + + if (argc < 2) { + lwan_status_critical("Usage: %s /path/to/config/file.conf", argv[0]); + return 1; + } + + config = config_open(argv[1]); + if (!config) { + lwan_status_critical_perror("Could not open configuration file %s", + argv[1]); + return 1; + } + + dump(config, &line, indent_level); + + config_close(config); + + return 0; +} From c9ac13203b6685d3bf89ab6d257b37dd6b0d5833 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 21 Oct 2019 18:54:31 -0700 Subject: [PATCH 1224/2505] Add fuzzer for configuration file reader --- fuzz/corpus/corpus-config-234910329 | 125 ++++++++++++++++++++++++++++ src/bin/fuzz/config_fuzzer.cc | 62 ++++++++++++++ src/lib/lwan-config.c | 53 ++++++++++-- src/lib/lwan-config.h | 12 +++ 4 files changed, 244 insertions(+), 8 deletions(-) create mode 100644 fuzz/corpus/corpus-config-234910329 create mode 100644 src/bin/fuzz/config_fuzzer.cc diff --git a/fuzz/corpus/corpus-config-234910329 b/fuzz/corpus/corpus-config-234910329 new file mode 100644 index 000000000..5fc1d7064 --- /dev/null +++ b/fuzz/corpus/corpus-config-234910329 @@ -0,0 +1,125 @@ +# Timeout in seconds to keep a connection alive. +keep_alive_timeout = ${KEEP_ALIVE_TIMEOUT:15} + +# Set to true to not print any debugging messages. (Only effective in +# release builds.) +quiet = false + +# Set SO_REUSEPORT=1 in the master socket. +reuse_port = false + +# Value of "Expires" header. Default is 1 month and 1 week. +expires = 1M 1w + +# Number of I/O threads. Default (0) is number of online CPUs. +threads = 0 + +# This flag is enabled here so that the automated tests can be executed +# properly, but should be disabled unless absolutely needed (an example +# would be haproxy). +proxy_protocol = true + +# Maximum post data size of slightly less than 1MiB. The default is too +# small for testing purposes. +max_post_data_size = 1000000 + +# Enable straitjacket by default. The `drop_capabilities` option is `true` +# by default. Other options may require more privileges. +straitjacket + +listener *:8080 { + &custom_header /customhdr + + &sleep /sleep + + &hello_world /hello + + &quit_lwan /quit-lwan + + &test_proxy /proxy + + &test_chunked_encoding /chunked + + &test_server_sent_event /sse + + &gif_beacon /beacon + + &gif_beacon /favicon.ico + + &test_post_will_it_blend /post/blend + + &test_post_big /post/big + + redirect /elsewhere { to = http://lwan.ws } + + redirect /redirect307 { + to = http://lwan.ws + code = 307 + } + + rewrite /read-env { + pattern user { rewrite as = /hello?name=${USER} } + } + + response /brew-coffee { code = 418 } + + &hello_world /admin { + authorization basic { + realm = Administration Page + password file = htpasswd + } + } + lua /inline { + default type = text/html + cache period = 30s + script = '''function handle_get_root(req) + req:say('Hello') + end''' + } + lua /lua { + default type = text/html + script file = test.lua + cache period = 30s + } + lua /luawait { + script='''function handle_get_root(req) + local ms = req:query_param[[ms]] + if not ms then ms = 1234 end + req:say("sleeping "..ms.."ms") + req:sleep(ms) + req:say("slept") + end''' + } + rewrite /pattern { + pattern foo/(%d+)(%a)(%d+) { + redirect to = /hello?name=pre%2middle%3othermiddle%1post + } + pattern bar/(%d+)/test { + rewrite as = /hello?name=rewritten%1 + } + pattern lua/redir/(%d+)x(%d+) { + expand_with_lua = true + redirect to = ''' + function handle_rewrite(req, captures) + local r = captures[1] * captures[2] + return '/hello?name=redirected' .. r + end + ''' + } + pattern lua/rewrite/(%d+)x(%d+) { + expand_with_lua = true + rewrite as = """function handle_rewrite(req, captures) + local r = captures[1] * captures[2] + return '/hello?name=rewritten' .. r + end""" + } + } + serve_files / { + path = ./wwwroot + + # When requesting for file.ext, look for a smaller/newer file.ext.gz, + # and serve that instead if `Accept-Encoding: gzip` is in the + # request headers. + serve precompressed files = true + } +} diff --git a/src/bin/fuzz/config_fuzzer.cc b/src/bin/fuzz/config_fuzzer.cc new file mode 100644 index 000000000..9c10a47f9 --- /dev/null +++ b/src/bin/fuzz/config_fuzzer.cc @@ -0,0 +1,62 @@ +#include +#include +#include + +#include "lwan-config.h" + +static bool +dump(struct config *config, struct config_line *line, int indent_level) +{ + if (indent_level > 64) + return false; + + while (config_read_line(config, line)) { + switch (line->type) { + case CONFIG_LINE_TYPE_LINE: + break; + + case CONFIG_LINE_TYPE_SECTION_END: + if (indent_level == 0) + return false; + + return true; + + case CONFIG_LINE_TYPE_SECTION: + if (!dump(config, line, indent_level + 1)) + return false; + + break; + } + } + + if (config_last_error(config)) { + fprintf(stderr, + "Error while reading configuration file (line %d): %s\n", + config_cur_line(config), config_last_error(config)); + return false; + } + + return true; +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + static uint8_t static_data[32768]; + struct config *config; + struct config_line line; + int indent_level = 0; + + if (size > sizeof(static_data)) + size = sizeof(static_data); + memcpy(static_data, data, size); + + config = config_open_for_fuzzing(static_data, size); + if (!config) + return 1; + + bool dumped = dump(config, &line, indent_level); + + config_close(config); + + return dumped ? 1 : 0; +} diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 5ed897c9b..ab370aaaf 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -690,11 +690,12 @@ static bool parser_next(struct parser *parser, struct config_line **line) return config_buffer_consume(&parser->items, line); } -struct config *config_open(const char *path) +static struct config * +config_open_path(const char *path, void **data, size_t *size) { struct config *config; struct stat st; - void *data; + void *mapped; int fd; fd = open(path, O_RDONLY | O_CLOEXEC); @@ -708,29 +709,37 @@ struct config *config_open(const char *path) return NULL; } - data = mmap(NULL, (size_t)st.st_size, PROT_READ, MAP_SHARED, fd, 0); + mapped = mmap(NULL, (size_t)st.st_size, PROT_READ, MAP_SHARED, fd, 0); close(fd); - if (data == MAP_FAILED) + if (mapped == MAP_FAILED) return NULL; config = malloc(sizeof(*config)); if (!config) { - munmap(data, (size_t)st.st_size); + munmap(mapped, (size_t)st.st_size); return NULL; } + *data = config->mapped.addr = mapped; + *size = config->mapped.sz = (size_t)st.st_size; + + return config; +} + +static struct config *config_init_data(struct config *config, + void *data, size_t len) +{ config->parser = (struct parser) { .state = parse_config, .lexer = { .state = lex_config, .pos = data, .start = data, - .end = (char *)data + st.st_size, + .end = (char *)data + len, .cur_line = 1, } }; - config->mapped.addr = data; - config->mapped.sz = (size_t)st.st_size; + config->error_message = NULL; lwan_strbuf_init(&config->parser.strbuf); @@ -740,13 +749,41 @@ struct config *config_open(const char *path) return config; } +struct config *config_open(const char *path) +{ + struct config *config; + void *data; + size_t len; + + config = config_open_path(path, &data, &len); + return config ? config_init_data(config, data, len) : NULL; +} + +#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) +struct config *config_open_for_fuzzing(void *data, size_t len) +{ + struct config *config = malloc(sizeof(*config)); + + if (config) { + config->mapped.addr = NULL; + config->mapped.sz = 0; + + return config_init_data(config, data, len); + } + + return NULL; +} +#endif + void config_close(struct config *config) { if (!config) return; +#if !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) if (config->mapped.addr) munmap(config->mapped.addr, config->mapped.sz); +#endif free(config->error_message); lwan_strbuf_free(&config->parser.strbuf); diff --git a/src/lib/lwan-config.h b/src/lib/lwan-config.h index fe2efa0f5..be8428776 100644 --- a/src/lib/lwan-config.h +++ b/src/lib/lwan-config.h @@ -19,6 +19,10 @@ #pragma once +#if defined(__cplusplus) +extern "C" { +#endif + #define ONE_MINUTE 60 #define ONE_HOUR (ONE_MINUTE * 60) #define ONE_DAY (ONE_HOUR * 24) @@ -63,3 +67,11 @@ bool parse_bool(const char *value, bool default_value); long parse_long(const char *value, long default_value); int parse_int(const char *value, int default_value); unsigned int parse_time_period(const char *str, unsigned int default_value); + +#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) +struct config *config_open_for_fuzzing(void *data, size_t len); +#endif + +#if defined(__cplusplus) +} +#endif From f3aaf8f603b69f89343c06b33d234dd0a1ea8149 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 21 Oct 2019 20:45:31 -0700 Subject: [PATCH 1225/2505] Fix build on macOS --- src/bin/tools/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/bin/tools/CMakeLists.txt b/src/bin/tools/CMakeLists.txt index cf0ce69c9..6eef514b2 100644 --- a/src/bin/tools/CMakeLists.txt +++ b/src/bin/tools/CMakeLists.txt @@ -32,8 +32,10 @@ else () ${CMAKE_SOURCE_DIR}/src/lib/lwan-config.c ${CMAKE_SOURCE_DIR}/src/lib/lwan-status.c ${CMAKE_SOURCE_DIR}/src/lib/lwan-strbuf.c + ${CMAKE_SOURCE_DIR}/src/lib/hash.c + ${CMAKE_SOURCE_DIR}/src/lib/murmur3.c + ${CMAKE_SOURCE_DIR}/src/lib/missing.c ) export(TARGETS configdump mimegen bin2hex FILE ${CMAKE_BINARY_DIR}/ImportExecutables.cmake) - endif () From 8d0a7dc8e9590f7d2c941460a4d9e95c1791fac2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 23 Oct 2019 07:30:02 -0700 Subject: [PATCH 1226/2505] Get rid of misspelled check for INSTURMENT_FOR_FUZZING This is a remnant from the older way of building fuzzers for Lwan (it's now built by oss-fuzz with a build script in another repository.) --- src/CMakeLists.txt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c80f7c315..00589b92f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,6 +1,3 @@ add_subdirectory(lib) add_subdirectory(bin) - -if (NOT INSTURMENT_FOR_FUZZING) - add_subdirectory(samples) -endif () +add_subdirectory(samples) From 3289a66db7145c081ff1d5fa987f0cfec0563dc5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 23 Oct 2019 07:34:12 -0700 Subject: [PATCH 1227/2505] Check for lexeme length while consuming key/value in config reader Maybe fix https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=18448 Thanks to oss-fuzz. --- src/lib/lwan-config.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index ab370aaaf..60f2051d1 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -500,6 +500,9 @@ static void *parse_key_value(struct parser *parser) size_t key_size; while (lexeme_buffer_consume(&parser->buffer, &lexeme)) { + if (lexeme->value.len == 0) + continue; + lwan_strbuf_append_str(&parser->strbuf, lexeme->value.value, lexeme->value.len); From ab882fe013ec880425bd918e0bcbf9e798361585 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 23 Oct 2019 08:58:54 -0700 Subject: [PATCH 1228/2505] Fix buffer overflow in config file reader fuzzer Maybe fix: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=18464 https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=18447 --- src/lib/lwan-config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 60f2051d1..54ed4e5ed 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -771,7 +771,7 @@ struct config *config_open_for_fuzzing(void *data, size_t len) config->mapped.addr = NULL; config->mapped.sz = 0; - return config_init_data(config, data, len); + return config_init_data(config, data, len - 1); } return NULL; From a66a5418cb1da037747b28c5266f9214dc42c04b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 24 Oct 2019 07:07:40 -0700 Subject: [PATCH 1229/2505] Fix infinite loop when parsing variables with default values Fixes https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=18442 --- src/lib/lwan-config.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 54ed4e5ed..e19249c1c 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -360,14 +360,22 @@ static bool isvariable(int chr) static void *lex_variable_default(struct lexer *lexer) { - while (next(lexer) != '}') - ; - backup(lexer); - emit(lexer, LEXEME_STRING); + int chr; - advance_n(lexer, strlen("}")); + do { + chr = next(lexer); - return lex_config; + if (chr == '}') { + backup(lexer); + emit(lexer, LEXEME_STRING); + + advance_n(lexer, strlen("}")); + + return lex_config; + } + } while (chr != '\0'); + + return lex_error(lexer, "EOF while scanning for end of variable"); } static void *lex_variable(struct lexer *lexer) From 742ecf1c535b0b3b1350253faef6289fdc8f8e12 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 25 Oct 2019 07:20:01 -0700 Subject: [PATCH 1230/2505] Fix crash while parsing section Fixes https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=18448 --- src/lib/lwan-config.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index e19249c1c..bf282b23e 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -606,6 +606,11 @@ static void *parse_section(struct parser *parser) if (!lexeme_buffer_consume(&parser->buffer, &lexeme)) return NULL; + if (!lexeme->value.len) { + lwan_status_error("Section is empty"); + return NULL; + } + lwan_strbuf_append_str(&parser->strbuf, lexeme->value.value, lexeme->value.len); name_len = lexeme->value.len; lwan_strbuf_append_char(&parser->strbuf, '\0'); From 56c5aae6cb30c0247596d72916c8f2f47d6abde3 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 26 Oct 2019 08:31:53 -0700 Subject: [PATCH 1231/2505] Do not assume that an empty length means "calculate length of string" Add new strbuf APIs specifically for this purpose. This should properly fix issues found by OSS-Fuzz in the configuration file parser (and remove branches/strlen() calls where it's not really needed!) --- src/bin/testrunner/main.c | 14 +++++++------- src/lib/liblwan.sym | 3 +++ src/lib/lwan-config.c | 12 ++---------- src/lib/lwan-strbuf.c | 9 --------- src/lib/lwan-strbuf.h | 24 ++++++++++++++++++++++-- src/lib/lwan-template.c | 2 +- 6 files changed, 35 insertions(+), 29 deletions(-) diff --git a/src/bin/testrunner/main.c b/src/bin/testrunner/main.c index ff636973d..6a61c5332 100644 --- a/src/bin/testrunner/main.c +++ b/src/bin/testrunner/main.c @@ -175,16 +175,16 @@ LWAN_HANDLER(hello_world) if (!dump_vars) goto end; - lwan_strbuf_append_str(response->buffer, "\n\nCookies\n", 0); - lwan_strbuf_append_str(response->buffer, "-------\n\n", 0); + lwan_strbuf_append_strz(response->buffer, "\n\nCookies\n"); + lwan_strbuf_append_strz(response->buffer, "-------\n\n"); LWAN_ARRAY_FOREACH(lwan_request_get_cookies(request), iter) { lwan_strbuf_append_printf(response->buffer, "Key = \"%s\"; Value = \"%s\"\n", iter->key, iter->value); } - lwan_strbuf_append_str(response->buffer, "\n\nQuery String Variables\n", 0); - lwan_strbuf_append_str(response->buffer, "----------------------\n\n", 0); + lwan_strbuf_append_strz(response->buffer, "\n\nQuery String Variables\n"); + lwan_strbuf_append_strz(response->buffer, "----------------------\n\n"); LWAN_ARRAY_FOREACH(lwan_request_get_query_params(request), iter) { lwan_strbuf_append_printf(response->buffer, @@ -194,8 +194,8 @@ LWAN_HANDLER(hello_world) if (lwan_request_get_method(request) != REQUEST_METHOD_POST) goto end; - lwan_strbuf_append_str(response->buffer, "\n\nPOST data\n", 0); - lwan_strbuf_append_str(response->buffer, "---------\n\n", 0); + lwan_strbuf_append_strz(response->buffer, "\n\nPOST data\n"); + lwan_strbuf_append_strz(response->buffer, "---------\n\n"); LWAN_ARRAY_FOREACH(lwan_request_get_post_params(request), iter) { lwan_strbuf_append_printf(response->buffer, @@ -232,7 +232,7 @@ LWAN_HANDLER(sleep) lwan_strbuf_printf(response->buffer, "Returned from sleep. diff_ms = %"PRIi64, diff_ms); } else { - lwan_strbuf_set_static(response->buffer, "Did not sleep", 0); + lwan_strbuf_set_staticz(response->buffer, "Did not sleep"); } return HTTP_OK; diff --git a/src/lib/liblwan.sym b/src/lib/liblwan.sym index adb504ce4..410cedb65 100644 --- a/src/lib/liblwan.sym +++ b/src/lib/liblwan.sym @@ -67,6 +67,7 @@ global: lwan_strbuf_append_char; lwan_strbuf_append_printf; lwan_strbuf_append_str; + lwan_strbuf_append_strz; lwan_strbuf_free; lwan_strbuf_init; lwan_strbuf_init_with_size; @@ -77,6 +78,8 @@ global: lwan_strbuf_reset; lwan_strbuf_set; lwan_strbuf_set_static; + lwan_strbuf_setz; + lwan_strbuf_set_staticz; lwan_module_info_*; diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index bf282b23e..670faea5d 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -508,9 +508,6 @@ static void *parse_key_value(struct parser *parser) size_t key_size; while (lexeme_buffer_consume(&parser->buffer, &lexeme)) { - if (lexeme->value.len == 0) - continue; - lwan_strbuf_append_str(&parser->strbuf, lexeme->value.value, lexeme->value.len); @@ -534,7 +531,7 @@ static void *parse_key_value(struct parser *parser) (int)lexeme->value.len, lexeme->value.value); return NULL; } else { - lwan_strbuf_append_str(&parser->strbuf, value, 0); + lwan_strbuf_append_strz(&parser->strbuf, value); } } else { struct lexeme *var_name = lexeme; @@ -559,7 +556,7 @@ static void *parse_key_value(struct parser *parser) lwan_strbuf_append_str(&parser->strbuf, lexeme->value.value, lexeme->value.len); } else { - lwan_strbuf_append_str(&parser->strbuf, value, 0); + lwan_strbuf_append_strz(&parser->strbuf, value); } } @@ -606,11 +603,6 @@ static void *parse_section(struct parser *parser) if (!lexeme_buffer_consume(&parser->buffer, &lexeme)) return NULL; - if (!lexeme->value.len) { - lwan_status_error("Section is empty"); - return NULL; - } - lwan_strbuf_append_str(&parser->strbuf, lexeme->value.value, lexeme->value.len); name_len = lexeme->value.len; lwan_strbuf_append_char(&parser->strbuf, '\0'); diff --git a/src/lib/lwan-strbuf.c b/src/lib/lwan-strbuf.c index 3fd004e09..99240829b 100644 --- a/src/lib/lwan-strbuf.c +++ b/src/lib/lwan-strbuf.c @@ -166,9 +166,6 @@ bool lwan_strbuf_append_char(struct lwan_strbuf *s, const char c) bool lwan_strbuf_append_str(struct lwan_strbuf *s1, const char *s2, size_t sz) { - if (!sz) - sz = strlen(s2); - if (UNLIKELY(!grow_buffer_if_needed(s1, s1->used + sz + 2))) return false; @@ -181,9 +178,6 @@ bool lwan_strbuf_append_str(struct lwan_strbuf *s1, const char *s2, size_t sz) bool lwan_strbuf_set_static(struct lwan_strbuf *s1, const char *s2, size_t sz) { - if (!sz) - sz = strlen(s2); - if (!(s1->flags & STATIC)) free(s1->value.buffer); @@ -196,9 +190,6 @@ bool lwan_strbuf_set_static(struct lwan_strbuf *s1, const char *s2, size_t sz) bool lwan_strbuf_set(struct lwan_strbuf *s1, const char *s2, size_t sz) { - if (!sz) - sz = strlen(s2); - if (UNLIKELY(!grow_buffer_if_needed(s1, sz + 1))) return false; diff --git a/src/lib/lwan-strbuf.h b/src/lib/lwan-strbuf.h index 927f29142..a3b0ed0ff 100644 --- a/src/lib/lwan-strbuf.h +++ b/src/lib/lwan-strbuf.h @@ -21,6 +21,7 @@ #pragma once #include +#include #include struct lwan_strbuf { @@ -42,12 +43,31 @@ void lwan_strbuf_free(struct lwan_strbuf *s); void lwan_strbuf_reset(struct lwan_strbuf *s); bool lwan_strbuf_append_char(struct lwan_strbuf *s, const char c); + bool lwan_strbuf_append_str(struct lwan_strbuf *s1, const char *s2, size_t sz); -bool lwan_strbuf_append_printf(struct lwan_strbuf *s, const char *fmt, ...) - __attribute__((format(printf, 2, 3))); +static inline bool lwan_strbuf_append_strz(struct lwan_strbuf *s1, + const char *s2) +{ + return lwan_strbuf_append_str(s1, s2, strlen(s2)); +} bool lwan_strbuf_set_static(struct lwan_strbuf *s1, const char *s2, size_t sz); +static inline bool lwan_strbuf_set_staticz(struct lwan_strbuf *s1, + const char *s2) +{ + return lwan_strbuf_set_static(s1, s2, strlen(s2)); +} + bool lwan_strbuf_set(struct lwan_strbuf *s1, const char *s2, size_t sz); +static inline bool lwan_strbuf_setz(struct lwan_strbuf *s1, const char *s2) +{ + return lwan_strbuf_set(s1, s2, strlen(s2)); +} + + +bool lwan_strbuf_append_printf(struct lwan_strbuf *s, const char *fmt, ...) + __attribute__((format(printf, 2, 3))); + bool lwan_strbuf_printf(struct lwan_strbuf *s1, const char *fmt, ...) __attribute__((format(printf, 2, 3))); diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index d4e6e459f..d803408eb 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -897,7 +897,7 @@ void lwan_append_str_to_strbuf(struct lwan_strbuf *buf, void *ptr) const char *str = *(char **)ptr; if (LIKELY(str)) - lwan_strbuf_append_str(buf, str, 0); + lwan_strbuf_append_strz(buf, str); } void lwan_append_str_escaped_to_strbuf(struct lwan_strbuf *buf, void *ptr) From e09f4084c3edaa1565daa1cc652872610ef1b445 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 26 Oct 2019 08:33:12 -0700 Subject: [PATCH 1232/2505] Cleanup initial flags calculation in process_request_coro() --- src/lib/lwan-thread.c | 6 +++--- src/lib/lwan.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 2b3e03426..0684e076e 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -117,15 +117,15 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, char request_buffer[DEFAULT_BUFFER_SIZE]; struct lwan_value buffer = {.value = request_buffer, .len = 0}; char *next_request = NULL; - enum lwan_request_flags flags = 0; struct lwan_proxy proxy; if (UNLIKELY(!lwan_strbuf_init(&strbuf))) goto out; coro_defer(coro, lwan_strbuf_free_defer, &strbuf); - flags |= REQUEST_FLAG(proxy_protocol, ALLOW_PROXY_REQS) | - REQUEST_FLAG(allow_cors, ALLOW_CORS); + enum lwan_request_flags flags = + REQUEST_FLAG(proxy_protocol, ALLOW_PROXY_REQS) | + REQUEST_FLAG(allow_cors, ALLOW_CORS); while (true) { struct lwan_request request = {.conn = conn, diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 6cc524584..58cd94af2 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -210,7 +210,7 @@ enum lwan_request_flags { * request-processing coroutine. * * Allows this: if (some_boolean) flags |= SOME_FLAG; - * To turn into: flags |= some_boolean << SOME_FLAG_SHIFT; + * To turn into: flags |= (uint32_t)some_boolean << SOME_FLAG_SHIFT; */ REQUEST_ALLOW_PROXY_REQS_SHIFT = 7, REQUEST_ALLOW_CORS_SHIFT = 9, From 2e932a0872344f54ec3732efa4523ac8eec4cb82 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 26 Oct 2019 08:35:30 -0700 Subject: [PATCH 1233/2505] Cleanups in fuzz_parse_http_request() / use NO_DISCARD() more --- src/lib/lwan-request.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index fa0c9ce96..4797e0cdf 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1711,7 +1711,6 @@ __attribute__((used)) int fuzz_parse_http_request(const uint8_t *data, data_copy[length - 1] = '\0'; if (parse_http_request(&request) == HTTP_OK) { - const char *trash0; off_t trash1; time_t trash2; size_t gen = coro_deferred_get_generation(coro); @@ -1724,7 +1723,7 @@ __attribute__((used)) int fuzz_parse_http_request(const uint8_t *data, #define NO_DISCARD(...) \ do { \ - const char *no_discard_ = __VA_ARGS__; \ + typeof(__VA_ARGS__) no_discard_ = __VA_ARGS__; \ __asm__ __volatile__("" ::"g"(no_discard_) : "memory"); \ } while (0) @@ -1736,10 +1735,13 @@ __attribute__((used)) int fuzz_parse_http_request(const uint8_t *data, NO_DISCARD(lwan_request_get_post_param(&request, "Non-Existing-Post-Param")); NO_DISCARD(fuzz_websocket_handshake(&request)); -#undef NO_DISCARD - lwan_request_get_range(&request, &trash1, &trash1); + NO_DISCARD(trash1); + lwan_request_get_if_modified_since(&request, &trash2); + NO_DISCARD(trash2); + +#undef NO_DISCARD coro_deferred_run(coro, gen); } From 604eb733ecdbe8f637787825223c4c4ba5a3459c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 27 Oct 2019 08:40:32 -0700 Subject: [PATCH 1234/2505] Ensure there's enough characters while looking for string terminator Fixes https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=18562 --- src/lib/lwan-config.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 670faea5d..b34984163 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -289,6 +289,11 @@ static int peek(struct lexer *lexer) return chr; } +static size_t remaining(struct lexer *lexer) +{ + return (size_t)(lexer->end - lexer->pos); +} + static void *lex_config(struct lexer *lexer); static void *lex_variable(struct lexer *lexer); @@ -337,11 +342,14 @@ static void *lex_error(struct lexer *lexer, const char *msg) static void *lex_multiline_string(struct lexer *lexer) { - char *end = (peek(lexer) == '"') ? "\"\"\"" : "'''"; + const char *end = (peek(lexer) == '"') ? "\"\"\"" : "'''"; advance_n(lexer, strlen("'''") - 1); do { + if (remaining(lexer) < 3) + break; + if (!strncmp(lexer->pos, end, 3)) { emit(lexer, LEXEME_STRING); lexer->pos += 3; From 1bc152cb11ef20d73d2bf974f482e8114be1e124 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 27 Oct 2019 20:57:29 -0700 Subject: [PATCH 1235/2505] Reindent lwan-http-authorize.[ch] --- src/lib/lwan-http-authorize.c | 54 +++++++++++++++-------------------- src/lib/lwan-http-authorize.h | 12 ++++---- 2 files changed, 29 insertions(+), 37 deletions(-) diff --git a/src/lib/lwan-http-authorize.c b/src/lib/lwan-http-authorize.c index 079329d1f..091c6a04d 100644 --- a/src/lib/lwan-http-authorize.c +++ b/src/lib/lwan-http-authorize.c @@ -14,7 +14,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include @@ -40,14 +41,13 @@ static void fourty_two_and_free(void *str) char *s = str; while (*s) *s++ = 42; - __asm__ __volatile__("" :: "g"(s) : "memory"); + __asm__ __volatile__("" ::"g"(s) : "memory"); free(str); } } -static struct cache_entry *create_realm_file( - const char *key, - void *context __attribute__((unused))) +static struct cache_entry * +create_realm_file(const char *key, void *context __attribute__((unused))) { struct realm_password_file_t *rpf = malloc(sizeof(*rpf)); struct config *f; @@ -87,8 +87,7 @@ static struct cache_entry *create_realm_file( if (err == -EEXIST) { lwan_status_warning( - "Username entry already exists, ignoring: \"%s\"", - l.key); + "Username entry already exists, ignoring: \"%s\"", l.key); continue; } @@ -101,8 +100,8 @@ static struct cache_entry *create_realm_file( } if (config_last_error(f)) { - lwan_status_error("Error on password file \"%s\", line %d: %s", - key, config_cur_line(f), config_last_error(f)); + lwan_status_error("Error on password file \"%s\", line %d: %s", key, + config_cur_line(f), config_last_error(f)); goto error; } @@ -118,32 +117,26 @@ static struct cache_entry *create_realm_file( } static void destroy_realm_file(struct cache_entry *entry, - void *context __attribute__((unused))) + void *context __attribute__((unused))) { struct realm_password_file_t *rpf = (struct realm_password_file_t *)entry; hash_free(rpf->entries); free(rpf); } -bool -lwan_http_authorize_init(void) +bool lwan_http_authorize_init(void) { - realm_password_cache = cache_create(create_realm_file, - destroy_realm_file, NULL, 60); + realm_password_cache = + cache_create(create_realm_file, destroy_realm_file, NULL, 60); return !!realm_password_cache; } -void -lwan_http_authorize_shutdown(void) -{ - cache_destroy(realm_password_cache); -} +void lwan_http_authorize_shutdown(void) { cache_destroy(realm_password_cache); } -static bool -authorize(struct coro *coro, - struct lwan_value *authorization, - const char *password_file) +static bool authorize(struct coro *coro, + struct lwan_value *authorization, + const char *password_file) { struct realm_password_file_t *rpf; unsigned char *decoded; @@ -154,7 +147,7 @@ authorize(struct coro *coro, bool password_ok = false; rpf = (struct realm_password_file_t *)cache_coro_get_and_ref_entry( - realm_password_cache, coro, password_file); + realm_password_cache, coro, password_file); if (UNLIKELY(!rpf)) return false; @@ -179,11 +172,10 @@ authorize(struct coro *coro, return password_ok; } -bool -lwan_http_authorize(struct lwan_request *request, - struct lwan_value *authorization, - const char *realm, - const char *password_file) +bool lwan_http_authorize(struct lwan_request *request, + struct lwan_value *authorization, + const char *realm, + const char *password_file) { static const char authenticate_tmpl[] = "Basic realm=\"%s\""; static const size_t basic_len = sizeof("Basic ") - 1; @@ -207,8 +199,8 @@ lwan_http_authorize(struct lwan_request *request, return false; headers[0].key = "WWW-Authenticate"; - headers[0].value = coro_printf(request->conn->coro, - authenticate_tmpl, realm); + headers[0].value = + coro_printf(request->conn->coro, authenticate_tmpl, realm); headers[1].key = headers[1].value = NULL; request->response.headers = headers; diff --git a/src/lib/lwan-http-authorize.h b/src/lib/lwan-http-authorize.h index b56bc5986..81bf36c32 100644 --- a/src/lib/lwan-http-authorize.h +++ b/src/lib/lwan-http-authorize.h @@ -14,7 +14,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #pragma once @@ -24,8 +25,7 @@ bool lwan_http_authorize_init(void); void lwan_http_authorize_shutdown(void); -bool -lwan_http_authorize(struct lwan_request *request, - struct lwan_value *authorization, - const char *realm, - const char *password_file); +bool lwan_http_authorize(struct lwan_request *request, + struct lwan_value *authorization, + const char *realm, + const char *password_file); From d96d404d91601ad65c02e33bf298b69b005dcb08 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 27 Oct 2019 20:58:08 -0700 Subject: [PATCH 1236/2505] Fuzz-test HTTP authorization routines too --- src/lib/lwan-request.c | 41 +++++++++++++++++++++++++++-------------- src/lib/lwan.h | 2 +- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 4797e0cdf..c0bb124ec 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1313,25 +1313,29 @@ lwan_request_websocket_upgrade(struct lwan_request *request) return HTTP_SWITCHING_PROTOCOLS; } +static bool authorize(struct lwan_request *request, + const struct lwan_url_map *url_map) +{ + const char *authorization = + lwan_request_get_header(request, "Authorization"); + struct lwan_value header = { + .value = (char *)authorization, + .len = authorization ? strlen(authorization) : 0, + }; + + return lwan_http_authorize(request, &header, url_map->authorization.realm, + url_map->authorization.password_file); +} + static enum lwan_http_status prepare_for_response(struct lwan_url_map *url_map, struct lwan_request *request) { request->url.value += url_map->prefix_len; request->url.len -= url_map->prefix_len; - if (url_map->flags & HANDLER_MUST_AUTHORIZE) { - const char *authorization = - lwan_request_get_header(request, "Authorization"); - struct lwan_value header = { - .value = (char*)authorization, - .len = authorization ? strlen(authorization) : 0, - }; - - if (!lwan_http_authorize(request, &header, - url_map->authorization.realm, - url_map->authorization.password_file)) - return HTTP_NOT_AUTHORIZED; - } + if (UNLIKELY(url_map->flags & HANDLER_MUST_AUTHORIZE && + !authorize(request, url_map))) + return HTTP_NOT_AUTHORIZED; while (*request->url.value == '/' && request->url.len > 0) { request->url.value++; @@ -1479,7 +1483,7 @@ const char *lwan_request_get_cookie(struct lwan_request *request, return value_lookup(lwan_request_get_cookies(request), key); } -const char *lwan_request_get_header(struct lwan_request *request, +const char *lwan_request_get_header(const struct lwan_request *request, const char *header) { char name[64]; @@ -1741,6 +1745,15 @@ __attribute__((used)) int fuzz_parse_http_request(const uint8_t *data, lwan_request_get_if_modified_since(&request, &trash2); NO_DISCARD(trash2); + /* FIXME: Write to a temporary file with bogus bug valid data? */ + struct lwan_url_map url_map = { + .authorization = { + .realm = "Fuzzy Realm", + .password_file = "/dev/null", + }, + }; + NO_DISCARD(authorize(&request, &url_map)); + #undef NO_DISCARD coro_deferred_run(coro, gen); diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 58cd94af2..a09f89c49 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -476,7 +476,7 @@ const char *lwan_request_get_query_param(struct lwan_request *request, const char *lwan_request_get_cookie(struct lwan_request *request, const char *key) __attribute__((warn_unused_result, pure)); -const char *lwan_request_get_header(struct lwan_request *request, +const char *lwan_request_get_header(const struct lwan_request *request, const char *header) __attribute__((warn_unused_result)); From 0710a8ea985e819a153d56e9d5448c0c2d91a124 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 27 Oct 2019 23:09:56 -0700 Subject: [PATCH 1237/2505] Remove compile warning when building Lua bindings Partially reverts d96d404d. --- src/lib/lwan-request.c | 2 +- src/lib/lwan.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index c0bb124ec..4d53ce6d2 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1483,7 +1483,7 @@ const char *lwan_request_get_cookie(struct lwan_request *request, return value_lookup(lwan_request_get_cookies(request), key); } -const char *lwan_request_get_header(const struct lwan_request *request, +const char *lwan_request_get_header(struct lwan_request *request, const char *header) { char name[64]; diff --git a/src/lib/lwan.h b/src/lib/lwan.h index a09f89c49..58cd94af2 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -476,7 +476,7 @@ const char *lwan_request_get_query_param(struct lwan_request *request, const char *lwan_request_get_cookie(struct lwan_request *request, const char *key) __attribute__((warn_unused_result, pure)); -const char *lwan_request_get_header(const struct lwan_request *request, +const char *lwan_request_get_header(struct lwan_request *request, const char *header) __attribute__((warn_unused_result)); From 402769206dfcd5cf348a844eafc679f684d29d05 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 27 Oct 2019 23:44:10 -0700 Subject: [PATCH 1238/2505] Add fuzzer for Lua pattern matching routines --- src/bin/fuzz/pattern_fuzzer.cc | 43 ++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/bin/fuzz/pattern_fuzzer.cc diff --git a/src/bin/fuzz/pattern_fuzzer.cc b/src/bin/fuzz/pattern_fuzzer.cc new file mode 100644 index 000000000..875c82707 --- /dev/null +++ b/src/bin/fuzz/pattern_fuzzer.cc @@ -0,0 +1,43 @@ +#include +#include +#include + +extern "C" { +#include "patterns.h" +#include "lwan-private.h" +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + struct str_find sf[16]; + static uint8_t static_data[32768]; + struct config *config; + struct config_line line; + int indent_level = 0; + const char *errmsg; + + if (size == 0) + return 1; + + if (size > sizeof(static_data)) + size = sizeof(static_data); + memcpy(static_data, data, size); + static_data[size - 1] = '\0'; + +#define NO_DISCARD(...) \ + do { \ + __typeof__(__VA_ARGS__) no_discard_ = __VA_ARGS__; \ + __asm__ __volatile__("" ::"g"(no_discard_) : "memory"); \ + } while (0) + + NO_DISCARD(str_find((char *)static_data, "foo/(%d+)(%a)(%d+)", sf, + N_ELEMENTS(sf), &errmsg)); + NO_DISCARD(str_find((char *)static_data, "bar/(%d+)/test", sf, + N_ELEMENTS(sf), &errmsg)); + NO_DISCARD(str_find((char *)static_data, "lua/rewrite/(%d+)x(%d+)", sf, + N_ELEMENTS(sf), &errmsg)); + +#undef NO_DISCARD + + return 0; +} From a67942e1333e23ec50313f42b4e60d5ead01e6c7 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 28 Oct 2019 00:12:32 -0700 Subject: [PATCH 1239/2505] Use hardcoded user/password list when fuzzing --- src/lib/lwan-http-authorize.c | 7 +++++++ src/lib/lwan-request.c | 4 +++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-http-authorize.c b/src/lib/lwan-http-authorize.c index 091c6a04d..4f0d5f59d 100644 --- a/src/lib/lwan-http-authorize.c +++ b/src/lib/lwan-http-authorize.c @@ -60,7 +60,14 @@ create_realm_file(const char *key, void *context __attribute__((unused))) if (UNLIKELY(!rpf->entries)) goto error_no_close; +#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) + static char hardcoded_user_config[] = "user=password\n" + "root=hunter2\n"; + f = config_open_for_fuzzing(hardcoded_user_config, + sizeof(hardcoded_user_config)); +#else f = config_open(key); +#endif if (!f) goto error_no_close; diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 4d53ce6d2..e1a2ba68d 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1737,6 +1737,7 @@ __attribute__((used)) int fuzz_parse_http_request(const uint8_t *data, NO_DISCARD(lwan_request_get_cookie(&request, "FOO")); /* Set by some tests */ NO_DISCARD(lwan_request_get_query_param(&request, "Non-Existing-Query-Param")); NO_DISCARD(lwan_request_get_post_param(&request, "Non-Existing-Post-Param")); + NO_DISCARD(fuzz_websocket_handshake(&request)); lwan_request_get_range(&request, &trash1, &trash1); @@ -1745,10 +1746,11 @@ __attribute__((used)) int fuzz_parse_http_request(const uint8_t *data, lwan_request_get_if_modified_since(&request, &trash2); NO_DISCARD(trash2); - /* FIXME: Write to a temporary file with bogus bug valid data? */ struct lwan_url_map url_map = { .authorization = { .realm = "Fuzzy Realm", + /* The file name doesn't matter here, a hardcoded user/password + * list will be used instead if Lwan is built for fuzzing. */ .password_file = "/dev/null", }, }; From d31cc0b539868cf4a738b26c05d59be299aa178d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 28 Oct 2019 22:28:15 -0700 Subject: [PATCH 1240/2505] Don't copy configuration file input while fuzzing The parser doesn't modify the data, so it's safe to use the const uint8_t* directly. --- src/bin/fuzz/config_fuzzer.cc | 7 +------ src/lib/lwan-config.c | 23 ++++++++++++----------- src/lib/lwan-config.h | 3 ++- src/lib/lwan-http-authorize.c | 4 ++-- 4 files changed, 17 insertions(+), 20 deletions(-) diff --git a/src/bin/fuzz/config_fuzzer.cc b/src/bin/fuzz/config_fuzzer.cc index 9c10a47f9..b9b26f136 100644 --- a/src/bin/fuzz/config_fuzzer.cc +++ b/src/bin/fuzz/config_fuzzer.cc @@ -41,16 +41,11 @@ dump(struct config *config, struct config_line *line, int indent_level) extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { - static uint8_t static_data[32768]; struct config *config; struct config_line line; int indent_level = 0; - if (size > sizeof(static_data)) - size = sizeof(static_data); - memcpy(static_data, data, size); - - config = config_open_for_fuzzing(static_data, size); + config = config_open_for_fuzzing(data, size); if (!config) return 1; diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index b34984163..78ec2ffdd 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -742,18 +742,19 @@ config_open_path(const char *path, void **data, size_t *size) return config; } -static struct config *config_init_data(struct config *config, - void *data, size_t len) +static struct config * +config_init_data(struct config *config, const void *data, size_t len) { - config->parser = (struct parser) { + config->parser = (struct parser){ .state = parse_config, - .lexer = { - .state = lex_config, - .pos = data, - .start = data, - .end = (char *)data + len, - .cur_line = 1, - } + .lexer = + { + .state = lex_config, + .pos = data, + .start = data, + .end = (char *)data + len, + .cur_line = 1, + }, }; config->error_message = NULL; @@ -776,7 +777,7 @@ struct config *config_open(const char *path) } #if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) -struct config *config_open_for_fuzzing(void *data, size_t len) +struct config *config_open_for_fuzzing(const uint8_t *data, size_t len) { struct config *config = malloc(sizeof(*config)); diff --git a/src/lib/lwan-config.h b/src/lib/lwan-config.h index be8428776..960b55c07 100644 --- a/src/lib/lwan-config.h +++ b/src/lib/lwan-config.h @@ -69,7 +69,8 @@ int parse_int(const char *value, int default_value); unsigned int parse_time_period(const char *str, unsigned int default_value); #if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) -struct config *config_open_for_fuzzing(void *data, size_t len); +#include +struct config *config_open_for_fuzzing(const uint8_t *data, size_t len); #endif #if defined(__cplusplus) diff --git a/src/lib/lwan-http-authorize.c b/src/lib/lwan-http-authorize.c index 4f0d5f59d..d6cd474fe 100644 --- a/src/lib/lwan-http-authorize.c +++ b/src/lib/lwan-http-authorize.c @@ -61,8 +61,8 @@ create_realm_file(const char *key, void *context __attribute__((unused))) goto error_no_close; #if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) - static char hardcoded_user_config[] = "user=password\n" - "root=hunter2\n"; + static const uint8_t hardcoded_user_config[] = "user=password\n" + "root=hunter2\n"; f = config_open_for_fuzzing(hardcoded_user_config, sizeof(hardcoded_user_config)); #else From 7d5e299657315fb7cfc7b4c0ddb27efe0fe2893a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 29 Oct 2019 20:11:23 -0700 Subject: [PATCH 1241/2505] Add corpus for pattern engine fuzzer OSS-Fuzz was unable to build Lwan because the corpus for this fuzzer was missing. Copy the inputs from the test suite. --- fuzz/corpus/corpus-pattern-1 | 1 + fuzz/corpus/corpus-pattern-2 | 1 + fuzz/corpus/corpus-pattern-3 | 1 + fuzz/corpus/corpus-pattern-4 | 1 + 4 files changed, 4 insertions(+) create mode 100644 fuzz/corpus/corpus-pattern-1 create mode 100644 fuzz/corpus/corpus-pattern-2 create mode 100644 fuzz/corpus/corpus-pattern-3 create mode 100644 fuzz/corpus/corpus-pattern-4 diff --git a/fuzz/corpus/corpus-pattern-1 b/fuzz/corpus/corpus-pattern-1 new file mode 100644 index 000000000..7513f19d0 --- /dev/null +++ b/fuzz/corpus/corpus-pattern-1 @@ -0,0 +1 @@ +lua/rewrite/7x6 \ No newline at end of file diff --git a/fuzz/corpus/corpus-pattern-2 b/fuzz/corpus/corpus-pattern-2 new file mode 100644 index 000000000..6a8320f92 --- /dev/null +++ b/fuzz/corpus/corpus-pattern-2 @@ -0,0 +1 @@ +lua/redir/6x7 \ No newline at end of file diff --git a/fuzz/corpus/corpus-pattern-3 b/fuzz/corpus/corpus-pattern-3 new file mode 100644 index 000000000..c841d324d --- /dev/null +++ b/fuzz/corpus/corpus-pattern-3 @@ -0,0 +1 @@ +bar/42/test \ No newline at end of file diff --git a/fuzz/corpus/corpus-pattern-4 b/fuzz/corpus/corpus-pattern-4 new file mode 100644 index 000000000..53e3de601 --- /dev/null +++ b/fuzz/corpus/corpus-pattern-4 @@ -0,0 +1 @@ +foo/1234x5678 \ No newline at end of file From 838570da5f01607a51f1c30df02434b9e6e2239a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 1 Nov 2019 19:31:51 -0700 Subject: [PATCH 1242/2505] Ensure HTTP authorization cache is initialized while fuzzing --- src/lib/lwan-request.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index e1a2ba68d..db2467d8d 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1688,9 +1688,12 @@ __attribute__((used)) int fuzz_parse_http_request(const uint8_t *data, length = sizeof(data_copy); memcpy(data_copy, data, length); - if (!coro) + if (!coro) { coro = coro_new(&switcher, useless_coro_for_fuzzing, NULL); + lwan_http_authorize_init(); + } + struct lwan_request_parser_helper helper = { .buffer = &(struct lwan_value){.value = data_copy, .len = length}, .header_start = header_start, From d443e53bf5f66e0968b777cee26d5772bf37698f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 1 Nov 2019 19:54:22 -0700 Subject: [PATCH 1243/2505] Cleanup string switch macros --- src/lib/lwan-request.c | 2 +- src/lib/lwan.h | 66 +++++++++++++++++++++++------------------- 2 files changed, 37 insertions(+), 31 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index db2467d8d..4b421892d 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -516,7 +516,7 @@ __attribute__((noinline)) static void set_header_value( { p += header_len; - if (LIKELY(string_as_int16(p) == STR2_INT(':', ' '))) { + if (LIKELY(string_as_uint16(p) == STR2_INT(':', ' '))) { *end = '\0'; char *value = p + sizeof(": ") - 1; diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 58cd94af2..4504ed5c6 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -83,54 +83,60 @@ extern "C" { #define ALWAYS_INLINE inline __attribute__((always_inline)) #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ -#define STR4_INT(a, b, c, d) ((int32_t)((a) | (b) << 8 | (c) << 16 | (d) << 24)) -#define STR2_INT(a, b) ((int16_t)((a) | (b) << 8)) +#define STR4_INT(a, b, c, d) ((uint32_t)((a) | (b) << 8 | (c) << 16 | (d) << 24)) +#define STR2_INT(a, b) ((uint16_t)((a) | (b) << 8)) #define STR8_INT(a, b, c, d, e, f, g, h) \ - ((int64_t)STR4_INT(a, b, c, d) | (int64_t)STR4_INT(e, f, g, h) << 32) + ((uint64_t)STR4_INT(a, b, c, d) | (uint64_t)STR4_INT(e, f, g, h) << 32) #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ -#define STR4_INT(d, c, b, a) ((int32_t)((a) | (b) << 8 | (c) << 16 | (d) << 24)) -#define STR2_INT(b, a) ((int16_t)((a) | (b) << 8)) +#define STR4_INT(d, c, b, a) ((uint32_t)((a) | (b) << 8 | (c) << 16 | (d) << 24)) +#define STR2_INT(b, a) ((uint16_t)((a) | (b) << 8)) #define STR8_INT(a, b, c, d, e, f, g, h) \ - ((int64_t)STR4_INT(a, b, c, d) << 32 | (int64_t)STR4_INT(e, f, g, h)) + ((uint64_t)STR4_INT(a, b, c, d) << 32 | (uint64_t)STR4_INT(e, f, g, h)) #elif __BYTE_ORDER__ == __ORDER_PDP_ENDIAN__ #error A PDP? Seriously? #endif -#define STR4_INT_L(a, b, c, d) (STR4_INT(a, b, c, d) | 0x20202020) -#define STR2_INT_L(a, b) (STR2_INT(a, b) | 0x2020) -#define STR8_INT_L(a, b, c, d, e, f, g, h) \ - (STR8_INT(a, b, c, d, e, f, g, h) | 0x2020202020202020) - -static ALWAYS_INLINE int64_t string_as_int64(const char *s) +static ALWAYS_INLINE uint16_t string_as_uint16(const char *s) { - int64_t i; - memcpy(&i, s, sizeof(int64_t)); - return i; + uint16_t u; + + memcpy(&u, s, sizeof(u)); + + return u; } -static ALWAYS_INLINE int32_t string_as_int32(const char *s) +static ALWAYS_INLINE uint32_t string_as_uint32(const char *s) { - int32_t i; - memcpy(&i, s, sizeof(int32_t)); - return i; + uint32_t u; + + memcpy(&u, s, sizeof(u)); + + return u; } -static ALWAYS_INLINE int16_t string_as_int16(const char *s) +static ALWAYS_INLINE uint64_t string_as_uint64(const char *s) { - int16_t i; - memcpy(&i, s, sizeof(int16_t)); - return i; + uint64_t u; + + memcpy(&u, s, sizeof(u)); + + return u; } -#define STRING_SWITCH(s) switch (string_as_int32(s)) -#define STRING_SWITCH_L(s) switch (string_as_int32(s) | 0x20202020) +#define LOWER2(s) ((s) | (uint16_t)0x2020) +#define LOWER4(s) ((s) | (uint32_t)0x20202020) +#define LOWER8(s) ((s) | (uint64_t)0x2020202020202020) -#define STRING_SWITCH_SMALL(s) switch (string_as_int16(s)) -#define STRING_SWITCH_SMALL_L(s) switch (string_as_int16(s) | 0x2020) +#define STR2_INT_L(a, b) LOWER2(STR2_INT(a, b)) +#define STR4_INT_L(a, b, c, d) LOWER4(STR4_INT(a, b, c, d)) +#define STR8_INT_L(a, b, c, d, e, f, g, h) LOWER8(STR8_INT(a, b, c, d, e, f, g, h)) -#define STRING_SWITCH_LARGE(s) switch (string_as_int64(s)) -#define STRING_SWITCH_LARGE_L(s) \ - switch (string_as_int64(s) | 0x2020202020202020) +#define STRING_SWITCH_SMALL(s) switch (string_as_uint16(s)) +#define STRING_SWITCH_SMALL_L(s) switch (LOWER2(string_as_uint16(s))) +#define STRING_SWITCH(s) switch (string_as_uint32(s)) +#define STRING_SWITCH_L(s) switch (LOWER4(string_as_uint32(s))) +#define STRING_SWITCH_LARGE(s) switch (string_as_uint64(s)) +#define STRING_SWITCH_LARGE_L(s) switch (LOWER8(string_as_uint64(s))) #define LIKELY_IS(x, y) __builtin_expect((x), (y)) #define LIKELY(x) LIKELY_IS(!!(x), 1) From 098fd68acfdfe5d55da7f343ec7a89a2fafc938a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 2 Nov 2019 14:19:43 -0700 Subject: [PATCH 1244/2505] Job thread needs to be initialized when using cache This was causing build issues in OSS-Fuzz. --- src/lib/lwan-request.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 4b421892d..080fee1a4 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1691,6 +1691,7 @@ __attribute__((used)) int fuzz_parse_http_request(const uint8_t *data, if (!coro) { coro = coro_new(&switcher, useless_coro_for_fuzzing, NULL); + lwan_job_thread_init(); lwan_http_authorize_init(); } From 2ac0a12a7142263552b8af783698c67c2833bd04 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 2 Nov 2019 14:29:09 -0700 Subject: [PATCH 1245/2505] Don't use strncmp() in config reader unless there's enough bytes to read Thanks to OSS-Fuzz. --- src/lib/lwan-config.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 78ec2ffdd..38c1b1231 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -340,6 +340,14 @@ static void *lex_error(struct lexer *lexer, const char *msg) return NULL; } +static bool lex_streq(struct lexer *lexer, const char *str, size_t s) +{ + if (remaining(lexer) < s) + return false; + + return !strncmp(lexer->pos, str, s); +} + static void *lex_multiline_string(struct lexer *lexer) { const char *end = (peek(lexer) == '"') ? "\"\"\"" : "'''"; @@ -347,10 +355,7 @@ static void *lex_multiline_string(struct lexer *lexer) advance_n(lexer, strlen("'''") - 1); do { - if (remaining(lexer) < 3) - break; - - if (!strncmp(lexer->pos, end, 3)) { + if (lex_streq(lexer, end, 3)) { emit(lexer, LEXEME_STRING); lexer->pos += 3; @@ -463,9 +468,9 @@ static void *lex_config(struct lexer *lexer) if (chr == '#') return lex_comment; - if (chr == '\'' && !strncmp(lexer->pos, "''", 2)) + if (chr == '\'' && lex_streq(lexer, "''", 2)) return lex_multiline_string; - if (chr == '"' && !strncmp(lexer->pos, "\"\"", 2)) + if (chr == '"' && lex_streq(lexer, "\"\"", 2)) return lex_multiline_string; if (chr == '$' && peek(lexer) == '{') From 5a4604be1ce111531fd6cb8f3f17c9e1ba016b8b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 3 Nov 2019 09:10:52 -0800 Subject: [PATCH 1246/2505] STATUS_ERROR messages should have a color in start_colors table Otherwise, fwrite() will be called with a NULL pointer for those messages for the first parameter. Use the same color for the STATUS_PERROR messages. --- src/lib/lwan-status.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/lwan-status.c b/src/lib/lwan-status.c index 65e1b35d0..02f6c3e1d 100644 --- a/src/lib/lwan-status.c +++ b/src/lib/lwan-status.c @@ -84,6 +84,7 @@ static const struct lwan_value start_colors[] = { [STATUS_INFO] = V("\033[36m"), [STATUS_WARNING] = V("\033[33m"), [STATUS_DEBUG] = V("\033[37m"), [STATUS_PERROR] = V("\033[35m"), [STATUS_CRITICAL] = V("\033[31;1m"), [STATUS_NONE] = V(""), + [STATUS_ERROR] = V("\033[35m"), }; static inline struct lwan_value start_color(enum lwan_status_type type) From 20f6d83e7cef33ada257f72de6f98cac96702454 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 4 Nov 2019 18:56:15 -0800 Subject: [PATCH 1247/2505] Cleanup: lwan_http_authorize() doesn't need a lwan_value parameter --- src/lib/lwan-http-authorize.c | 15 +++++++++------ src/lib/lwan-http-authorize.h | 1 - src/lib/lwan-request.c | 27 +++------------------------ 3 files changed, 12 insertions(+), 31 deletions(-) diff --git a/src/lib/lwan-http-authorize.c b/src/lib/lwan-http-authorize.c index d6cd474fe..2f0f7479e 100644 --- a/src/lib/lwan-http-authorize.c +++ b/src/lib/lwan-http-authorize.c @@ -180,24 +180,27 @@ static bool authorize(struct coro *coro, } bool lwan_http_authorize(struct lwan_request *request, - struct lwan_value *authorization, const char *realm, const char *password_file) { static const char authenticate_tmpl[] = "Basic realm=\"%s\""; static const size_t basic_len = sizeof("Basic ") - 1; struct lwan_key_value *headers; + const char *authorization = + lwan_request_get_header(request, "Authorization"); - if (!authorization->value) + if (!authorization) goto unauthorized; - if (UNLIKELY(strncmp(authorization->value, "Basic ", basic_len))) + if (UNLIKELY(strncmp(authorization, "Basic ", basic_len))) goto unauthorized; - authorization->value += basic_len; - authorization->len -= basic_len; + struct lwan_value header = { + .value = (char *)(authorization + basic_len), + .len = strlen(authorization) - basic_len, + }; - if (authorize(request->conn->coro, authorization, password_file)) + if (authorize(request->conn->coro, &header, password_file)) return true; unauthorized: diff --git a/src/lib/lwan-http-authorize.h b/src/lib/lwan-http-authorize.h index 81bf36c32..d571aac2e 100644 --- a/src/lib/lwan-http-authorize.h +++ b/src/lib/lwan-http-authorize.h @@ -26,6 +26,5 @@ bool lwan_http_authorize_init(void); void lwan_http_authorize_shutdown(void); bool lwan_http_authorize(struct lwan_request *request, - struct lwan_value *authorization, const char *realm, const char *password_file); diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 080fee1a4..075f52869 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1313,20 +1313,6 @@ lwan_request_websocket_upgrade(struct lwan_request *request) return HTTP_SWITCHING_PROTOCOLS; } -static bool authorize(struct lwan_request *request, - const struct lwan_url_map *url_map) -{ - const char *authorization = - lwan_request_get_header(request, "Authorization"); - struct lwan_value header = { - .value = (char *)authorization, - .len = authorization ? strlen(authorization) : 0, - }; - - return lwan_http_authorize(request, &header, url_map->authorization.realm, - url_map->authorization.password_file); -} - static enum lwan_http_status prepare_for_response(struct lwan_url_map *url_map, struct lwan_request *request) { @@ -1334,7 +1320,8 @@ static enum lwan_http_status prepare_for_response(struct lwan_url_map *url_map, request->url.len -= url_map->prefix_len; if (UNLIKELY(url_map->flags & HANDLER_MUST_AUTHORIZE && - !authorize(request, url_map))) + !lwan_http_authorize(request, url_map->authorization.realm, + url_map->authorization.password_file))) return HTTP_NOT_AUTHORIZED; while (*request->url.value == '/' && request->url.len > 0) { @@ -1750,15 +1737,7 @@ __attribute__((used)) int fuzz_parse_http_request(const uint8_t *data, lwan_request_get_if_modified_since(&request, &trash2); NO_DISCARD(trash2); - struct lwan_url_map url_map = { - .authorization = { - .realm = "Fuzzy Realm", - /* The file name doesn't matter here, a hardcoded user/password - * list will be used instead if Lwan is built for fuzzing. */ - .password_file = "/dev/null", - }, - }; - NO_DISCARD(authorize(&request, &url_map)); + NO_DISCARD(lwan_http_authorize(&request, "Fuzzy Realm", "/dev/null")); #undef NO_DISCARD From d1aceafc107c14d049b6e87aeed110148a8732f5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 4 Nov 2019 18:56:36 -0800 Subject: [PATCH 1248/2505] Reindent lwan_request_get_remote_address() --- src/lib/lwan-request.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 075f52869..7ab5891e0 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1504,9 +1504,9 @@ lwan_connection_get_fd(const struct lwan *lwan, const struct lwan_connection *co const char * lwan_request_get_remote_address(struct lwan_request *request, - char buffer[static INET6_ADDRSTRLEN]) + char buffer[static INET6_ADDRSTRLEN]) { - struct sockaddr_storage non_proxied_addr = { .ss_family = AF_UNSPEC }; + struct sockaddr_storage non_proxied_addr = {.ss_family = AF_UNSPEC}; struct sockaddr_storage *sock_addr; if (request->flags & REQUEST_PROXIED) { @@ -1519,19 +1519,17 @@ lwan_request_get_remote_address(struct lwan_request *request, sock_addr = &non_proxied_addr; - if (UNLIKELY(getpeername(request->fd, - (struct sockaddr *) sock_addr, + if (UNLIKELY(getpeername(request->fd, (struct sockaddr *)sock_addr, &sock_len) < 0)) return NULL; } - if (sock_addr->ss_family == AF_INET) - return inet_ntop(AF_INET, - &((struct sockaddr_in *) sock_addr)->sin_addr, + if (sock_addr->ss_family == AF_INET) { + return inet_ntop(AF_INET, &((struct sockaddr_in *)sock_addr)->sin_addr, buffer, INET6_ADDRSTRLEN); + } - return inet_ntop(AF_INET6, - &((struct sockaddr_in6 *) sock_addr)->sin6_addr, + return inet_ntop(AF_INET6, &((struct sockaddr_in6 *)sock_addr)->sin6_addr, buffer, INET6_ADDRSTRLEN); } From 32bf565f14c300d617784dc3ae0d4f54540c67aa Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 4 Nov 2019 18:59:35 -0800 Subject: [PATCH 1249/2505] Ensure critical *and* perror status messages also get a color --- src/lib/lwan-status.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-status.c b/src/lib/lwan-status.c index 02f6c3e1d..f96078fd7 100644 --- a/src/lib/lwan-status.c +++ b/src/lib/lwan-status.c @@ -81,10 +81,14 @@ static int status_index(enum lwan_status_type type) #define V(c) { .value = c, .len = sizeof(c) - 1 } static const struct lwan_value start_colors[] = { - [STATUS_INFO] = V("\033[36m"), [STATUS_WARNING] = V("\033[33m"), - [STATUS_DEBUG] = V("\033[37m"), [STATUS_PERROR] = V("\033[35m"), - [STATUS_CRITICAL] = V("\033[31;1m"), [STATUS_NONE] = V(""), + [STATUS_INFO] = V("\033[36m"), + [STATUS_WARNING] = V("\033[33m"), + [STATUS_DEBUG] = V("\033[37m"), + [STATUS_PERROR] = V("\033[35m"), + [STATUS_CRITICAL] = V("\033[31;1m"), + [STATUS_NONE] = V(""), [STATUS_ERROR] = V("\033[35m"), + [STATUS_CRITICAL | STATUS_PERROR] = V("\033[31;1m"), }; static inline struct lwan_value start_color(enum lwan_status_type type) From 3f5fc6634130d9ded6398499039523320b61c0b6 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 4 Nov 2019 19:00:04 -0800 Subject: [PATCH 1250/2505] Get rid of REQUEST_FLAG() This macro is cryptic, and GCC/Clang can already generate the same code this was supposed to generate with the (much cleaner) ternary operator. --- src/lib/lwan-thread.c | 10 ++-------- src/lib/lwan.h | 13 ++----------- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 0684e076e..a2f388058 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -41,12 +41,6 @@ static ALWAYS_INLINE int min(const int a, const int b) { return a < b ? a : b; } -#define REQUEST_FLAG(bool_, name_) \ - ((enum lwan_request_flags)(((uint32_t)lwan->config.bool_) \ - << REQUEST_##name_##_SHIFT)) -static_assert(sizeof(enum lwan_request_flags) == sizeof(uint32_t), - "lwan_request_flags has the same size as uint32_t"); - static void lwan_strbuf_free_defer(void *data) { lwan_strbuf_free((struct lwan_strbuf *)data); @@ -124,8 +118,8 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, coro_defer(coro, lwan_strbuf_free_defer, &strbuf); enum lwan_request_flags flags = - REQUEST_FLAG(proxy_protocol, ALLOW_PROXY_REQS) | - REQUEST_FLAG(allow_cors, ALLOW_CORS); + (lwan->config.proxy_protocol ? REQUEST_ALLOW_PROXY_REQS : 0) | + (lwan->config.allow_cors ? REQUEST_ALLOW_CORS : 0); while (true) { struct lwan_request request = {.conn = conn, diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 4504ed5c6..ddfe0d3fb 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -212,15 +212,6 @@ enum lwan_handler_flags { enum lwan_request_flags { REQUEST_ALL_FLAGS = -1, - /* Shift values to make easier to build flags while booting a - * request-processing coroutine. - * - * Allows this: if (some_boolean) flags |= SOME_FLAG; - * To turn into: flags |= (uint32_t)some_boolean << SOME_FLAG_SHIFT; - */ - REQUEST_ALLOW_PROXY_REQS_SHIFT = 7, - REQUEST_ALLOW_CORS_SHIFT = 9, - REQUEST_METHOD_MASK = FOR_EACH_REQUEST_METHOD(SELECT_MASK) 0, FOR_EACH_REQUEST_METHOD(GENERATE_ENUM_ITEM) @@ -228,9 +219,9 @@ enum lwan_request_flags { REQUEST_ACCEPT_GZIP = 1 << 4, REQUEST_ACCEPT_BROTLI = 1 << 5, REQUEST_IS_HTTP_1_0 = 1 << 6, - REQUEST_ALLOW_PROXY_REQS = 1 << REQUEST_ALLOW_PROXY_REQS_SHIFT, + REQUEST_ALLOW_PROXY_REQS = 1 << 7, REQUEST_PROXIED = 1 << 8, - REQUEST_ALLOW_CORS = 1 << REQUEST_ALLOW_CORS_SHIFT, + REQUEST_ALLOW_CORS = 1 << 9, RESPONSE_SENT_HEADERS = 1 << 10, RESPONSE_CHUNKED_ENCODING = 1 << 11, From 0fda32f7e1088e4a3b21580b5bf8fe00971dc2ef Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 4 Nov 2019 19:42:29 -0800 Subject: [PATCH 1251/2505] Template: NULL-check chunk before dispatching apply() might return NULL, so null-check the chunk before dereferencing. --- src/lib/lwan-template.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index d803408eb..b3d90aadf 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -1354,12 +1354,26 @@ static const struct chunk *apply(struct lwan_tpl *tpl, if (UNLIKELY(!chunk)) return NULL; -#define DISPATCH_ACTION() goto *dispatch_table[chunk->action] +#define RETURN_IF_NO_CHUNK() \ + do { \ + if (UNLIKELY(!chunk)) { \ + lwan_status_error("Chunk is NULL while dispatching"); \ + return NULL; \ + } \ + } while (false) + +#define DISPATCH_ACTION() \ + do { \ + RETURN_IF_NO_CHUNK(); \ + goto *dispatch_table[chunk->action]; \ + } while (false) #define DISPATCH_NEXT_ACTION() \ do { \ + RETURN_IF_NO_CHUNK(); \ + \ chunk++; \ - DISPATCH_ACTION(); \ + goto *dispatch_table[chunk->action]; \ } while (false) DISPATCH_ACTION(); From 4f3c14550d88b914e16ff7841447342dc3e46d5a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 4 Nov 2019 22:12:13 -0800 Subject: [PATCH 1252/2505] config_read_line() shouldn't copy the line struct Return a pointer to the internal ringbuffer instead, or NULL on error. --- src/bin/fuzz/config_fuzzer.cc | 20 ++++------ src/bin/tools/configdump.c | 11 ++--- src/lib/lwan-config.c | 23 +++++------ src/lib/lwan-config.h | 6 +-- src/lib/lwan-http-authorize.c | 12 +++--- src/lib/lwan-mod-rewrite.c | 18 ++++----- src/lib/lwan-straitjacket.c | 20 +++++----- src/lib/lwan.c | 75 +++++++++++++++++------------------ 8 files changed, 88 insertions(+), 97 deletions(-) diff --git a/src/bin/fuzz/config_fuzzer.cc b/src/bin/fuzz/config_fuzzer.cc index b9b26f136..b713615fd 100644 --- a/src/bin/fuzz/config_fuzzer.cc +++ b/src/bin/fuzz/config_fuzzer.cc @@ -5,12 +5,14 @@ #include "lwan-config.h" static bool -dump(struct config *config, struct config_line *line, int indent_level) +dump(struct config *config, int indent_level) { + const struct config_line *line; + if (indent_level > 64) return false; - while (config_read_line(config, line)) { + while ((line = config_read_line(config))) { switch (line->type) { case CONFIG_LINE_TYPE_LINE: break; @@ -22,34 +24,26 @@ dump(struct config *config, struct config_line *line, int indent_level) return true; case CONFIG_LINE_TYPE_SECTION: - if (!dump(config, line, indent_level + 1)) + if (!dump(config, indent_level + 1)) return false; break; } } - if (config_last_error(config)) { - fprintf(stderr, - "Error while reading configuration file (line %d): %s\n", - config_cur_line(config), config_last_error(config)); - return false; - } - - return true; + return config_last_error(config); } extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { struct config *config; - struct config_line line; int indent_level = 0; config = config_open_for_fuzzing(data, size); if (!config) return 1; - bool dumped = dump(config, &line, indent_level); + bool dumped = dump(config, indent_level); config_close(config); diff --git a/src/bin/tools/configdump.c b/src/bin/tools/configdump.c index 84d3edea1..bbc3fd876 100644 --- a/src/bin/tools/configdump.c +++ b/src/bin/tools/configdump.c @@ -32,15 +32,17 @@ static void indent(int level) } static void -dump(struct config *config, struct config_line *line, int indent_level) +dump(struct config *config, int indent_level) { + const struct config_line *line; + if (indent_level > 64) { lwan_status_critical("Indent level %d above limit, aborting", indent_level); return; } - while (config_read_line(config, line)) { + while ((line = config_read_line(config))) { switch (line->type) { case CONFIG_LINE_TYPE_LINE: indent(indent_level); @@ -60,7 +62,7 @@ dump(struct config *config, struct config_line *line, int indent_level) indent(indent_level); printf("%s %s {\n", line->key, line->value); - dump(config, line, indent_level + 1); + dump(config, indent_level + 1); indent(indent_level); printf("}\n"); @@ -78,7 +80,6 @@ dump(struct config *config, struct config_line *line, int indent_level) int main(int argc, char *argv[]) { struct config *config; - struct config_line line; int indent_level = 0; if (argc < 2) { @@ -93,7 +94,7 @@ int main(int argc, char *argv[]) return 1; } - dump(config, &line, indent_level); + dump(config, indent_level); config_close(config); diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 38c1b1231..a5b96f694 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -812,19 +812,16 @@ void config_close(struct config *config) free(config); } -bool config_read_line(struct config *conf, struct config_line *cl) +const struct config_line *config_read_line(struct config *conf) { - struct config_line *ptr; - bool ret; + if (!conf->error_message) { + struct config_line *ptr; - if (conf->error_message) - return false; - - ret = parser_next(&conf->parser, &ptr); - if (ret) - *cl = *ptr; + if (parser_next(&conf->parser, &ptr)) + return ptr; + } - return ret; + return NULL; } static bool find_section_end(struct config *config) @@ -850,7 +847,7 @@ static bool find_section_end(struct config *config) } struct config *config_isolate_section(struct config *current_conf, - struct config_line *current_line) + const struct config_line *current_line) { struct lexer *lexer; struct config *isolated; @@ -878,7 +875,7 @@ struct config *config_isolate_section(struct config *current_conf, free(isolated); config_error(current_conf, - "Could not find section end while trying to isolate"); + "Could not find section end while trying to isolate"); return NULL; } @@ -889,7 +886,7 @@ struct config *config_isolate_section(struct config *current_conf, return isolated; } -bool config_skip_section(struct config *conf, struct config_line *line) +bool config_skip_section(struct config *conf, const struct config_line *line) { if (line->type != CONFIG_LINE_TYPE_SECTION) return false; diff --git a/src/lib/lwan-config.h b/src/lib/lwan-config.h index 960b55c07..cc775da15 100644 --- a/src/lib/lwan-config.h +++ b/src/lib/lwan-config.h @@ -54,14 +54,14 @@ struct config *config_open(const char *path); void config_close(struct config *conf); bool config_error(struct config *conf, const char *fmt, ...) __attribute__((format(printf, 2, 3))); -bool config_read_line(struct config *conf, struct config_line *l); +const struct config_line *config_read_line(struct config *conf); const char *config_last_error(struct config *conf); int config_cur_line(struct config *conf); struct config *config_isolate_section(struct config *current_conf, - struct config_line *current_line); -bool config_skip_section(struct config *conf, struct config_line *line); + const struct config_line *current_line); +bool config_skip_section(struct config *conf, const struct config_line *line); bool parse_bool(const char *value, bool default_value); long parse_long(const char *value, long default_value); diff --git a/src/lib/lwan-http-authorize.c b/src/lib/lwan-http-authorize.c index 2f0f7479e..39b1e31be 100644 --- a/src/lib/lwan-http-authorize.c +++ b/src/lib/lwan-http-authorize.c @@ -50,8 +50,8 @@ static struct cache_entry * create_realm_file(const char *key, void *context __attribute__((unused))) { struct realm_password_file_t *rpf = malloc(sizeof(*rpf)); + const struct config_line *l; struct config *f; - struct config_line l; if (UNLIKELY(!rpf)) return NULL; @@ -71,15 +71,15 @@ create_realm_file(const char *key, void *context __attribute__((unused))) if (!f) goto error_no_close; - while (config_read_line(f, &l)) { + while ((l = config_read_line(f))) { /* FIXME: Storing plain-text passwords in memory isn't a good idea. */ - switch (l.type) { + switch (l->type) { case CONFIG_LINE_TYPE_LINE: { - char *username = strdup(l.key); + char *username = strdup(l->key); if (!username) goto error; - char *password = strdup(l.value); + char *password = strdup(l->value); if (!password) { free(username); goto error; @@ -94,7 +94,7 @@ create_realm_file(const char *key, void *context __attribute__((unused))) if (err == -EEXIST) { lwan_status_warning( - "Username entry already exists, ignoring: \"%s\"", l.key); + "Username entry already exists, ignoring: \"%s\"", l->key); continue; } diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index e86f5f7d4..31c0de883 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -323,7 +323,7 @@ static void *rewrite_create_from_hash(const char *prefix, static bool rewrite_parse_conf_pattern(struct private_data *pd, struct config *config, - struct config_line *line) + const struct config_line *line) { struct pattern *pattern; char *redirect_to = NULL, *rewrite_as = NULL; @@ -337,7 +337,7 @@ static bool rewrite_parse_conf_pattern(struct private_data *pd, if (!pattern->pattern) goto out; - while (config_read_line(config, line)) { + while ((line = config_read_line(config))) { switch (line->type) { case CONFIG_LINE_TYPE_LINE: if (streq(line->key, "redirect_to")) { @@ -409,18 +409,18 @@ static bool rewrite_parse_conf_pattern(struct private_data *pd, static bool rewrite_parse_conf(void *instance, struct config *config) { struct private_data *pd = instance; - struct config_line line; + const struct config_line *line; - while (config_read_line(config, &line)) { - switch (line.type) { + while ((line = config_read_line(config))) { + switch (line->type) { case CONFIG_LINE_TYPE_LINE: - config_error(config, "Unknown option: %s", line.key); + config_error(config, "Unknown option: %s", line->key); break; case CONFIG_LINE_TYPE_SECTION: - if (streq(line.key, "pattern")) { - rewrite_parse_conf_pattern(pd, config, &line); + if (streq(line->key, "pattern")) { + rewrite_parse_conf_pattern(pd, config, line); } else { - config_error(config, "Unknown section: %s", line.key); + config_error(config, "Unknown section: %s", line->key); } break; case CONFIG_LINE_TYPE_SECTION_END: diff --git a/src/lib/lwan-straitjacket.c b/src/lib/lwan-straitjacket.c index eeb652540..ef97c2bc0 100644 --- a/src/lib/lwan-straitjacket.c +++ b/src/lib/lwan-straitjacket.c @@ -229,23 +229,23 @@ void lwan_straitjacket_enforce(const struct lwan_straitjacket *sj) void lwan_straitjacket_enforce_from_config(struct config *c) { - struct config_line l; + const struct config_line *l; char *user_name = NULL; char *chroot_path = NULL; bool drop_capabilities = true; - while (config_read_line(c, &l)) { - switch (l.type) { + while ((l = config_read_line(c))) { + switch (l->type) { case CONFIG_LINE_TYPE_LINE: /* TODO: limit_syscalls */ - if (streq(l.key, "user")) { - user_name = strdupa(l.value); - } else if (streq(l.key, "chroot")) { - chroot_path = strdupa(l.value); - } else if (streq(l.key, "drop_capabilities")) { - drop_capabilities = parse_bool(l.value, true); + if (streq(l->key, "user")) { + user_name = strdupa(l->value); + } else if (streq(l->key, "chroot")) { + chroot_path = strdupa(l->value); + } else if (streq(l->key, "drop_capabilities")) { + drop_capabilities = parse_bool(l->value, true); } else { - config_error(c, "Invalid key: %s", l.key); + config_error(c, "Invalid key: %s", l->key); return; } break; diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 19f7c9a1b..d8ea7fd33 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -138,7 +138,7 @@ static struct lwan_url_map *add_url_map(struct lwan_trie *t, const char *prefix, } static void parse_listener_prefix_authorization(struct config *c, - struct config_line *l, + const struct config_line *l, struct lwan_url_map *url_map) { if (!streq(l->value, "basic")) { @@ -148,7 +148,7 @@ static void parse_listener_prefix_authorization(struct config *c, memset(&url_map->authorization, 0, sizeof(url_map->authorization)); - while (config_read_line(c, l)) { + while ((l = config_read_line(c))) { switch (l->type) { case CONFIG_LINE_TYPE_LINE: if (streq(l->key, "realm")) { @@ -183,7 +183,7 @@ static void parse_listener_prefix_authorization(struct config *c, } static void parse_listener_prefix(struct config *c, - struct config_line *l, + const struct config_line *l, struct lwan *lwan, const struct lwan_module *module, void *handler) @@ -199,7 +199,7 @@ static void parse_listener_prefix(struct config *c, goto out; } - while (config_read_line(c, l)) { + while ((l = config_read_line(c))) { switch (l->type) { case CONFIG_LINE_TYPE_LINE: hash_add(hash, strdup(l->key), strdup(l->value)); @@ -284,28 +284,27 @@ void lwan_set_url_map(struct lwan *l, const struct lwan_url_map *map) } } -static void parse_listener(struct config *c, struct config_line *l, +static void parse_listener(struct config *c, + const struct config_line *l, struct lwan *lwan) { free(lwan->config.listener); lwan->config.listener = strdup(l->value); - while (config_read_line(c, l)) { + while ((l = config_read_line(c))) { switch (l->type) { case CONFIG_LINE_TYPE_LINE: config_error(c, "Expecting prefix section"); return; case CONFIG_LINE_TYPE_SECTION: if (l->key[0] == '&') { - l->key++; - - void *handler = find_handler(l->key); + void *handler = find_handler(l->key + 1); if (handler) { parse_listener_prefix(c, l, lwan, NULL, handler); continue; } - config_error(c, "Could not find handler name: %s", l->key); + config_error(c, "Could not find handler name: %s", l->key + 1); return; } @@ -347,8 +346,8 @@ const char *lwan_get_config_path(char *path_buf, size_t path_buf_len) static bool setup_from_config(struct lwan *lwan, const char *path) { + const struct config_line *line; struct config *conf; - struct config_line line; bool has_listener = false; char path_buf[PATH_MAX]; @@ -363,65 +362,65 @@ static bool setup_from_config(struct lwan *lwan, const char *path) if (!lwan_trie_init(&lwan->url_map_trie, destroy_urlmap)) return false; - while (config_read_line(conf, &line)) { - switch (line.type) { + while ((line = config_read_line(conf))) { + switch (line->type) { case CONFIG_LINE_TYPE_LINE: - if (streq(line.key, "keep_alive_timeout")) { + if (streq(line->key, "keep_alive_timeout")) { lwan->config.keep_alive_timeout = (unsigned short)parse_long( - line.value, default_config.keep_alive_timeout); - } else if (streq(line.key, "quiet")) { + line->value, default_config.keep_alive_timeout); + } else if (streq(line->key, "quiet")) { lwan->config.quiet = - parse_bool(line.value, default_config.quiet); - } else if (streq(line.key, "reuse_port")) { + parse_bool(line->value, default_config.quiet); + } else if (streq(line->key, "reuse_port")) { lwan->config.reuse_port = - parse_bool(line.value, default_config.reuse_port); - } else if (streq(line.key, "proxy_protocol")) { + parse_bool(line->value, default_config.reuse_port); + } else if (streq(line->key, "proxy_protocol")) { lwan->config.proxy_protocol = - parse_bool(line.value, default_config.proxy_protocol); - } else if (streq(line.key, "allow_cors")) { + parse_bool(line->value, default_config.proxy_protocol); + } else if (streq(line->key, "allow_cors")) { lwan->config.allow_cors = - parse_bool(line.value, default_config.allow_cors); - } else if (streq(line.key, "expires")) { + parse_bool(line->value, default_config.allow_cors); + } else if (streq(line->key, "expires")) { lwan->config.expires = - parse_time_period(line.value, default_config.expires); - } else if (streq(line.key, "error_template")) { + parse_time_period(line->value, default_config.expires); + } else if (streq(line->key, "error_template")) { free(lwan->config.error_template); - lwan->config.error_template = strdup(line.value); - } else if (streq(line.key, "threads")) { + lwan->config.error_template = strdup(line->value); + } else if (streq(line->key, "threads")) { long n_threads = - parse_long(line.value, default_config.n_threads); + parse_long(line->value, default_config.n_threads); if (n_threads < 0) config_error(conf, "Invalid number of threads: %ld", n_threads); lwan->config.n_threads = (unsigned short int)n_threads; - } else if (streq(line.key, "max_post_data_size")) { + } else if (streq(line->key, "max_post_data_size")) { long max_post_data_size = parse_long( - line.value, (long)default_config.max_post_data_size); + line->value, (long)default_config.max_post_data_size); if (max_post_data_size < 0) config_error(conf, "Negative maximum post data size"); else if (max_post_data_size > 128 * (1 << 20)) config_error(conf, "Maximum post data can't be over 128MiB"); lwan->config.max_post_data_size = (size_t)max_post_data_size; - } else if (streq(line.key, "allow_temp_files")) { + } else if (streq(line->key, "allow_temp_files")) { lwan->config.allow_post_temp_file = - !!strstr(line.value, "post"); + !!strstr(line->value, "post"); } else { - config_error(conf, "Unknown config key: %s", line.key); + config_error(conf, "Unknown config key: %s", line->key); } break; case CONFIG_LINE_TYPE_SECTION: - if (streq(line.key, "listener")) { + if (streq(line->key, "listener")) { if (!has_listener) { - parse_listener(conf, &line, lwan); + parse_listener(conf, line, lwan); has_listener = true; } else { config_error(conf, "Only one listener supported"); } - } else if (streq(line.key, "straitjacket")) { + } else if (streq(line->key, "straitjacket")) { lwan_straitjacket_enforce_from_config(conf); } else { - config_error(conf, "Unknown section type: %s", line.key); + config_error(conf, "Unknown section type: %s", line->key); } break; case CONFIG_LINE_TYPE_SECTION_END: From 7b7c49eef0d896ebf955223a20d3cb8f99698c03 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 5 Nov 2019 08:26:54 -0800 Subject: [PATCH 1253/2505] Pass copy of buffer to save_to_corpus_for_fuzzing() instead of const ptr --- src/lib/lwan-request.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 7ab5891e0..ad8dfc82c 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -764,9 +764,8 @@ static ALWAYS_INLINE void parse_connection_header(struct lwan_request *request) } #if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) -static void save_to_corpus_for_fuzzing(const struct lwan_value *orig_buffer) +static void save_to_corpus_for_fuzzing(struct lwan_value buffer) { - struct lwan_value buffer = *orig_buffer; char corpus_name[PATH_MAX]; const char *crlfcrlf; int fd; @@ -872,7 +871,7 @@ read_from_request_socket(struct lwan_request *request, buffer->value[buffer->len] = '\0'; #if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) - save_to_corpus_for_fuzzing(buffer); + save_to_corpus_for_fuzzing(*buffer); #endif return HTTP_OK; From f542cce0172c73ee1fee2bc589ecb88152850dbc Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 5 Nov 2019 18:23:41 -0800 Subject: [PATCH 1254/2505] Move NO_DISCARD macro to lwan.h --- src/bin/fuzz/pattern_fuzzer.cc | 20 +++++----------- src/lib/lwan-http-authorize.c | 2 +- src/lib/lwan-request.c | 42 ++++++++++++++++------------------ src/lib/lwan.h | 6 +++++ 4 files changed, 33 insertions(+), 37 deletions(-) diff --git a/src/bin/fuzz/pattern_fuzzer.cc b/src/bin/fuzz/pattern_fuzzer.cc index 875c82707..68d45ff53 100644 --- a/src/bin/fuzz/pattern_fuzzer.cc +++ b/src/bin/fuzz/pattern_fuzzer.cc @@ -24,20 +24,12 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) memcpy(static_data, data, size); static_data[size - 1] = '\0'; -#define NO_DISCARD(...) \ - do { \ - __typeof__(__VA_ARGS__) no_discard_ = __VA_ARGS__; \ - __asm__ __volatile__("" ::"g"(no_discard_) : "memory"); \ - } while (0) - - NO_DISCARD(str_find((char *)static_data, "foo/(%d+)(%a)(%d+)", sf, - N_ELEMENTS(sf), &errmsg)); - NO_DISCARD(str_find((char *)static_data, "bar/(%d+)/test", sf, - N_ELEMENTS(sf), &errmsg)); - NO_DISCARD(str_find((char *)static_data, "lua/rewrite/(%d+)x(%d+)", sf, - N_ELEMENTS(sf), &errmsg)); - -#undef NO_DISCARD + LWAN_NO_DISCARD(str_find((char *)static_data, "foo/(%d+)(%a)(%d+)", sf, + N_ELEMENTS(sf), &errmsg)); + LWAN_NO_DISCARD(str_find((char *)static_data, "bar/(%d+)/test", sf, + N_ELEMENTS(sf), &errmsg)); + LWAN_NO_DISCARD(str_find((char *)static_data, "lua/rewrite/(%d+)x(%d+)", sf, + N_ELEMENTS(sf), &errmsg)); return 0; } diff --git a/src/lib/lwan-http-authorize.c b/src/lib/lwan-http-authorize.c index 39b1e31be..c7b9178e7 100644 --- a/src/lib/lwan-http-authorize.c +++ b/src/lib/lwan-http-authorize.c @@ -41,7 +41,7 @@ static void fourty_two_and_free(void *str) char *s = str; while (*s) *s++ = 42; - __asm__ __volatile__("" ::"g"(s) : "memory"); + LWAN_NO_DISCARD(str); free(str); } } diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index ad8dfc82c..d5dc9ad06 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1710,33 +1710,31 @@ __attribute__((used)) int fuzz_parse_http_request(const uint8_t *data, /* Only pointers were set in helper struct; actually parse them here. */ parse_accept_encoding(&request); - /* Requesting these items will force them to be parsed, and also exercise - * the lookup function. */ - -#define NO_DISCARD(...) \ - do { \ - typeof(__VA_ARGS__) no_discard_ = __VA_ARGS__; \ - __asm__ __volatile__("" ::"g"(no_discard_) : "memory"); \ - } while (0) - - NO_DISCARD(lwan_request_get_header(&request, "Non-Existing-Header")); - NO_DISCARD(lwan_request_get_header(&request, "Host")); /* Usually existing short header */ - NO_DISCARD(lwan_request_get_cookie(&request, "Non-Existing-Cookie")); - NO_DISCARD(lwan_request_get_cookie(&request, "FOO")); /* Set by some tests */ - NO_DISCARD(lwan_request_get_query_param(&request, "Non-Existing-Query-Param")); - NO_DISCARD(lwan_request_get_post_param(&request, "Non-Existing-Post-Param")); - - NO_DISCARD(fuzz_websocket_handshake(&request)); + /* Requesting these items will force them to be parsed, and also + * exercise the lookup function. */ + LWAN_NO_DISCARD( + lwan_request_get_header(&request, "Non-Existing-Header")); + LWAN_NO_DISCARD(lwan_request_get_header( + &request, "Host")); /* Usually existing short header */ + LWAN_NO_DISCARD( + lwan_request_get_cookie(&request, "Non-Existing-Cookie")); + LWAN_NO_DISCARD( + lwan_request_get_cookie(&request, "FOO")); /* Set by some tests */ + LWAN_NO_DISCARD( + lwan_request_get_query_param(&request, "Non-Existing-Query-Param")); + LWAN_NO_DISCARD( + lwan_request_get_post_param(&request, "Non-Existing-Post-Param")); + + LWAN_NO_DISCARD(fuzz_websocket_handshake(&request)); lwan_request_get_range(&request, &trash1, &trash1); - NO_DISCARD(trash1); + LWAN_NO_DISCARD(trash1); lwan_request_get_if_modified_since(&request, &trash2); - NO_DISCARD(trash2); - - NO_DISCARD(lwan_http_authorize(&request, "Fuzzy Realm", "/dev/null")); + LWAN_NO_DISCARD(trash2); -#undef NO_DISCARD + LWAN_NO_DISCARD( + lwan_http_authorize(&request, "Fuzzy Realm", "/dev/null")); coro_deferred_run(coro, gen); } diff --git a/src/lib/lwan.h b/src/lib/lwan.h index ddfe0d3fb..5a775412d 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -45,6 +45,12 @@ extern "C" { #define N_ELEMENTS(array) (sizeof(array) / sizeof(array[0])) +#define LWAN_NO_DISCARD(...) \ + do { \ + __typeof__(__VA_ARGS__) no_discard_ = __VA_ARGS__; \ + __asm__ __volatile__("" ::"g"(no_discard_) : "memory"); \ + } while (0) + #ifdef __APPLE__ #define LWAN_SECTION_NAME(name_) "__DATA," #name_ #else From 0b5d01ba5f7c3cb2bf8428de9c9675accf074eb1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 5 Nov 2019 18:38:16 -0800 Subject: [PATCH 1255/2505] Skip checking if chunk is NULL when it can't possibly be NULL Chunk can be NULL only after recursively calling apply(). --- src/lib/lwan-template.c | 54 +++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index b3d90aadf..94ed308bc 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -1354,34 +1354,39 @@ static const struct chunk *apply(struct lwan_tpl *tpl, if (UNLIKELY(!chunk)) return NULL; -#define RETURN_IF_NO_CHUNK() \ +#define RETURN_IF_NO_CHUNK(force_) \ do { \ - if (UNLIKELY(!chunk)) { \ + if (force_ UNLIKELY(!chunk)) { \ lwan_status_error("Chunk is NULL while dispatching"); \ return NULL; \ } \ } while (false) -#define DISPATCH_ACTION() \ +#define DISPATCH_ACTION(force_check_) \ do { \ - RETURN_IF_NO_CHUNK(); \ + RETURN_IF_NO_CHUNK(force_check_); \ goto *dispatch_table[chunk->action]; \ } while (false) -#define DISPATCH_NEXT_ACTION() \ +#define DISPATCH_NEXT_ACTION(force_check_) \ do { \ - RETURN_IF_NO_CHUNK(); \ + RETURN_IF_NO_CHUNK(force_check_); \ \ chunk++; \ goto *dispatch_table[chunk->action]; \ } while (false) - DISPATCH_ACTION(); +#define DISPATCH_ACTION_FAST() DISPATCH_ACTION(0 &&) +#define DISPATCH_ACTION_CHECK() DISPATCH_ACTION(1 &&) +#define DISPATCH_NEXT_ACTION_FAST() DISPATCH_NEXT_ACTION(0 &&) +#define DISPATCH_NEXT_ACTION_CHECK() DISPATCH_NEXT_ACTION(1 &&) + + DISPATCH_ACTION_FAST(); action_append: lwan_strbuf_append_str(buf, lwan_strbuf_get_buffer(chunk->data), lwan_strbuf_get_length(chunk->data)); - DISPATCH_NEXT_ACTION(); + DISPATCH_NEXT_ACTION_FAST(); action_append_small: { uintptr_t val = (uintptr_t)chunk->data; @@ -1389,23 +1394,23 @@ action_append_small: { lwan_strbuf_append_str(buf, (char*)&val, len); - DISPATCH_NEXT_ACTION(); + DISPATCH_NEXT_ACTION_FAST(); } action_variable: { struct lwan_var_descriptor *descriptor = chunk->data; descriptor->append_to_strbuf(buf, (char *)variables + descriptor->offset); - DISPATCH_NEXT_ACTION(); + DISPATCH_NEXT_ACTION_FAST(); } action_variable_str: lwan_append_str_to_strbuf(buf, (char *)variables + (uintptr_t)chunk->data); - DISPATCH_NEXT_ACTION(); + DISPATCH_NEXT_ACTION_FAST(); action_variable_str_escape: lwan_append_str_escaped_to_strbuf(buf, (char *)variables + (uintptr_t)chunk->data); - DISPATCH_NEXT_ACTION(); + DISPATCH_NEXT_ACTION_FAST(); action_if_variable_not_empty: { struct chunk_descriptor *cd = chunk->data; @@ -1415,16 +1420,17 @@ action_if_variable_not_empty: { empty = !empty; if (empty) { chunk = cd->chunk; + DISPATCH_NEXT_ACTION_FAST(); } else { chunk = apply(tpl, chunk + 1, buf, variables, cd->chunk); + DISPATCH_NEXT_ACTION_CHECK(); } - DISPATCH_NEXT_ACTION(); } action_end_if_variable_not_empty: if (LIKELY(data == chunk)) goto finalize; - DISPATCH_NEXT_ACTION(); + DISPATCH_NEXT_ACTION_FAST(); action_apply_tpl: { struct lwan_tpl *inner_tpl = chunk->data; @@ -1440,7 +1446,7 @@ action_apply_tpl: { return NULL; } - DISPATCH_NEXT_ACTION(); + DISPATCH_NEXT_ACTION_FAST(); } action_start_iter: @@ -1466,12 +1472,13 @@ action_apply_tpl: { coro = NULL; if (negate) - DISPATCH_ACTION(); - DISPATCH_NEXT_ACTION(); + DISPATCH_ACTION_FAST(); + + DISPATCH_NEXT_ACTION_FAST(); } chunk = apply(tpl, chunk + 1, buf, variables, chunk); - DISPATCH_ACTION(); + DISPATCH_ACTION_CHECK(); action_end_iter: if (data == chunk->data) @@ -1482,23 +1489,28 @@ action_apply_tpl: { lwan_status_warning("Coroutine is NULL when finishing iteration"); return NULL; } - DISPATCH_NEXT_ACTION(); + DISPATCH_NEXT_ACTION_FAST(); } if (!coro_resume_value(coro, 0)) { coro_free(coro); coro = NULL; - DISPATCH_NEXT_ACTION(); + DISPATCH_NEXT_ACTION_FAST(); } chunk = apply(tpl, ((struct chunk *)chunk->data) + 1, buf, variables, chunk->data); - DISPATCH_ACTION(); + DISPATCH_ACTION_CHECK(); finalize: return chunk; #undef DISPATCH_ACTION #undef DISPATCH_NEXT_ACTION +#undef DISPATCH_ACTION_CHECK +#undef DISPATCH_NEXT_ACTION_CHECK +#undef DISPATCH_ACTION_FAST +#undef DISPATCH_NEXT_ACTION_FAST +#undef RETURN_IF_NO_CHUNK } bool lwan_tpl_apply_with_buffer(struct lwan_tpl *tpl, From 5a86ed1eae46f64a3b6155e860c111a8c87eb33d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 7 Nov 2019 07:44:41 -0800 Subject: [PATCH 1256/2505] Simplify lwan_http_authorize() No need to create a temporary lwan_value in the stack, just pass the two values as parameters to authorize(). --- src/lib/lwan-http-authorize.c | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/lib/lwan-http-authorize.c b/src/lib/lwan-http-authorize.c index c7b9178e7..0cf5e3194 100644 --- a/src/lib/lwan-http-authorize.c +++ b/src/lib/lwan-http-authorize.c @@ -142,7 +142,8 @@ bool lwan_http_authorize_init(void) void lwan_http_authorize_shutdown(void) { cache_destroy(realm_password_cache); } static bool authorize(struct coro *coro, - struct lwan_value *authorization, + const char *header, + size_t header_len, const char *password_file) { struct realm_password_file_t *rpf; @@ -158,8 +159,7 @@ static bool authorize(struct coro *coro, if (UNLIKELY(!rpf)) return false; - decoded = base64_decode((unsigned char *)authorization->value, - authorization->len, &decoded_len); + decoded = base64_decode((unsigned char *)header, header_len, &decoded_len); if (UNLIKELY(!decoded)) return false; @@ -189,21 +189,14 @@ bool lwan_http_authorize(struct lwan_request *request, const char *authorization = lwan_request_get_header(request, "Authorization"); - if (!authorization) - goto unauthorized; + if (LIKELY(authorization && !strncmp(authorization, "Basic ", basic_len))) { + const char *header = authorization + basic_len; + size_t header_len = strlen(authorization) - basic_len; - if (UNLIKELY(strncmp(authorization, "Basic ", basic_len))) - goto unauthorized; - - struct lwan_value header = { - .value = (char *)(authorization + basic_len), - .len = strlen(authorization) - basic_len, - }; - - if (authorize(request->conn->coro, &header, password_file)) - return true; + if (authorize(request->conn->coro, header, header_len, password_file)) + return true; + } -unauthorized: headers = coro_malloc(request->conn->coro, 2 * sizeof(*headers)); if (UNLIKELY(!headers)) return false; From c8abd29486516f1e81cf1899bf4300ced19ff9b9 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 7 Nov 2019 07:45:22 -0800 Subject: [PATCH 1257/2505] Batch execution of deferred callbacks when processing requests This should keep the deferred callback array storage within the inline storage for the coro_defer_array struct, while reducing the frequency of calls. --- src/lib/lwan-coro.c | 2 +- src/lib/lwan-thread.c | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index b0e669603..53e5f8406 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -203,7 +203,7 @@ void coro_deferred_run(struct coro *coro, size_t generation) array->elements = generation; } -size_t coro_deferred_get_generation(const struct coro *coro) +ALWAYS_INLINE size_t coro_deferred_get_generation(const struct coro *coro) { const struct lwan_array *array = (struct lwan_array *)&coro->defer; diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index a2f388058..c0af2cfd3 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -128,10 +128,16 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, .flags = flags, .proxy = &proxy}; - size_t generation = coro_deferred_get_generation(coro); + size_t prev_gen = coro_deferred_get_generation(coro); + next_request = lwan_process_request(lwan, &request, &buffer, next_request); - coro_deferred_run(coro, generation); + + if (coro_deferred_get_generation(coro) > (LWAN_ARRAY_INCREMENT - 1)) { + /* Batch execution of coro_defers() up to LWAN_ARRAY_INCREMENT-1 times, + * to avoid moving deferred array to heap in most cases. */ + coro_deferred_run(coro, prev_gen); + } if (LIKELY(conn->flags & CONN_IS_KEEP_ALIVE)) { if (next_request && *next_request) { From 66aa99b5b233e24e9f215c28c133470ae2079193 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 7 Nov 2019 07:57:56 -0800 Subject: [PATCH 1258/2505] coro_deferred_get_generation() isn't a pure function Calling it multiple times with the same coro may give a different value. --- src/lib/lwan-coro.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/lwan-coro.h b/src/lib/lwan-coro.h index 74590d44b..922834a4a 100644 --- a/src/lib/lwan-coro.h +++ b/src/lib/lwan-coro.h @@ -58,8 +58,7 @@ void coro_defer2(struct coro *coro, void *data2); void coro_deferred_run(struct coro *coro, size_t generation); -size_t coro_deferred_get_generation(const struct coro *coro) - __attribute__((pure)); +size_t coro_deferred_get_generation(const struct coro *coro); void *coro_malloc(struct coro *coro, size_t sz) __attribute__((malloc)); void *coro_malloc_full(struct coro *coro, From c59f739343feca0022eb38a22340c7c28d861e6a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 7 Nov 2019 19:20:29 -0800 Subject: [PATCH 1259/2505] Fix coro_defer batching --- src/lib/lwan-thread.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index c0af2cfd3..6bf8013fd 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -121,6 +121,9 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, (lwan->config.proxy_protocol ? REQUEST_ALLOW_PROXY_REQS : 0) | (lwan->config.allow_cors ? REQUEST_ALLOW_CORS : 0); + const size_t init_gen = 1; /* 1 call to coro_defer() */ + assert(init_gen == coro_deferred_get_generation(coro)); + while (true) { struct lwan_request request = {.conn = conn, .fd = fd, @@ -128,15 +131,13 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, .flags = flags, .proxy = &proxy}; - size_t prev_gen = coro_deferred_get_generation(coro); - next_request = lwan_process_request(lwan, &request, &buffer, next_request); if (coro_deferred_get_generation(coro) > (LWAN_ARRAY_INCREMENT - 1)) { /* Batch execution of coro_defers() up to LWAN_ARRAY_INCREMENT-1 times, * to avoid moving deferred array to heap in most cases. */ - coro_deferred_run(coro, prev_gen); + coro_deferred_run(coro, init_gen); } if (LIKELY(conn->flags & CONN_IS_KEEP_ALIVE)) { From 09e88ea9b299c8324acddfc87d234de2b01e43ef Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 9 Nov 2019 09:55:13 -0800 Subject: [PATCH 1260/2505] Simplify usage of ring buffers in config file parser --- src/lib/lwan-config.c | 79 +++++++++++++++++++------------------------ src/lib/ringbuffer.h | 2 +- 2 files changed, 36 insertions(+), 45 deletions(-) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index a5b96f694..11b514618 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -185,18 +185,15 @@ bool config_error(struct config *conf, const char *fmt, ...) return false; } -static bool config_buffer_consume(struct config_ring_buffer *buf, - struct config_line **line) +static const struct config_line * +config_buffer_consume(struct config_ring_buffer *buf) { - if (config_ring_buffer_empty(buf)) - return false; - - *line = config_ring_buffer_get_ptr(buf); - return true; + return config_ring_buffer_empty(buf) ? NULL + : config_ring_buffer_get_ptr(buf); } static bool config_buffer_emit(struct config_ring_buffer *buf, - struct config_line *line) + const struct config_line *line) { if (config_ring_buffer_full(buf)) return false; @@ -205,18 +202,15 @@ static bool config_buffer_emit(struct config_ring_buffer *buf, return true; } -static bool lexeme_buffer_consume(struct lexeme_ring_buffer *buf, - struct lexeme **lexeme) +static const struct lexeme * +lexeme_buffer_consume(struct lexeme_ring_buffer *buf) { - if (lexeme_ring_buffer_empty(buf)) - return false; - - *lexeme = lexeme_ring_buffer_get_ptr(buf); - return true; + return lexeme_ring_buffer_empty(buf) ? NULL + : lexeme_ring_buffer_get_ptr(buf); } static bool lexeme_buffer_emit(struct lexeme_ring_buffer *buf, - struct lexeme *lexeme) + const struct lexeme *lexeme) { if (lexeme_ring_buffer_full(buf)) return false; @@ -488,16 +482,18 @@ static void *lex_config(struct lexer *lexer) return NULL; } -static bool lex_next(struct lexer *lexer, struct lexeme **lexeme) +static const struct lexeme *lex_next(struct lexer *lexer) { while (lexer->state) { - if (lexeme_buffer_consume(&lexer->buffer, lexeme)) - return true; + const struct lexeme *lexeme; + + if ((lexeme = lexeme_buffer_consume(&lexer->buffer))) + return lexeme; lexer->state = lexer->state(lexer); } - return lexeme_buffer_consume(&lexer->buffer, lexeme); + return lexeme_buffer_consume(&lexer->buffer); } static void *parse_config(struct parser *parser); @@ -517,10 +513,10 @@ static __attribute__((noinline)) const char *secure_getenv_len(const char *key, static void *parse_key_value(struct parser *parser) { struct config_line line = {.type = CONFIG_LINE_TYPE_LINE}; - struct lexeme *lexeme; + const struct lexeme *lexeme; size_t key_size; - while (lexeme_buffer_consume(&parser->buffer, &lexeme)) { + while ((lexeme = lexeme_buffer_consume(&parser->buffer))) { lwan_strbuf_append_str(&parser->strbuf, lexeme->value.value, lexeme->value.len); @@ -530,7 +526,7 @@ static void *parse_key_value(struct parser *parser) key_size = lwan_strbuf_get_length(&parser->strbuf); lwan_strbuf_append_char(&parser->strbuf, '\0'); - while (lex_next(&parser->lexer, &lexeme)) { + while ((lexeme = lex_next(&parser->lexer))) { switch (lexeme->type) { case LEXEME_VARIABLE_DEFAULT: case LEXEME_VARIABLE: { @@ -547,9 +543,9 @@ static void *parse_key_value(struct parser *parser) lwan_strbuf_append_strz(&parser->strbuf, value); } } else { - struct lexeme *var_name = lexeme; + const struct lexeme *var_name = lexeme; - if (!lex_next(&parser->lexer, &lexeme)) { + if (!(lexeme = lex_next(&parser->lexer))) { lwan_status_error( "Default value for variable '$%.*s' not given", (int)var_name->value.len, var_name->value.value); @@ -610,17 +606,17 @@ static void *parse_key_value(struct parser *parser) static void *parse_section(struct parser *parser) { - struct lexeme *lexeme; + const struct lexeme *lexeme; size_t name_len; - if (!lexeme_buffer_consume(&parser->buffer, &lexeme)) + if (!(lexeme = lexeme_buffer_consume(&parser->buffer))) return NULL; lwan_strbuf_append_str(&parser->strbuf, lexeme->value.value, lexeme->value.len); name_len = lexeme->value.len; lwan_strbuf_append_char(&parser->strbuf, '\0'); - while (lexeme_buffer_consume(&parser->buffer, &lexeme)) { + while ((lexeme = lexeme_buffer_consume(&parser->buffer))) { lwan_strbuf_append_str(&parser->strbuf, lexeme->value.value, lexeme->value.len); if (!lexeme_ring_buffer_empty(&parser->buffer)) @@ -656,9 +652,9 @@ static void *parse_section_shorthand(struct parser *parser) static void *parse_config(struct parser *parser) { - struct lexeme *lexeme; + const struct lexeme *lexeme; - if (!lex_next(&parser->lexer, &lexeme)) + if (!(lexeme = lex_next(&parser->lexer))) return NULL; switch (lexeme->type) { @@ -697,18 +693,20 @@ static void *parse_config(struct parser *parser) } } -static bool parser_next(struct parser *parser, struct config_line **line) +static const struct config_line *parser_next(struct parser *parser) { while (parser->state) { - if (config_buffer_consume(&parser->items, line)) - return true; + const struct config_line *line; + + if ((line = config_buffer_consume(&parser->items))) + return line; lwan_strbuf_reset(&parser->strbuf); parser->state = parser->state(parser); } - return config_buffer_consume(&parser->items, line); + return config_buffer_consume(&parser->items); } static struct config * @@ -814,25 +812,18 @@ void config_close(struct config *config) const struct config_line *config_read_line(struct config *conf) { - if (!conf->error_message) { - struct config_line *ptr; - - if (parser_next(&conf->parser, &ptr)) - return ptr; - } - - return NULL; + return conf->error_message ? NULL : parser_next(&conf->parser); } static bool find_section_end(struct config *config) { - struct config_line *line; + const struct config_line *line; int cur_level = 1; if (config->error_message) return false; - while (parser_next(&config->parser, &line)) { + while ((line = parser_next(&config->parser))) { if (line->type == CONFIG_LINE_TYPE_SECTION) { cur_level++; } else if (line->type == CONFIG_LINE_TYPE_SECTION_END) { diff --git a/src/lib/ringbuffer.h b/src/lib/ringbuffer.h index 06cfd09f1..b298236ad 100644 --- a/src/lib/ringbuffer.h +++ b/src/lib/ringbuffer.h @@ -68,7 +68,7 @@ } \ \ __attribute__((unused)) static inline void type_name_##_put( \ - struct type_name_ *rb, element_type_ *e) \ + struct type_name_ *rb, const element_type_ *e) \ { \ assert(!type_name_##_full(rb)); \ memcpy(&rb->array[type_name_##_mask(rb->write++)], e, sizeof(*e)); \ From 0b567f9f409b9f281c9e46836136cff104300448 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 9 Nov 2019 09:56:25 -0800 Subject: [PATCH 1261/2505] Ble clearer in lwan_request_get_remote_address() for proxied+AF_UNSPEC addrs --- src/lib/lwan-request.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index d5dc9ad06..02c0f130b 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1511,16 +1511,23 @@ lwan_request_get_remote_address(struct lwan_request *request, if (request->flags & REQUEST_PROXIED) { sock_addr = (struct sockaddr_storage *)&request->proxy->from; - if (UNLIKELY(sock_addr->ss_family == AF_UNSPEC)) - return memcpy(buffer, "*unspecified*", sizeof("*unspecified*")); + if (UNLIKELY(sock_addr->ss_family == AF_UNSPEC)) { + static const char unspecified[] = "*unspecified*"; + + static_assert(sizeof(unspecified) <= INET6_ADDRSTRLEN, + "Enough space for unspecified address family"); + + return memcpy(buffer, unspecified, sizeof(unspecified)); + } } else { socklen_t sock_len = sizeof(non_proxied_addr); sock_addr = &non_proxied_addr; if (UNLIKELY(getpeername(request->fd, (struct sockaddr *)sock_addr, - &sock_len) < 0)) + &sock_len) < 0)) { return NULL; + } } if (sock_addr->ss_family == AF_INET) { From a93c844b391ba35da9dd69f4a06883494bf7b7db Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 9 Nov 2019 09:56:49 -0800 Subject: [PATCH 1262/2505] Give some slack to next request to use defer without moving array to heap --- src/lib/lwan-thread.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 6bf8013fd..1bf0ddba2 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -134,9 +134,10 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, next_request = lwan_process_request(lwan, &request, &buffer, next_request); - if (coro_deferred_get_generation(coro) > (LWAN_ARRAY_INCREMENT - 1)) { - /* Batch execution of coro_defers() up to LWAN_ARRAY_INCREMENT-1 times, - * to avoid moving deferred array to heap in most cases. */ + if (coro_deferred_get_generation(coro) > ((2 * LWAN_ARRAY_INCREMENT) / 3)) { + /* Batch execution of coro_defers() up to 2/3 LWAN_ARRAY_INCREMENT times, + * to avoid moving deferred array to heap in most cases. (This is to give + * some slack to the next request being processed by this coro.) */ coro_deferred_run(coro, init_gen); } From 4df2e5eba45b7d6162adda626278d899c8a2deea Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 10 Nov 2019 09:39:26 -0800 Subject: [PATCH 1263/2505] Pre-calculate request flags after reading configuration --- src/lib/lwan-thread.c | 13 +++++-------- src/lib/lwan.c | 10 +++++++--- src/lib/lwan.h | 3 +++ 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 1bf0ddba2..653d5fb60 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -103,11 +103,10 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, * instead. This ensures the storage for `strbuf` is alive when the * coroutine ends and lwan_strbuf_free() is called. */ struct lwan_connection *conn = data; - const enum lwan_request_flags flags_filter = - (REQUEST_PROXIED | REQUEST_ALLOW_CORS); - struct lwan_strbuf strbuf; struct lwan *lwan = conn->thread->lwan; int fd = lwan_connection_get_fd(lwan, conn); + enum lwan_request_flags flags = lwan->config.request_flags; + struct lwan_strbuf strbuf; char request_buffer[DEFAULT_BUFFER_SIZE]; struct lwan_value buffer = {.value = request_buffer, .len = 0}; char *next_request = NULL; @@ -117,10 +116,6 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, goto out; coro_defer(coro, lwan_strbuf_free_defer, &strbuf); - enum lwan_request_flags flags = - (lwan->config.proxy_protocol ? REQUEST_ALLOW_PROXY_REQS : 0) | - (lwan->config.allow_cors ? REQUEST_ALLOW_CORS : 0); - const size_t init_gen = 1; /* 1 call to coro_defer() */ assert(init_gen == coro_deferred_get_generation(coro)); @@ -155,7 +150,9 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, } lwan_strbuf_reset(&strbuf); - flags = request.flags & flags_filter; + + /* Only allow flags from config. */ + flags = request.flags & (REQUEST_PROXIED | REQUEST_ALLOW_CORS); } out: diff --git a/src/lib/lwan.c b/src/lib/lwan.c index d8ea7fd33..d4307f882 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -438,7 +438,8 @@ static bool setup_from_config(struct lwan *lwan, const char *path) return true; } -static void try_setup_from_config(struct lwan *l, const struct lwan_config *config) +static void try_setup_from_config(struct lwan *l, + const struct lwan_config *config) { if (!setup_from_config(l, config->config_file_path)) { if (config->config_file_path) { @@ -447,8 +448,11 @@ static void try_setup_from_config(struct lwan *l, const struct lwan_config *conf } } - /* `quiet` key might have changed value. */ - lwan_status_init(l); + lwan_status_init(l); /* `quiet` key might have changed value. */ + + l->config.request_flags = + (l->config.proxy_protocol ? REQUEST_ALLOW_PROXY_REQS : 0) | + (l->config.allow_cors ? REQUEST_ALLOW_CORS : 0); } static rlim_t setup_open_file_count_limits(void) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 5a775412d..d21359314 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -420,6 +420,9 @@ struct lwan_straitjacket { }; struct lwan_config { + /* Field will be overridden during initialization. */ + enum lwan_request_flags request_flags; + char *listener; char *error_template; char *config_file_path; From f627efb97dd61cce25eb9f7d2a38ee3af5459e69 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 12 Nov 2019 19:55:52 -0800 Subject: [PATCH 1264/2505] Rename death queue to timeout queue --- src/lib/CMakeLists.txt | 2 +- src/lib/lwan-dq.c | 120 ------------------------------- src/lib/lwan-thread.c | 64 ++++++++--------- src/lib/lwan-tq.c | 120 +++++++++++++++++++++++++++++++ src/lib/{lwan-dq.h => lwan-tq.h} | 21 +++--- 5 files changed, 163 insertions(+), 164 deletions(-) delete mode 100644 src/lib/lwan-dq.c create mode 100644 src/lib/lwan-tq.c rename src/lib/{lwan-dq.h => lwan-tq.h} (63%) diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index 1739a910d..e6af20dca 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -41,7 +41,7 @@ set(SOURCES sd-daemon.c timeout.c sha1.c - lwan-dq.c + lwan-tq.c ) if (HAVE_LUA) diff --git a/src/lib/lwan-dq.c b/src/lib/lwan-dq.c deleted file mode 100644 index 48455eac6..000000000 --- a/src/lib/lwan-dq.c +++ /dev/null @@ -1,120 +0,0 @@ -/* - * lwan - simple web server - * Copyright (c) 2019 Leandro A. F. Pereira - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - */ - -#include - -#include "lwan-private.h" -#include "lwan-dq.h" - -static inline int death_queue_node_to_idx(struct death_queue *dq, - struct lwan_connection *conn) -{ - return (conn == &dq->head) ? -1 : (int)(ptrdiff_t)(conn - dq->conns); -} - -static inline struct lwan_connection * -death_queue_idx_to_node(struct death_queue *dq, int idx) -{ - return (idx < 0) ? &dq->head : &dq->conns[idx]; -} - -void death_queue_insert(struct death_queue *dq, - struct lwan_connection *new_node) -{ - new_node->next = -1; - new_node->prev = dq->head.prev; - struct lwan_connection *prev = death_queue_idx_to_node(dq, dq->head.prev); - dq->head.prev = prev->next = death_queue_node_to_idx(dq, new_node); -} - -static void death_queue_remove(struct death_queue *dq, - struct lwan_connection *node) -{ - struct lwan_connection *prev = death_queue_idx_to_node(dq, node->prev); - struct lwan_connection *next = death_queue_idx_to_node(dq, node->next); - - next->prev = node->prev; - prev->next = node->next; - - node->next = node->prev = -1; -} - -bool death_queue_empty(struct death_queue *dq) { return dq->head.next < 0; } - -void death_queue_move_to_last(struct death_queue *dq, - struct lwan_connection *conn) -{ - /* CONN_IS_KEEP_ALIVE isn't checked here because non-keep-alive connections - * are closed in the request processing coroutine after they have been - * served. In practice, if this is called, it's a keep-alive connection. */ - conn->time_to_die = dq->time + dq->keep_alive_timeout; - - death_queue_remove(dq, conn); - death_queue_insert(dq, conn); -} - -void death_queue_init(struct death_queue *dq, const struct lwan *lwan) -{ - dq->lwan = lwan; - dq->conns = lwan->conns; - dq->time = 0; - dq->keep_alive_timeout = lwan->config.keep_alive_timeout; - dq->head.next = dq->head.prev = -1; - dq->timeout = (struct timeout){}; -} - -void death_queue_kill(struct death_queue *dq, struct lwan_connection *conn) -{ - death_queue_remove(dq, conn); - - if (LIKELY(conn->coro)) { - coro_free(conn->coro); - conn->coro = NULL; - - close(lwan_connection_get_fd(dq->lwan, conn)); - } -} - -void death_queue_kill_waiting(struct death_queue *dq) -{ - dq->time++; - - while (!death_queue_empty(dq)) { - struct lwan_connection *conn = - death_queue_idx_to_node(dq, dq->head.next); - - if (conn->time_to_die > dq->time) - return; - - death_queue_kill(dq, conn); - } - - /* Death queue exhausted: reset epoch */ - dq->time = 0; -} - -void death_queue_kill_all(struct death_queue *dq) -{ - while (!death_queue_empty(dq)) { - struct lwan_connection *conn = - death_queue_idx_to_node(dq, dq->head.next); - death_queue_kill(dq, conn); - } -} diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 653d5fb60..04c12fce3 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -36,7 +36,7 @@ #endif #include "lwan-private.h" -#include "lwan-dq.h" +#include "lwan-tq.h" #include "list.h" static ALWAYS_INLINE int min(const int a, const int b) { return a < b ? a : b; } @@ -234,17 +234,17 @@ static void update_epoll_flags(int fd, } static ALWAYS_INLINE void -resume_coro(struct death_queue *dq, struct lwan_connection *conn, int epoll_fd) +resume_coro(struct timeout_queue *tq, struct lwan_connection *conn, int epoll_fd) { assert(conn->coro); enum lwan_connection_coro_yield yield_result = coro_resume(conn->coro); if (yield_result == CONN_CORO_ABORT) { - death_queue_kill(dq, conn); + timeout_queue_kill(tq, conn); return; } - update_epoll_flags(lwan_connection_get_fd(dq->lwan, conn), conn, epoll_fd, + update_epoll_flags(lwan_connection_get_fd(tq->lwan, conn), conn, epoll_fd, yield_result); } @@ -259,20 +259,20 @@ static void update_date_cache(struct lwan_thread *thread) static ALWAYS_INLINE void spawn_coro(struct lwan_connection *conn, struct coro_switcher *switcher, - struct death_queue *dq) + struct timeout_queue *tq) { struct lwan_thread *t = conn->thread; assert(!conn->coro); assert(t); - assert((uintptr_t)t >= (uintptr_t)dq->lwan->thread.threads); + assert((uintptr_t)t >= (uintptr_t)tq->lwan->thread.threads); assert((uintptr_t)t < - (uintptr_t)(dq->lwan->thread.threads + dq->lwan->thread.count)); + (uintptr_t)(tq->lwan->thread.threads + tq->lwan->thread.count)); *conn = (struct lwan_connection) { .coro = coro_new(switcher, process_request_coro, conn), .flags = CONN_EVENTS_READ, - .time_to_die = dq->time + dq->keep_alive_timeout, + .time_to_die = tq->time + tq->keep_alive_timeout, .thread = t, }; if (UNLIKELY(!conn->coro)) { @@ -281,13 +281,13 @@ static ALWAYS_INLINE void spawn_coro(struct lwan_connection *conn, return; } - death_queue_insert(dq, conn); + timeout_queue_insert(tq, conn); } static void accept_nudge(int pipe_fd, struct lwan_thread *t, struct lwan_connection *conns, - struct death_queue *dq, + struct timeout_queue *tq, struct coro_switcher *switcher, int epoll_fd) { @@ -307,25 +307,25 @@ static void accept_nudge(int pipe_fd, }; if (LIKELY(!epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_fd, &ev))) - spawn_coro(conn, switcher, dq); + spawn_coro(conn, switcher, tq); } - timeouts_add(t->wheel, &dq->timeout, 1000); + timeouts_add(t->wheel, &tq->timeout, 1000); } -static bool process_pending_timers(struct death_queue *dq, +static bool process_pending_timers(struct timeout_queue *tq, struct lwan_thread *t, int epoll_fd) { struct timeout *timeout; - bool processed_dq_timeout = false; + bool processed_tq_timeout = false; while ((timeout = timeouts_get(t->wheel))) { struct lwan_request *request; - if (timeout == &dq->timeout) { - death_queue_kill_waiting(dq); - processed_dq_timeout = true; + if (timeout == &tq->timeout) { + timeout_queue_kill_waiting(tq); + processed_tq_timeout = true; continue; } @@ -335,24 +335,24 @@ static bool process_pending_timers(struct death_queue *dq, CONN_CORO_RESUME_TIMER); } - if (processed_dq_timeout) { - /* dq timeout expires every 1000ms if there are connections, so + if (processed_tq_timeout) { + /* tq timeout expires every 1000ms if there are connections, so * update the date cache at this point as well. */ update_date_cache(t); - if (!death_queue_empty(dq)) { - timeouts_add(t->wheel, &dq->timeout, 1000); + if (!timeout_queue_empty(tq)) { + timeouts_add(t->wheel, &tq->timeout, 1000); return true; } - timeouts_del(t->wheel, &dq->timeout); + timeouts_del(t->wheel, &tq->timeout); } return false; } static int -turn_timer_wheel(struct death_queue *dq, struct lwan_thread *t, int epoll_fd) +turn_timer_wheel(struct timeout_queue *tq, struct lwan_thread *t, int epoll_fd) { timeout_t wheel_timeout; struct timespec now; @@ -368,7 +368,7 @@ turn_timer_wheel(struct death_queue *dq, struct lwan_thread *t, int epoll_fd) goto infinite_timeout; if (wheel_timeout == 0) { - if (!process_pending_timers(dq, t, epoll_fd)) + if (!process_pending_timers(tq, t, epoll_fd)) goto infinite_timeout; wheel_timeout = timeouts_timeout(t->wheel); @@ -391,7 +391,7 @@ static void *thread_io_loop(void *data) struct lwan *lwan = t->lwan; struct epoll_event *events; struct coro_switcher switcher; - struct death_queue dq; + struct timeout_queue tq; lwan_status_debug("Worker thread #%zd starting", t - t->lwan->thread.threads + 1); @@ -403,12 +403,12 @@ static void *thread_io_loop(void *data) update_date_cache(t); - death_queue_init(&dq, lwan); + timeout_queue_init(&tq, lwan); pthread_barrier_wait(&lwan->thread.barrier); for (;;) { - int timeout = turn_timer_wheel(&dq, t, epoll_fd); + int timeout = turn_timer_wheel(&tq, t, epoll_fd); int n_fds = epoll_wait(epoll_fd, events, max_events, timeout); if (UNLIKELY(n_fds < 0)) { @@ -421,7 +421,7 @@ static void *thread_io_loop(void *data) struct lwan_connection *conn; if (UNLIKELY(!event->data.ptr)) { - accept_nudge(read_pipe_fd, t, lwan->conns, &dq, &switcher, + accept_nudge(read_pipe_fd, t, lwan->conns, &tq, &switcher, epoll_fd); continue; } @@ -429,18 +429,18 @@ static void *thread_io_loop(void *data) conn = event->data.ptr; if (UNLIKELY(event->events & (EPOLLRDHUP | EPOLLHUP))) { - death_queue_kill(&dq, conn); + timeout_queue_kill(&tq, conn); continue; } - resume_coro(&dq, conn, epoll_fd); - death_queue_move_to_last(&dq, conn); + resume_coro(&tq, conn, epoll_fd); + timeout_queue_move_to_last(&tq, conn); } } pthread_barrier_wait(&lwan->thread.barrier); - death_queue_kill_all(&dq); + timeout_queue_kill_all(&tq); free(events); return NULL; diff --git a/src/lib/lwan-tq.c b/src/lib/lwan-tq.c new file mode 100644 index 000000000..1246b2d3e --- /dev/null +++ b/src/lib/lwan-tq.c @@ -0,0 +1,120 @@ +/* + * lwan - simple web server + * Copyright (c) 2019 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include + +#include "lwan-private.h" +#include "lwan-tq.h" + +static inline int timeout_queue_node_to_idx(struct timeout_queue *tq, + struct lwan_connection *conn) +{ + return (conn == &tq->head) ? -1 : (int)(ptrdiff_t)(conn - tq->conns); +} + +static inline struct lwan_connection * +timeout_queue_idx_to_node(struct timeout_queue *tq, int idx) +{ + return (idx < 0) ? &tq->head : &tq->conns[idx]; +} + +void timeout_queue_insert(struct timeout_queue *tq, + struct lwan_connection *new_node) +{ + new_node->next = -1; + new_node->prev = tq->head.prev; + struct lwan_connection *prev = timeout_queue_idx_to_node(tq, tq->head.prev); + tq->head.prev = prev->next = timeout_queue_node_to_idx(tq, new_node); +} + +static void timeout_queue_remove(struct timeout_queue *tq, + struct lwan_connection *node) +{ + struct lwan_connection *prev = timeout_queue_idx_to_node(tq, node->prev); + struct lwan_connection *next = timeout_queue_idx_to_node(tq, node->next); + + next->prev = node->prev; + prev->next = node->next; + + node->next = node->prev = -1; +} + +bool timeout_queue_empty(struct timeout_queue *tq) { return tq->head.next < 0; } + +void timeout_queue_move_to_last(struct timeout_queue *tq, + struct lwan_connection *conn) +{ + /* CONN_IS_KEEP_ALIVE isn't checked here because non-keep-alive connections + * are closed in the request processing coroutine after they have been + * served. In practice, if this is called, it's a keep-alive connection. */ + conn->time_to_die = tq->time + tq->keep_alive_timeout; + + timeout_queue_remove(tq, conn); + timeout_queue_insert(tq, conn); +} + +void timeout_queue_init(struct timeout_queue *tq, const struct lwan *lwan) +{ + tq->lwan = lwan; + tq->conns = lwan->conns; + tq->time = 0; + tq->keep_alive_timeout = lwan->config.keep_alive_timeout; + tq->head.next = tq->head.prev = -1; + tq->timeout = (struct timeout){}; +} + +void timeout_queue_kill(struct timeout_queue *tq, struct lwan_connection *conn) +{ + timeout_queue_remove(tq, conn); + + if (LIKELY(conn->coro)) { + coro_free(conn->coro); + conn->coro = NULL; + + close(lwan_connection_get_fd(tq->lwan, conn)); + } +} + +void timeout_queue_kill_waiting(struct timeout_queue *tq) +{ + tq->time++; + + while (!timeout_queue_empty(tq)) { + struct lwan_connection *conn = + timeout_queue_idx_to_node(tq, tq->head.next); + + if (conn->time_to_die > tq->time) + return; + + timeout_queue_kill(tq, conn); + } + + /* Death queue exhausted: reset epoch */ + tq->time = 0; +} + +void timeout_queue_kill_all(struct timeout_queue *tq) +{ + while (!timeout_queue_empty(tq)) { + struct lwan_connection *conn = + timeout_queue_idx_to_node(tq, tq->head.next); + timeout_queue_kill(tq, conn); + } +} diff --git a/src/lib/lwan-dq.h b/src/lib/lwan-tq.h similarity index 63% rename from src/lib/lwan-dq.h rename to src/lib/lwan-tq.h index 895f97520..a3dd7efc2 100644 --- a/src/lib/lwan-dq.h +++ b/src/lib/lwan-tq.h @@ -20,7 +20,7 @@ #pragma once -struct death_queue { +struct timeout_queue { const struct lwan *lwan; struct lwan_connection *conns; struct lwan_connection head; @@ -29,16 +29,15 @@ struct death_queue { unsigned short keep_alive_timeout; }; -void death_queue_init(struct death_queue *dq, const struct lwan *l); +void timeout_queue_init(struct timeout_queue *tq, const struct lwan *l); -void death_queue_insert(struct death_queue *dq, - struct lwan_connection *new_node); -void death_queue_kill(struct death_queue *dq, struct lwan_connection *node); -void death_queue_move_to_last(struct death_queue *dq, - struct lwan_connection *conn); +void timeout_queue_insert(struct timeout_queue *tq, + struct lwan_connection *new_node); +void timeout_queue_kill(struct timeout_queue *tq, struct lwan_connection *node); +void timeout_queue_move_to_last(struct timeout_queue *tq, + struct lwan_connection *conn); -void death_queue_kill_waiting(struct death_queue *dq); -void death_queue_kill_all(struct death_queue *dq); - -bool death_queue_empty(struct death_queue *dq); +void timeout_queue_kill_waiting(struct timeout_queue *tq); +void timeout_queue_kill_all(struct timeout_queue *tq); +bool timeout_queue_empty(struct timeout_queue *tq); From 01bd0e9f782d9dd9a7cbcf188a1f2ee4067b6522 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 13 Nov 2019 18:13:16 -0800 Subject: [PATCH 1265/2505] Simplify how ring buffer elements are consumed --- src/lib/lwan-config.c | 87 ++++++++++++----------------------------- src/lib/lwan-template.c | 55 ++++++++++++-------------- src/lib/ringbuffer.h | 22 ++++++++++- 3 files changed, 70 insertions(+), 94 deletions(-) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 11b514618..c9ff02f62 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -185,46 +185,10 @@ bool config_error(struct config *conf, const char *fmt, ...) return false; } -static const struct config_line * -config_buffer_consume(struct config_ring_buffer *buf) -{ - return config_ring_buffer_empty(buf) ? NULL - : config_ring_buffer_get_ptr(buf); -} - -static bool config_buffer_emit(struct config_ring_buffer *buf, - const struct config_line *line) -{ - if (config_ring_buffer_full(buf)) - return false; - - config_ring_buffer_put(buf, line); - return true; -} - -static const struct lexeme * -lexeme_buffer_consume(struct lexeme_ring_buffer *buf) -{ - return lexeme_ring_buffer_empty(buf) ? NULL - : lexeme_ring_buffer_get_ptr(buf); -} - -static bool lexeme_buffer_emit(struct lexeme_ring_buffer *buf, - const struct lexeme *lexeme) -{ - if (lexeme_ring_buffer_full(buf)) - return false; - - lexeme_ring_buffer_put(buf, lexeme); - return true; -} - static void emit_lexeme(struct lexer *lexer, struct lexeme *lexeme) { - if (!lexeme_buffer_emit(&lexer->buffer, lexeme)) - return; - - lexer->start = lexer->pos; + if (lexeme_ring_buffer_try_put(&lexer->buffer, lexeme)) + lexer->start = lexer->pos; } static void emit(struct lexer *lexer, enum lexeme_type type) @@ -487,13 +451,13 @@ static const struct lexeme *lex_next(struct lexer *lexer) while (lexer->state) { const struct lexeme *lexeme; - if ((lexeme = lexeme_buffer_consume(&lexer->buffer))) + if ((lexeme = lexeme_ring_buffer_get_ptr_or_null(&lexer->buffer))) return lexeme; lexer->state = lexer->state(lexer); } - return lexeme_buffer_consume(&lexer->buffer); + return lexeme_ring_buffer_get_ptr_or_null(&lexer->buffer); } static void *parse_config(struct parser *parser); @@ -516,7 +480,7 @@ static void *parse_key_value(struct parser *parser) const struct lexeme *lexeme; size_t key_size; - while ((lexeme = lexeme_buffer_consume(&parser->buffer))) { + while ((lexeme = lexeme_ring_buffer_get_ptr_or_null(&parser->buffer))) { lwan_strbuf_append_str(&parser->strbuf, lexeme->value.value, lexeme->value.len); @@ -588,10 +552,9 @@ static void *parse_key_value(struct parser *parser) case LEXEME_LINEFEED: line.key = lwan_strbuf_get_buffer(&parser->strbuf); line.value = line.key + key_size + 1; - if (!config_buffer_emit(&parser->items, &line)) - return NULL; - - return parse_config; + return config_ring_buffer_try_put(&parser->items, &line) + ? parse_config + : NULL; default: lwan_status_error("Unexpected token while parsing key-value: %s", @@ -609,15 +572,17 @@ static void *parse_section(struct parser *parser) const struct lexeme *lexeme; size_t name_len; - if (!(lexeme = lexeme_buffer_consume(&parser->buffer))) + if (!(lexeme = lexeme_ring_buffer_get_ptr_or_null(&parser->buffer))) return NULL; - lwan_strbuf_append_str(&parser->strbuf, lexeme->value.value, lexeme->value.len); + lwan_strbuf_append_str(&parser->strbuf, lexeme->value.value, + lexeme->value.len); name_len = lexeme->value.len; lwan_strbuf_append_char(&parser->strbuf, '\0'); - while ((lexeme = lexeme_buffer_consume(&parser->buffer))) { - lwan_strbuf_append_str(&parser->strbuf, lexeme->value.value, lexeme->value.len); + while ((lexeme = lexeme_ring_buffer_get_ptr_or_null(&parser->buffer))) { + lwan_strbuf_append_str(&parser->strbuf, lexeme->value.value, + lexeme->value.len); if (!lexeme_ring_buffer_empty(&parser->buffer)) lwan_strbuf_append_char(&parser->strbuf, ' '); @@ -626,12 +591,10 @@ static void *parse_section(struct parser *parser) struct config_line line = { .type = CONFIG_LINE_TYPE_SECTION, .key = lwan_strbuf_get_buffer(&parser->strbuf), - .value = lwan_strbuf_get_buffer(&parser->strbuf) + name_len + 1 + .value = lwan_strbuf_get_buffer(&parser->strbuf) + name_len + 1, }; - if (!config_buffer_emit(&parser->items, &line)) - return NULL; - - return parse_config; + return config_ring_buffer_try_put(&parser->items, &line) ? parse_config + : NULL; } static void *parse_section_shorthand(struct parser *parser) @@ -639,12 +602,10 @@ static void *parse_section_shorthand(struct parser *parser) void *next_state = parse_section(parser); if (next_state) { - struct config_line line = { .type = CONFIG_LINE_TYPE_SECTION_END }; - - if (!config_buffer_emit(&parser->items, &line)) - return NULL; + struct config_line line = {.type = CONFIG_LINE_TYPE_SECTION_END}; - return next_state; + if (config_ring_buffer_try_put(&parser->items, &line)) + return next_state; } return NULL; @@ -671,14 +632,14 @@ static void *parse_config(struct parser *parser) return parse_config; case LEXEME_STRING: - lexeme_buffer_emit(&parser->buffer, lexeme); + lexeme_ring_buffer_try_put(&parser->buffer, lexeme); return parse_config; case LEXEME_CLOSE_BRACKET: { struct config_line line = { .type = CONFIG_LINE_TYPE_SECTION_END }; - config_buffer_emit(&parser->items, &line); + config_ring_buffer_try_put(&parser->items, &line); return parse_config; } @@ -698,7 +659,7 @@ static const struct config_line *parser_next(struct parser *parser) while (parser->state) { const struct config_line *line; - if ((line = config_buffer_consume(&parser->items))) + if ((line = config_ring_buffer_get_ptr_or_null(&parser->items))) return line; lwan_strbuf_reset(&parser->strbuf); @@ -706,7 +667,7 @@ static const struct config_line *parser_next(struct parser *parser) parser->state = parser->state(parser); } - return config_buffer_consume(&parser->items); + return config_ring_buffer_get_ptr_or_null(&parser->items); } static struct config * diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index 94ed308bc..deade1ca0 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -260,15 +260,6 @@ static void emit_lexeme(struct lexer *lexer, struct lexeme *lexeme) lexer->start = lexer->pos; } -static bool consume_lexeme(struct lexer *lexer, struct lexeme **lexeme) -{ - if (lexeme_ring_buffer_empty(&lexer->ring_buffer)) - return false; - - *lexeme = lexeme_ring_buffer_get_ptr(&lexer->ring_buffer); - return true; -} - static void emit(struct lexer *lexer, enum lexeme_type lexeme_type) { struct lexeme lexeme = { @@ -489,15 +480,18 @@ static void *lex_text(struct lexer *lexer) return NULL; } -static bool lex_next(struct lexer *lexer, struct lexeme **lexeme) +static struct lexeme *lex_next(struct lexer *lexer) { + struct lexeme *lexeme; + while (lexer->state) { - if (consume_lexeme(lexer, lexeme)) - return true; + if ((lexeme = lexeme_ring_buffer_get_ptr_or_null(&lexer->ring_buffer))) + return lexeme; + lexer->state = lexer->state(lexer); } - return consume_lexeme(lexer, lexeme); + return lexeme_ring_buffer_get_ptr_or_null(&lexer->ring_buffer); } static void lex_init(struct lexer *lexer, const char *input) @@ -658,16 +652,15 @@ static void *parser_end_var_not_empty(struct parser *parser, static void *parser_slash(struct parser *parser, struct lexeme *lexeme) { if (lexeme->type == LEXEME_IDENTIFIER) { - struct lexeme *next = NULL; + struct lexeme *next; - if (!lex_next(&parser->lexer, &next)) - return unexpected_lexeme_or_lex_error(lexeme, next); - - if (next->type == LEXEME_RIGHT_META) - return parser_end_iter(parser, lexeme); + if ((next = lex_next(&parser->lexer))) { + if (next->type == LEXEME_RIGHT_META) + return parser_end_iter(parser, lexeme); - if (next->type == LEXEME_QUESTION_MARK) - return parser_end_var_not_empty(parser, lexeme); + if (next->type == LEXEME_QUESTION_MARK) + return parser_end_var_not_empty(parser, lexeme); + } return unexpected_lexeme_or_lex_error(lexeme, next); } @@ -725,18 +718,15 @@ static void *parser_negate(struct parser *parser, struct lexeme *lexeme) static void *parser_identifier(struct parser *parser, struct lexeme *lexeme) { - struct lexeme *next = NULL; + struct lexeme *next; - if (!lex_next(&parser->lexer, &next)) { - if (next) - *lexeme = *next; + if (!(next = lex_next(&parser->lexer))) return NULL; - } if (parser->flags & FLAGS_QUOTE) { if (next->type != LEXEME_CLOSE_CURLY_BRACE) return error_lexeme(lexeme, "Expecting closing brace"); - if (!lex_next(&parser->lexer, &next)) + if (!(next = lex_next(&parser->lexer))) return unexpected_lexeme_or_lex_error(lexeme, next); } @@ -1152,14 +1142,19 @@ static bool parse_string(struct lwan_tpl *tpl, .template_flags = flags }; void *(*state)(struct parser *parser, struct lexeme *lexeme) = parser_text; - struct lexeme *lexeme = NULL; + struct lexeme *lexeme; if (!parser_init(&parser, descriptor, string)) return false; - while (state && lex_next(&parser.lexer, &lexeme) && - lexeme->type != LEXEME_ERROR) + while (state) { + lexeme = lex_next(&parser.lexer); + + if (!lexeme || lexeme->type == LEXEME_ERROR) + break; + state = state(&parser, lexeme); + } return parser_shutdown(&parser, lexeme); } diff --git a/src/lib/ringbuffer.h b/src/lib/ringbuffer.h index b298236ad..ffbb8fdc0 100644 --- a/src/lib/ringbuffer.h +++ b/src/lib/ringbuffer.h @@ -14,7 +14,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ /* @@ -25,6 +26,7 @@ #pragma once #include +#include #include #include @@ -74,6 +76,16 @@ memcpy(&rb->array[type_name_##_mask(rb->write++)], e, sizeof(*e)); \ } \ \ + __attribute__((unused)) static inline bool type_name_##_try_put( \ + struct type_name_ *rb, const element_type_ *e) \ + { \ + if (type_name_##_full(rb)) \ + return false; \ + \ + memcpy(&rb->array[type_name_##_mask(rb->write++)], e, sizeof(*e)); \ + return true; \ + } \ + \ __attribute__((unused)) static inline element_type_ type_name_##_get( \ struct type_name_ *rb) \ { \ @@ -86,4 +98,12 @@ { \ assert(!type_name_##_empty(rb)); \ return &rb->array[type_name_##_mask(rb->read++)]; \ + } \ + \ + __attribute__((unused)) static inline element_type_ \ + *type_name_##_get_ptr_or_null(struct type_name_ *rb) \ + { \ + return type_name_##_empty(rb) \ + ? NULL \ + : &rb->array[type_name_##_mask(rb->read++)]; \ } From e1eea5e49856a6a9293ff7bbd50f08476a23e236 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 13 Nov 2019 19:10:48 -0800 Subject: [PATCH 1266/2505] Ensure template parser won't read beyond data when parsing meta Similar fix applied in the configuration file parser after fuzzing. --- src/lib/lwan-template.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index deade1ca0..c37943a05 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -281,6 +281,19 @@ static int next(struct lexer *lexer) return r; } +static size_t remaining(struct lexer *lexer) +{ + return (size_t)(lexer->end - lexer->pos); +} + +static bool lex_streq(struct lexer *lexer, const char *str, size_t s) +{ + if (remaining(lexer) < s) + return false; + + return !strncmp(lexer->pos, str, s); +} + static void ignore(struct lexer *lexer) { lexer->start = lexer->pos; } static void backup(struct lexer *lexer) { lexer->pos--; } @@ -401,7 +414,7 @@ static void *lex_inside_action(struct lexer *lexer) while (true) { int r; - if (!strncmp(lexer->pos, right_meta, strlen(right_meta))) + if (lex_streq(lexer, right_meta, strlen(right_meta))) return lex_right_meta; r = next(lexer); @@ -466,12 +479,13 @@ static void *lex_right_meta(struct lexer *lexer) static void *lex_text(struct lexer *lexer) { do { - if (!strncmp(lexer->pos, left_meta, strlen(left_meta))) { + if (lex_streq(lexer, left_meta, strlen(left_meta))) { if (lexer->pos > lexer->start) emit(lexer, LEXEME_TEXT); return lex_left_meta; } - if (!strncmp(lexer->pos, right_meta, strlen(right_meta))) + + if (lex_streq(lexer, right_meta, strlen(right_meta))) return lex_error(lexer, "unexpected action close sequence"); } while (next(lexer) != EOF); if (lexer->pos > lexer->start) From 6aab7a72d5ac7a1d8518a07ee939be84b0f01c5f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 13 Nov 2019 20:39:54 -0800 Subject: [PATCH 1267/2505] Plug memory leaks in template parser --- src/lib/lwan-template.c | 89 +++++++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 38 deletions(-) diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index c37943a05..b2b7242e8 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -300,20 +300,25 @@ static void backup(struct lexer *lexer) { lexer->pos--; } static void error_vlexeme(struct lexeme *lexeme, const char *msg, va_list ap) { + char *formatted; + size_t formatted_len; int r; - lexeme->type = LEXEME_ERROR; + *lexeme = (struct lexeme){.type = LEXEME_ERROR}; - r = vasprintf((char **)&lexeme->value.value, msg, ap); + r = vasprintf(&formatted, msg, ap); if (r < 0) { lexeme->value.value = strdup(strerror(errno)); if (!lexeme->value.value) return; - lexeme->value.len = strlen(lexeme->value.value); + formatted_len = strlen(lexeme->value.value); } else { - lexeme->value.len = (size_t)r; + formatted_len = (size_t)r; } + + lwan_status_error("Error while parsing template: %.*s", (int)formatted_len, formatted); + free(formatted); } static void *error_lexeme(struct lexeme *lexeme, const char *msg, ...) @@ -494,7 +499,7 @@ static void *lex_text(struct lexer *lexer) return NULL; } -static struct lexeme *lex_next(struct lexer *lexer) +static struct lexeme *lex_next_fsm_loop(struct lexer *lexer) { struct lexeme *lexeme; @@ -508,6 +513,18 @@ static struct lexeme *lex_next(struct lexer *lexer) return lexeme_ring_buffer_get_ptr_or_null(&lexer->ring_buffer); } +static struct lexeme *lex_next(struct lexer *lexer) +{ + struct lexeme *lexeme = lex_next_fsm_loop(lexer); + + if (lexeme) { + if (lexeme->type == LEXEME_ERROR || lexeme->type == LEXEME_EOF) + return NULL; + } + + return lexeme; +} + static void lex_init(struct lexer *lexer, const char *input) { lexer->state = lex_text; @@ -518,23 +535,14 @@ static void lex_init(struct lexer *lexer, const char *input) static void *unexpected_lexeme(struct lexeme *lexeme) { + if (lexeme->type == LEXEME_ERROR) + return NULL; + return error_lexeme(lexeme, "unexpected lexeme: %s [%.*s]", lexeme_type_str[lexeme->type], (int)lexeme->value.len, lexeme->value.value); } -static void *unexpected_lexeme_or_lex_error(struct lexeme *lexeme, - struct lexeme *lex_error) -{ - if (lex_error && - (lex_error->type == LEXEME_ERROR || lex_error->type == LEXEME_EOF)) { - *lexeme = *lex_error; - return NULL; - } - - return unexpected_lexeme(lexeme); -} - static void parser_push_lexeme(struct parser *parser, struct lexeme *lexeme) { struct stacked_lexeme *stacked_lexeme = malloc(sizeof(*stacked_lexeme)); @@ -674,9 +682,9 @@ static void *parser_slash(struct parser *parser, struct lexeme *lexeme) if (next->type == LEXEME_QUESTION_MARK) return parser_end_var_not_empty(parser, lexeme); - } - return unexpected_lexeme_or_lex_error(lexeme, next); + return unexpected_lexeme(next); + } } return unexpected_lexeme(lexeme); @@ -741,7 +749,7 @@ static void *parser_identifier(struct parser *parser, struct lexeme *lexeme) if (next->type != LEXEME_CLOSE_CURLY_BRACE) return error_lexeme(lexeme, "Expecting closing brace"); if (!(next = lex_next(&parser->lexer))) - return unexpected_lexeme_or_lex_error(lexeme, next); + return unexpected_lexeme(lexeme); } if (next->type == LEXEME_RIGHT_META) { @@ -756,6 +764,7 @@ static void *parser_identifier(struct parser *parser, struct lexeme *lexeme) parser->flags &= ~FLAGS_QUOTE; parser->tpl->minimum_size += lexeme->value.len + 1; + return parser_text; } @@ -776,7 +785,7 @@ static void *parser_identifier(struct parser *parser, struct lexeme *lexeme) return parser_right_meta; } - return unexpected_lexeme_or_lex_error(lexeme, next); + return unexpected_lexeme(next); } static void *parser_partial(struct parser *parser, struct lexeme *lexeme) @@ -970,21 +979,21 @@ static void free_chunk(struct chunk *chunk) } } -void lwan_tpl_free(struct lwan_tpl *tpl) +static void free_chunk_array(struct chunk_array *array) { - if (!tpl) - return; - - if (tpl->chunks.base.elements) { - struct chunk *iter; + struct chunk *iter; - LWAN_ARRAY_FOREACH(&tpl->chunks, iter) - free_chunk(iter); + LWAN_ARRAY_FOREACH(array, iter) + free_chunk(iter); + chunk_array_reset(array); +} - chunk_array_reset(&tpl->chunks); +void lwan_tpl_free(struct lwan_tpl *tpl) +{ + if (tpl) { + free_chunk_array(&tpl->chunks); + free(tpl); } - - free(tpl); } static bool post_process_template(struct parser *parser) @@ -1099,7 +1108,7 @@ static bool parser_shutdown(struct parser *parser, struct lexeme *lexeme) { bool success = true; - if (lexeme->type == LEXEME_ERROR && lexeme->value.value) { + if (lexeme && lexeme->type == LEXEME_ERROR && lexeme->value.value) { lwan_status_error("Parser error: %.*s", (int)lexeme->value.len, lexeme->value.value); free((char *)lexeme->value.value); @@ -1141,7 +1150,12 @@ static bool parser_shutdown(struct parser *parser, struct lexeme *lexeme) success = false; } - return success && post_process_template(parser); + success = success && post_process_template(parser); + + if (!success) + free_chunk_array(&parser->chunks); + + return success; } static bool parse_string(struct lwan_tpl *tpl, @@ -1162,9 +1176,7 @@ static bool parse_string(struct lwan_tpl *tpl, return false; while (state) { - lexeme = lex_next(&parser.lexer); - - if (!lexeme || lexeme->type == LEXEME_ERROR) + if (!(lexeme = lex_next(&parser.lexer))) break; state = state(&parser, lexeme); @@ -1293,9 +1305,10 @@ lwan_tpl_compile_string_full(const char *string, return tpl; } + + lwan_tpl_free(tpl); } - lwan_tpl_free(tpl); return NULL; } From 0fc10a8c657339b2a26b8bf1b64d4431a293a24f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 14 Nov 2019 08:58:44 -0800 Subject: [PATCH 1268/2505] Fuzz-test the mustache template parser --- fuzz/corpus/corpus-template-1 | 41 +++++++++++++++++++ src/bin/fuzz/template_fuzzer.cc | 71 +++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 fuzz/corpus/corpus-template-1 create mode 100644 src/bin/fuzz/template_fuzzer.cc diff --git a/fuzz/corpus/corpus-template-1 b/fuzz/corpus/corpus-template-1 new file mode 100644 index 000000000..916980142 --- /dev/null +++ b/fuzz/corpus/corpus-template-1 @@ -0,0 +1,41 @@ + + +{{rel_path?}} Index of {{rel_path}}{{/rel_path?}} +{{^rel_path?}} Index of /{{/rel_path?}} + + + +{{rel_path?}}

Index of {{rel_path}}

{{/rel_path?}} +{{^rel_path?}}

Index of /

{{/rel_path?}} +{{readme?}}
{{readme}}
{{/readme?}} +
+ + + + + + + + + + +{{#file_list}} + + + + + + +{{/file_list}} +{{^#file_list}} + + + +{{/file_list}} +
Parent directory
 File nameTypeSize
{{file_list.icon_alt}}{{{file_list.name}}}{{file_list.type}}{{file_list.size}}{{file_list.unit}}
Empty directory.
+ + diff --git a/src/bin/fuzz/template_fuzzer.cc b/src/bin/fuzz/template_fuzzer.cc new file mode 100644 index 000000000..3c0dd562e --- /dev/null +++ b/src/bin/fuzz/template_fuzzer.cc @@ -0,0 +1,71 @@ +#include +#include +#include + +extern "C" { +#include "lwan.h" +#include "lwan-template.h" +} + +struct file_list { + const char *full_path; + const char *rel_path; + const char *readme; + struct { + coro_function_t generator; + + const char *icon; + const char *icon_alt; + const char *name; + const char *type; + + int size; + const char *unit; + + const char *zebra_class; + } file_list; +}; + +int directory_list_generator(struct coro *, void *) +{ + return 0; +} + +#undef TPL_STRUCT +#define TPL_STRUCT struct file_list +static const struct lwan_var_descriptor file_list_desc[] = { + TPL_VAR_STR_ESCAPE(full_path), + TPL_VAR_STR_ESCAPE(rel_path), + TPL_VAR_STR_ESCAPE(readme), + TPL_VAR_SEQUENCE(file_list, + directory_list_generator, + ((const struct lwan_var_descriptor[]){ + TPL_VAR_STR(file_list.icon), + TPL_VAR_STR(file_list.icon_alt), + TPL_VAR_STR(file_list.name), + TPL_VAR_STR(file_list.type), + TPL_VAR_INT(file_list.size), + TPL_VAR_STR(file_list.unit), + TPL_VAR_STR(file_list.zebra_class), + TPL_VAR_SENTINEL, + })), + TPL_VAR_SENTINEL, +}; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + static char copy[32768]; + struct lwan_tpl *tpl; + + if (size > sizeof(copy)) + size = sizeof(copy) - 1; + memcpy(copy, data, size); + copy[size] = '\0'; + + tpl = lwan_tpl_compile_string_full(copy, file_list_desc, + LWAN_TPL_FLAG_CONST_TEMPLATE); + if (tpl) + lwan_tpl_free(tpl); + + return tpl ? 1 : 0; +} From d40b19f256b8038633d9a217fffae88ac25b0864 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 14 Nov 2019 19:39:17 -0800 Subject: [PATCH 1269/2505] Add support for zstd compression (mimegen and file serving) --- CMakeLists.txt | 6 +++ src/bin/tools/CMakeLists.txt | 5 ++- src/bin/tools/mimegen.c | 30 ++++++++++++++- src/cmake/lwan-build-config.h.cmake | 1 + src/lib/lwan-mod-serve-files.c | 59 ++++++++++++++++++++++++++++- src/lib/lwan-request.c | 6 +++ src/lib/lwan-tables.c | 12 +++++- src/lib/lwan.h | 37 +++++++++--------- 8 files changed, 132 insertions(+), 24 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f303e6433..452e4c605 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,6 +61,12 @@ if (BROTLI_FOUND) set(HAVE_BROTLI 1) endif () +pkg_check_modules(ZSTD libzstd) +if (ZSTD_FOUND) + list(APPEND ADDITIONAL_LIBRARIES "${ZSTD_LDFLAGS}") + set(HAVE_ZSTD 1) +endif () + option(USE_ALTERNATIVE_MALLOC "Use alternative malloc implementations" "OFF") if (USE_ALTERNATIVE_MALLOC) unset(ALTMALLOC_LIBS CACHE) diff --git a/src/bin/tools/CMakeLists.txt b/src/bin/tools/CMakeLists.txt index 6eef514b2..5b6ffcd26 100644 --- a/src/bin/tools/CMakeLists.txt +++ b/src/bin/tools/CMakeLists.txt @@ -8,7 +8,10 @@ else () ${CMAKE_SOURCE_DIR}/src/lib/murmur3.c ${CMAKE_SOURCE_DIR}/src/lib/missing.c ) - if (HAVE_BROTLI) + if (HAVE_ZSTD) + message(STATUS "Using Zstd for mimegen") + target_link_libraries(mimegen ${ZSTD_LDFLAGS}) + elseif (HAVE_BROTLI) message(STATUS "Using Brotli for mimegen") target_link_libraries(mimegen ${BROTLI_LDFLAGS}) else () diff --git a/src/bin/tools/mimegen.c b/src/bin/tools/mimegen.c index 3ac11d9d7..d1af50483 100644 --- a/src/bin/tools/mimegen.c +++ b/src/bin/tools/mimegen.c @@ -25,7 +25,9 @@ #include #include -#if defined(HAVE_BROTLI) +#if defined(HAVE_ZSTD) +#include +#elif defined(HAVE_BROTLI) #include #elif defined(HAVE_ZOPFLI) #include @@ -109,7 +111,22 @@ static char *compress_output(const struct output *output, size_t *outlen) { char *compressed; -#if defined(HAVE_BROTLI) +#if defined(HAVE_ZSTD) + *outlen = ZSTD_compressBound(output->used); + + compressed = malloc(*outlen); + if (!compressed) { + fprintf(stderr, "Could not allocate memory for compressed data\n"); + exit(1); + } + + *outlen = ZSTD_compress(compressed, *outlen, output->ptr, output->used, + ZSTD_maxCLevel()); + if (ZSTD_isError(*outlen)) { + fprintf(stderr, "Could not compress mime type table with Brotli\n"); + exit(1); + } +#elif defined(HAVE_BROTLI) *outlen = BrotliEncoderMaxCompressedSize(output->used); compressed = malloc(*outlen); @@ -299,6 +316,15 @@ int main(int argc, char *argv[]) } /* Print output. */ +#if defined(HAVE_ZSTD) + printf("/* Compressed with zstd */\n"); +#elif defined(HAVE_BROTLI) + printf("/* Compressed with brotli */\n"); +#elif defined(HAVE_ZOPFLI) + printf("/* Compressed with zopfli (deflate) */\n"); +#else + printf("/* Compressed with zlib (deflate) */\n"); +#endif printf("#pragma once\n"); printf("#define MIME_UNCOMPRESSED_LEN %zu\n", output.used); printf("#define MIME_COMPRESSED_LEN %lu\n", compressed_size); diff --git a/src/cmake/lwan-build-config.h.cmake b/src/cmake/lwan-build-config.h.cmake index 7716f9189..adb3809f6 100644 --- a/src/cmake/lwan-build-config.h.cmake +++ b/src/cmake/lwan-build-config.h.cmake @@ -58,6 +58,7 @@ /* Libraries */ #cmakedefine HAVE_LUA #cmakedefine HAVE_BROTLI +#cmakedefine HAVE_ZSTD /* Valgrind support for coroutines */ #cmakedefine HAVE_VALGRIND diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index dbf102c8e..d4653ba0f 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -46,6 +46,10 @@ #include #endif +#if defined(HAVE_ZSTD) +#include +#endif + static const struct lwan_key_value deflate_compression_hdr = { .key = "Content-Encoding", .value = "deflate", @@ -60,6 +64,12 @@ static const struct lwan_key_value br_compression_hdr = { .value = "br", }; #endif +#if defined(HAVE_ZSTD) +static const struct lwan_key_value zstd_compression_hdr = { + .key = "Content-Encoding", + .value = "zstd", +}; +#endif static const int open_mode = O_RDONLY | O_NONBLOCK | O_CLOEXEC; @@ -99,6 +109,9 @@ struct mmap_cache_data { #if defined(HAVE_BROTLI) struct lwan_value brotli; #endif +#if defined(HAVE_ZSTD) + struct lwan_value zstd; +#endif }; struct sendfile_cache_data { @@ -425,6 +438,36 @@ static void brotli_value(const struct lwan_value *uncompressed, } #endif +#if defined(HAVE_ZSTD) +static void zstd_value(const struct lwan_value *uncompressed, + struct lwan_value *zstd, + const struct lwan_value *deflated) +{ + const size_t bound = ZSTD_compressBound(uncompressed->len); + + zstd->len = bound; + + if (UNLIKELY(!(zstd->value = malloc(zstd->len)))) + goto error_zero_out; + + zstd->len = ZSTD_compress(zstd->value, zstd->len, uncompressed->value, + uncompressed->len, 1); + if (UNLIKELY(ZSTD_isError(zstd->len))) + goto error_free_compressed; + + /* is_compression_worthy() is already called for deflate-compressed data, + * so only consider zstd-compressed data if it's worth it WRT deflate */ + if (LIKELY(zstd->len < deflated->len)) + return realloc_if_needed(zstd, bound); + +error_free_compressed: + free(zstd->value); + zstd->value = NULL; +error_zero_out: + zstd->len = 0; +} +#endif + static bool mmap_init(struct file_cache_entry *ce, struct serve_files_priv *priv, const char *full_path, @@ -455,6 +498,9 @@ static bool mmap_init(struct file_cache_entry *ce, #if defined(HAVE_BROTLI) brotli_value(&md->uncompressed, &md->brotli, &md->deflated); #endif +#if defined(HAVE_ZSTD) + zstd_value(&md->uncompressed, &md->zstd, &md->deflated); +#endif ce->mime_type = lwan_determine_mime_type_for_file_name(full_path + priv->root_path_len); @@ -847,6 +893,9 @@ static void mmap_free(struct file_cache_entry *fce) #if defined(HAVE_BROTLI) free(md->brotli.value); #endif +#if defined(HAVE_ZSTD) + free(md->zstd.value); +#endif } static void sendfile_free(struct file_cache_entry *fce) @@ -1170,7 +1219,15 @@ static enum lwan_http_status mmap_serve(struct lwan_request *request, size_t size; enum lwan_http_status status; -#if defined(HAVE_BROTLI) +#if defined(HAVE_ZSTD) + if (md->zstd.len && (request->flags & REQUEST_ACCEPT_ZSTD)) { + contents = md->zstd.value; + size = md->zstd.len; + compressed = &zstd_compression_hdr; + + status = HTTP_OK; + } else +#elif defined(HAVE_BROTLI) if (md->brotli.len && (request->flags & REQUEST_ACCEPT_BROTLI)) { contents = md->brotli.value; size = md->brotli.len; diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 02c0f130b..022a0f93d 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -698,6 +698,12 @@ parse_accept_encoding(struct lwan_request *request) case STR4_INT(' ','g','z','i'): request->flags |= REQUEST_ACCEPT_GZIP; break; +#if defined(HAVE_ZSTD) + case STR4_INT('z','s','t','d'): + case STR4_INT(' ','z','s','t'): + request->flags |= REQUEST_ACCEPT_ZSTD; + break; +#endif #if defined(HAVE_BROTLI) default: while (lwan_char_isspace(*p)) diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index d7ba9aa1c..a6003ee8b 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -22,7 +22,9 @@ #include #include -#if defined(HAVE_BROTLI) +#if defined(HAVE_ZSTD) +#include +#elif defined(HAVE_BROTLI) #include #else #include @@ -44,7 +46,13 @@ void lwan_tables_init(void) lwan_status_debug("Uncompressing MIME type table: %u->%u bytes, %d entries", MIME_COMPRESSED_LEN, MIME_UNCOMPRESSED_LEN, MIME_ENTRIES); -#if defined(HAVE_BROTLI) +#if defined(HAVE_ZSTD) + size_t uncompressed_length = + ZSTD_decompress(uncompressed_mime_entries, MIME_UNCOMPRESSED_LEN, + mime_entries_compressed, MIME_COMPRESSED_LEN); + if (ZSTD_isError(uncompressed_length)) + lwan_status_critical("Error while uncompressing table with Zstd"); +#elif defined(HAVE_BROTLI) size_t uncompressed_length = MIME_UNCOMPRESSED_LEN; BrotliDecoderResult ret; diff --git a/src/lib/lwan.h b/src/lib/lwan.h index d21359314..394954643 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -224,24 +224,25 @@ enum lwan_request_flags { REQUEST_ACCEPT_DEFLATE = 1 << 3, REQUEST_ACCEPT_GZIP = 1 << 4, REQUEST_ACCEPT_BROTLI = 1 << 5, - REQUEST_IS_HTTP_1_0 = 1 << 6, - REQUEST_ALLOW_PROXY_REQS = 1 << 7, - REQUEST_PROXIED = 1 << 8, - REQUEST_ALLOW_CORS = 1 << 9, - - RESPONSE_SENT_HEADERS = 1 << 10, - RESPONSE_CHUNKED_ENCODING = 1 << 11, - RESPONSE_NO_CONTENT_LENGTH = 1 << 12, - RESPONSE_URL_REWRITTEN = 1 << 13, - - RESPONSE_STREAM = 1 << 14, - - REQUEST_PARSED_QUERY_STRING = 1 << 15, - REQUEST_PARSED_IF_MODIFIED_SINCE = 1 << 16, - REQUEST_PARSED_RANGE = 1 << 17, - REQUEST_PARSED_POST_DATA = 1 << 18, - REQUEST_PARSED_COOKIES = 1 << 19, - REQUEST_PARSED_ACCEPT_ENCODING = 1 << 20, + REQUEST_ACCEPT_ZSTD = 1 << 6, + REQUEST_IS_HTTP_1_0 = 1 << 7, + REQUEST_ALLOW_PROXY_REQS = 1 << 8, + REQUEST_PROXIED = 1 << 9, + REQUEST_ALLOW_CORS = 1 << 10, + + RESPONSE_SENT_HEADERS = 1 << 11, + RESPONSE_CHUNKED_ENCODING = 1 << 12, + RESPONSE_NO_CONTENT_LENGTH = 1 << 13, + RESPONSE_URL_REWRITTEN = 1 << 14, + + RESPONSE_STREAM = 1 << 15, + + REQUEST_PARSED_QUERY_STRING = 1 << 16, + REQUEST_PARSED_IF_MODIFIED_SINCE = 1 << 17, + REQUEST_PARSED_RANGE = 1 << 18, + REQUEST_PARSED_POST_DATA = 1 << 19, + REQUEST_PARSED_COOKIES = 1 << 20, + REQUEST_PARSED_ACCEPT_ENCODING = 1 << 21, }; #undef SELECT_MASK From f905fa80d71d6a0f6d773938f39e3f9d7558d537 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 14 Nov 2019 19:40:34 -0800 Subject: [PATCH 1270/2505] Fix reallocation of compressed blobs for small files --- src/lib/lwan-mod-serve-files.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index d4653ba0f..58e3feb0b 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -370,10 +370,10 @@ static ALWAYS_INLINE bool is_compression_worthy(const size_t compressed_sz, return ((compressed_sz + deflated_header_size) < uncompressed_sz); } -static void realloc_if_needed(struct lwan_value *value, size_t needed_size) +static void realloc_if_needed(struct lwan_value *value, size_t bound) { - if (needed_size < value->len) { - char *tmp = realloc(value->value, needed_size); + if (bound > value->len) { + char *tmp = realloc(value->value, value->len); if (tmp) value->value = tmp; @@ -415,7 +415,7 @@ static void brotli_value(const struct lwan_value *uncompressed, brotli->len = bound; - if (UNLIKELY(!(brotli->value = malloc(brotli->len)))) + if (UNLIKELY(!(brotli->value = malloc(bound)))) goto error_zero_out; if (UNLIKELY( From 72465e19cc57c40def0e2e9b252afbac66d40e67 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 14 Nov 2019 22:19:30 -0800 Subject: [PATCH 1271/2505] LEXEME_EOF shouldn't be treated as errors while lexing templates They're used to generate an ACTION_LAST opcode, which is required by the VM. --- src/lib/lwan-template.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index b2b7242e8..684c1c5bf 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -517,10 +517,8 @@ static struct lexeme *lex_next(struct lexer *lexer) { struct lexeme *lexeme = lex_next_fsm_loop(lexer); - if (lexeme) { - if (lexeme->type == LEXEME_ERROR || lexeme->type == LEXEME_EOF) - return NULL; - } + if (lexeme && lexeme->type == LEXEME_ERROR) + return NULL; return lexeme; } From 7c8b524d454ee6c19a46c07a1f67043de6e598fa Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 15 Nov 2019 09:35:58 -0800 Subject: [PATCH 1272/2505] Fix coroutine yield_value signaling When moving the coro_defer array to inlinefirst, the size of that structure changed, and with it the offset of the yield_value member was pushed forward. Since, on x86_64, the trampoline function is written in assembly, this is was corrupting memory. (A static_assert() is now in place to ensure that this doesn't happen again.) --- src/lib/lwan-coro.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 53e5f8406..3fca117b8 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -169,6 +169,10 @@ coro_entry_point(struct coro *coro, coro_function_t func, void *data) coro_yield(coro, return_value); } #else +static_assert(offsetof(struct coro, yield_value) == 616, + "yield_value at the correct offset (coro_defer array should be " + "inlinefirst)"); + void __attribute__((noinline, visibility("internal"))) coro_entry_point(struct coro *coro, coro_function_t func, void *data); asm(".text\n\t" @@ -180,7 +184,7 @@ asm(".text\n\t" "movq %r15, %rsi\n\t" /* data = r15 */ "call *%rdx\n\t" /* eax = func(coro, data) */ "movq (%rbx), %rsi\n\t" - "movl %eax, 0x68(%rbx)\n\t" /* coro->yield_value eax */ + "movl %eax, 0x616(%rbx)\n\t" /* coro->yield_value = eax */ "popq %rbx\n\t" "leaq 0x50(%rsi), %rdi\n\t" /* get coro context from coro */ "jmp " ASM_SYMBOL(coro_swapcontext) "\n\t"); From e3078d3338f959e970215f2f907ecca811c8ec94 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 15 Nov 2019 10:34:11 -0800 Subject: [PATCH 1273/2505] coro_yield() might not be inlinable when used outside liblwan --- src/lib/lwan-coro.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 3fca117b8..d55ed2ad6 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -317,7 +317,7 @@ ALWAYS_INLINE int coro_resume_value(struct coro *coro, int value) return coro_resume(coro); } -ALWAYS_INLINE int coro_yield(struct coro *coro, int value) +inline int coro_yield(struct coro *coro, int value) { assert(coro); coro->yield_value = value; From b59b888425e186349024cbeb177271d6342cf633 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 15 Nov 2019 12:20:30 -0800 Subject: [PATCH 1274/2505] Really fix the yield_value offset It's 616 in decimal, not hex! --- src/lib/lwan-coro.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index d55ed2ad6..3e00dd575 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -184,7 +184,7 @@ asm(".text\n\t" "movq %r15, %rsi\n\t" /* data = r15 */ "call *%rdx\n\t" /* eax = func(coro, data) */ "movq (%rbx), %rsi\n\t" - "movl %eax, 0x616(%rbx)\n\t" /* coro->yield_value = eax */ + "movl %eax, 616(%rbx)\n\t" /* coro->yield_value = eax */ "popq %rbx\n\t" "leaq 0x50(%rsi), %rdi\n\t" /* get coro context from coro */ "jmp " ASM_SYMBOL(coro_swapcontext) "\n\t"); From 1ddaed371f4711f12ad8c6092d38df782091f9b0 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 15 Nov 2019 12:21:07 -0800 Subject: [PATCH 1275/2505] Increase size of per-thread client queue --- src/lib/lwan-thread.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 04c12fce3..d3af106b8 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -492,8 +492,6 @@ static void create_thread(struct lwan *l, struct lwan_thread *thread) lwan_status_critical_perror("pthread_attr_destroy"); size_t n_queue_fds = thread->lwan->thread.max_fd; - if (n_queue_fds > 128) - n_queue_fds = 128; if (spsc_queue_init(&thread->pending_fds, n_queue_fds) < 0) { lwan_status_critical("Could not initialize pending fd " "queue width %zu elements", n_queue_fds); From f7b91535da39550693214dbe7ae2c16d83d70a4f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 16 Nov 2019 09:48:57 -0800 Subject: [PATCH 1276/2505] Fix heap overflow while parsing templates Fixes https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=18952 Thanks to OSS-Fuzz. --- src/lib/lwan-array.h | 9 +++++++-- src/lib/lwan-template.c | 12 +++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-array.h b/src/lib/lwan-array.h index 1ebe964c2..fdf04b3f2 100644 --- a/src/lib/lwan-array.h +++ b/src/lib/lwan-array.h @@ -116,12 +116,17 @@ struct lwan_array *coro_lwan_array_new(struct coro *coro, bool inline_first); lwan_array_sort(&array->base, sizeof(element_type_), cmp); \ } \ __attribute__((unused)) static inline size_t array_type_##_get_elem_index( \ - struct array_type_ *array, element_type_ *elem) \ + const struct array_type_ *array, element_type_ *elem) \ { \ return (size_t)(elem - (element_type_ *)array->base.base); \ } \ __attribute__((unused)) static inline element_type_ \ - *array_type_##_get_elem(struct array_type_ *array, size_t index) \ + *array_type_##_get_elem(const struct array_type_ *array, size_t index) \ { \ return &((element_type_ *)array->base.base)[index]; \ + } \ + __attribute__((unused)) static inline size_t array_type_##_len( \ + const struct array_type_ *array) \ + { \ + return array->base.elements; \ } diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index 684c1c5bf..3d68360c2 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -996,12 +996,16 @@ void lwan_tpl_free(struct lwan_tpl *tpl) static bool post_process_template(struct parser *parser) { + struct chunk *last_chunk = + chunk_array_get_elem(&parser->chunks, chunk_array_len(&parser->chunks)); struct chunk *prev_chunk; struct chunk *chunk; - LWAN_ARRAY_FOREACH(&parser->chunks, chunk) { + LWAN_ARRAY_FOREACH (&parser->chunks, chunk) { if (chunk->action == ACTION_IF_VARIABLE_NOT_EMPTY) { for (prev_chunk = chunk;; chunk++) { + if (chunk > last_chunk) + goto error; if (chunk->action == ACTION_LAST) { lwan_status_error("Internal error: Could not find the end " "var not empty chunk"); @@ -1026,6 +1030,8 @@ static bool post_process_template(struct parser *parser) enum flags flags = chunk->flags; for (prev_chunk = chunk;; chunk++) { + if (chunk > last_chunk) + goto error; if (chunk->action == ACTION_LAST) { lwan_status_error( "Internal error: Could not find the end iter chunk"); @@ -1084,6 +1090,10 @@ static bool post_process_template(struct parser *parser) parser->tpl->chunks = parser->chunks; return true; + +error: + lwan_status_error("Unknown error while parsing template; bug?"); + return false; } static bool parser_init(struct parser *parser, From d7fc0d27fbea5c68d61444033517d0e962e822e6 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 17 Nov 2019 08:45:41 -0800 Subject: [PATCH 1277/2505] Fix buffer overflow in template parser harness Fixes https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=18988 --- src/bin/fuzz/template_fuzzer.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/bin/fuzz/template_fuzzer.cc b/src/bin/fuzz/template_fuzzer.cc index 3c0dd562e..0f1c2f758 100644 --- a/src/bin/fuzz/template_fuzzer.cc +++ b/src/bin/fuzz/template_fuzzer.cc @@ -52,13 +52,14 @@ static const struct lwan_var_descriptor file_list_desc[] = { TPL_VAR_SENTINEL, }; +static size_t min(size_t a, size_t b) { return a > b ? b : a; } + extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { static char copy[32768]; struct lwan_tpl *tpl; - if (size > sizeof(copy)) - size = sizeof(copy) - 1; + size = min(sizeof(copy) - 1, size); memcpy(copy, data, size); copy[size] = '\0'; From 2c9dc72966276f35f9bf697ee49038c1cdee24bb Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 19 Nov 2019 18:39:33 -0800 Subject: [PATCH 1278/2505] Correctly compare with last_chunk when post-processing template Thanks to OSS-Fuzz: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=19013 --- src/lib/lwan-template.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index 3d68360c2..91553db89 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -1004,7 +1004,7 @@ static bool post_process_template(struct parser *parser) LWAN_ARRAY_FOREACH (&parser->chunks, chunk) { if (chunk->action == ACTION_IF_VARIABLE_NOT_EMPTY) { for (prev_chunk = chunk;; chunk++) { - if (chunk > last_chunk) + if (chunk == last_chunk) goto error; if (chunk->action == ACTION_LAST) { lwan_status_error("Internal error: Could not find the end " @@ -1030,7 +1030,7 @@ static bool post_process_template(struct parser *parser) enum flags flags = chunk->flags; for (prev_chunk = chunk;; chunk++) { - if (chunk > last_chunk) + if (chunk == last_chunk) goto error; if (chunk->action == ACTION_LAST) { lwan_status_error( From 0d84e136a8abf7fca9ac8133a3c79743ecdddbb2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 20 Nov 2019 07:49:49 -0800 Subject: [PATCH 1279/2505] s/DQ/timeout queue/ in comments --- src/lib/lwan-request.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 022a0f93d..ba687ef7f 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -907,10 +907,10 @@ read_request_finalizer(size_t total_read, /* Yield a timeout error to avoid clients being intentionally slow and * hogging the server. (Clients can't only connect and do nothing, they - * need to send data, otherwise the DQ timer will kick in and close the - * connection. Limit the number of packets to avoid them sending just - * a byte at a time.) - * See calculate_n_packets() to see how this is calculated. */ + * need to send data, otherwise the timeout queue timer will kick in and + * close the connection. Limit the number of packets to avoid them sending + * just a byte at a time.) See calculate_n_packets() to see how this is + * calculated. */ if (UNLIKELY(n_packets > helper->error_when_n_packets)) return FINALIZER_ERROR_TIMEOUT; From da1049b19f378d4000ed925272148a7fee6a91b1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 20 Nov 2019 08:42:31 -0800 Subject: [PATCH 1280/2505] key_value_compare() takes const void* parameters --- src/lib/lwan-request.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index ba687ef7f..522f8c20d 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -330,10 +330,10 @@ static ssize_t url_decode(char *str) return (ssize_t)(decoded - str); } -static int -key_value_compare(const void *a, const void *b) +static int key_value_compare(const void *a, const void *b) { - return strcmp(((struct lwan_key_value *)a)->key, ((struct lwan_key_value *)b)->key); + return strcmp(((const struct lwan_key_value *)a)->key, + ((const struct lwan_key_value *)b)->key); } static void From cbf73d0f4fa3cae7566e15c6ec03221450f44d7f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 20 Nov 2019 08:43:22 -0800 Subject: [PATCH 1281/2505] Declare variables inline in read_from_request_socket() --- src/lib/lwan-request.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 522f8c20d..05bb4af11 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -818,9 +818,7 @@ read_from_request_socket(struct lwan_request *request, int n_packets)) { struct lwan_request_parser_helper *helper = request->helper; - ssize_t n; size_t total_read = 0; - int n_packets = 0; if (helper->next_request) { const size_t next_request_len = (size_t)(helper->next_request - buffer->value); @@ -839,13 +837,13 @@ read_from_request_socket(struct lwan_request *request, } } - for (;; n_packets++) { + for (int n_packets = 0;; n_packets++) { size_t to_read = (size_t)(buffer_size - total_read); if (UNLIKELY(to_read == 0)) return HTTP_TOO_LARGE; - n = read(request->fd, buffer->value + total_read, to_read); + ssize_t n = read(request->fd, buffer->value + total_read, to_read); if (UNLIKELY(n <= 0)) { if (n < 0) { switch (errno) { From 113b3d8e0dafcfdce470101b0ca73dddddc5bd97 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 20 Nov 2019 08:39:17 -0800 Subject: [PATCH 1282/2505] Sync techempower sample with latest version of TWFB This new version: - Uses a different, malloc-less JSON library. It's licensed under the Apache 2 license, so the resulting "techempower" binary is licensed under GPLv3 or later (rather than GPLv2 or later), as it's incompatible with the older version of the GPL but compatible with the current version. - Uses a per-thread database connection. The MySQL library isn't thread-safe, so this is required for correctness. This shold also improve performance a tiny bit, but ideally in the future we should switch to using MariaDB's non-blocking extensions instead (this will require changes to the event loop yet again, but it's not a lot easier to work with). - Has a few tweaks here and there in the test harness. --- src/samples/techempower/database.c | 110 +- src/samples/techempower/json.c | 2055 ++++++++++--------------- src/samples/techempower/json.h | 728 +++++++-- src/samples/techempower/techempower.c | 316 ++-- 4 files changed, 1695 insertions(+), 1514 deletions(-) diff --git a/src/samples/techempower/database.c b/src/samples/techempower/database.c index c3db38d73..8d1518bf8 100644 --- a/src/samples/techempower/database.c +++ b/src/samples/techempower/database.c @@ -14,7 +14,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include @@ -23,16 +24,21 @@ #include #include "database.h" +#include "lwan-status.h" struct db_stmt { - bool (*bind)(const struct db_stmt *stmt, struct db_row *rows, size_t n_rows); + bool (*bind)(const struct db_stmt *stmt, + struct db_row *rows, + size_t n_rows); bool (*step)(const struct db_stmt *stmt, struct db_row *row); void (*finalize)(struct db_stmt *stmt); }; struct db { void (*disconnect)(struct db *db); - struct db_stmt *(*prepare)(const struct db *db, const char *sql, const size_t sql_len); + struct db_stmt *(*prepare)(const struct db *db, + const char *sql, + const size_t sql_len); }; /* MySQL */ @@ -51,22 +57,25 @@ struct db_stmt_mysql { }; static bool db_stmt_bind_mysql(const struct db_stmt *stmt, - struct db_row *rows, size_t n_rows) + struct db_row *rows, + size_t n_rows) { struct db_stmt_mysql *stmt_mysql = (struct db_stmt_mysql *)stmt; stmt_mysql->must_execute_again = true; if (!stmt_mysql->param_bind) { - stmt_mysql->param_bind = calloc(n_rows, sizeof(*stmt_mysql->param_bind)); - if (!stmt_mysql->param_bind) + stmt_mysql->param_bind = calloc(n_rows, sizeof(MYSQL_BIND)); + if (!stmt_mysql->param_bind) { return false; + } } else { mysql_stmt_reset(stmt_mysql->stmt); } for (size_t row = 0; row < n_rows; row++) { - if (rows[row].kind == '\0') break; + if (rows[row].kind == '\0') + break; MYSQL_BIND *param = &stmt_mysql->param_bind[row]; if (rows[row].kind == 's') { @@ -101,11 +110,13 @@ static bool db_stmt_step_mysql(const struct db_stmt *stmt, struct db_row *row) if (!n_rows) return false; - stmt_mysql->result_bind = calloc(n_rows, sizeof(*stmt_mysql->result_bind)); + stmt_mysql->result_bind = + calloc(n_rows, sizeof(*stmt_mysql->result_bind)); if (!stmt_mysql->result_bind) return false; - stmt_mysql->param_bind = calloc(n_rows, sizeof(*stmt_mysql->param_bind)); + stmt_mysql->param_bind = + calloc(n_rows, sizeof(*stmt_mysql->param_bind)); if (!stmt_mysql->param_bind) { free(stmt_mysql->result_bind); return false; @@ -144,8 +155,8 @@ static void db_stmt_finalize_mysql(struct db_stmt *stmt) free(stmt_mysql); } -static struct db_stmt *db_prepare_mysql(const struct db *db, const char *sql, - const size_t sql_len) +static struct db_stmt * +db_prepare_mysql(const struct db *db, const char *sql, const size_t sql_len) { const struct db_mysql *db_mysql = (const struct db_mysql *)db; struct db_stmt_mysql *stmt_mysql = malloc(sizeof(*stmt_mysql)); @@ -154,16 +165,11 @@ static struct db_stmt *db_prepare_mysql(const struct db *db, const char *sql, return NULL; stmt_mysql->stmt = mysql_stmt_init(db_mysql->con); - if (!stmt_mysql->stmt) { - free(stmt_mysql); - return NULL; - } + if (!stmt_mysql->stmt) + goto out_free_stmt; - if (mysql_stmt_prepare(stmt_mysql->stmt, sql, sql_len)) { - mysql_stmt_close(stmt_mysql->stmt); - free(stmt_mysql); - return NULL; - } + if (mysql_stmt_prepare(stmt_mysql->stmt, sql, sql_len)) + goto out_close_stmt; stmt_mysql->base.bind = db_stmt_bind_mysql; stmt_mysql->base.step = db_stmt_step_mysql; @@ -172,7 +178,14 @@ static struct db_stmt *db_prepare_mysql(const struct db *db, const char *sql, stmt_mysql->param_bind = NULL; stmt_mysql->must_execute_again = true; - return (struct db_stmt*)stmt_mysql; + return (struct db_stmt *)stmt_mysql; + +out_close_stmt: + mysql_stmt_close(stmt_mysql->stmt); +out_free_stmt: + free(stmt_mysql); + + return NULL; } static void db_disconnect_mysql(struct db *db) @@ -183,8 +196,10 @@ static void db_disconnect_mysql(struct db *db) free(db); } -struct db *db_connect_mysql(const char *host, const char *user, const char *pass, - const char *database) +struct db *db_connect_mysql(const char *host, + const char *user, + const char *pass, + const char *database) { struct db_mysql *db_mysql = malloc(sizeof(*db_mysql)); @@ -197,7 +212,8 @@ struct db *db_connect_mysql(const char *host, const char *user, const char *pass return NULL; } - if (!mysql_real_connect(db_mysql->con, host, user, pass, database, 0, NULL, 0)) + if (!mysql_real_connect(db_mysql->con, host, user, pass, database, 0, NULL, + 0)) goto error; if (mysql_set_character_set(db_mysql->con, "utf8")) @@ -226,9 +242,12 @@ struct db_stmt_sqlite { sqlite3_stmt *sqlite; }; -static bool db_stmt_bind_sqlite(const struct db_stmt *stmt, struct db_row *rows, size_t n_rows) +static bool db_stmt_bind_sqlite(const struct db_stmt *stmt, + struct db_row *rows, + size_t n_rows) { - const struct db_stmt_sqlite *stmt_sqlite = (const struct db_stmt_sqlite *)stmt; + const struct db_stmt_sqlite *stmt_sqlite = + (const struct db_stmt_sqlite *)stmt; const struct db_row *rows_1_based = rows - 1; int ret; @@ -237,10 +256,12 @@ static bool db_stmt_bind_sqlite(const struct db_stmt *stmt, struct db_row *rows, for (size_t row = 1; row <= n_rows; row++) { const struct db_row *r = &rows_1_based[row]; - if (r->kind == '\0') break; + if (r->kind == '\0') + break; if (r->kind == 's') { - ret = sqlite3_bind_text(stmt_sqlite->sqlite, (int)row, r->u.s, -1, NULL); + ret = sqlite3_bind_text(stmt_sqlite->sqlite, (int)row, r->u.s, -1, + NULL); if (ret != SQLITE_OK) return false; } else if (r->kind == 'i') { @@ -257,7 +278,8 @@ static bool db_stmt_bind_sqlite(const struct db_stmt *stmt, struct db_row *rows, static bool db_stmt_step_sqlite(const struct db_stmt *stmt, struct db_row *row) { - const struct db_stmt_sqlite *stmt_sqlite = (const struct db_stmt_sqlite *)stmt; + const struct db_stmt_sqlite *stmt_sqlite = + (const struct db_stmt_sqlite *)stmt; if (sqlite3_step(stmt_sqlite->sqlite) != SQLITE_ROW) return false; @@ -267,7 +289,8 @@ static bool db_stmt_step_sqlite(const struct db_stmt *stmt, struct db_row *row) if (r->kind == 'i') { r->u.i = sqlite3_column_int(stmt_sqlite->sqlite, column_id); } else if (r->kind == 's') { - r->u.s = (char *)sqlite3_column_text(stmt_sqlite->sqlite, column_id); + r->u.s = + (char *)sqlite3_column_text(stmt_sqlite->sqlite, column_id); } else { return false; } @@ -284,8 +307,8 @@ static void db_stmt_finalize_sqlite(struct db_stmt *stmt) free(stmt_sqlite); } -static struct db_stmt *db_prepare_sqlite(const struct db *db, const char *sql, - const size_t sql_len) +static struct db_stmt * +db_prepare_sqlite(const struct db *db, const char *sql, const size_t sql_len) { const struct db_sqlite *db_sqlite = (const struct db_sqlite *)db; struct db_stmt_sqlite *stmt_sqlite = malloc(sizeof(*stmt_sqlite)); @@ -293,7 +316,8 @@ static struct db_stmt *db_prepare_sqlite(const struct db *db, const char *sql, if (!stmt_sqlite) return NULL; - int ret = sqlite3_prepare(db_sqlite->sqlite, sql, (int)sql_len, &stmt_sqlite->sqlite, NULL); + int ret = sqlite3_prepare(db_sqlite->sqlite, sql, (int)sql_len, + &stmt_sqlite->sqlite, NULL); if (ret != SQLITE_OK) { free(stmt_sqlite); return NULL; @@ -314,7 +338,8 @@ static void db_disconnect_sqlite(struct db *db) free(db); } -struct db *db_connect_sqlite(const char *path, bool read_only, const char *pragmas[]) +struct db * +db_connect_sqlite(const char *path, bool read_only, const char *pragmas[]) { struct db_sqlite *db_sqlite = malloc(sizeof(*db_sqlite)); @@ -341,7 +366,8 @@ struct db *db_connect_sqlite(const char *path, bool read_only, const char *pragm /* Generic */ -inline bool db_stmt_bind(const struct db_stmt *stmt, struct db_row *rows, size_t n_rows) +inline bool +db_stmt_bind(const struct db_stmt *stmt, struct db_row *rows, size_t n_rows) { return stmt->bind(stmt, rows, n_rows); } @@ -351,18 +377,12 @@ inline bool db_stmt_step(const struct db_stmt *stmt, struct db_row *row) return stmt->step(stmt, row); } -inline void db_stmt_finalize(struct db_stmt *stmt) -{ - stmt->finalize(stmt); -} +inline void db_stmt_finalize(struct db_stmt *stmt) { stmt->finalize(stmt); } -inline void db_disconnect(struct db *db) -{ - db->disconnect(db); -} +inline void db_disconnect(struct db *db) { db->disconnect(db); } -inline struct db_stmt *db_prepare_stmt(const struct db *db, const char *sql, - const size_t sql_len) +inline struct db_stmt * +db_prepare_stmt(const struct db *db, const char *sql, const size_t sql_len) { return db->prepare(db, sql, sql_len); } diff --git a/src/samples/techempower/json.c b/src/samples/techempower/json.c index 739a251cd..0b8d7c118 100644 --- a/src/samples/techempower/json.c +++ b/src/samples/techempower/json.c @@ -1,1381 +1,938 @@ /* - Copyright (C) 2011 Joseph A. Adams (joeyadams3.14159@gmail.com) - All rights reserved. - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. -*/ - -#include "json.h" + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ #include +#include +#include +#include +#include #include #include #include #include -#define out_of_memory() do { \ - fprintf(stderr, "Out of memory.\n"); \ - exit(EXIT_FAILURE); \ - } while (0) +#include "json.h" + +struct token { + enum json_tokens type; + char *start; + char *end; +}; -/* String buffer */ +struct lexer { + void *(*state)(struct lexer *lexer); + char *start; + char *pos; + char *end; + struct token token; +}; -typedef struct -{ - char *cur; - char *end; - char *start; -} SB; +struct json_obj { + struct lexer lexer; +}; + +struct json_obj_key_value { + const char *key; + size_t key_len; + struct token value; +}; -static void sb_init(SB *sb) +static bool lexer_consume(struct lexer *lexer, + struct token *token, + enum json_tokens empty_token) { - sb->start = (char*) malloc(17); - if (sb->start == NULL) - out_of_memory(); - sb->cur = sb->start; - sb->end = sb->start + 16; -} + if (lexer->token.type == empty_token) { + return false; + } -/* sb and need may be evaluated multiple times. */ -#define sb_need(sb, need) do { \ - if ((size_t)((sb)->end - (sb)->cur) < (need)) \ - sb_grow(sb, need); \ - } while (0) + *token = lexer->token; + lexer->token.type = empty_token; -static void sb_grow(SB *sb, size_t need) -{ - size_t length = (size_t)(sb->cur - sb->start); - size_t alloc = (size_t)(sb->end - sb->start); - - do { - alloc *= 2; - } while (alloc < length + need); - - sb->start = (char*) realloc(sb->start, alloc + 1); - if (sb->start == NULL) - out_of_memory(); - sb->cur = sb->start + length; - sb->end = sb->start + alloc; + return true; } -static void sb_put(SB *sb, const char *bytes, size_t count) +static bool lexer_next(struct lexer *lexer, struct token *token) { - sb_need(sb, count); - memcpy(sb->cur, bytes, count); - sb->cur += count; -} + while (lexer->state) { + if (lexer_consume(lexer, token, JSON_TOK_NONE)) { + return true; + } -#define sb_putc(sb, c) do { \ - if ((sb)->cur >= (sb)->end) \ - sb_grow(sb, 1); \ - *(sb)->cur++ = (c); \ - } while (0) + lexer->state = lexer->state(lexer); + } -static void sb_puts(SB *sb, const char *str) -{ - sb_put(sb, str, strlen(str)); + return lexer_consume(lexer, token, JSON_TOK_EOF); } -static char *sb_finish(SB *sb) -{ - *sb->cur = 0; - assert(sb->start <= sb->cur && strlen(sb->start) == (size_t)(sb->cur - sb->start)); - return sb->start; -} +static void *lexer_json(struct lexer *lexer); -static char *sb_finish_length(SB *sb, size_t *length) +static void emit(struct lexer *lexer, enum json_tokens token) { - *sb->cur = 0; - assert(sb->start <= sb->cur && strlen(sb->start) == (size_t)(sb->cur - sb->start)); - *length = (size_t)(sb->cur - sb->start); - return sb->start; + lexer->token.type = token; + lexer->token.start = lexer->start; + lexer->token.end = lexer->pos; + lexer->start = lexer->pos; } -static void sb_free(SB *sb) +static int next(struct lexer *lexer) { - free(sb->start); + if (lexer->pos >= lexer->end) { + lexer->pos = lexer->end + 1; + + return '\0'; + } + + return *lexer->pos++; } -/* - * Unicode helper functions - * - * These are taken from the ccan/charset module and customized a bit. - * Putting them here means the compiler can (choose to) inline them, - * and it keeps ccan/json from having a dependency. - */ +static void ignore(struct lexer *lexer) { lexer->start = lexer->pos; } -/* - * Type for Unicode codepoints. - * We need our own because wchar_t might be 16 bits. - */ -typedef uint32_t uchar_t; +static void backup(struct lexer *lexer) { lexer->pos--; } -/* - * Validate a single UTF-8 character starting at @s. - * The string must be null-terminated. - * - * If it's valid, return its length (1 thru 4). - * If it's invalid or clipped, return 0. - * - * This function implements the syntax given in RFC3629, which is - * the same as that given in The Unicode Standard, Version 6.0. - * - * It has the following properties: - * - * * All codepoints U+0000..U+10FFFF may be encoded, - * except for U+D800..U+DFFF, which are reserved - * for UTF-16 surrogate pair encoding. - * * UTF-8 byte sequences longer than 4 bytes are not permitted, - * as they exceed the range of Unicode. - * * The sixty-six Unicode "non-characters" are permitted - * (namely, U+FDD0..U+FDEF, U+xxFFFE, and U+xxFFFF). - */ -static size_t utf8_validate_cz(const char *s) +static int peek(struct lexer *lexer) { - unsigned char c = (unsigned char)*s++; - - if (c <= 0x7F) { /* 00..7F */ - return 1; - } else if (c <= 0xC1) { /* 80..C1 */ - /* Disallow overlong 2-byte sequence. */ - return 0; - } else if (c <= 0xDF) { /* C2..DF */ - /* Make sure subsequent byte is in the range 0x80..0xBF. */ - if (((unsigned char)*s++ & 0xC0) != 0x80) - return 0; - - return 2; - } else if (c <= 0xEF) { /* E0..EF */ - /* Disallow overlong 3-byte sequence. */ - if (c == 0xE0 && (unsigned char)*s < 0xA0) - return 0; - - /* Disallow U+D800..U+DFFF. */ - if (c == 0xED && (unsigned char)*s > 0x9F) - return 0; - - /* Make sure subsequent bytes are in the range 0x80..0xBF. */ - if (((unsigned char)*s++ & 0xC0) != 0x80) - return 0; - if (((unsigned char)*s++ & 0xC0) != 0x80) - return 0; - - return 3; - } else if (c <= 0xF4) { /* F0..F4 */ - /* Disallow overlong 4-byte sequence. */ - if (c == 0xF0 && (unsigned char)*s < 0x90) - return 0; - - /* Disallow codepoints beyond U+10FFFF. */ - if (c == 0xF4 && (unsigned char)*s > 0x8F) - return 0; - - /* Make sure subsequent bytes are in the range 0x80..0xBF. */ - if (((unsigned char)*s++ & 0xC0) != 0x80) - return 0; - if (((unsigned char)*s++ & 0xC0) != 0x80) - return 0; - if (((unsigned char)*s++ & 0xC0) != 0x80) - return 0; - - return 4; - } else { /* F5..FF */ - return 0; - } -} + int chr = next(lexer); -/* Validate a null-terminated UTF-8 string. */ -static bool utf8_validate(const char *s) -{ - size_t len; - - for (; *s != 0; s += len) { - len = utf8_validate_cz(s); - if (len == 0) - return false; - } - - return true; -} + backup(lexer); -/* - * Read a single UTF-8 character starting at @s, - * returning the length, in bytes, of the character read. - * - * This function assumes input is valid UTF-8, - * and that there are enough characters in front of @s. - */ -static int utf8_read_char(const char *s, uchar_t *out) -{ - const unsigned char *c = (const unsigned char*) s; - - assert(utf8_validate_cz(s)); - - if (c[0] <= 0x7F) { - /* 00..7F */ - *out = c[0]; - return 1; - } else if (c[0] <= 0xDF) { - /* C2..DF (unless input is invalid) */ - *out = ((uchar_t)c[0] & 0x1F) << 6 | - ((uchar_t)c[1] & 0x3F); - return 2; - } else if (c[0] <= 0xEF) { - /* E0..EF */ - *out = ((uchar_t)c[0] & 0xF) << 12 | - ((uchar_t)c[1] & 0x3F) << 6 | - ((uchar_t)c[2] & 0x3F); - return 3; - } else { - /* F0..F4 (unless input is invalid) */ - *out = ((uchar_t)c[0] & 0x7) << 18 | - ((uchar_t)c[1] & 0x3F) << 12 | - ((uchar_t)c[2] & 0x3F) << 6 | - ((uchar_t)c[3] & 0x3F); - return 4; - } + return chr; } -/* - * Write a single UTF-8 character to @s, - * returning the length, in bytes, of the character written. - * - * @unicode must be U+0000..U+10FFFF, but not U+D800..U+DFFF. - * - * This function will write up to 4 bytes to @out. - */ -static int utf8_write_char(uchar_t unicode, char *out) +static void *lexer_string(struct lexer *lexer) { - unsigned char *o = (unsigned char*) out; - - assert(unicode <= 0x10FFFF && !(unicode >= 0xD800 && unicode <= 0xDFFF)); - - if (unicode <= 0x7F) { - /* U+0000..U+007F */ - *o++ = (unsigned char)unicode; - return 1; - } else if (unicode <= 0x7FF) { - /* U+0080..U+07FF */ - *o++ = (unsigned char)(0xC0 | unicode >> 6); - *o++ = (unsigned char)(0x80 | (unicode & 0x3F)); - return 2; - } else if (unicode <= 0xFFFF) { - /* U+0800..U+FFFF */ - *o++ = (unsigned char)(0xE0 | unicode >> 12); - *o++ = (unsigned char)(0x80 | (unicode >> 6 & 0x3F)); - *o++ = (unsigned char)(0x80 | (unicode & 0x3F)); - return 3; - } else { - /* U+10000..U+10FFFF */ - *o++ = (unsigned char)(0xF0 | unicode >> 18); - *o++ = (unsigned char)(0x80 | (unicode >> 12 & 0x3F)); - *o++ = (unsigned char)(0x80 | (unicode >> 6 & 0x3F)); - *o++ = (unsigned char)(0x80 | (unicode & 0x3F)); - return 4; - } -} + ignore(lexer); -/* - * Compute the Unicode codepoint of a UTF-16 surrogate pair. - * - * @uc should be 0xD800..0xDBFF, and @lc should be 0xDC00..0xDFFF. - * If they aren't, this function returns false. - */ -static bool from_surrogate_pair(uint16_t uc, uint16_t lc, uchar_t *unicode) -{ - if (uc >= 0xD800 && uc <= 0xDBFF && lc >= 0xDC00 && lc <= 0xDFFF) { - *unicode = 0x10000 + ((((uchar_t)uc & 0x3FF) << 10) | (lc & 0x3FF)); - return true; - } else { - return false; - } -} + while (true) { + int chr = next(lexer); -/* - * Construct a UTF-16 surrogate pair given a Unicode codepoint. - * - * @unicode must be U+10000..U+10FFFF. - */ -static void to_surrogate_pair(uchar_t unicode, uint16_t *uc, uint16_t *lc) -{ - uchar_t n; - - assert(unicode >= 0x10000 && unicode <= 0x10FFFF); - - n = unicode - 0x10000; - *uc = (uint16_t)(((n >> 10) & 0x3FF) | 0xD800); - *lc = (uint16_t)((n & 0x3FF) | 0xDC00); -} + if (chr == '\0') { + emit(lexer, JSON_TOK_ERROR); + return NULL; + } -#define is_space(c) ((c) == '\t' || (c) == '\n' || (c) == '\r' || (c) == ' ') -#define is_digit(c) ((c) >= '0' && (c) <= '9') + if (chr == '\\') { + switch (next(lexer)) { + case '"': + case '\\': + case '/': + case 'b': + case 'f': + case 'n': + case 'r': + case 't': + continue; + case 'u': + if (!isxdigit(next(lexer))) { + goto error; + } -static bool parse_value (const char **sp, JsonNode **out); -static bool parse_string (const char **sp, char **out); -static bool parse_number (const char **sp, double *out); -static bool parse_array (const char **sp, JsonNode **out); -static bool parse_object (const char **sp, JsonNode **out); -static bool parse_hex16 (const char **sp, uint16_t *out); + if (!isxdigit(next(lexer))) { + goto error; + } -static bool expect_literal (const char **sp, const char *str); -static void skip_space (const char **sp); + if (!isxdigit(next(lexer))) { + goto error; + } -static void emit_value (SB *out, const JsonNode *node); -static void emit_value_indented (SB *out, const JsonNode *node, const char *space, int indent_level); -static void emit_string (SB *out, const char *str); -static void emit_number (SB *out, double num); -static void emit_array (SB *out, const JsonNode *array); -static void emit_array_indented (SB *out, const JsonNode *array, const char *space, int indent_level); -static void emit_object (SB *out, const JsonNode *object); -static void emit_object_indented (SB *out, const JsonNode *object, const char *space, int indent_level); + if (!isxdigit(next(lexer))) { + goto error; + } -static int write_hex16(char *out, uint16_t val); + break; + default: + goto error; + } + } -static JsonNode *mknode(JsonTag tag); -static void append_node(JsonNode *parent, JsonNode *child); -static void prepend_node(JsonNode *parent, JsonNode *child); -static void append_member(JsonNode *object, char *key, JsonNode *value); + if (chr == '"') { + backup(lexer); + emit(lexer, JSON_TOK_STRING); -/* Assertion-friendly validity checks */ -static bool tag_is_valid(unsigned int tag); -static bool number_is_valid(const char *num); + next(lexer); + ignore(lexer); -JsonNode *json_decode(const char *json) -{ - const char *s = json; - JsonNode *ret; - - skip_space(&s); - if (!parse_value(&s, &ret)) - return NULL; - - skip_space(&s); - if (*s != 0) { - json_delete(ret); - return NULL; - } - - return ret; -} + return lexer_json; + } + } -char *json_encode(const JsonNode *node) -{ - return json_stringify(node, NULL); +error: + emit(lexer, JSON_TOK_ERROR); + return NULL; } -char *json_encode_string(const char *str) +static int accept_run(struct lexer *lexer, const char *run) { - SB sb; - sb_init(&sb); - - emit_string(&sb, str); - - return sb_finish(&sb); -} + for (; *run; run++) { + if (next(lexer) != *run) { + return -EINVAL; + } + } -char *json_stringify(const JsonNode *node, const char *space) -{ - SB sb; - sb_init(&sb); - - if (space != NULL) - emit_value_indented(&sb, node, space, 0); - else - emit_value(&sb, node); - - return sb_finish(&sb); + return 0; } -char *json_stringify_length(const JsonNode *node, const char *space, size_t *length) +static void *lexer_boolean(struct lexer *lexer) { - SB sb; - sb_init(&sb); - - if (space != NULL) - emit_value_indented(&sb, node, space, 0); - else - emit_value(&sb, node); - - return sb_finish_length(&sb, length); -} + backup(lexer); -void json_delete(JsonNode *node) -{ - if (node != NULL) { - json_remove_from_parent(node); - - switch (node->tag) { - case JSON_STRING: - free(node->string_); - break; - case JSON_ARRAY: - case JSON_OBJECT: - { - JsonNode *child, *next; - for (child = node->children.head; child != NULL; child = next) { - next = child->next; - json_delete(child); - } - break; - } - default:; - } - - free(node); - } -} + switch (next(lexer)) { + case 't': + if (!accept_run(lexer, "rue")) { + emit(lexer, JSON_TOK_TRUE); + return lexer_json; + } + break; + case 'f': + if (!accept_run(lexer, "alse")) { + emit(lexer, JSON_TOK_FALSE); + return lexer_json; + } + break; + } -bool json_validate(const char *json) -{ - const char *s = json; - - skip_space(&s); - if (!parse_value(&s, NULL)) - return false; - - skip_space(&s); - if (*s != 0) - return false; - - return true; + emit(lexer, JSON_TOK_ERROR); + return NULL; } -JsonNode *json_find_element(JsonNode *array, int index) +static void *lexer_null(struct lexer *lexer) { - JsonNode *element; - int i = 0; - - if (array == NULL || array->tag != JSON_ARRAY) - return NULL; - - json_foreach(element, array) { - if (i == index) - return element; - i++; - } - - return NULL; -} + if (accept_run(lexer, "ull") < 0) { + emit(lexer, JSON_TOK_ERROR); + return NULL; + } -JsonNode *json_find_member(JsonNode *object, const char *name) -{ - JsonNode *member; - - if (object == NULL || object->tag != JSON_OBJECT) - return NULL; - - json_foreach(member, object) - if (strcmp(member->key, name) == 0) - return member; - - return NULL; -} + emit(lexer, JSON_TOK_NULL); + return lexer_json; +} + +static void *lexer_number(struct lexer *lexer) +{ + while (true) { + int chr = next(lexer); -JsonNode *json_first_child(const JsonNode *node) -{ - if (node != NULL && (node->tag == JSON_ARRAY || node->tag == JSON_OBJECT)) - return node->children.head; - return NULL; -} + if (isdigit(chr) || chr == '.') { + continue; + } -static JsonNode *mknode(JsonTag tag) -{ - JsonNode *ret = (JsonNode*) calloc(1, sizeof(JsonNode)); - if (ret == NULL) - out_of_memory(); - ret->tag = tag; - return ret; -} + backup(lexer); + emit(lexer, JSON_TOK_NUMBER); -JsonNode *json_mknull(void) -{ - return mknode(JSON_NULL); + return lexer_json; + } } -JsonNode *json_mkbool(bool b) +static void *lexer_json(struct lexer *lexer) { - JsonNode *ret = mknode(JSON_BOOL); - ret->bool_ = b; - return ret; -} + while (true) { + int chr = next(lexer); -static JsonNode *mkstring(char *s) -{ - JsonNode *ret = mknode(JSON_STRING); - ret->string_ = s; - return ret; -} + switch (chr) { + case '\0': + emit(lexer, JSON_TOK_EOF); + return NULL; + case '}': + case '{': + case '[': + case ']': + case ',': + case ':': + emit(lexer, (enum json_tokens)chr); + return lexer_json; + case '"': + return lexer_string; + case 'n': + return lexer_null; + case 't': + case 'f': + return lexer_boolean; + case '-': + if (isdigit(peek(lexer))) { + return lexer_number; + } -JsonNode *json_mkstring(const char *s) -{ - return mkstring(strdup(s)); -} + /* fallthrough */ + default: + if (isspace(chr)) { + ignore(lexer); + continue; + } -JsonNode *json_mknumber(double n) -{ - JsonNode *node = mknode(JSON_NUMBER); - node->number_ = n; - return node; -} + if (isdigit(chr)) { + return lexer_number; + } -JsonNode *json_mkarray(void) -{ - return mknode(JSON_ARRAY); + emit(lexer, JSON_TOK_ERROR); + return NULL; + } + } } -JsonNode *json_mkobject(void) +static void lexer_init(struct lexer *lexer, char *data, size_t len) { - return mknode(JSON_OBJECT); + lexer->state = lexer_json; + lexer->start = data; + lexer->pos = data; + lexer->end = data + len; + lexer->token.type = JSON_TOK_NONE; } - -static void append_node(JsonNode *parent, JsonNode *child) + +static int obj_init(struct json_obj *json, char *data, size_t len) { - child->parent = parent; - child->prev = parent->children.tail; - child->next = NULL; - - if (parent->children.tail != NULL) - parent->children.tail->next = child; - else - parent->children.head = child; - parent->children.tail = child; -} + struct token token; -static void prepend_node(JsonNode *parent, JsonNode *child) -{ - child->parent = parent; - child->prev = NULL; - child->next = parent->children.head; - - if (parent->children.head != NULL) - parent->children.head->prev = child; - else - parent->children.tail = child; - parent->children.head = child; -} + lexer_init(&json->lexer, data, len); -static void append_member(JsonNode *object, char *key, JsonNode *value) -{ - value->key = key; - append_node(object, value); -} + if (!lexer_next(&json->lexer, &token)) { + return -EINVAL; + } -void json_append_element(JsonNode *array, JsonNode *element) -{ - assert(array->tag == JSON_ARRAY); - assert(element->parent == NULL); - - append_node(array, element); -} + if (token.type != JSON_TOK_OBJECT_START) { + return -EINVAL; + } -void json_prepend_element(JsonNode *array, JsonNode *element) -{ - assert(array->tag == JSON_ARRAY); - assert(element->parent == NULL); - - prepend_node(array, element); + return 0; } -void json_append_member(JsonNode *object, const char *key, JsonNode *value) +static int element_token(enum json_tokens token) { - assert(object->tag == JSON_OBJECT); - assert(value->parent == NULL); - - append_member(object, strdup(key), value); + switch (token) { + case JSON_TOK_OBJECT_START: + case JSON_TOK_LIST_START: + case JSON_TOK_STRING: + case JSON_TOK_NUMBER: + case JSON_TOK_TRUE: + case JSON_TOK_FALSE: + return 0; + default: + return -EINVAL; + } } -void json_prepend_member(JsonNode *object, const char *key, JsonNode *value) +static int obj_next(struct json_obj *json, struct json_obj_key_value *kv) { - assert(object->tag == JSON_OBJECT); - assert(value->parent == NULL); - - value->key = strdup(key); - prepend_node(object, value); -} + struct token token; -void json_remove_from_parent(JsonNode *node) -{ - JsonNode *parent = node->parent; - - if (parent != NULL) { - if (node->prev != NULL) - node->prev->next = node->next; - else - parent->children.head = node->next; - if (node->next != NULL) - node->next->prev = node->prev; - else - parent->children.tail = node->prev; - - free(node->key); - - node->parent = NULL; - node->prev = node->next = NULL; - node->key = NULL; - } -} + if (!lexer_next(&json->lexer, &token)) { + return -EINVAL; + } -static bool parse_value(const char **sp, JsonNode **out) -{ - const char *s = *sp; - - switch (*s) { - case 'n': - if (expect_literal(&s, "null")) { - if (out) - *out = json_mknull(); - *sp = s; - return true; - } - return false; - - case 'f': - if (expect_literal(&s, "false")) { - if (out) - *out = json_mkbool(false); - *sp = s; - return true; - } - return false; - - case 't': - if (expect_literal(&s, "true")) { - if (out) - *out = json_mkbool(true); - *sp = s; - return true; - } - return false; - - case '"': { - char *str; - if (parse_string(&s, out ? &str : NULL)) { - if (out) - *out = mkstring(str); - *sp = s; - return true; - } - return false; - } - - case '[': - if (parse_array(&s, out)) { - *sp = s; - return true; - } - return false; - - case '{': - if (parse_object(&s, out)) { - *sp = s; - return true; - } - return false; - - default: { - double num; - if (parse_number(&s, out ? &num : NULL)) { - if (out) - *out = json_mknumber(num); - *sp = s; - return true; - } - return false; - } - } -} + /* Match end of object or next key */ + switch (token.type) { + case JSON_TOK_OBJECT_END: + kv->key = NULL; + kv->key_len = 0; + kv->value = token; -static bool parse_array(const char **sp, JsonNode **out) -{ - const char *s = *sp; - JsonNode *ret = out ? json_mkarray() : NULL; - JsonNode *element; - - if (*s++ != '[') - goto failure; - skip_space(&s); - - if (*s == ']') { - s++; - goto success; - } - - for (;;) { - if (!parse_value(&s, out ? &element : NULL)) - goto failure; - skip_space(&s); - - if (out) - json_append_element(ret, element); - - if (*s == ']') { - s++; - goto success; - } - - if (*s++ != ',') - goto failure; - skip_space(&s); - } - -success: - *sp = s; - if (out) - *out = ret; - return true; - -failure: - json_delete(ret); - return false; -} + return 0; + case JSON_TOK_COMMA: + if (!lexer_next(&json->lexer, &token)) { + return -EINVAL; + } -static bool parse_object(const char **sp, JsonNode **out) -{ - const char *s = *sp; - JsonNode *ret = out ? json_mkobject() : NULL; - char *key; - JsonNode *value; - - if (*s++ != '{') - goto failure; - skip_space(&s); - - if (*s == '}') { - s++; - goto success; - } - - for (;;) { - if (!parse_string(&s, out ? &key : NULL)) - goto failure; - skip_space(&s); - - if (*s++ != ':') - goto failure_free_key; - skip_space(&s); - - if (!parse_value(&s, out ? &value : NULL)) - goto failure_free_key; - skip_space(&s); - - if (out) - append_member(ret, key, value); - - if (*s == '}') { - s++; - goto success; - } - - if (*s++ != ',') - goto failure; - skip_space(&s); - } - -success: - *sp = s; - if (out) - *out = ret; - return true; - -failure_free_key: - if (out) - free(key); -failure: - json_delete(ret); - return false; -} + if (token.type != JSON_TOK_STRING) { + return -EINVAL; + } -bool parse_string(const char **sp, char **out) -{ - const char *s = (char *)*sp; - SB sb = { }; - char throwaway_buffer[4]; - /* enough space for a UTF-8 character */ - char *b; - - if (*s++ != '"') - return false; - - if (out) { - sb_init(&sb); - sb_need(&sb, 4); - b = sb.cur; - } else { - b = throwaway_buffer; - } - - while (*s != '"') { - char c = *s++; - - /* Parse next character, and write it to b. */ - if (c == '\\') { - c = *s++; - switch (c) { - case '"': - case '\\': - case '/': - *b++ = (char)c; - break; - case 'b': - *b++ = '\b'; - break; - case 'f': - *b++ = '\f'; - break; - case 'n': - *b++ = '\n'; - break; - case 'r': - *b++ = '\r'; - break; - case 't': - *b++ = '\t'; - break; - case 'u': - { - uint16_t uc, lc; - uchar_t unicode; - - if (!parse_hex16(&s, &uc)) - goto failed; - - if (uc >= 0xD800 && uc <= 0xDFFF) { - /* Handle UTF-16 surrogate pair. */ - if (*s++ != '\\' || *s++ != 'u' || !parse_hex16(&s, &lc)) - goto failed; /* Incomplete surrogate pair. */ - if (!from_surrogate_pair(uc, lc, &unicode)) - goto failed; /* Invalid surrogate pair. */ - } else if (uc == 0) { - /* Disallow "\u0000". */ - goto failed; - } else { - unicode = uc; - } - - b += utf8_write_char(unicode, b); - break; - } - default: - /* Invalid escape */ - goto failed; - } - } else if (c <= 0x1F) { - /* Control characters are not allowed in string literals. */ - goto failed; - } else { - /* Validate and echo a UTF-8 character. */ - size_t len; - - s--; - len = utf8_validate_cz(s); - if (len == 0) - goto failed; /* Invalid UTF-8 character. */ - - while (len--) - *b++ = *s++; - } - - /* - * Update sb to know about the new bytes, - * and set up b to write another character. - */ - if (out) { - sb.cur = b; - sb_need(&sb, 4); - b = sb.cur; - } else { - b = throwaway_buffer; - } - } - s++; - - if (out) - *out = sb_finish(&sb); - *sp = s; - return true; - -failed: - if (out) - sb_free(&sb); - return false; -} + /* fallthrough */ + case JSON_TOK_STRING: + kv->key = token.start; + kv->key_len = (size_t)(token.end - token.start); + break; + default: + return -EINVAL; + } -/* - * The JSON spec says that a number shall follow this precise pattern - * (spaces and quotes added for readability): - * '-'? (0 | [1-9][0-9]*) ('.' [0-9]+)? ([Ee] [+-]? [0-9]+)? - * - * However, some JSON parsers are more liberal. For instance, PHP accepts - * '.5' and '1.'. JSON.parse accepts '+3'. - * - * This function takes the strict approach. - */ -bool parse_number(const char **sp, double *out) -{ - const char *s = *sp; - - /* '-'? */ - if (*s == '-') - s++; - - /* (0 | [1-9][0-9]*) */ - if (*s == '0') { - s++; - } else { - if (!is_digit(*s)) - return false; - do { - s++; - } while (is_digit(*s)); - } - - /* ('.' [0-9]+)? */ - if (*s == '.') { - s++; - if (!is_digit(*s)) - return false; - do { - s++; - } while (is_digit(*s)); - } - - /* ([Ee] [+-]? [0-9]+)? */ - if (*s == 'E' || *s == 'e') { - s++; - if (*s == '+' || *s == '-') - s++; - if (!is_digit(*s)) - return false; - do { - s++; - } while (is_digit(*s)); - } - - if (out) - *out = strtod(*sp, NULL); - - *sp = s; - return true; -} + /* Match : after key */ + if (!lexer_next(&json->lexer, &token)) { + return -EINVAL; + } -static void skip_space(const char **sp) -{ - const char *s = *sp; - while (is_space(*s)) - s++; - *sp = s; -} + if (token.type != JSON_TOK_COLON) { + return -EINVAL; + } -static void emit_value(SB *out, const JsonNode *node) -{ - assert(tag_is_valid(node->tag)); - switch (node->tag) { - case JSON_NULL: - sb_puts(out, "null"); - break; - case JSON_BOOL: - sb_puts(out, node->bool_ ? "true" : "false"); - break; - case JSON_STRING: - emit_string(out, node->string_); - break; - case JSON_NUMBER: - emit_number(out, node->number_); - break; - case JSON_ARRAY: - emit_array(out, node); - break; - case JSON_OBJECT: - emit_object(out, node); - break; - default: - assert(false); - } + /* Match value */ + if (!lexer_next(&json->lexer, &kv->value)) { + return -EINVAL; + } + + return element_token(kv->value.type); } -void emit_value_indented(SB *out, const JsonNode *node, const char *space, int indent_level) +static int arr_next(struct json_obj *json, struct token *value) { - assert(tag_is_valid(node->tag)); - switch (node->tag) { - case JSON_NULL: - sb_puts(out, "null"); - break; - case JSON_BOOL: - sb_puts(out, node->bool_ ? "true" : "false"); - break; - case JSON_STRING: - emit_string(out, node->string_); - break; - case JSON_NUMBER: - emit_number(out, node->number_); - break; - case JSON_ARRAY: - emit_array_indented(out, node, space, indent_level); - break; - case JSON_OBJECT: - emit_object_indented(out, node, space, indent_level); - break; - default: - assert(false); - } + if (!lexer_next(&json->lexer, value)) { + return -EINVAL; + } + + if (value->type == JSON_TOK_LIST_END) { + return 0; + } + + if (value->type == JSON_TOK_COMMA) { + if (!lexer_next(&json->lexer, value)) { + return -EINVAL; + } + } + + return element_token(value->type); } -static void emit_array(SB *out, const JsonNode *array) +static int decode_num(const struct token *token, int32_t *num) { - const JsonNode *element; - - sb_putc(out, '['); - json_foreach(element, array) { - emit_value(out, element); - if (element->next != NULL) - sb_putc(out, ','); - } - sb_putc(out, ']'); + /* FIXME: strtod() is not available in newlib/minimal libc, + * so using strtol() here. + */ + char *endptr; + char prev_end; + + prev_end = *token->end; + *token->end = '\0'; + + errno = 0; + long v = strtol(token->start, &endptr, 10); + if ((long)(int)v != v) { + return -ERANGE; + } + + *num = (int)v; + + *token->end = prev_end; + + if (errno != 0) { + return -errno; + } + + if (endptr != token->end) { + return -EINVAL; + } + + return 0; } -static void emit_array_indented(SB *out, const JsonNode *array, const char *space, int indent_level) -{ - const JsonNode *element = array->children.head; - int i; - - if (element == NULL) { - sb_puts(out, "[]"); - return; - } - - sb_puts(out, "[\n"); - while (element != NULL) { - for (i = 0; i < indent_level + 1; i++) - sb_puts(out, space); - emit_value_indented(out, element, space, indent_level + 1); - - element = element->next; - sb_puts(out, element != NULL ? ",\n" : "\n"); - } - for (i = 0; i < indent_level; i++) - sb_puts(out, space); - sb_putc(out, ']'); +static bool equivalent_types(enum json_tokens type1, enum json_tokens type2) +{ + if (type1 == JSON_TOK_TRUE || type1 == JSON_TOK_FALSE) { + return type2 == JSON_TOK_TRUE || type2 == JSON_TOK_FALSE; + } + + return type1 == type2; } -static void emit_object(SB *out, const JsonNode *object) +static int obj_parse(struct json_obj *obj, + const struct json_obj_descr *descr, + size_t descr_len, + void *val); +static int arr_parse(struct json_obj *obj, + const struct json_obj_descr *elem_descr, + size_t max_elements, + void *field, + void *val); + +static int decode_value(struct json_obj *obj, + const struct json_obj_descr *descr, + struct token *value, + void *field, + void *val) { - const JsonNode *member; - - sb_putc(out, '{'); - json_foreach(member, object) { - emit_string(out, member->key); - sb_putc(out, ':'); - emit_value(out, member); - if (member->next != NULL) - sb_putc(out, ','); - } - sb_putc(out, '}'); + + if (!equivalent_types(value->type, descr->type)) { + return -EINVAL; + } + + switch (descr->type) { + case JSON_TOK_OBJECT_START: + return obj_parse(obj, descr->object.sub_descr, + descr->object.sub_descr_len, field); + case JSON_TOK_LIST_START: + return arr_parse(obj, descr->array.element_descr, + descr->array.n_elements, field, val); + case JSON_TOK_FALSE: + case JSON_TOK_TRUE: { + bool *v = field; + + *v = value->type == JSON_TOK_TRUE; + + return 0; + } + case JSON_TOK_NUMBER: { + int32_t *num = field; + + return decode_num(value, num); + } + case JSON_TOK_STRING: { + char **str = field; + + *value->end = '\0'; + *str = value->start; + + return 0; + } + default: + return -EINVAL; + } } -static void emit_object_indented(SB *out, const JsonNode *object, const char *space, int indent_level) +static ptrdiff_t get_elem_size(const struct json_obj_descr *descr) { - const JsonNode *member = object->children.head; - int i; - - if (member == NULL) { - sb_puts(out, "{}"); - return; - } - - sb_puts(out, "{\n"); - while (member != NULL) { - for (i = 0; i < indent_level + 1; i++) - sb_puts(out, space); - emit_string(out, member->key); - sb_puts(out, ": "); - emit_value_indented(out, member, space, indent_level + 1); - - member = member->next; - sb_puts(out, member != NULL ? ",\n" : "\n"); - } - for (i = 0; i < indent_level; i++) - sb_puts(out, space); - sb_putc(out, '}'); + switch (descr->type) { + case JSON_TOK_NUMBER: + return sizeof(int32_t); + case JSON_TOK_STRING: + return sizeof(char *); + case JSON_TOK_TRUE: + case JSON_TOK_FALSE: + return sizeof(bool); + case JSON_TOK_LIST_START: + return (ptrdiff_t)descr->array.n_elements * + get_elem_size(descr->array.element_descr); + case JSON_TOK_OBJECT_START: { + ptrdiff_t total = 0; + size_t i; + + for (i = 0; i < descr->object.sub_descr_len; i++) { + ptrdiff_t s = get_elem_size(&descr->object.sub_descr[i]); + + total += (ptrdiff_t)ROUND_UP(s, 1 << descr->object.sub_descr[i].align_shift); + } + + return total; + } + default: + return -EINVAL; + } } -void emit_string(SB *out, const char *str) +static int arr_parse(struct json_obj *obj, + const struct json_obj_descr *elem_descr, + size_t max_elements, + void *field, + void *val) { - const char *s = str; - char *b; - - assert(utf8_validate(str)); - - /* - * 14 bytes is enough space to write up to two - * \uXXXX escapes and two quotation marks. - */ - sb_need(out, 14); - b = out->cur; - - *b++ = '"'; - while (*s != 0) { - char c = *s++; - - /* Encode the next character, and write it to b. */ - switch (c) { - case '"': - *b++ = '\\'; - *b++ = '"'; - break; - case '\\': - *b++ = '\\'; - *b++ = '\\'; - break; - case '\b': - *b++ = '\\'; - *b++ = 'b'; - break; - case '\f': - *b++ = '\\'; - *b++ = 'f'; - break; - case '\n': - *b++ = '\\'; - *b++ = 'n'; - break; - case '\r': - *b++ = '\\'; - *b++ = 'r'; - break; - case '\t': - *b++ = '\\'; - *b++ = 't'; - break; - default: { - size_t len; - - s--; - len = utf8_validate_cz(s); - - if (len == 0) { - /* - * Handle invalid UTF-8 character gracefully in production - * by writing a replacement character (U+FFFD) - * and skipping a single byte. - * - * This should never happen when assertions are enabled - * due to the assertion at the beginning of this function. - */ - assert(false); - *b++ = (char)0xEF; - *b++ = (char)0xBF; - *b++ = (char)0xBD; - s++; - } else if (c < 0x1F) { - /* Encode using \u.... */ - uint32_t unicode; - - s += utf8_read_char(s, &unicode); - - if (unicode <= 0xFFFF) { - *b++ = '\\'; - *b++ = 'u'; - b += write_hex16(b, (uint16_t)unicode); - } else { - /* Produce a surrogate pair. */ - uint16_t uc, lc; - assert(unicode <= 0x10FFFF); - to_surrogate_pair(unicode, &uc, &lc); - *b++ = '\\'; - *b++ = 'u'; - b += write_hex16(b, uc); - *b++ = '\\'; - *b++ = 'u'; - b += write_hex16(b, lc); - } - } else { - /* Write the character directly. */ - while (len--) - *b++ = *s++; - } - - break; - } - } - - /* - * Update *out to know about the new bytes, - * and set up b to write another encoded character. - */ - out->cur = b; - sb_need(out, 14); - b = out->cur; - } - *b++ = '"'; - - out->cur = b; + ptrdiff_t elem_size = get_elem_size(elem_descr); + void *last_elem = (char *)field + elem_size * (ptrdiff_t)max_elements; + size_t *elements = (size_t *)((char *)val + elem_descr->offset); + struct token value; + + assert(elem_size > 0); + + *elements = 0; + + while (!arr_next(obj, &value)) { + if (value.type == JSON_TOK_LIST_END) { + return 0; + } + + if (field == last_elem) { + return -ENOSPC; + } + + if (decode_value(obj, elem_descr, &value, field, val) < 0) { + return -EINVAL; + } + + (*elements)++; + field = (char *)field + elem_size; + } + + return -EINVAL; +} + +static int obj_parse(struct json_obj *obj, + const struct json_obj_descr *descr, + size_t descr_len, + void *val) +{ + struct json_obj_key_value kv; + int32_t decoded_fields = 0; + size_t i; + int ret; + + while (!obj_next(obj, &kv)) { + if (kv.value.type == JSON_TOK_OBJECT_END) { + return decoded_fields; + } + + for (i = 0; i < descr_len; i++) { + void *decode_field = (char *)val + descr[i].offset; + + /* Field has been decoded already, skip */ + if (decoded_fields & (1 << i)) { + continue; + } + + /* Check if it's the i-th field */ + if (kv.key_len != descr[i].field_name_len) { + continue; + } + + if (memcmp(kv.key, descr[i].field_name, descr[i].field_name_len)) { + continue; + } + + /* Store the decoded value */ + ret = decode_value(obj, &descr[i], &kv.value, decode_field, val); + if (ret < 0) { + return ret; + } + + decoded_fields |= 1 << i; + break; + } + } + + return -EINVAL; } -static void emit_number(SB *out, double num) +int json_obj_parse(char *payload, + size_t len, + const struct json_obj_descr *descr, + size_t descr_len, + void *val) { - /* - * This isn't exactly how JavaScript renders numbers, - * but it should produce valid JSON for reasonable numbers - * preserve precision well enough, and avoid some oddities - * like 0.3 -> 0.299999999999999988898 . - */ - char buf[64]; - sprintf(buf, "%.16g", num); - - if (number_is_valid(buf)) - sb_puts(out, buf); - else - sb_puts(out, "null"); + struct json_obj obj; + int ret; + + assert(descr_len < (sizeof(ret) * CHAR_BIT - 1)); + + ret = obj_init(&obj, payload, len); + if (ret < 0) { + return ret; + } + + return obj_parse(&obj, descr, descr_len, val); } -static bool tag_is_valid(unsigned int tag) +static char escape_as(char chr) { - return (/* tag >= JSON_NULL && */ tag <= JSON_OBJECT); + switch (chr) { + case '"': + return '"'; + case '\\': + return '\\'; + case '\b': + return 'b'; + case '\f': + return 'f'; + case '\n': + return 'n'; + case '\r': + return 'r'; + case '\t': + return 't'; + } + + return 0; +} + +static int json_escape_internal(const char *str, + json_append_bytes_t append_bytes, + void *data) +{ + const char *cur; + int ret = 0; + + for (cur = str; ret == 0 && *cur; cur++) { + char escaped = escape_as(*cur); + + if (escaped) { + char bytes[2] = {'\\', escaped}; + + ret = append_bytes(bytes, 2, data); + } else { + ret = append_bytes(cur, 1, data); + } + } + + return ret; +} + +size_t json_calc_escaped_len(const char *str, size_t len) +{ + size_t escaped_len = len; + size_t pos; + + for (pos = 0; pos < len; pos++) { + if (escape_as(str[pos])) { + escaped_len++; + } + } + + return escaped_len; +} + +ssize_t json_escape(char *str, size_t *len, size_t buf_size) +{ + char *next; /* Points after next character to escape. */ + char *dest; /* Points after next place to write escaped character. */ + size_t escaped_len = json_calc_escaped_len(str, *len); + + if (escaped_len == *len) { + /* + * If no escape is necessary, there is nothing to do. + */ + return 0; + } + + if (escaped_len >= buf_size) { + return -ENOMEM; + } + + /* + * By walking backwards in the buffer from the end positions + * of both the original and escaped strings, we avoid using + * extra space. Characters in the original string are + * overwritten only after they have already been escaped. + */ + str[escaped_len] = '\0'; + for (next = &str[*len], dest = &str[escaped_len]; next != str;) { + char next_c = *(--next); + char escape = escape_as(next_c); + + if (escape) { + *(--dest) = escape; + *(--dest) = '\\'; + } else { + *(--dest) = next_c; + } + } + *len = escaped_len; + + return 0; +} + +static int encode(const struct json_obj_descr *descr, + const void *val, + json_append_bytes_t append_bytes, + void *data); + +static int arr_encode(const struct json_obj_descr *elem_descr, + const void *field, + const void *val, + json_append_bytes_t append_bytes, + void *data) +{ + ptrdiff_t elem_size = get_elem_size(elem_descr); + /* + * NOTE: Since an element descriptor's offset isn't meaningful + * (array elements occur at multiple offsets in `val'), we use + * its space in elem_descr to store the offset to the field + * containing the number of elements. + */ + size_t n_elem = *(size_t *)((char *)val + elem_descr->offset); + size_t i; + int ret; + + ret = append_bytes("[", 1, data); + if (ret < 0) { + return ret; + } + + for (i = 0; i < n_elem; i++) { + /* + * Though "field" points at the next element in the + * array which we need to encode, the value in + * elem_descr->offset is actually the offset of the + * length field in the "parent" struct containing the + * array. + * + * To patch things up, we lie to encode() about where + * the field is by exactly the amount it will offset + * it. This is a size optimization for struct + * json_obj_descr: the alternative is to keep a + * separate field next to element_descr which is an + * offset to the length field in the parent struct, + * but that would add a size_t to every descriptor. + */ + ret = encode(elem_descr, (char *)field - elem_descr->offset, + append_bytes, data); + if (ret < 0) { + return ret; + } + + if (i < n_elem - 1) { + ret = append_bytes(",", 1, data); + if (ret < 0) { + return ret; + } + } + + field = (char *)field + elem_size; + } + + return append_bytes("]", 1, data); +} + +static int +str_encode(const char **str, json_append_bytes_t append_bytes, void *data) +{ + int ret; + + ret = append_bytes("\"", 1, data); + if (ret < 0) { + return ret; + } + + ret = json_escape_internal(*str, append_bytes, data); + if (!ret) { + return append_bytes("\"", 1, data); + } + + return ret; +} + +static int +num_encode(const int32_t *num, json_append_bytes_t append_bytes, void *data) +{ + char buf[3 * sizeof(int32_t)]; + int ret; + + ret = snprintf(buf, sizeof(buf), "%d", *num); + if (ret < 0) { + return ret; + } + if (ret >= (int)sizeof(buf)) { + return -ENOMEM; + } + + return append_bytes(buf, (size_t)ret, data); +} + +static int +bool_encode(const bool *value, json_append_bytes_t append_bytes, void *data) +{ + if (*value) { + return append_bytes("true", 4, data); + } + + return append_bytes("false", 5, data); +} + +static int encode(const struct json_obj_descr *descr, + const void *val, + json_append_bytes_t append_bytes, + void *data) +{ + void *ptr = (char *)val + descr->offset; + + switch (descr->type) { + case JSON_TOK_FALSE: + case JSON_TOK_TRUE: + return bool_encode(ptr, append_bytes, data); + case JSON_TOK_STRING: + return str_encode(ptr, append_bytes, data); + case JSON_TOK_LIST_START: + return arr_encode(descr->array.element_descr, ptr, val, append_bytes, data); + case JSON_TOK_OBJECT_START: + return json_obj_encode(descr->object.sub_descr, + descr->object.sub_descr_len, ptr, append_bytes, + data); + case JSON_TOK_NUMBER: + return num_encode(ptr, append_bytes, data); + default: + return -EINVAL; + } } -static bool number_is_valid(const char *num) +int json_obj_encode(const struct json_obj_descr *descr, + size_t descr_len, + const void *val, + json_append_bytes_t append_bytes, + void *data) { - return (parse_number(&num, NULL) && *num == '\0'); + size_t i; + int ret; + + ret = append_bytes("{", 1, data); + if (ret < 0) { + return ret; + } + + for (i = 0; i < descr_len; i++) { + ret = + str_encode((const char **)&descr[i].field_name, append_bytes, data); + if (ret < 0) { + return ret; + } + + ret = append_bytes(":", 1, data); + if (ret < 0) { + return ret; + } + + ret = encode(&descr[i], val, append_bytes, data); + if (ret < 0) { + return ret; + } + + if (i < descr_len - 1) { + ret = append_bytes(",", 1, data); + if (ret < 0) { + return ret; + } + } + } + + return append_bytes("}", 1, data); } -static bool expect_literal(const char **sp, const char *str) +struct appender { + char *buffer; + size_t used; + size_t size; +}; + +static int append_bytes_to_buf(const char *bytes, size_t len, void *data) { - const char *s = *sp; - - while (*str != '\0') - if (*s++ != *str++) - return false; - - *sp = s; - return true; + struct appender *appender = data; + + if (len > appender->size - appender->used) { + return -ENOMEM; + } + + memcpy(appender->buffer + appender->used, bytes, len); + appender->used += len; + appender->buffer[appender->used] = '\0'; + + return 0; } -/* - * Parses exactly 4 hex characters (capital or lowercase). - * Fails if any input chars are not [0-9A-Fa-f]. - */ -static bool parse_hex16(const char **sp, uint16_t *out) +int json_obj_encode_buf(const struct json_obj_descr *descr, + size_t descr_len, + const void *val, + char *buffer, + size_t buf_size) { - const char *s = *sp; - uint16_t ret = 0; - uint16_t i; - uint16_t tmp; - - for (i = 0; i < 4; i++) { - char c = *s++; - if (c >= '0' && c <= '9') - tmp = (uint16_t)(c - '0'); - else if (c >= 'A' && c <= 'F') - tmp = (uint16_t)(c - 'A' + 10); - else if (c >= 'a' && c <= 'f') - tmp = (uint16_t)(c - 'a' + 10); - else - return false; - - ret = (uint16_t)((ret << 4) + tmp); - } - - if (out) - *out = ret; - *sp = s; - return true; + struct appender appender = {.buffer = buffer, .size = buf_size}; + + return json_obj_encode(descr, descr_len, val, append_bytes_to_buf, + &appender); } -/* - * Encodes a 16-bit number into hexadecimal, - * writing exactly 4 hex chars. - */ -static int write_hex16(char *out, uint16_t val) +static int +measure_bytes(const char *bytes __attribute__((unused)), size_t len, void *data) { - const char *hex = "0123456789ABCDEF"; - - *out++ = hex[(val >> 12) & 0xF]; - *out++ = hex[(val >> 8) & 0xF]; - *out++ = hex[(val >> 4) & 0xF]; - *out++ = hex[ val & 0xF]; - - return 4; + ssize_t *total = data; + + *total += (ssize_t)len; + + return 0; } -bool json_check(const JsonNode *node, char errmsg[256]) +ssize_t json_calc_encoded_len(const struct json_obj_descr *descr, + size_t descr_len, + const void *val) { - #define problem(...) do { \ - if (errmsg != NULL) \ - snprintf(errmsg, 256, __VA_ARGS__); \ - return false; \ - } while (0) - - if (node->key != NULL && !utf8_validate(node->key)) - problem("key contains invalid UTF-8"); - - if (!tag_is_valid(node->tag)) - problem("tag is invalid (%u)", node->tag); - - if (node->tag == JSON_STRING) { - if (node->string_ == NULL) - problem("string_ is NULL"); - if (!utf8_validate(node->string_)) - problem("string_ contains invalid UTF-8"); - } else if (node->tag == JSON_ARRAY || node->tag == JSON_OBJECT) { - JsonNode *head = node->children.head; - JsonNode *tail = node->children.tail; - - if (head == NULL || tail == NULL) { - if (head != NULL) - problem("tail is NULL, but head is not"); - if (tail != NULL) - problem("head is NULL, but tail is not"); - } else { - JsonNode *child; - JsonNode *last = NULL; - - if (head->prev != NULL) - problem("First child's prev pointer is not NULL"); - - for (child = head; child != NULL; last = child, child = child->next) { - if (child == node) - problem("node is its own child"); - if (child->next == child) - problem("child->next == child (cycle)"); - if (child->next == head) - problem("child->next == head (cycle)"); - - if (child->parent != node) - problem("child does not point back to parent"); - if (child->next != NULL && child->next->prev != child) - problem("child->next does not point back to child"); - - if (node->tag == JSON_ARRAY && child->key != NULL) - problem("Array element's key is not NULL"); - if (node->tag == JSON_OBJECT && child->key == NULL) - problem("Object member's key is NULL"); - - if (!json_check(child, errmsg)) - return false; - } - - if (last != tail) - problem("tail does not match pointer found by starting at head and following next links"); - } - } - - return true; - - #undef problem + ssize_t total = 0; + int ret; + + ret = json_obj_encode(descr, descr_len, val, measure_bytes, &total); + if (ret < 0) { + return ret; + } + + return total; } diff --git a/src/samples/techempower/json.h b/src/samples/techempower/json.h index 5a48b55ab..d5dbe6b13 100644 --- a/src/samples/techempower/json.h +++ b/src/samples/techempower/json.h @@ -1,116 +1,652 @@ /* - Copyright (C) 2011 Joseph A. Adams (joeyadams3.14159@gmail.com) - All rights reserved. - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. -*/ - -#pragma once - -#include + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DATA_JSON_H_ +#define ZEPHYR_INCLUDE_DATA_JSON_H_ + #include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define ROUND_UP(x, align) \ + (((unsigned long)(x) + ((unsigned long)(align)-1)) & \ + ~((unsigned long)(align)-1)) + +/** + * @brief Structured Data + * @defgroup structured_data Structured Data + */ + +/** + * @defgroup json JSON + * @ingroup structured_data + * @{ + */ -typedef enum { - JSON_NULL, - JSON_BOOL, - JSON_STRING, - JSON_NUMBER, - JSON_ARRAY, - JSON_OBJECT, -} JsonTag; - -typedef struct JsonNode JsonNode; - -struct JsonNode -{ - /* only if parent is an object or array (NULL otherwise) */ - JsonNode *parent; - JsonNode *prev, *next; - - /* only if parent is an object (NULL otherwise) */ - char *key; /* Must be valid UTF-8. */ - - JsonTag tag; - union { - /* JSON_BOOL */ - bool bool_; - - /* JSON_STRING */ - char *string_; /* Must be valid UTF-8. */ - - /* JSON_NUMBER */ - double number_; - - /* JSON_ARRAY */ - /* JSON_OBJECT */ - struct { - JsonNode *head, *tail; - } children; - }; +enum json_tokens { + /* Before changing this enum, ensure that its maximum + * value is still within 7 bits. See comment next to the + * declaration of `type` in struct json_obj_descr. + */ + + JSON_TOK_NONE = '_', + JSON_TOK_OBJECT_START = '{', + JSON_TOK_OBJECT_END = '}', + JSON_TOK_LIST_START = '[', + JSON_TOK_LIST_END = ']', + JSON_TOK_STRING = '"', + JSON_TOK_COLON = ':', + JSON_TOK_COMMA = ',', + JSON_TOK_NUMBER = '0', + JSON_TOK_TRUE = 't', + JSON_TOK_FALSE = 'f', + JSON_TOK_NULL = 'n', + JSON_TOK_ERROR = '!', + JSON_TOK_EOF = '\0', }; -/*** Encoding, decoding, and validation ***/ +struct json_obj_descr { + const char *field_name; -JsonNode *json_decode (const char *json); -char *json_encode (const JsonNode *node); -char *json_encode_string (const char *str); -char *json_stringify (const JsonNode *node, const char *space); -char *json_stringify_length(const JsonNode *node, const char *space, size_t *length); -void json_delete (JsonNode *node); + /* Alignment can be 1, 2, 4, or 8. The macros to create + * a struct json_obj_descr will store the alignment's + * power of 2 in order to keep this value in the 0-3 range + * and thus use only 2 bits. + */ + uint32_t align_shift : 2; -bool json_validate (const char *json); + /* 127 characters is more than enough for a field name. */ + uint32_t field_name_len : 7; -/*** Lookup and traversal ***/ + /* Valid values here (enum json_tokens): JSON_TOK_STRING, + * JSON_TOK_NUMBER, JSON_TOK_TRUE, JSON_TOK_FALSE, + * JSON_TOK_OBJECT_START, JSON_TOK_LIST_START. (All others + * ignored.) Maximum value is '}' (125), so this has to be 7 bits + * long. + */ + uint32_t type : 7; -JsonNode *json_find_element (JsonNode *array, int index); -JsonNode *json_find_member (JsonNode *object, const char *key); + /* 65535 bytes is more than enough for many JSON payloads. */ + uint32_t offset : 16; -JsonNode *json_first_child (const JsonNode *node); + union { + struct { + const struct json_obj_descr *sub_descr; + size_t sub_descr_len; + } object; + struct { + const struct json_obj_descr *element_descr; + size_t n_elements; + } array; + }; +}; -#define json_foreach(i, object_or_array) \ - for ((i) = json_first_child(object_or_array); \ - (i) != NULL; \ - (i) = (i)->next) +/** + * @brief Function pointer type to append bytes to a buffer while + * encoding JSON data. + * + * @param bytes Contents to write to the output + * @param len Number of bytes in @param bytes to append to output + * @param data User-provided pointer + * + * @return This callback function should return a negative number on + * error (which will be propagated to the return value of + * json_obj_encode()), or 0 on success. + */ +typedef int (*json_append_bytes_t)(const char *bytes, size_t len, void *data); -/*** Construction and manipulation ***/ +#define Z_ALIGN_SHIFT(type) \ + (__alignof__(type) == 1 \ + ? 0 \ + : __alignof__(type) == 2 ? 1 : __alignof__(type) == 4 ? 2 : 3) -JsonNode *json_mknull(void); -JsonNode *json_mkbool(bool b); -JsonNode *json_mkstring(const char *s); -JsonNode *json_mknumber(double n); -JsonNode *json_mkarray(void); -JsonNode *json_mkobject(void); +/** + * @brief Helper macro to declare a descriptor for supported primitive + * values. + * + * @param struct_ Struct packing the values + * + * @param field_name_ Field name in the struct + * + * @param type_ Token type for JSON value corresponding to a primitive + * type. Must be one of: JSON_TOK_STRING for strings, JSON_TOK_NUMBER + * for numbers, JSON_TOK_TRUE (or JSON_TOK_FALSE) for booleans. + * + * Here's an example of use: + * + * struct foo { + * int some_int; + * }; + * + * struct json_obj_descr foo[] = { + * JSON_OBJ_DESCR_PRIM(struct foo, some_int, JSON_TOK_NUMBER), + * }; + */ +#define JSON_OBJ_DESCR_PRIM(struct_, field_name_, type_) \ + { \ + .field_name = (#field_name_), .align_shift = Z_ALIGN_SHIFT(struct_), \ + .field_name_len = sizeof(#field_name_) - 1, .type = type_, \ + .offset = offsetof(struct_, field_name_), \ + } -void json_append_element(JsonNode *array, JsonNode *element); -void json_prepend_element(JsonNode *array, JsonNode *element); -void json_append_member(JsonNode *object, const char *key, JsonNode *value); -void json_prepend_member(JsonNode *object, const char *key, JsonNode *value); +/** + * @brief Helper macro to declare a descriptor for an object value + * + * @param struct_ Struct packing the values + * + * @param field_name_ Field name in the struct + * + * @param sub_descr_ Array of json_obj_descr describing the subobject + * + * Here's an example of use: + * + * struct nested { + * int foo; + * struct { + * int baz; + * } bar; + * }; + * + * struct json_obj_descr nested_bar[] = { + * { ... declare bar.baz descriptor ... }, + * }; + * struct json_obj_descr nested[] = { + * { ... declare foo descriptor ... }, + * JSON_OBJ_DESCR_OBJECT(struct nested, bar, nested_bar), + * }; + */ +#define JSON_OBJ_DESCR_OBJECT(struct_, field_name_, sub_descr_) \ + { \ + .field_name = (#field_name_), .align_shift = Z_ALIGN_SHIFT(struct_), \ + .field_name_len = (sizeof(#field_name_) - 1), \ + .type = JSON_TOK_OBJECT_START, \ + .offset = offsetof(struct_, field_name_), \ + .object = { \ + .sub_descr = sub_descr_, \ + .sub_descr_len = ARRAY_SIZE(sub_descr_), \ + }, \ + } -void json_remove_from_parent(JsonNode *node); +/** + * @brief Helper macro to declare a descriptor for an array of primitives + * + * @param struct_ Struct packing the values + * + * @param field_name_ Field name in the struct + * + * @param max_len_ Maximum number of elements in array + * + * @param len_field_ Field name in the struct for the number of elements + * in the array + * + * @param elem_type_ Element type, must be a primitive type + * + * Here's an example of use: + * + * struct example { + * int foo[10]; + * size_t foo_len; + * }; + * + * struct json_obj_descr array[] = { + * JSON_OBJ_DESCR_ARRAY(struct example, foo, 10, foo_len, + * JSON_TOK_NUMBER) + * }; + */ +#define JSON_OBJ_DESCR_ARRAY(struct_, field_name_, max_len_, len_field_, \ + elem_type_) \ + { \ + .field_name = (#field_name_), .align_shift = Z_ALIGN_SHIFT(struct_), \ + .field_name_len = sizeof(#field_name_) - 1, \ + .type = JSON_TOK_LIST_START, .offset = offsetof(struct_, field_name_), \ + .array = { \ + .element_descr = \ + &(struct json_obj_descr){ \ + .align_shift = Z_ALIGN_SHIFT(struct_), \ + .type = elem_type_, \ + .offset = offsetof(struct_, len_field_), \ + }, \ + .n_elements = (max_len_), \ + }, \ + } -/*** Debugging ***/ +/** + * @brief Helper macro to declare a descriptor for an array of objects + * + * @param struct_ Struct packing the values + * + * @param field_name_ Field name in the struct containing the array + * + * @param max_len_ Maximum number of elements in the array + * + * @param len_field_ Field name in the struct for the number of elements + * in the array + * + * @param elem_descr_ Element descriptor, pointer to a descriptor array + * + * @param elem_descr_len_ Number of elements in elem_descr_ + * + * Here's an example of use: + * + * struct person_height { + * const char *name; + * int height; + * }; + * + * struct people_heights { + * struct person_height heights[10]; + * size_t heights_len; + * }; + * + * struct json_obj_descr person_height_descr[] = { + * JSON_OBJ_DESCR_PRIM(struct person_height, name, JSON_TOK_STRING), + * JSON_OBJ_DESCR_PRIM(struct person_height, height, JSON_TOK_NUMBER), + * }; + * + * struct json_obj_descr array[] = { + * JSON_OBJ_DESCR_OBJ_ARRAY(struct people_heights, heights, 10, + * heights_len, person_height_descr, + * ARRAY_SIZE(person_height_descr)), + * }; + */ +#define JSON_OBJ_DESCR_OBJ_ARRAY(struct_, field_name_, max_len_, len_field_, \ + elem_descr_, elem_descr_len_) \ + { \ + .field_name = (#field_name_), .align_shift = Z_ALIGN_SHIFT(struct_), \ + .field_name_len = sizeof(#field_name_) - 1, \ + .type = JSON_TOK_LIST_START, .offset = offsetof(struct_, field_name_), \ + .array = { \ + .element_descr = \ + &(struct json_obj_descr){ \ + .align_shift = Z_ALIGN_SHIFT(struct_), \ + .type = JSON_TOK_OBJECT_START, \ + .offset = offsetof(struct_, len_field_), \ + .object = \ + { \ + .sub_descr = elem_descr_, \ + .sub_descr_len = elem_descr_len_, \ + }, \ + }, \ + .n_elements = (max_len_), \ + }, \ + } -/* - * Look for structure and encoding problems in a JsonNode or its descendents. +/** + * @brief Helper macro to declare a descriptor for an array of array + * + * @param struct_ Struct packing the values + * + * @param field_name_ Field name in the struct containing the array + * + * @param max_len_ Maximum number of elements in the array + * + * @param len_field_ Field name in the struct for the number of elements + * in the array + * + * @param elem_descr_ Element descriptor, pointer to a descriptor array + * + * @param elem_descr_len_ Number of elements in elem_descr_ * - * If a problem is detected, return false, writing a description of the problem - * to errmsg (unless errmsg is NULL). + * Here's an example of use: + * + * struct person_height { + * const char *name; + * int height; + * }; + * + * struct person_heights_array { + * struct person_height heights; + * } + * + * struct people_heights { + * struct person_height_array heights[10]; + * size_t heights_len; + * }; + * + * struct json_obj_descr person_height_descr[] = { + * JSON_OBJ_DESCR_PRIM(struct person_height, name, JSON_TOK_STRING), + * JSON_OBJ_DESCR_PRIM(struct person_height, height, JSON_TOK_NUMBER), + * }; + * + * struct json_obj_descr person_height_array_descr[] = { + * JSON_OBJ_DESCR_OBJECT(struct person_heights_array, + * heights, person_heigth_descr), + * }; + * + * struct json_obj_descr array_array[] = { + * JSON_OBJ_DESCR_ARRAY_ARRAY(struct people_heights, heights, 10, + * heights_len, person_height_array_descr, + * ARRAY_SIZE(person_height_array_descr)), + * }; + */ +#define JSON_OBJ_DESCR_ARRAY_ARRAY(struct_, field_name_, max_len_, len_field_, \ + elem_descr_, elem_descr_len_) \ + { \ + .field_name = (#field_name_), .align_shift = Z_ALIGN_SHIFT(struct_), \ + .field_name_len = sizeof(#field_name_) - 1, \ + .type = JSON_TOK_LIST_START, .offset = offsetof(struct_, field_name_), \ + .array = { \ + .element_descr = \ + &(struct json_obj_descr){ \ + .align_shift = Z_ALIGN_SHIFT(struct_), \ + .type = JSON_TOK_LIST_START, \ + .offset = offsetof(struct_, len_field_), \ + .object = \ + { \ + .sub_descr = elem_descr_, \ + .sub_descr_len = elem_descr_len_, \ + }, \ + }, \ + .n_elements = (max_len_), \ + }, \ + } + +/** + * @brief Variant of JSON_OBJ_DESCR_PRIM that can be used when the + * structure and JSON field names differ. + * + * This is useful when the JSON field is not a valid C identifier. + * + * @param struct_ Struct packing the values. + * + * @param json_field_name_ String, field name in JSON strings + * + * @param struct_field_name_ Field name in the struct + * + * @param type_ Token type for JSON value corresponding to a primitive + * type. + * + * @see JSON_OBJ_DESCR_PRIM */ -bool json_check(const JsonNode *node, char errmsg[256]); +#define JSON_OBJ_DESCR_PRIM_NAMED(struct_, json_field_name_, \ + struct_field_name_, type_) \ + { \ + .field_name = (json_field_name_), \ + .align_shift = Z_ALIGN_SHIFT(struct_), \ + .field_name_len = sizeof(json_field_name_) - 1, .type = type_, \ + .offset = offsetof(struct_, struct_field_name_), \ + } +/** + * @brief Variant of JSON_OBJ_DESCR_OBJECT that can be used when the + * structure and JSON field names differ. + * + * This is useful when the JSON field is not a valid C identifier. + * + * @param struct_ Struct packing the values + * + * @param json_field_name_ String, field name in JSON strings + * + * @param struct_field_name_ Field name in the struct + * + * @param sub_descr_ Array of json_obj_descr describing the subobject + * + * @see JSON_OBJ_DESCR_OBJECT + */ +#define JSON_OBJ_DESCR_OBJECT_NAMED(struct_, json_field_name_, \ + struct_field_name_, sub_descr_) \ + { \ + .field_name = (json_field_name_), \ + .align_shift = Z_ALIGN_SHIFT(struct_), \ + .field_name_len = (sizeof(json_field_name_) - 1), \ + .type = JSON_TOK_OBJECT_START, \ + .offset = offsetof(struct_, struct_field_name_), \ + .object = { \ + .sub_descr = sub_descr_, \ + .sub_descr_len = ARRAY_SIZE(sub_descr_), \ + }, \ + } + +/** + * @brief Variant of JSON_OBJ_DESCR_ARRAY that can be used when the + * structure and JSON field names differ. + * + * This is useful when the JSON field is not a valid C identifier. + * + * @param struct_ Struct packing the values + * + * @param json_field_name_ String, field name in JSON strings + * + * @param struct_field_name_ Field name in the struct + * + * @param max_len_ Maximum number of elements in array + * + * @param len_field_ Field name in the struct for the number of elements + * in the array + * + * @param elem_type_ Element type, must be a primitive type + * + * @see JSON_OBJ_DESCR_ARRAY + */ +#define JSON_OBJ_DESCR_ARRAY_NAMED(struct_, json_field_name_, \ + struct_field_name_, max_len_, len_field_, \ + elem_type_) \ + { \ + .field_name = (json_field_name_), \ + .align_shift = Z_ALIGN_SHIFT(struct_), \ + .field_name_len = sizeof(json_field_name_) - 1, \ + .type = JSON_TOK_LIST_START, \ + .offset = offsetof(struct_, struct_field_name_), \ + .array = { \ + .element_descr = \ + &(struct json_obj_descr){ \ + .align_shift = Z_ALIGN_SHIFT(struct_), \ + .type = elem_type_, \ + .offset = offsetof(struct_, len_field_), \ + }, \ + .n_elements = (max_len_), \ + }, \ + } + +/** + * @brief Variant of JSON_OBJ_DESCR_OBJ_ARRAY that can be used when + * the structure and JSON field names differ. + * + * This is useful when the JSON field is not a valid C identifier. + * + * @param struct_ Struct packing the values + * + * @param json_field_name_ String, field name of the array in JSON strings + * + * @param struct_field_name_ Field name in the struct containing the array + * + * @param max_len_ Maximum number of elements in the array + * + * @param len_field_ Field name in the struct for the number of elements + * in the array + * + * @param elem_descr_ Element descriptor, pointer to a descriptor array + * + * @param elem_descr_len_ Number of elements in elem_descr_ + * + * Here's an example of use: + * + * struct person_height { + * const char *name; + * int height; + * }; + * + * struct people_heights { + * struct person_height heights[10]; + * size_t heights_len; + * }; + * + * struct json_obj_descr person_height_descr[] = { + * JSON_OBJ_DESCR_PRIM(struct person_height, name, JSON_TOK_STRING), + * JSON_OBJ_DESCR_PRIM(struct person_height, height, JSON_TOK_NUMBER), + * }; + * + * struct json_obj_descr array[] = { + * JSON_OBJ_DESCR_OBJ_ARRAY_NAMED(struct people_heights, + * "people-heights", heights, + * 10, heights_len, + * person_height_descr, + * ARRAY_SIZE(person_height_descr)), + * }; + */ +#define JSON_OBJ_DESCR_OBJ_ARRAY_NAMED( \ + struct_, json_field_name_, struct_field_name_, max_len_, len_field_, \ + elem_descr_, elem_descr_len_) \ + { \ + .field_name = json_field_name_, .align_shift = Z_ALIGN_SHIFT(struct_), \ + .field_name_len = sizeof(json_field_name_) - 1, \ + .type = JSON_TOK_LIST_START, \ + .offset = offsetof(struct_, struct_field_name_), \ + .element_descr = \ + &(struct json_obj_descr){ \ + .align_shift = Z_ALIGN_SHIFT(struct_), \ + .type = JSON_TOK_OBJECT_START, \ + .offset = offsetof(struct_, len_field_), \ + .object = \ + { \ + .sub_descr = elem_descr_, \ + .sub_descr_len = elem_descr_len_, \ + }, \ + }, \ + .n_elements = (max_len_), \ + } + +/** + * @brief Parses the JSON-encoded object pointer to by @a json, with + * size @a len, according to the descriptor pointed to by @a descr. + * Values are stored in a struct pointed to by @a val. Set up the + * descriptor like this: + * + * struct s { int foo; char *bar; } + * struct json_obj_descr descr[] = { + * JSON_OBJ_DESCR_PRIM(struct s, foo, JSON_TOK_NUMBER), + * JSON_OBJ_DESCR_PRIM(struct s, bar, JSON_TOK_STRING), + * }; + * + * Since this parser is designed for machine-to-machine communications, some + * liberties were taken to simplify the design: + * (1) strings are not unescaped (but only valid escape sequences are + * accepted); + * (2) no UTF-8 validation is performed; and + * (3) only integer numbers are supported (no strtod() in the minimal libc). + * + * @param json Pointer to JSON-encoded value to be parsed + * + * @param len Length of JSON-encoded value + * + * @param descr Pointer to the descriptor array + * + * @param descr_len Number of elements in the descriptor array. Must be less + * than 31 due to implementation detail reasons (if more fields are + * necessary, use two descriptors) + * + * @param val Pointer to the struct to hold the decoded values + * + * @return < 0 if error, bitmap of decoded fields on success (bit 0 + * is set if first field in the descriptor has been properly decoded, etc). + */ +int json_obj_parse(char *json, + size_t len, + const struct json_obj_descr *descr, + size_t descr_len, + void *val); + +/** + * @brief Escapes the string so it can be used to encode JSON objects + * + * @param str The string to escape; the escape string is stored the + * buffer pointed to by this parameter + * + * @param len Points to a size_t containing the size before and after + * the escaping process + * + * @param buf_size The size of buffer str points to + * + * @return 0 if string has been escaped properly, or -ENOMEM if there + * was not enough space to escape the buffer + */ +ssize_t json_escape(char *str, size_t *len, size_t buf_size); + +/** + * @brief Calculates the JSON-escaped string length + * + * @param str The string to analyze + * + * @param len String size + * + * @return The length str would have if it were escaped + */ +size_t json_calc_escaped_len(const char *str, size_t len); + +/** + * @brief Calculates the string length to fully encode an object + * + * @param descr Pointer to the descriptor array + * + * @param descr_len Number of elements in the descriptor array + * + * @param val Struct holding the values + * + * @return Number of bytes necessary to encode the values if >0, + * an error code is returned. + */ +ssize_t json_calc_encoded_len(const struct json_obj_descr *descr, + size_t descr_len, + const void *val); + +/** + * @brief Encodes an object in a contiguous memory location + * + * @param descr Pointer to the descriptor array + * + * @param descr_len Number of elements in the descriptor array + * + * @param val Struct holding the values + * + * @param buffer Buffer to store the JSON data + * + * @param buf_size Size of buffer, in bytes, with space for the terminating + * NUL character + * + * @return 0 if object has been successfully encoded. A negative value + * indicates an error (as defined on errno.h). + */ +int json_obj_encode_buf(const struct json_obj_descr *descr, + size_t descr_len, + const void *val, + char *buffer, + size_t buf_size); + +/** + * @brief Encodes an object using an arbitrary writer function + * + * @param descr Pointer to the descriptor array + * + * @param descr_len Number of elements in the descriptor array + * + * @param val Struct holding the values + * + * @param append_bytes Function to append bytes to the output + * + * @param data Data pointer to be passed to the append_bytes callback + * function. + * + * @return 0 if object has been successfully encoded. A negative value + * indicates an error. + */ +int json_obj_encode(const struct json_obj_descr *descr, + size_t descr_len, + const void *val, + json_append_bytes_t append_bytes, + void *data); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ +#endif /* ZEPHYR_INCLUDE_DATA_JSON_H_ */ diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index 15c55634a..8f4cf3d80 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -14,9 +14,11 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ +#include #include #include @@ -27,8 +29,53 @@ #include "database.h" #include "json.h" +enum db_connect_type { DB_CONN_MYSQL, DB_CONN_SQLITE }; + +struct db_connection_params { + enum db_connect_type type; + union { + struct { + const char *user; + const char *password; + const char *database; + const char *hostname; + } mysql; + struct { + const char *path; + const char **pragmas; + } sqlite; + }; +}; + +static struct db_connection_params db_connection_params; + +static struct db *get_db(void) +{ + static __thread struct db *database; + + if (!database) { + switch (db_connection_params.type) { + case DB_CONN_MYSQL: + database = db_connect_mysql(db_connection_params.mysql.hostname, + db_connection_params.mysql.user, + db_connection_params.mysql.password, + db_connection_params.mysql.database); + break; + case DB_CONN_SQLITE: + database = db_connect_sqlite(db_connection_params.sqlite.path, true, + db_connection_params.sqlite.pragmas); + break; + } + if (!database) + lwan_status_critical("Could not connect to the database"); + } + + return database; +} + static const char hello_world[] = "Hello, World!"; -static const char random_number_query[] = "SELECT randomNumber FROM World WHERE id=?"; +static const char random_number_query[] = + "SELECT randomNumber FROM world WHERE id=?"; struct Fortune { struct { @@ -41,18 +88,19 @@ struct Fortune { DEFINE_ARRAY_TYPE_INLINEFIRST(fortune_array, struct Fortune) -static const char fortunes_template_str[] = "" \ -"" \ -"Fortunes" \ -"" \ -"" \ -"" \ -"{{#item}}" \ -"" \ -"{{/item}}" \ -"
idmessage
{{item.id}}{{item.message}}
" \ -"" \ -""; +static const char fortunes_template_str[] = + "" + "" + "Fortunes" + "" + "" + "" + "{{#item}}" + "" + "{{/item}}" + "
idmessage
{{item.id}}{{item.message}}
" + "" + ""; static int fortune_list_generator(struct coro *coro, void *data); @@ -69,22 +117,52 @@ static const struct lwan_var_descriptor fortune_desc[] = { TPL_VAR_SENTINEL, }; -static struct db *database; static struct lwan_tpl *fortune_tpl; -static enum lwan_http_status -json_response(struct lwan_response *response, JsonNode *node) +struct hello_world_json { + const char *message; +}; +static const struct json_obj_descr hello_world_json_desc[] = { + JSON_OBJ_DESCR_PRIM(struct hello_world_json, message, JSON_TOK_STRING), +}; + +struct db_json { + int id; + int randomNumber; +}; +static const struct json_obj_descr db_json_desc[] = { + JSON_OBJ_DESCR_PRIM(struct db_json, id, JSON_TOK_NUMBER), + JSON_OBJ_DESCR_PRIM(struct db_json, randomNumber, JSON_TOK_NUMBER), +}; + +struct queries_json { + struct db_json queries[500]; + size_t queries_len; +}; +static const struct json_obj_descr queries_json_desc[] = { + JSON_OBJ_DESCR_OBJ_ARRAY(struct queries_json, + queries, + 500, + queries_len, + db_json_desc, + N_ELEMENTS(db_json_desc)), +}; + +static int append_to_strbuf(const char *bytes, size_t len, void *data) { - size_t length; - char *serialized; + struct lwan_strbuf *strbuf = data; - serialized = json_stringify_length(node, NULL, &length); - json_delete(node); - if (UNLIKELY(!serialized)) - return HTTP_INTERNAL_ERROR; + return lwan_strbuf_append_str(strbuf, bytes, len) ? 0 : -EINVAL; +} - lwan_strbuf_set(response->buffer, serialized, length); - free(serialized); +static enum lwan_http_status json_response(struct lwan_response *response, + const struct json_obj_descr *descr, + size_t descr_len, + const void *data) +{ + if (json_obj_encode(descr, descr_len, data, append_to_strbuf, + response->buffer) < 0) + return HTTP_INTERNAL_ERROR; response->mime_type = "application/json"; return HTTP_OK; @@ -92,60 +170,60 @@ json_response(struct lwan_response *response, JsonNode *node) LWAN_HANDLER(json) { - JsonNode *hello = json_mkobject(); - if (UNLIKELY(!hello)) - return HTTP_INTERNAL_ERROR; - - json_append_member(hello, "message", json_mkstring(hello_world)); + struct hello_world_json j = {.message = hello_world}; - return json_response(response, hello); + return json_response(response, hello_world_json_desc, + N_ELEMENTS(hello_world_json_desc), &j); } -static JsonNode * -db_query(struct db_stmt *stmt, struct db_row rows[], struct db_row results[]) +static bool db_query(struct db_stmt *stmt, + struct db_row rows[], + struct db_row results[], + struct db_json *out) { - JsonNode *object = NULL; int id = rand() % 10000; rows[0].u.i = id; if (UNLIKELY(!db_stmt_bind(stmt, rows, 1))) - goto out; + return false; if (UNLIKELY(!db_stmt_step(stmt, results))) - goto out; - - object = json_mkobject(); - if (UNLIKELY(!object)) - goto out; + return false; - json_append_member(object, "id", json_mknumber(id)); - json_append_member(object, "randomNumber", json_mknumber(results[0].u.i)); + out->id = id; + out->randomNumber = results[0].u.i; -out: - return object; + return true; } LWAN_HANDLER(db) { - struct db_row rows[1] = {{ .kind = 'i' }}; - struct db_row results[] = {{ .kind = 'i' }, { .kind = '\0' }}; - struct db_stmt *stmt = db_prepare_stmt(database, random_number_query, - sizeof(random_number_query) - 1); - if (UNLIKELY(!stmt)) + struct db_row rows[1] = {{.kind = 'i'}}; + struct db_row results[] = {{.kind = 'i'}, {.kind = '\0'}}; + struct db_stmt *stmt = db_prepare_stmt(get_db(), random_number_query, + sizeof(random_number_query) - 1); + struct db_json db_json; + + if (UNLIKELY(!stmt)) { + lwan_status_debug("preparing stmt failed"); return HTTP_INTERNAL_ERROR; + } + + bool queried = db_query(stmt, rows, results, &db_json); - JsonNode *object = db_query(stmt, rows, results); db_stmt_finalize(stmt); - if (UNLIKELY(!object)) + if (!queried) return HTTP_INTERNAL_ERROR; - return json_response(response, object); + return json_response(response, db_json_desc, N_ELEMENTS(db_json_desc), + &db_json); } LWAN_HANDLER(queries) { + enum lwan_http_status ret = HTTP_INTERNAL_ERROR; const char *queries_str = lwan_request_get_query_param(request, "queries"); long queries; @@ -159,39 +237,32 @@ LWAN_HANDLER(queries) queries = 1; } - struct db_stmt *stmt = db_prepare_stmt(database, random_number_query, - sizeof(random_number_query) - 1); + struct db_stmt *stmt = db_prepare_stmt(get_db(), random_number_query, + sizeof(random_number_query) - 1); if (UNLIKELY(!stmt)) return HTTP_INTERNAL_ERROR; - JsonNode *array = json_mkarray(); - if (UNLIKELY(!array)) - goto out_no_array; - - struct db_row rows[1] = {{ .kind = 'i' }}; - struct db_row results[] = {{ .kind = 'i' }, { .kind = '\0' }}; - while (queries--) { - JsonNode *object = db_query(stmt, rows, results); - - if (UNLIKELY(!object)) - goto out_array; - - json_append_element(array, object); + struct queries_json qj = {.queries_len = (size_t)queries}; + struct db_row rows[1] = {{.kind = 'i'}}; + struct db_row results[] = {{.kind = 'i'}, {.kind = '\0'}}; + for (long i = 0; i < queries; i++) { + if (!db_query(stmt, rows, results, &qj.queries[i])) + goto out; } - db_stmt_finalize(stmt); - return json_response(response, array); + ret = json_response(response, queries_json_desc, + N_ELEMENTS(queries_json_desc), &qj); -out_array: - json_delete(array); -out_no_array: +out: db_stmt_finalize(stmt); - return HTTP_INTERNAL_ERROR; + + return ret; } LWAN_HANDLER(plaintext) { - lwan_strbuf_set_static(response->buffer, hello_world, sizeof(hello_world) - 1); + lwan_strbuf_set_static(response->buffer, hello_world, + sizeof(hello_world) - 1); response->mime_type = "text/plain"; return HTTP_OK; @@ -208,15 +279,15 @@ static int fortune_compare(const void *a, const void *b) return a_len > b_len; size_t min_len = a_len < b_len ? a_len : b_len; - int cmp = memcmp(fortune_a->item.message, fortune_b->item.message, min_len); - if (cmp == 0) - return a_len > b_len; - return cmp > 0; + int cmp = memcmp(fortune_a->item.message, fortune_b->item.message, min_len); + return cmp == 0 ? -(int)(ssize_t)min_len : cmp; } -static bool append_fortune(struct coro *coro, struct fortune_array *fortunes, - int id, const char *message) +static bool append_fortune(struct coro *coro, + struct fortune_array *fortunes, + int id, + const char *message) { struct Fortune *fortune; char *message_copy; @@ -242,34 +313,33 @@ static int fortune_list_generator(struct coro *coro, void *data) struct Fortune *fortune = data; struct fortune_array fortunes; struct db_stmt *stmt; - size_t i; - stmt = db_prepare_stmt(database, fortune_query, sizeof(fortune_query) - 1); + stmt = db_prepare_stmt(get_db(), fortune_query, sizeof(fortune_query) - 1); if (UNLIKELY(!stmt)) return 0; fortune_array_init(&fortunes); - struct db_row results[] = { - { .kind = 'i' }, - { .kind = 's', .u.s = fortune_buffer, .buffer_length = sizeof(fortune_buffer) }, - { .kind = '\0' } - }; + struct db_row results[] = {{.kind = 'i'}, + {.kind = 's', + .u.s = fortune_buffer, + .buffer_length = sizeof(fortune_buffer)}, + {.kind = '\0'}}; while (db_stmt_step(stmt, results)) { if (!append_fortune(coro, &fortunes, results[0].u.i, results[1].u.s)) goto out; } if (!append_fortune(coro, &fortunes, 0, - "Additional fortune added at request time.")) + "Additional fortune added at request time.")) goto out; fortune_array_sort(&fortunes, fortune_compare); - for (i = 0; i < fortunes.base.elements; i++) { - struct Fortune *f = &((struct Fortune *)fortunes.base.base)[i]; - fortune->item.id = f->item.id; - fortune->item.message = f->item.message; + struct Fortune *iter; + LWAN_ARRAY_FOREACH (&fortunes, iter) { + fortune->item.id = iter->item.id; + fortune->item.message = iter->item.message; coro_yield(coro, 1); } @@ -283,24 +353,23 @@ LWAN_HANDLER(fortunes) { struct Fortune fortune; - if (UNLIKELY(!lwan_tpl_apply_with_buffer(fortune_tpl, - response->buffer, &fortune))) - return HTTP_INTERNAL_ERROR; + if (UNLIKELY(!lwan_tpl_apply_with_buffer(fortune_tpl, response->buffer, + &fortune))) + return HTTP_INTERNAL_ERROR; response->mime_type = "text/html; charset=UTF-8"; return HTTP_OK; } -int -main(void) +int main(void) { static const struct lwan_url_map url_map[] = { - { .prefix = "/json", .handler = LWAN_HANDLER_REF(json) }, - { .prefix = "/db", .handler = LWAN_HANDLER_REF(db) }, - { .prefix = "/queries", .handler = LWAN_HANDLER_REF(queries) }, - { .prefix = "/plaintext", .handler = LWAN_HANDLER_REF(plaintext) }, - { .prefix = "/fortunes", .handler = LWAN_HANDLER_REF(fortunes) }, - { .prefix = NULL } + {.prefix = "/json", .handler = LWAN_HANDLER_REF(json)}, + {.prefix = "/db", .handler = LWAN_HANDLER_REF(db)}, + {.prefix = "/queries", .handler = LWAN_HANDLER_REF(queries)}, + {.prefix = "/plaintext", .handler = LWAN_HANDLER_REF(plaintext)}, + {.prefix = "/fortunes", .handler = LWAN_HANDLER_REF(fortunes)}, + {.prefix = NULL}, }; struct lwan l; @@ -309,35 +378,35 @@ main(void) srand((unsigned int)time(NULL)); if (getenv("USE_MYSQL")) { - const char *user = getenv("MYSQL_USER"); - const char *password = getenv("MYSQL_PASS"); - const char *hostname = getenv("MYSQL_HOST"); - const char *db = getenv("MYSQL_DB"); + db_connection_params = (struct db_connection_params) { + .type = DB_CONN_MYSQL, + .mysql.user = getenv("MYSQL_USER"), + .mysql.password = getenv("MYSQL_PASS"), + .mysql.hostname = getenv("MYSQL_HOST"), + .mysql.database = getenv("MYSQL_DB") + }; - if (!user) + if (!db_connection_params.mysql.user) lwan_status_critical("No MySQL user provided"); - if (!password) + if (!db_connection_params.mysql.password) lwan_status_critical("No MySQL password provided"); - if (!hostname) + if (!db_connection_params.mysql.hostname) lwan_status_critical("No MySQL hostname provided"); - if (!db) + if (!db_connection_params.mysql.database) lwan_status_critical("No MySQL database provided"); - - database = db_connect_mysql(hostname, user, password, db); } else { - const char *pragmas[] = { - "PRAGMA mmap_size=44040192", - "PRAGMA journal_mode=OFF", - "PRAGMA locking_mode=EXCLUSIVE", - NULL + const char *pragmas[] = {"PRAGMA mmap_size=44040192", + "PRAGMA journal_mode=OFF", + "PRAGMA locking_mode=EXCLUSIVE", NULL}; + db_connection_params = (struct db_connection_params) { + .type = DB_CONN_SQLITE, + .sqlite.path = "techempower.db", + .sqlite.pragmas = pragmas, }; - database = db_connect_sqlite("techempower.db", true, pragmas); } - if (!database) - lwan_status_critical("Could not connect to the database"); - - fortune_tpl = lwan_tpl_compile_string(fortunes_template_str, fortune_desc); + fortune_tpl = lwan_tpl_compile_string_full( + fortunes_template_str, fortune_desc, LWAN_TPL_FLAG_CONST_TEMPLATE); if (!fortune_tpl) lwan_status_critical("Could not compile fortune templates"); @@ -345,7 +414,6 @@ main(void) lwan_main_loop(&l); lwan_tpl_free(fortune_tpl); - db_disconnect(database); lwan_shutdown(&l); return 0; From 76edf541bfa11d12b1e02ad7ee1e13a782e04b79 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 20 Nov 2019 18:24:42 -0800 Subject: [PATCH 1283/2505] Fix error message when mimegen fails to compress with ZSTD --- src/bin/tools/mimegen.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/tools/mimegen.c b/src/bin/tools/mimegen.c index d1af50483..0af81efbe 100644 --- a/src/bin/tools/mimegen.c +++ b/src/bin/tools/mimegen.c @@ -123,7 +123,7 @@ static char *compress_output(const struct output *output, size_t *outlen) *outlen = ZSTD_compress(compressed, *outlen, output->ptr, output->used, ZSTD_maxCLevel()); if (ZSTD_isError(*outlen)) { - fprintf(stderr, "Could not compress mime type table with Brotli\n"); + fprintf(stderr, "Could not compress mime type table with ZSTD\n"); exit(1); } #elif defined(HAVE_BROTLI) From 24b2294d2c8ce7299dbb1a58a880c283cd3cb08d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 20 Nov 2019 18:25:25 -0800 Subject: [PATCH 1284/2505] No need to cast array to element type when reverse-iterating over it --- src/lib/lwan-array.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-array.h b/src/lib/lwan-array.h index fdf04b3f2..96c9d6a3c 100644 --- a/src/lib/lwan-array.h +++ b/src/lib/lwan-array.h @@ -49,7 +49,7 @@ struct lwan_array *coro_lwan_array_new(struct coro *coro, bool inline_first); iter_++) #define LWAN_ARRAY_FOREACH_REVERSE(array_, iter_) \ - if ((typeof(iter_))(array_)->base.elements) \ + if ((array_)->base.elements) \ for (iter_ = ((typeof(iter_))(array_)->base.base + \ (array_)->base.elements - 1); \ iter_ >= (typeof(iter_))(array_)->base.base; iter_--) From da803b4485b56c6c9105f5df4af1822931d119f7 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 20 Nov 2019 18:48:09 -0800 Subject: [PATCH 1285/2505] Using `*` for listeners should suport both IPv6 and IPv4 --- src/lib/lwan-socket.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-socket.c b/src/lib/lwan-socket.c index f9a97e938..fdacda223 100644 --- a/src/lib/lwan-socket.c +++ b/src/lib/lwan-socket.c @@ -106,6 +106,8 @@ static sa_family_t parse_listener_ipv4(char *listener, char **node, char **port) if (streq(*node, "*")) { /* *:8080 */ *node = "0.0.0.0"; + + return AF_UNSPEC; /* IPv4 or IPv6 */ } } @@ -116,7 +118,7 @@ static sa_family_t parse_listener_ipv6(char *listener, char **node, char **port) { char *last_colon = strrchr(listener, ':'); if (!last_colon) - return AF_UNSPEC; + return AF_MAX; if (*(last_colon - 1) == ']') { /* [::]:8080 */ @@ -139,7 +141,7 @@ static sa_family_t parse_listener(char *listener, char **node, char **port) lwan_status_critical( "Listener configured to use systemd socket activation, " "but started outside systemd."); - return AF_UNSPEC; + return AF_MAX; } if (*listener == '[') @@ -214,9 +216,10 @@ static int setup_socket_normally(struct lwan *l) char *node, *port; char *listener = strdupa(l->config.listener); sa_family_t family = parse_listener(listener, &node, &port); - if (family == AF_UNSPEC) + if (family == AF_MAX) { lwan_status_critical("Could not parse listener: %s", l->config.listener); + } struct addrinfo *addrs; struct addrinfo hints = {.ai_family = family, From 58dab1209223452a36f847f39f1914d87e6b9298 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 20 Nov 2019 18:48:50 -0800 Subject: [PATCH 1286/2505] Reindent lwan-straitjacket.c --- src/lib/lwan-straitjacket.c | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/lib/lwan-straitjacket.c b/src/lib/lwan-straitjacket.c index ef97c2bc0..61fe287ae 100644 --- a/src/lib/lwan-straitjacket.c +++ b/src/lib/lwan-straitjacket.c @@ -14,7 +14,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #define _GNU_SOURCE @@ -79,8 +80,8 @@ static bool switch_to_user(uid_t uid, gid_t gid, const char *username) uid_t ruid, euid, suid; gid_t rgid, egid, sgid; - lwan_status_info("Dropping privileges to UID %d, GID %d (%s)", - uid, gid, username); + lwan_status_info("Dropping privileges to UID %d, GID %d (%s)", uid, gid, + username); if (setresgid(gid, gid, gid) < 0) return false; @@ -127,7 +128,7 @@ static void abort_on_open_directories(void) ret = snprintf(own_fd, sizeof(own_fd), "%d", dirfd(dir)); if (ret < 0 || ret >= (int)sizeof(own_fd)) { lwan_status_critical("Could not get descriptor of /proc/self/fd"); - } + } while ((ent = readdir(dir))) { char path[PATH_MAX]; @@ -141,8 +142,8 @@ static void abort_on_open_directories(void) len = readlinkat(dirfd(dir), ent->d_name, path, sizeof(path)); if (len < 0) { - lwan_status_critical_perror( - "Could not get information about fd %s", ent->d_name); + lwan_status_critical_perror("Could not get information about fd %s", + ent->d_name); } path[len] = '\0'; @@ -163,8 +164,8 @@ static void abort_on_open_directories(void) closedir(dir); lwan_status_critical( - "The directory '%s' is open (fd %s), can't chroot", - path, ent->d_name); + "The directory '%s' is open (fd %s), can't chroot", path, + ent->d_name); return; } } @@ -172,9 +173,7 @@ static void abort_on_open_directories(void) closedir(dir); } #else -static void abort_on_open_directories(void) -{ -} +static void abort_on_open_directories(void) {} #endif void lwan_straitjacket_enforce(const struct lwan_straitjacket *sj) @@ -209,16 +208,15 @@ void lwan_straitjacket_enforce(const struct lwan_straitjacket *sj) lwan_status_info("Jailed to %s", sj->chroot_path); } - if (got_uid_gid) { - if (!switch_to_user(uid, gid, sj->user_name)) - lwan_status_critical("Could not drop privileges to %s, aborting", - sj->user_name); + if (got_uid_gid && !switch_to_user(uid, gid, sj->user_name)) { + lwan_status_critical("Could not drop privileges to %s, aborting", + sj->user_name); } out: if (sj->drop_capabilities) { struct __user_cap_header_struct header = { - .version = _LINUX_CAPABILITY_VERSION_1 + .version = _LINUX_CAPABILITY_VERSION_1, }; struct __user_cap_data_struct data = {}; @@ -253,7 +251,7 @@ void lwan_straitjacket_enforce_from_config(struct config *c) config_error(c, "Straitjacket accepts no sections"); return; case CONFIG_LINE_TYPE_SECTION_END: - lwan_straitjacket_enforce(&(struct lwan_straitjacket) { + lwan_straitjacket_enforce(&(struct lwan_straitjacket){ .user_name = user_name, .chroot_path = chroot_path, .drop_capabilities = drop_capabilities, From befab968a4dd342da7008bac0233365b164f1ae0 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 20 Nov 2019 18:49:02 -0800 Subject: [PATCH 1287/2505] Make get_length() and get_buffer() methods from strbuf inline funcs --- src/lib/lwan-strbuf.h | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-strbuf.h b/src/lib/lwan-strbuf.h index a3b0ed0ff..c0f4de912 100644 --- a/src/lib/lwan-strbuf.h +++ b/src/lib/lwan-strbuf.h @@ -21,8 +21,8 @@ #pragma once #include -#include #include +#include struct lwan_strbuf { union { @@ -64,7 +64,6 @@ static inline bool lwan_strbuf_setz(struct lwan_strbuf *s1, const char *s2) return lwan_strbuf_set(s1, s2, strlen(s2)); } - bool lwan_strbuf_append_printf(struct lwan_strbuf *s, const char *fmt, ...) __attribute__((format(printf, 2, 3))); @@ -74,5 +73,12 @@ bool lwan_strbuf_printf(struct lwan_strbuf *s1, const char *fmt, ...) bool lwan_strbuf_grow_to(struct lwan_strbuf *s, size_t new_size); bool lwan_strbuf_grow_by(struct lwan_strbuf *s, size_t offset); -#define lwan_strbuf_get_length(s) (((struct lwan_strbuf *)(s))->used) -#define lwan_strbuf_get_buffer(s) (((struct lwan_strbuf *)(s))->value.buffer) +static inline size_t lwan_strbuf_get_length(const struct lwan_strbuf *s) +{ + return s->used; +} + +static inline char *lwan_strbuf_get_buffer(const struct lwan_strbuf *s) +{ + return s->value.buffer; +} From 04b73011362ef45901fee46f2de09bdcc2075c76 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 20 Nov 2019 18:37:21 -0800 Subject: [PATCH 1288/2505] Provide a bump-pointer allocator for coro_malloc() This will create "bump pointer arenas" that are at most 1024 bytes long. (This value has been chosen arbitrarily and might change in the future, but this should be transparent to coro_malloc() users.) If there's enough space in the arena after aligning the size to a sizeof(void*) boundary, allocation is super quick: just a pointer increment. If the requested size is greater than 1024, malloc() will be used instead, as usual; if there's not enough space in the current arena (and it fits in 1024 bytes), a new one will be created. Arenas are tracked with coro_defer, which is already efficient as they're stored in a inlinefirst array. Most uses of coro_malloc() are through coro_strdup(), which are to copy small strings. This is important in benchmarks such as "Fortunes" from TechEmpower Web Framework benchmarks, which Lwan is participating again. A few other places in Lwan will use coro_strdup(), including Lua support and the rewrite module. coro_malloc() is used by a few things, including the HTTP authorization module. Unfortunately it's not possible to use this for coro_malloc_full(), which assumes that the user-provided callback will be responsible for freeing the memory. BPA is disabled when building Lwan for fuzzing, and if Valgrind support is enabled, the new regions are notified through client requests (much like coroutine stacks are). ASAN support will be implemented later (it's simple to do, just have to do it). --- src/lib/lwan-coro.c | 80 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 3e00dd575..ce8ddfdbb 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -46,6 +46,8 @@ #define CORO_STACK_MIN (4 * SIGSTKSZ) #endif +#define CORO_BUMP_PTR_ALLOC_SIZE 1024 + static_assert(DEFAULT_BUFFER_SIZE < (CORO_STACK_MIN + SIGSTKSZ), "Request buffer fits inside coroutine stack"); @@ -76,6 +78,17 @@ struct coro { int yield_value; +#if !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) + /* The bump pointer allocator is disabled for fuzzing builds as that + * will make it more difficult to find overflows. + * See: https://blog.fuzzing-project.org/65-When-your-Memory-Allocator-hides-Security-Bugs.html + */ + struct { + void *ptr; + size_t remaining; + } bump_ptr_alloc; +#endif + #if !defined(NDEBUG) && defined(HAVE_VALGRIND) unsigned int vg_stack_id; #endif @@ -220,6 +233,9 @@ void coro_reset(struct coro *coro, coro_function_t func, void *data) coro_deferred_run(coro, 0); coro_defer_array_reset(&coro->defer); +#if !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) + coro->bump_ptr_alloc.remaining = 0; +#endif #if defined(__x86_64__) /* coro_entry_point() for x86-64 has 3 arguments, but RDX isn't @@ -373,21 +389,81 @@ void *coro_malloc_full(struct coro *coro, void (*destroy_func)(void *data)) { void *ptr = malloc(size); - if (LIKELY(ptr)) coro_defer(coro, destroy_func, ptr); return ptr; } -inline void *coro_malloc(struct coro *coro, size_t size) +#if !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) +#if !defined(NDEBUG) && defined(HAVE_VALGRIND) +static void perform_valgrind_malloc_freelike_block(void *ptr) +{ + VALGRIND_FREELIKE_BLOCK(ptr, 0); +} +#endif + +static inline void *coro_malloc_bump_ptr(struct coro *coro, size_t aligned_size) +{ + void *ptr = coro->bump_ptr_alloc.ptr; + + coro->bump_ptr_alloc.remaining -= aligned_size; + coro->bump_ptr_alloc.ptr = (char *)ptr + aligned_size; + + /* FIXME: Poison for ASAN too? + * https://github.com/google/sanitizers/wiki/AddressSanitizerManualPoisoning + */ + +#if !defined(NDEBUG) && defined(HAVE_VALGRIND) + VALGRIND_MALLOCLIKE_BLOCK(ptr, aligned_size, 0, 0); + coro_defer(coro, perform_valgrind_malloc_freelike_block, ptr); +#endif + + return ptr; +} +#endif + +void *coro_malloc(struct coro *coro, size_t size) { +#if !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) + /* The bump pointer allocator can't be in the generic coro_malloc_full() + * since destroy_funcs are supposed to free the memory. In this function, we + * guarantee that the destroy_func is free(), so that if an allocation goes + * through the bump pointer allocator, there's nothing that needs to be done + * to free the memory (other than freeing the whole bump pointer arena with + * the defer call below). */ + + const size_t aligned_size = + (size + sizeof(void *) - 1ul) & ~(sizeof(void *) - 1ul); + + if (LIKELY(coro->bump_ptr_alloc.remaining >= aligned_size)) + return coro_malloc_bump_ptr(coro, aligned_size); + + /* This will allocate as many "bump pointer arenas" as necessary; the + * old ones will be freed automatically as each allocations coro_defers + * the free() call. Just don't bother allocating an arena larger than + * CORO_BUMP_PTR_ALLOC.*/ + if (LIKELY(aligned_size <= CORO_BUMP_PTR_ALLOC_SIZE)) { + coro->bump_ptr_alloc.ptr = malloc(CORO_BUMP_PTR_ALLOC_SIZE); + if (UNLIKELY(!coro->bump_ptr_alloc.ptr)) + return NULL; + + coro->bump_ptr_alloc.remaining = CORO_BUMP_PTR_ALLOC_SIZE; + coro_defer(coro, free, coro->bump_ptr_alloc.ptr); + + /* Avoid checking if there's still space in the arena again. */ + return coro_malloc_bump_ptr(coro, aligned_size); + } +#endif + return coro_malloc_full(coro, size, free); } char *coro_strndup(struct coro *coro, const char *str, size_t max_len) { const size_t len = strnlen(str, max_len) + 1; + /* FIXME: Can we ask the bump ptr allocator to allocate without aligning + * this to 8-byte boundary? */ char *dup = coro_malloc(coro, len); if (LIKELY(dup)) { From e53ce1d9ee6ca9723df62ff205b5778aa7dbd667 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 20 Nov 2019 19:34:53 -0800 Subject: [PATCH 1289/2505] n_packets must be declared before the first `goto` --- src/lib/lwan-request.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 05bb4af11..64bd3bd6e 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -819,6 +819,7 @@ read_from_request_socket(struct lwan_request *request, { struct lwan_request_parser_helper *helper = request->helper; size_t total_read = 0; + int n_packets = 0; if (helper->next_request) { const size_t next_request_len = (size_t)(helper->next_request - buffer->value); @@ -837,7 +838,7 @@ read_from_request_socket(struct lwan_request *request, } } - for (int n_packets = 0;; n_packets++) { + for (;; n_packets++) { size_t to_read = (size_t)(buffer_size - total_read); if (UNLIKELY(to_read == 0)) From 3a7383121630eb01db822ffdfae40a509ab8f10c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 20 Nov 2019 19:46:37 -0800 Subject: [PATCH 1290/2505] Fix Fortunes benchmark when running with SQLite --- src/samples/techempower/techempower.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index 8f4cf3d80..8da61b247 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -378,13 +378,12 @@ int main(void) srand((unsigned int)time(NULL)); if (getenv("USE_MYSQL")) { - db_connection_params = (struct db_connection_params) { + db_connection_params = (struct db_connection_params){ .type = DB_CONN_MYSQL, .mysql.user = getenv("MYSQL_USER"), .mysql.password = getenv("MYSQL_PASS"), .mysql.hostname = getenv("MYSQL_HOST"), - .mysql.database = getenv("MYSQL_DB") - }; + .mysql.database = getenv("MYSQL_DB")}; if (!db_connection_params.mysql.user) lwan_status_critical("No MySQL user provided"); @@ -395,10 +394,10 @@ int main(void) if (!db_connection_params.mysql.database) lwan_status_critical("No MySQL database provided"); } else { - const char *pragmas[] = {"PRAGMA mmap_size=44040192", - "PRAGMA journal_mode=OFF", - "PRAGMA locking_mode=EXCLUSIVE", NULL}; - db_connection_params = (struct db_connection_params) { + static const char *pragmas[] = {"PRAGMA mmap_size=44040192", + "PRAGMA journal_mode=OFF", + "PRAGMA locking_mode=EXCLUSIVE", NULL}; + db_connection_params = (struct db_connection_params){ .type = DB_CONN_SQLITE, .sqlite.path = "techempower.db", .sqlite.pragmas = pragmas, From 7b4853cd16e5bd033ff5a62d1e154a23891be792 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 21 Nov 2019 18:43:55 -0800 Subject: [PATCH 1291/2505] Instrument coro_malloc() BPA with ASAN too This also cleans up how parts of the code is conditionally compiled for this kind of instrumentation for both ASAN and Valgrind, and enables the BPA even when building with fuzzing. --- src/lib/lwan-coro.c | 61 ++++++++++++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 20 deletions(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index ce8ddfdbb..420d27d5e 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -32,10 +32,22 @@ #include "lwan-array.h" #include "lwan-coro.h" -#ifdef HAVE_VALGRIND +#if !defined(NDEBUG) && defined(HAVE_VALGRIND) +#define INSTRUMENT_FOR_VALGRIND #include #endif +#if defined(__clang__) +# if defined(__has_feature) && __has_feature(address_sanitizer) +# #define __SANITIZE_ADDRESS +# endif +#endif +#if defined(__SANITIZE_ADDRESS__) +#define INSTRUMENT_FOR_ASAN +void __asan_poison_memory_region(void const volatile *addr, size_t size); +void __asan_unpoison_memory_region(void const volatile *addr, size_t size); +#endif + #if !defined(SIGSTKSZ) #define SIGSTKSZ 16384 #endif @@ -78,7 +90,6 @@ struct coro { int yield_value; -#if !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) /* The bump pointer allocator is disabled for fuzzing builds as that * will make it more difficult to find overflows. * See: https://blog.fuzzing-project.org/65-When-your-Memory-Allocator-hides-Security-Bugs.html @@ -87,9 +98,8 @@ struct coro { void *ptr; size_t remaining; } bump_ptr_alloc; -#endif -#if !defined(NDEBUG) && defined(HAVE_VALGRIND) +#if defined(INSTRUMENT_FOR_VALGRIND) unsigned int vg_stack_id; #endif @@ -233,9 +243,7 @@ void coro_reset(struct coro *coro, coro_function_t func, void *data) coro_deferred_run(coro, 0); coro_defer_array_reset(&coro->defer); -#if !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) coro->bump_ptr_alloc.remaining = 0; -#endif #if defined(__x86_64__) /* coro_entry_point() for x86-64 has 3 arguments, but RDX isn't @@ -301,7 +309,7 @@ coro_new(struct coro_switcher *switcher, coro_function_t function, void *data) coro->switcher = switcher; coro_reset(coro, function, data); -#if !defined(NDEBUG) && defined(HAVE_VALGRIND) +#if defined(INSTRUMENT_FOR_VALGRIND) unsigned char *stack = coro->stack; coro->vg_stack_id = VALGRIND_STACK_REGISTER(stack, stack + CORO_STACK_MIN); #endif @@ -344,7 +352,7 @@ inline int coro_yield(struct coro *coro, int value) void coro_free(struct coro *coro) { assert(coro); -#if !defined(NDEBUG) && defined(HAVE_VALGRIND) +#if defined(INSTRUMENT_FOR_VALGRIND) VALGRIND_STACK_DEREGISTER(coro->vg_stack_id); #endif coro_deferred_run(coro, 0); @@ -395,11 +403,16 @@ void *coro_malloc_full(struct coro *coro, return ptr; } -#if !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) -#if !defined(NDEBUG) && defined(HAVE_VALGRIND) -static void perform_valgrind_malloc_freelike_block(void *ptr) +#if defined(INSTRUMENT_FOR_VALGRIND) || defined(INSTRUMENT_FOR_ASAN) +static void instrument_bpa_free(void *ptr, void *size) { +#if defined(INSTRUMENT_FOR_VALGRIND) VALGRIND_FREELIKE_BLOCK(ptr, 0); +#endif + +#if defined(INSTRUMENT_FOR_ASAN) + __asan_poison_memory_region(ptr, (size_t)(uintptr_t)size); +#endif } #endif @@ -410,22 +423,22 @@ static inline void *coro_malloc_bump_ptr(struct coro *coro, size_t aligned_size) coro->bump_ptr_alloc.remaining -= aligned_size; coro->bump_ptr_alloc.ptr = (char *)ptr + aligned_size; - /* FIXME: Poison for ASAN too? - * https://github.com/google/sanitizers/wiki/AddressSanitizerManualPoisoning - */ - -#if !defined(NDEBUG) && defined(HAVE_VALGRIND) +#if defined(INSTRUMENT_FOR_VALGRIND) VALGRIND_MALLOCLIKE_BLOCK(ptr, aligned_size, 0, 0); - coro_defer(coro, perform_valgrind_malloc_freelike_block, ptr); +#endif +#if defined(INSTRUMENT_FOR_ASAN) + __asan_unpoison_memory_region(ptr, aligned_size); +#endif +#if defined(INSTRUMENT_FOR_VALGRIND) || defined(INSTRUMENT_FOR_ASAN) + coro_defer2(coro, instrument_bpa_free, ptr, + (void *)(uintptr_t)aligned_size); #endif return ptr; } -#endif void *coro_malloc(struct coro *coro, size_t size) { -#if !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) /* The bump pointer allocator can't be in the generic coro_malloc_full() * since destroy_funcs are supposed to free the memory. In this function, we * guarantee that the destroy_func is free(), so that if an allocation goes @@ -448,13 +461,21 @@ void *coro_malloc(struct coro *coro, size_t size) if (UNLIKELY(!coro->bump_ptr_alloc.ptr)) return NULL; +#if defined(INSTRUMENT_FOR_ASAN) + /* There's apparently no way to poison the whole BPA arena with Valgrind + * like it's possible with ASAN (short of registering the BPA arena as + * a mempool, but that also doesn't really map 1:1 to how this works). + */ + __asan_poison_memory_region(coro->bump_ptr_alloc.ptr, + CORO_BUMP_PTR_ALLOC_SIZE); +#endif + coro->bump_ptr_alloc.remaining = CORO_BUMP_PTR_ALLOC_SIZE; coro_defer(coro, free, coro->bump_ptr_alloc.ptr); /* Avoid checking if there's still space in the arena again. */ return coro_malloc_bump_ptr(coro, aligned_size); } -#endif return coro_malloc_full(coro, size, free); } From c0503624ff0937e3ad00caf8ebe146e3c2ffca92 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 21 Nov 2019 18:59:34 -0800 Subject: [PATCH 1292/2505] Poison next/prev pointers when removing items from doubly linked lists (Only in debug mode.) --- src/lib/list.c | 5 +++++ src/lib/list.h | 21 ++++++++++++++++----- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/lib/list.c b/src/lib/list.c index ae27455b7..03827990e 100644 --- a/src/lib/list.c +++ b/src/lib/list.c @@ -51,6 +51,11 @@ struct list_node *list_check_node(const struct list_node *node, const struct list_node *p, *n; unsigned int count = 0; + if (node->prev == LIST_POISON1 || node->next == LIST_POISON1) + return corrupt(abortstr, node, node, 0); + if (node->prev == LIST_POISON2 || node->next == LIST_POISON2) + return corrupt(abortstr, node, node, 0); + for (p = node, n = node->next; n != node; p = n, n = n->next) { count++; if (n->prev != p) diff --git a/src/lib/list.h b/src/lib/list.h index 76f7999f3..9ccee5149 100644 --- a/src/lib/list.h +++ b/src/lib/list.h @@ -32,6 +32,16 @@ #include #include +#ifndef NDEBUG +# if __SIZEOF_SIZE_T__ == 8 +# define LIST_POISON1 ((void *)0xdeadbeefdeadbeef) +# define LIST_POISON2 ((void *)0xbebacafebebacafe) +# else +# define LIST_POISON1 ((void *)0xdeadbeef) +# define LIST_POISON2 ((void *)0xbebacafe) +# endif +#endif + /** * check_type - issue a warning or build failure if type is not correct. * @expr: the expression whose type we should check (not evaluated). @@ -257,7 +267,7 @@ struct list_head *list_check(const struct list_head *h, const char *abortstr); struct list_node *list_check_node(const struct list_node *n, const char *abortstr); -#ifdef _LIST_DEBUG +#ifndef NDEBUG #define list_debug(h) list_check((h), __func__) #define list_debug_node(n) list_check_node((n), __func__) #else @@ -356,9 +366,10 @@ static inline void list_del(struct list_node *n) (void)list_debug_node(n); n->next->prev = n->prev; n->prev->next = n->next; -#ifdef _LIST_DEBUG +#ifndef NDEBUG /* Catch use-after-del. */ - n->next = n->prev = NULL; + n->next = LIST_POISON1; + n->prev = LIST_POISON2; #endif } @@ -378,7 +389,7 @@ static inline void list_del(struct list_node *n) */ static inline void list_del_from(struct list_head *h, struct list_node *n) { -#ifdef _LIST_DEBUG +#ifndef NDEBUG { /* Thorough check: make sure it was in list! */ struct list_node *i; @@ -387,7 +398,7 @@ static inline void list_del_from(struct list_head *h, struct list_node *n) } #else (void)h; -#endif /* _LIST_DEBUG */ +#endif /* NDEBUG */ /* Quick test that catches a surprising number of bugs. */ assert(!list_empty(h)); From ae4b6c1e74aa67313089038157a30007a6bbbeff Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 21 Nov 2019 19:16:32 -0800 Subject: [PATCH 1293/2505] Fix release build after c0503624ff --- src/lib/list.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/list.c b/src/lib/list.c index 03827990e..b0161aa96 100644 --- a/src/lib/list.c +++ b/src/lib/list.c @@ -32,6 +32,7 @@ #include "list.h" +#ifndef NDEBUG static void *corrupt(const char *abortstr, const struct list_node *head, const struct list_node *node, @@ -44,10 +45,12 @@ static void *corrupt(const char *abortstr, } return NULL; } +#endif struct list_node *list_check_node(const struct list_node *node, const char *abortstr) { +#ifndef NDEBUG const struct list_node *p, *n; unsigned int count = 0; @@ -64,6 +67,7 @@ struct list_node *list_check_node(const struct list_node *node, /* Check prev on head node. */ if (node->prev != p) return corrupt(abortstr, node, node, 0); +#endif return (struct list_node *)node; } From d6b402bd1bfdd01f98714a8bd8165f68330bcfc6 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 22 Nov 2019 07:49:00 -0800 Subject: [PATCH 1294/2505] Enable coro_malloc() BPA ASAN instrumentation on Clang too Typos like these are what happens when you write patches at night while tired from work. :) --- src/lib/lwan-coro.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 420d27d5e..9105f8464 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -39,7 +39,7 @@ #if defined(__clang__) # if defined(__has_feature) && __has_feature(address_sanitizer) -# #define __SANITIZE_ADDRESS +# #define __SANITIZE_ADDRESS__ # endif #endif #if defined(__SANITIZE_ADDRESS__) From 8b2fdb642b7f5a24877409dad3eb7b316b825691 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 22 Nov 2019 18:56:07 -0800 Subject: [PATCH 1295/2505] Fix build with C++ compiler Necessary to build the fuzzers. --- src/lib/list.h | 8 ++++---- src/lib/lwan-template.c | 18 ++++++++++++------ 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/lib/list.h b/src/lib/list.h index 9ccee5149..48abaee77 100644 --- a/src/lib/list.h +++ b/src/lib/list.h @@ -34,11 +34,11 @@ #ifndef NDEBUG # if __SIZEOF_SIZE_T__ == 8 -# define LIST_POISON1 ((void *)0xdeadbeefdeadbeef) -# define LIST_POISON2 ((void *)0xbebacafebebacafe) +# define LIST_POISON1 ((struct list_node *)0xdeadbeefdeadbeef) +# define LIST_POISON2 ((struct list_node *)0xbebacafebebacafe) # else -# define LIST_POISON1 ((void *)0xdeadbeef) -# define LIST_POISON2 ((void *)0xbebacafe) +# define LIST_POISON1 ((struct list_node *)0xdeadbeef) +# define LIST_POISON2 ((struct list_node *)0xbebacafe) # endif #endif diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index 91553db89..81c817bab 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -1138,15 +1138,21 @@ static bool parser_shutdown(struct parser *parser, struct lexeme *lexeme) success = false; } - symtab_pop(parser); - if (parser->symtab) { + if (!parser->symtab) { lwan_status_error( - "Parser error: Symbol table not empty when finishing parser"); + "Parser error: No symbol table was found when finishing the parser"); + success = false; + } else { + symtab_pop(parser); + if (parser->symtab) { + lwan_status_error( + "Parser error: Symbol table not empty when finishing parser"); - while (parser->symtab) - symtab_pop(parser); + while (parser->symtab) + symtab_pop(parser); - success = false; + success = false; + } } if (parser->flags & FLAGS_NEGATE) { From 1708d522d61cb65e48aba2a333786b776dc7926e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 24 Nov 2019 12:36:14 -0800 Subject: [PATCH 1296/2505] Fix build with Clang Fixes https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=19078 --- src/lib/lwan-coro.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 9105f8464..5913c2c68 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -39,7 +39,7 @@ #if defined(__clang__) # if defined(__has_feature) && __has_feature(address_sanitizer) -# #define __SANITIZE_ADDRESS__ +# define __SANITIZE_ADDRESS__ # endif #endif #if defined(__SANITIZE_ADDRESS__) From 342a10ed6b8f7999b26f2e73113a57725f749547 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 25 Nov 2019 18:04:24 -0800 Subject: [PATCH 1297/2505] Poison list nodes with real, valid pointers --- src/lib/list.c | 26 +++++++++++++++++++++++--- src/lib/list.h | 20 ++++++-------------- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/lib/list.c b/src/lib/list.c index b0161aa96..bb888af91 100644 --- a/src/lib/list.c +++ b/src/lib/list.c @@ -33,6 +33,28 @@ #include "list.h" #ifndef NDEBUG +static struct list_node poison_node_1 = {.next = (void *)0xdeadbeef, + .prev = (void *)0xbebacafe}; +static struct list_node poison_node_2 = {.next = (void *)0xbebacafe, + .prev = (void *)0xdeadbeef}; + +void list_poison_node(struct list_node *node) +{ + node->prev = &poison_node_1; + node->next = &poison_node_2; +} + +static bool is_poisoned(const struct list_node *node) +{ + assert(poison_node_1.next == poison_node_2.prev); + assert(poison_node_1.prev == poison_node_2.next); + assert(poison_node_1.next == (void *)0xdeadbeef); + assert(poison_node_1.prev == (void *)0xbebacafe); + + return (node->prev == &poison_node_1 || node->prev == &poison_node_2) || + (node->next == &poison_node_1 || node->next == &poison_node_2); +} + static void *corrupt(const char *abortstr, const struct list_node *head, const struct list_node *node, @@ -54,9 +76,7 @@ struct list_node *list_check_node(const struct list_node *node, const struct list_node *p, *n; unsigned int count = 0; - if (node->prev == LIST_POISON1 || node->next == LIST_POISON1) - return corrupt(abortstr, node, node, 0); - if (node->prev == LIST_POISON2 || node->next == LIST_POISON2) + if (is_poisoned(node)) return corrupt(abortstr, node, node, 0); for (p = node, n = node->next; n != node; p = n, n = n->next) { diff --git a/src/lib/list.h b/src/lib/list.h index 48abaee77..1b5caeceb 100644 --- a/src/lib/list.h +++ b/src/lib/list.h @@ -32,16 +32,6 @@ #include #include -#ifndef NDEBUG -# if __SIZEOF_SIZE_T__ == 8 -# define LIST_POISON1 ((struct list_node *)0xdeadbeefdeadbeef) -# define LIST_POISON2 ((struct list_node *)0xbebacafebebacafe) -# else -# define LIST_POISON1 ((struct list_node *)0xdeadbeef) -# define LIST_POISON2 ((struct list_node *)0xbebacafe) -# endif -#endif - /** * check_type - issue a warning or build failure if type is not correct. * @expr: the expression whose type we should check (not evaluated). @@ -268,9 +258,13 @@ struct list_node *list_check_node(const struct list_node *n, const char *abortstr); #ifndef NDEBUG +void list_poison_node(struct list_node *node); + +#define list_poison(n) list_poison_node(n) #define list_debug(h) list_check((h), __func__) #define list_debug_node(n) list_check_node((n), __func__) #else +#define list_poison(n) #define list_debug(h) (h) #define list_debug_node(n) (n) #endif @@ -366,11 +360,9 @@ static inline void list_del(struct list_node *n) (void)list_debug_node(n); n->next->prev = n->prev; n->prev->next = n->next; -#ifndef NDEBUG + /* Catch use-after-del. */ - n->next = LIST_POISON1; - n->prev = LIST_POISON2; -#endif + list_poison(n); } /** From 9a21fb8d6fcdde2939f67cb12c2f697cddcedf02 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 25 Nov 2019 21:05:17 -0800 Subject: [PATCH 1298/2505] Optimize template HTML escaping using SSE & minimizing writes --- src/lib/lwan-template.c | 73 ++++++++++++++++++++++++++++++++--------- 1 file changed, 58 insertions(+), 15 deletions(-) diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index 81c817bab..908e336ef 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -911,8 +911,53 @@ void lwan_append_str_to_strbuf(struct lwan_strbuf *buf, void *ptr) lwan_strbuf_append_strz(buf, str); } +#if __x86_64__ +#include +#endif + +static ALWAYS_INLINE int escaped_index(char ch) +{ +#if __x86_64__ + /* FIXME: instead of calling escaped_index() for each byte that needs to be + * escaped, use SIMD to leap through input string until an escapable character + * is found. */ + const __m128i ch_mask = _mm_set1_epi8(ch); + const __m128i escapable = + _mm_set_epi8(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '<', '>', '&', '"', '\'', '/'); + + return __builtin_ffs(_mm_movemask_epi8(_mm_cmpeq_epi8(ch_mask, escapable))); +#else + switch (ch) { + default: + return 0; + case '/': + return 1; + case '\'': + return 2; + case '"': + return 3; + case '&': + return 4; + case '>': + return 5; + case '<': + return 6; + } +#endif +} + void lwan_append_str_escaped_to_strbuf(struct lwan_strbuf *buf, void *ptr) { + static const struct lwan_value escaped[] = { + {}, + { /* / */ "/", 6 }, + { /* ' */ "'", 6 }, + { /* " */ """, 6 }, + { /* & */ "&", 5 }, + { /* > */ ">", 4 }, + { /* < */ "<", 4 }, + }; + if (UNLIKELY(!ptr)) return; @@ -920,22 +965,20 @@ void lwan_append_str_escaped_to_strbuf(struct lwan_strbuf *buf, void *ptr) if (UNLIKELY(!str)) return; - for (const char *p = str; *p; p++) { - if (*p == '<') - lwan_strbuf_append_str(buf, "<", 4); - else if (*p == '>') - lwan_strbuf_append_str(buf, ">", 4); - else if (*p == '&') - lwan_strbuf_append_str(buf, "&", 5); - else if (*p == '"') - lwan_strbuf_append_str(buf, """, 6); - else if (*p == '\'') - lwan_strbuf_append_str(buf, "'", 6); - else if (*p == '/') - lwan_strbuf_append_str(buf, "/", 6); - else - lwan_strbuf_append_char(buf, *p); + const char *last, *p; + for (last = p = str; *p; p++) { + int index = escaped_index(*p); + + if (index) { + lwan_strbuf_append_str(buf, last, (size_t)(p - last)); + last = p + 1; + + lwan_strbuf_append_str(buf, escaped[index].value, escaped[index].len); + } } + + if (last != p) + lwan_strbuf_append_str(buf, last, (size_t)(p - last)); } bool lwan_tpl_str_is_empty(void *ptr) From 45d93cc566c8699fee798af9a8f45d783c214729 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 25 Nov 2019 21:26:37 -0800 Subject: [PATCH 1299/2505] Do not pack bits in JSON descriptor structs There's no need to do this if not on a microcontroller (the main use case for this JSON library), so free up extra cycles for something more useful. --- src/samples/techempower/json.h | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/src/samples/techempower/json.h b/src/samples/techempower/json.h index d5dbe6b13..e979418dc 100644 --- a/src/samples/techempower/json.h +++ b/src/samples/techempower/json.h @@ -55,26 +55,11 @@ enum json_tokens { struct json_obj_descr { const char *field_name; - /* Alignment can be 1, 2, 4, or 8. The macros to create - * a struct json_obj_descr will store the alignment's - * power of 2 in order to keep this value in the 0-3 range - * and thus use only 2 bits. - */ - uint32_t align_shift : 2; - - /* 127 characters is more than enough for a field name. */ - uint32_t field_name_len : 7; - - /* Valid values here (enum json_tokens): JSON_TOK_STRING, - * JSON_TOK_NUMBER, JSON_TOK_TRUE, JSON_TOK_FALSE, - * JSON_TOK_OBJECT_START, JSON_TOK_LIST_START. (All others - * ignored.) Maximum value is '}' (125), so this has to be 7 bits - * long. - */ - uint32_t type : 7; - /* 65535 bytes is more than enough for many JSON payloads. */ - uint32_t offset : 16; + uint32_t align_shift; + uint32_t field_name_len; + uint32_t type; + uint32_t offset; union { struct { From 09512b943ce020556ddada244e2fd97f12679298 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 25 Nov 2019 22:26:43 -0800 Subject: [PATCH 1300/2505] Prioritize Brotli over Zstd to compress MIME-Type table The default mime.types is ~640 bytes shorter with Brotli than with Zstd. --- src/bin/tools/CMakeLists.txt | 8 ++++---- src/bin/tools/mimegen.c | 38 ++++++++++++++++++------------------ src/lib/lwan-tables.c | 20 +++++++++---------- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/bin/tools/CMakeLists.txt b/src/bin/tools/CMakeLists.txt index 5b6ffcd26..275de1876 100644 --- a/src/bin/tools/CMakeLists.txt +++ b/src/bin/tools/CMakeLists.txt @@ -8,12 +8,12 @@ else () ${CMAKE_SOURCE_DIR}/src/lib/murmur3.c ${CMAKE_SOURCE_DIR}/src/lib/missing.c ) - if (HAVE_ZSTD) - message(STATUS "Using Zstd for mimegen") - target_link_libraries(mimegen ${ZSTD_LDFLAGS}) - elseif (HAVE_BROTLI) + if (HAVE_BROTLI) message(STATUS "Using Brotli for mimegen") target_link_libraries(mimegen ${BROTLI_LDFLAGS}) + elseif (HAVE_ZSTD) + message(STATUS "Using Zstd for mimegen") + target_link_libraries(mimegen ${ZSTD_LDFLAGS}) else () find_library(ZOPFLI_LIBRARY NAMES zopfli PATHS /usr/lib /usr/local/lib) if (ZOPFLI_LIBRARY) diff --git a/src/bin/tools/mimegen.c b/src/bin/tools/mimegen.c index 0af81efbe..7f8650fc9 100644 --- a/src/bin/tools/mimegen.c +++ b/src/bin/tools/mimegen.c @@ -25,10 +25,10 @@ #include #include -#if defined(HAVE_ZSTD) -#include -#elif defined(HAVE_BROTLI) +#if defined(HAVE_BROTLI) #include +#elif defined(HAVE_ZSTD) +#include #elif defined(HAVE_ZOPFLI) #include #else @@ -111,8 +111,8 @@ static char *compress_output(const struct output *output, size_t *outlen) { char *compressed; -#if defined(HAVE_ZSTD) - *outlen = ZSTD_compressBound(output->used); +#if defined(HAVE_BROTLI) + *outlen = BrotliEncoderMaxCompressedSize(output->used); compressed = malloc(*outlen); if (!compressed) { @@ -120,14 +120,15 @@ static char *compress_output(const struct output *output, size_t *outlen) exit(1); } - *outlen = ZSTD_compress(compressed, *outlen, output->ptr, output->used, - ZSTD_maxCLevel()); - if (ZSTD_isError(*outlen)) { - fprintf(stderr, "Could not compress mime type table with ZSTD\n"); + if (BrotliEncoderCompress(BROTLI_MAX_QUALITY, BROTLI_MAX_WINDOW_BITS, + BROTLI_MODE_TEXT, output->used, + (const unsigned char *)output->ptr, outlen, + (unsigned char *)compressed) != BROTLI_TRUE) { + fprintf(stderr, "Could not compress mime type table with Brotli\n"); exit(1); } -#elif defined(HAVE_BROTLI) - *outlen = BrotliEncoderMaxCompressedSize(output->used); +#elif defined(HAVE_ZSTD) + *outlen = ZSTD_compressBound(output->used); compressed = malloc(*outlen); if (!compressed) { @@ -135,11 +136,10 @@ static char *compress_output(const struct output *output, size_t *outlen) exit(1); } - if (BrotliEncoderCompress(BROTLI_MAX_QUALITY, BROTLI_MAX_WINDOW_BITS, - BROTLI_MODE_TEXT, output->used, - (const unsigned char *)output->ptr, outlen, - (unsigned char *)compressed) != BROTLI_TRUE) { - fprintf(stderr, "Could not compress mime type table with Brotli\n"); + *outlen = ZSTD_compress(compressed, *outlen, output->ptr, output->used, + ZSTD_maxCLevel()); + if (ZSTD_isError(*outlen)) { + fprintf(stderr, "Could not compress mime type table with ZSTD\n"); exit(1); } #elif defined(HAVE_ZOPFLI) @@ -316,10 +316,10 @@ int main(int argc, char *argv[]) } /* Print output. */ -#if defined(HAVE_ZSTD) - printf("/* Compressed with zstd */\n"); -#elif defined(HAVE_BROTLI) +#if defined(HAVE_BROTLI) printf("/* Compressed with brotli */\n"); +#elif defined(HAVE_ZSTD) + printf("/* Compressed with zstd */\n"); #elif defined(HAVE_ZOPFLI) printf("/* Compressed with zopfli (deflate) */\n"); #else diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index a6003ee8b..11cd6a74f 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -22,10 +22,10 @@ #include #include -#if defined(HAVE_ZSTD) -#include -#elif defined(HAVE_BROTLI) +#if defined(HAVE_BROTLI) #include +#elif defined(HAVE_ZSTD) +#include #else #include #endif @@ -46,13 +46,7 @@ void lwan_tables_init(void) lwan_status_debug("Uncompressing MIME type table: %u->%u bytes, %d entries", MIME_COMPRESSED_LEN, MIME_UNCOMPRESSED_LEN, MIME_ENTRIES); -#if defined(HAVE_ZSTD) - size_t uncompressed_length = - ZSTD_decompress(uncompressed_mime_entries, MIME_UNCOMPRESSED_LEN, - mime_entries_compressed, MIME_COMPRESSED_LEN); - if (ZSTD_isError(uncompressed_length)) - lwan_status_critical("Error while uncompressing table with Zstd"); -#elif defined(HAVE_BROTLI) +#if defined(HAVE_BROTLI) size_t uncompressed_length = MIME_UNCOMPRESSED_LEN; BrotliDecoderResult ret; @@ -61,6 +55,12 @@ void lwan_tables_init(void) uncompressed_mime_entries); if (ret != BROTLI_DECODER_RESULT_SUCCESS) lwan_status_critical("Error while uncompressing table with Brotli"); +#elif defined(HAVE_ZSTD) + size_t uncompressed_length = + ZSTD_decompress(uncompressed_mime_entries, MIME_UNCOMPRESSED_LEN, + mime_entries_compressed, MIME_COMPRESSED_LEN); + if (ZSTD_isError(uncompressed_length)) + lwan_status_critical("Error while uncompressing table with Zstd"); #else uLongf uncompressed_length = MIME_UNCOMPRESSED_LEN; int ret = From 910ddad1cf5c95bd57aacdab712bd21d207a3811 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 26 Nov 2019 19:30:48 -0800 Subject: [PATCH 1301/2505] Use sqlite3_prepare_v2() instead of the legacy sqlite3_prepare() --- src/samples/techempower/database.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/samples/techempower/database.c b/src/samples/techempower/database.c index 8d1518bf8..f8363196b 100644 --- a/src/samples/techempower/database.c +++ b/src/samples/techempower/database.c @@ -316,8 +316,8 @@ db_prepare_sqlite(const struct db *db, const char *sql, const size_t sql_len) if (!stmt_sqlite) return NULL; - int ret = sqlite3_prepare(db_sqlite->sqlite, sql, (int)sql_len, - &stmt_sqlite->sqlite, NULL); + int ret = sqlite3_prepare_v2(db_sqlite->sqlite, sql, (int)sql_len, + &stmt_sqlite->sqlite, NULL); if (ret != SQLITE_OK) { free(stmt_sqlite); return NULL; From 2da69b3f56a783579f6040a4b24d68bd5913e775 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 29 Nov 2019 09:30:43 -0800 Subject: [PATCH 1302/2505] Restore "capacity" field in strbuf Without this field, when the request coroutine resets the response string buffer, this will trigger a reallocation next time the string buffer is used, even though the allocation is (probably) big enough to contain the new data that will be stored in that strbuf. This is in preparation for the TechEmpower Web Frameworks benchmark, more specifically for the JSON and plaintext benchmarks which send the same pre-prepared response over and over again. --- src/lib/lwan-strbuf.c | 12 +++++++++--- src/lib/lwan-strbuf.h | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-strbuf.c b/src/lib/lwan-strbuf.c index 99240829b..507538afc 100644 --- a/src/lib/lwan-strbuf.c +++ b/src/lib/lwan-strbuf.c @@ -60,11 +60,12 @@ static bool grow_buffer_if_needed(struct lwan_strbuf *s, size_t size) s->flags &= ~STATIC; s->value.buffer = buffer; + s->capacity = aligned_size; return true; } - if (UNLIKELY(!s->used || lwan_nextpow2(s->used) < size)) { + if (UNLIKELY(s->capacity < size)) { const size_t aligned_size = align_size(size + 1); if (UNLIKELY(!aligned_size)) return false; @@ -72,7 +73,9 @@ static bool grow_buffer_if_needed(struct lwan_strbuf *s, size_t size) char *buffer = realloc(s->value.buffer, aligned_size); if (UNLIKELY(!buffer)) return false; + s->value.buffer = buffer; + s->capacity = aligned_size; } return true; @@ -86,6 +89,7 @@ bool lwan_strbuf_init_with_size(struct lwan_strbuf *s, size_t size) if (!size) { *s = (struct lwan_strbuf) { .used = 0, + .capacity = 0, .value.static_buffer = "", .flags = STATIC, }; @@ -137,7 +141,8 @@ ALWAYS_INLINE struct lwan_strbuf *lwan_strbuf_new_static(const char *str, *s = (struct lwan_strbuf) { .flags = STATIC | DYNAMICALLY_ALLOCATED, .value.static_buffer = str, - .used = size + .used = size, + .capacity = size, }; return s; @@ -182,7 +187,7 @@ bool lwan_strbuf_set_static(struct lwan_strbuf *s1, const char *s2, size_t sz) free(s1->value.buffer); s1->value.static_buffer = s2; - s1->used = sz; + s1->used = s1->capacity = sz; s1->flags |= STATIC; return true; @@ -261,6 +266,7 @@ void lwan_strbuf_reset(struct lwan_strbuf *s) { if (s->flags & STATIC) { s->value.static_buffer = ""; + s->capacity = 0; } else { s->value.buffer[0] = '\0'; } diff --git a/src/lib/lwan-strbuf.h b/src/lib/lwan-strbuf.h index c0f4de912..d68ff2cd2 100644 --- a/src/lib/lwan-strbuf.h +++ b/src/lib/lwan-strbuf.h @@ -29,7 +29,7 @@ struct lwan_strbuf { char *buffer; const char *static_buffer; } value; - size_t used; + size_t capacity, used; unsigned int flags; }; From 969e28b9c66adaeb455a0f926119cb318bfda220 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 26 Nov 2019 19:31:22 -0800 Subject: [PATCH 1303/2505] Pre-resize response strbuf to 128 bytes while writing JSON response Might not work as intended, actually; may want to bring back the capacity size_t to the strbuf struct to avoid reallocating these buffers every time a request is handled by the connection coro. --- src/samples/techempower/techempower.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index 8da61b247..57e3982bf 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -160,6 +160,8 @@ static enum lwan_http_status json_response(struct lwan_response *response, size_t descr_len, const void *data) { + lwan_strbuf_grow_to(response->buffer, 128); + if (json_obj_encode(descr, descr_len, data, append_to_strbuf, response->buffer) < 0) return HTTP_INTERNAL_ERROR; From 2a5d4622281598cf4ac83ffd51e8468a479131b9 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 29 Nov 2019 10:03:01 -0800 Subject: [PATCH 1304/2505] Avoid reallocating buffers while applying fortune template --- src/samples/techempower/techempower.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index 57e3982bf..883af1484 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -355,6 +355,8 @@ LWAN_HANDLER(fortunes) { struct Fortune fortune; + lwan_strbuf_grow_to(response->buffer, 1500); + if (UNLIKELY(!lwan_tpl_apply_with_buffer(fortune_tpl, response->buffer, &fortune))) return HTTP_INTERNAL_ERROR; From 9a04696079577e5f4a59182e395ac08745b7f9c4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 29 Nov 2019 18:05:59 -0800 Subject: [PATCH 1305/2505] Build with -fauto-tune using profile obtained with AutoFDO Events were obtained with `ocperf.py` from pmu-tools: ocperf.py record -b -e br_inst_retired.near_taken:pp \ -- ./src/bin/testrunner/testrunner Using a "RelWithDebugInfo" build. lwan.gcov was then generated using create_gcov from AutoFDO: create_gcov -binary=./src/bin/testrunner/testrunner \ -profile=perf.data -gcov=lwan.gcov -gcov_version=1 This improves performance slightly (from 3% to 5%). --- CMakeLists.txt | 7 +++++++ src/gcda/lwan.gcov | Bin 0 -> 23828 bytes 2 files changed, 7 insertions(+) create mode 100644 src/gcda/lwan.gcov diff --git a/CMakeLists.txt b/CMakeLists.txt index 452e4c605..7863c87d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -187,6 +187,13 @@ else () message(STATUS "Valgrind headers not found -- disabling valgrind support") endif() +if (EXISTS ${CMAKE_SOURCE_DIR}/src/gcda/lwan.gcov) + enable_c_flag_if_avail(-fauto-profile=${CMAKE_SOURCE_DIR}/src/gcda/lwan.gcov + C_FLAGS_REL HAVE_AUTO_PROFILE) + if (HAVE_AUTO_PROFILE) + message(STATUS "Using AutoFDO profile") + endif () +endif () enable_c_flag_if_avail(-mtune=native C_FLAGS_REL HAVE_MTUNE_NATIVE) enable_c_flag_if_avail(-march=native C_FLAGS_REL HAVE_MARCH_NATIVE) diff --git a/src/gcda/lwan.gcov b/src/gcda/lwan.gcov new file mode 100644 index 0000000000000000000000000000000000000000..58526d83198931ff4deca0c39337b624ccf49ed5 GIT binary patch literal 23828 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 W{p&&>&GaXP)?A?1UkUBC)c*qLPBei4 literal 0 HcmV?d00001 From abb09dc026e18b10f7206ebbf744765b9691c0bc Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 4 Dec 2019 18:49:47 -0800 Subject: [PATCH 1306/2505] Do not count evicted cache items in release builds --- src/lib/lwan-cache.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-cache.c b/src/lib/lwan-cache.c index 94424a1f2..dbef64167 100644 --- a/src/lib/lwan-cache.c +++ b/src/lib/lwan-cache.c @@ -269,8 +269,10 @@ static bool cache_pruner_job(void *data) struct cache_entry *node, *next; struct timespec now; bool shutting_down = cache->flags & SHUTTING_DOWN; - unsigned evicted = 0; struct list_head queue; +#ifndef NDEBUG + unsigned int evicted = 0; +#endif if (UNLIKELY(pthread_rwlock_trywrlock(&cache->queue.lock) == EBUSY)) return false; @@ -326,7 +328,9 @@ static bool cache_pruner_job(void *data) cache->cb.destroy_entry(node, cache->cb.context); } +#ifndef NDEBUG evicted++; +#endif } /* If local queue has been entirely processed, there's no need to From 50dce90fc6d42763bfafb90ad16c49273fcb2a7a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 4 Dec 2019 18:50:43 -0800 Subject: [PATCH 1307/2505] Define a LWAN_SECTION_FOREACH() macro to iterate over executable sections --- src/lib/lwan-lua.c | 5 +---- src/lib/lwan-private.h | 26 ++++++++++++++++++++------ src/lib/lwan.c | 9 ++------- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index 523dec717..701a0d293 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -247,13 +247,10 @@ __attribute__((constructor)) __attribute__((no_sanitize_address)) static void register_lua_methods(void) { - extern const struct lwan_lua_method_info SECTION_START(lwan_lua_method); - extern const struct lwan_lua_method_info SECTION_END(lwan_lua_method); const struct lwan_lua_method_info *info; luaL_reg *r; - for (info = __start_lwan_lua_method; info < __stop_lwan_lua_method; - info++) { + LWAN_SECTION_FOREACH(lwan_lua_method, info) { r = lwan_lua_method_array_append(&lua_methods); if (!r) { lwan_status_critical("Could not register Lua method `%s`", diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 462f43b74..069bbff16 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -110,15 +110,29 @@ const char *lwan_lua_state_last_error(lua_State *L); #endif #ifdef __APPLE__ -# define SECTION_START(name_) \ - __start_ ## name_[] __asm("section$start$__DATA$" #name_) -# define SECTION_END(name_) \ - __stop_ ## name_[] __asm("section$end$__DATA$" #name_) +#define SECTION_START(name_) __start_##name_[] __asm("section$start$__DATA$" #name_) +#define SECTION_END(name_) __stop_##name_[] __asm("section$end$__DATA$" #name_) #else -# define SECTION_START(name_) __start_ ## name_[] -# define SECTION_END(name_) __stop_ ## name_[] +#define SECTION_START(name_) __start_##name_[] +#define SECTION_END(name_) __stop_##name_[] #endif +#define SECTION_START_SYMBOL(section_name_, iter_) \ + ({ \ + extern const typeof(*iter_) SECTION_START(section_name_); \ + __start_##section_name_; \ + }) + +#define SECTION_STOP_SYMBOL(section_name_, iter_) \ + ({ \ + extern const typeof(*iter_) SECTION_END(section_name_); \ + __stop_##section_name_; \ + }) + +#define LWAN_SECTION_FOREACH(section_name_, iter_) \ + for (iter_ = SECTION_START_SYMBOL(section_name_, iter_); \ + iter_ < SECTION_STOP_SYMBOL(section_name_, iter_); (iter_)++) + extern clockid_t monotonic_clock_id; static inline void * diff --git a/src/lib/lwan.c b/src/lib/lwan.c index d4307f882..fddefc350 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -70,12 +70,9 @@ LWAN_HANDLER(brew_coffee) __attribute__((no_sanitize_address)) static void *find_handler(const char *name) { - extern const struct lwan_handler_info SECTION_START(lwan_handler); - extern const struct lwan_handler_info SECTION_END(lwan_handler); const struct lwan_handler_info *handler; - for (handler = __start_lwan_handler; handler < __stop_lwan_handler; - handler++) { + LWAN_SECTION_FOREACH(lwan_handler, handler) { if (streq(handler->name, name)) return handler->handler; } @@ -86,11 +83,9 @@ static void *find_handler(const char *name) __attribute__((no_sanitize_address)) static const struct lwan_module *find_module(const char *name) { - extern const struct lwan_module_info SECTION_START(lwan_module); - extern const struct lwan_module_info SECTION_END(lwan_module); const struct lwan_module_info *module; - for (module = __start_lwan_module; module < __stop_lwan_module; module++) { + LWAN_SECTION_FOREACH(lwan_module, module) { if (streq(module->name, name)) return module->module; } From 179d78e2c7e851cd10ee5936f6abe6bb92658a05 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 4 Dec 2019 18:53:45 -0800 Subject: [PATCH 1308/2505] Simplify how websocket handshake is fuzzed --- src/lib/lwan-request.c | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 64bd3bd6e..e93f92e6d 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1662,16 +1662,6 @@ static int useless_coro_for_fuzzing(struct coro *c __attribute__((unused)), return 0; } -static char *fuzz_websocket_handshake(struct lwan_request *r) -{ - char *encoded; - - if (prepare_websocket_handshake(r, &encoded) == HTTP_SWITCHING_PROTOCOLS) - return encoded; - - return NULL; -} - __attribute__((used)) int fuzz_parse_http_request(const uint8_t *data, size_t length) { @@ -1717,6 +1707,7 @@ __attribute__((used)) int fuzz_parse_http_request(const uint8_t *data, if (parse_http_request(&request) == HTTP_OK) { off_t trash1; time_t trash2; + char *trash3; size_t gen = coro_deferred_get_generation(coro); /* Only pointers were set in helper struct; actually parse them here. */ @@ -1737,14 +1728,15 @@ __attribute__((used)) int fuzz_parse_http_request(const uint8_t *data, LWAN_NO_DISCARD( lwan_request_get_post_param(&request, "Non-Existing-Post-Param")); - LWAN_NO_DISCARD(fuzz_websocket_handshake(&request)); - lwan_request_get_range(&request, &trash1, &trash1); LWAN_NO_DISCARD(trash1); lwan_request_get_if_modified_since(&request, &trash2); LWAN_NO_DISCARD(trash2); + prepare_websocket_handshake(&request, &trash3); + LWAN_NO_DISCARD(trash3); + LWAN_NO_DISCARD( lwan_http_authorize(&request, "Fuzzy Realm", "/dev/null")); From d249391f7e13045d18ccd3cb25a35005d4c8ae54 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 4 Dec 2019 19:01:21 -0800 Subject: [PATCH 1309/2505] Rename kill() -> expire() --- src/lib/lwan-thread.c | 15 ++++++++------- src/lib/lwan-tq.c | 12 ++++++------ src/lib/lwan-tq.h | 6 +++--- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index d3af106b8..a62112b79 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -240,7 +240,7 @@ resume_coro(struct timeout_queue *tq, struct lwan_connection *conn, int epoll_fd enum lwan_connection_coro_yield yield_result = coro_resume(conn->coro); if (yield_result == CONN_CORO_ABORT) { - timeout_queue_kill(tq, conn); + timeout_queue_expire(tq, conn); return; } @@ -318,14 +318,13 @@ static bool process_pending_timers(struct timeout_queue *tq, int epoll_fd) { struct timeout *timeout; - bool processed_tq_timeout = false; + bool should_expire_timers = false; while ((timeout = timeouts_get(t->wheel))) { struct lwan_request *request; if (timeout == &tq->timeout) { - timeout_queue_kill_waiting(tq); - processed_tq_timeout = true; + should_expire_timers = true; continue; } @@ -335,7 +334,9 @@ static bool process_pending_timers(struct timeout_queue *tq, CONN_CORO_RESUME_TIMER); } - if (processed_tq_timeout) { + if (should_expire_timers) { + timeout_queue_expire_waiting(tq); + /* tq timeout expires every 1000ms if there are connections, so * update the date cache at this point as well. */ update_date_cache(t); @@ -429,7 +430,7 @@ static void *thread_io_loop(void *data) conn = event->data.ptr; if (UNLIKELY(event->events & (EPOLLRDHUP | EPOLLHUP))) { - timeout_queue_kill(&tq, conn); + timeout_queue_expire(&tq, conn); continue; } @@ -440,7 +441,7 @@ static void *thread_io_loop(void *data) pthread_barrier_wait(&lwan->thread.barrier); - timeout_queue_kill_all(&tq); + timeout_queue_expire_all(&tq); free(events); return NULL; diff --git a/src/lib/lwan-tq.c b/src/lib/lwan-tq.c index 1246b2d3e..55b99ee19 100644 --- a/src/lib/lwan-tq.c +++ b/src/lib/lwan-tq.c @@ -80,7 +80,7 @@ void timeout_queue_init(struct timeout_queue *tq, const struct lwan *lwan) tq->timeout = (struct timeout){}; } -void timeout_queue_kill(struct timeout_queue *tq, struct lwan_connection *conn) +void timeout_queue_expire(struct timeout_queue *tq, struct lwan_connection *conn) { timeout_queue_remove(tq, conn); @@ -92,7 +92,7 @@ void timeout_queue_kill(struct timeout_queue *tq, struct lwan_connection *conn) } } -void timeout_queue_kill_waiting(struct timeout_queue *tq) +void timeout_queue_expire_waiting(struct timeout_queue *tq) { tq->time++; @@ -103,18 +103,18 @@ void timeout_queue_kill_waiting(struct timeout_queue *tq) if (conn->time_to_die > tq->time) return; - timeout_queue_kill(tq, conn); + timeout_queue_expire(tq, conn); } - /* Death queue exhausted: reset epoch */ + /* Timeout queue exhausted: reset epoch */ tq->time = 0; } -void timeout_queue_kill_all(struct timeout_queue *tq) +void timeout_queue_expire_all(struct timeout_queue *tq) { while (!timeout_queue_empty(tq)) { struct lwan_connection *conn = timeout_queue_idx_to_node(tq, tq->head.next); - timeout_queue_kill(tq, conn); + timeout_queue_expire(tq, conn); } } diff --git a/src/lib/lwan-tq.h b/src/lib/lwan-tq.h index a3dd7efc2..43025b627 100644 --- a/src/lib/lwan-tq.h +++ b/src/lib/lwan-tq.h @@ -33,11 +33,11 @@ void timeout_queue_init(struct timeout_queue *tq, const struct lwan *l); void timeout_queue_insert(struct timeout_queue *tq, struct lwan_connection *new_node); -void timeout_queue_kill(struct timeout_queue *tq, struct lwan_connection *node); +void timeout_queue_expire(struct timeout_queue *tq, struct lwan_connection *node); void timeout_queue_move_to_last(struct timeout_queue *tq, struct lwan_connection *conn); -void timeout_queue_kill_waiting(struct timeout_queue *tq); -void timeout_queue_kill_all(struct timeout_queue *tq); +void timeout_queue_expire_waiting(struct timeout_queue *tq); +void timeout_queue_expire_all(struct timeout_queue *tq); bool timeout_queue_empty(struct timeout_queue *tq); From 513984bb559e86b34245c9828a42ac73810189cd Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 4 Dec 2019 19:01:42 -0800 Subject: [PATCH 1310/2505] Tune SQLite usage in FreeGeoIP sample app Use the same pragmas from the TechEmpower benchmarks. --- src/samples/freegeoip/freegeoip.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/samples/freegeoip/freegeoip.c b/src/samples/freegeoip/freegeoip.c index 6cdb9979a..0879002c7 100644 --- a/src/samples/freegeoip/freegeoip.c +++ b/src/samples/freegeoip/freegeoip.c @@ -413,6 +413,10 @@ int main(void) lwan_status_critical("Could not open database: %s", sqlite3_errmsg(db)); cache = cache_create(create_ipinfo, destroy_ipinfo, NULL, 10); + sqlite3_exec(db, "PRAGMA mmap_size=123217920", NULL, NULL, NULL); + sqlite3_exec(db, "PRAGMA journal_mode=OFF", NULL, NULL, NULL); + sqlite3_exec(db, "PRAGMA locking_mode=EXCLUSIVE", NULL, NULL, NULL); + #if QUERIES_PER_HOUR != 0 lwan_status_info("Limiting to %d queries per hour per client", QUERIES_PER_HOUR); From a40e35ddb4e8c0d140a1bdb6d75b8fcf16e01c0d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 4 Dec 2019 19:52:10 -0800 Subject: [PATCH 1311/2505] Simplify how files are served without sendfile() Do not use streaming handlers when normal callbacks can be used instead. --- src/lib/lwan-mod-serve-files.c | 206 +++++++++++++-------------------- 1 file changed, 83 insertions(+), 123 deletions(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 58e3feb0b..632ca1052 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -50,24 +50,20 @@ #include #endif -static const struct lwan_key_value deflate_compression_hdr = { - .key = "Content-Encoding", - .value = "deflate", +static const struct lwan_key_value deflate_compression_hdr[] = { + {"Content-Encoding", "deflate"}, {} }; -static const struct lwan_key_value gzip_compression_hdr = { - .key = "Content-Encoding", - .value = "gzip", +static const struct lwan_key_value gzip_compression_hdr[] = { + {"Content-Encoding", "gzip"}, {} }; #if defined(HAVE_BROTLI) -static const struct lwan_key_value br_compression_hdr = { - .key = "Content-Encoding", - .value = "br", +static const struct lwan_key_value br_compression_hdr[] = { + {"Content-Encoding", "br"}, {} }; #endif #if defined(HAVE_ZSTD) -static const struct lwan_key_value zstd_compression_hdr = { - .key = "Content-Encoding", - .value = "zstd", +static const struct lwan_key_value zstd_compression_hdr[] = { + {"Content-Encoding", "zstd"}, {} }; #endif @@ -736,11 +732,7 @@ static bool redir_init(struct file_cache_entry *ce, { struct redir_cache_data *rd = &ce->redir_cache_data; - if (asprintf(&rd->redir_to, "%s/", full_path + priv->root_path_len) < 0) - return false; - - ce->mime_type = "text/plain"; - return true; + return asprintf(&rd->redir_to, "%s/", full_path + priv->root_path_len) >= 0; } static const struct cache_funcs *get_funcs(struct serve_files_priv *priv, @@ -1134,7 +1126,7 @@ static enum lwan_http_status sendfile_serve(struct lwan_request *request, from = 0; to = (off_t)sd->compressed.size; - compression_hdr = &gzip_compression_hdr; + compression_hdr = gzip_compression_hdr; fd = sd->compressed.fd; size = sd->compressed.size; @@ -1175,138 +1167,100 @@ static enum lwan_http_status sendfile_serve(struct lwan_request *request, return return_status; } -static enum lwan_http_status -serve_buffer(struct lwan_request *request, - struct file_cache_entry *fce, - const struct lwan_key_value *additional_hdr, - const void *contents, - size_t size, - enum lwan_http_status return_status) +static enum lwan_http_status serve_buffer(struct lwan_request *request, + const char *mime_type, + const void *buffer, + size_t buffer_len, + const struct lwan_key_value *headers, + enum lwan_http_status status_code) { - char headers[DEFAULT_HEADERS_SIZE]; - size_t header_len; + request->response.mime_type = mime_type; + request->response.headers = headers; - header_len = prepare_headers(request, return_status, fce, size, - additional_hdr, headers); - if (UNLIKELY(!header_len)) - return HTTP_INTERNAL_ERROR; + lwan_strbuf_set_static(request->response.buffer, buffer, buffer_len); - if (lwan_request_get_method(request) == REQUEST_METHOD_HEAD) { - lwan_send(request, headers, header_len, 0); - } else if (sizeof(headers) - header_len > size) { - /* See comment in lwan_response() about avoiding writev(). */ - memcpy(headers + header_len, contents, size); - lwan_send(request, headers, header_len + size, 0); - } else { - struct iovec response_vec[] = { - {.iov_base = headers, .iov_len = header_len}, - {.iov_base = (void *)contents, .iov_len = size}, - }; - - lwan_writev(request, response_vec, N_ELEMENTS(response_vec)); - } - - return return_status; + return status_code; } static enum lwan_http_status mmap_serve(struct lwan_request *request, void *data) { - const struct lwan_key_value *compressed; struct file_cache_entry *fce = data; struct mmap_cache_data *md = &fce->mmap_cache_data; - void *contents; - size_t size; - enum lwan_http_status status; #if defined(HAVE_ZSTD) if (md->zstd.len && (request->flags & REQUEST_ACCEPT_ZSTD)) { - contents = md->zstd.value; - size = md->zstd.len; - compressed = &zstd_compression_hdr; + return serve_buffer(request, fce->mime_type, md->zstd.value, + md->zstd.len, zstd_compression_hdr, HTTP_OK); + } +#endif - status = HTTP_OK; - } else -#elif defined(HAVE_BROTLI) +#if defined(HAVE_BROTLI) if (md->brotli.len && (request->flags & REQUEST_ACCEPT_BROTLI)) { - contents = md->brotli.value; - size = md->brotli.len; - compressed = &br_compression_hdr; - - status = HTTP_OK; - } else + return serve_buffer(request, fce->mime_type, md->brotli.value, + md->brotli.len, br_compression_hdr, HTTP_OK); + } #endif - if (md->deflated.len && (request->flags & REQUEST_ACCEPT_DEFLATE)) { - contents = md->deflated.value; - size = md->deflated.len; - compressed = &deflate_compression_hdr; - status = HTTP_OK; - } else { - off_t from, to; - - status = - compute_range(request, &from, &to, (off_t)md->uncompressed.len); - switch (status) { - case HTTP_PARTIAL_CONTENT: - case HTTP_OK: - contents = (char *)md->uncompressed.value + from; - size = (size_t)(to - from); - compressed = NULL; - break; + if (md->deflated.len && (request->flags & REQUEST_ACCEPT_DEFLATE)) { + return serve_buffer(request, fce->mime_type, md->deflated.value, + md->deflated.len, deflate_compression_hdr, HTTP_OK); + } - default: - return status; - } + off_t from, to; + enum lwan_http_status status = + compute_range(request, &from, &to, (off_t)md->uncompressed.len); + if (status == HTTP_OK || status == HTTP_PARTIAL_CONTENT) { + return serve_buffer(request, fce->mime_type, + (char *)md->uncompressed.value + from, + (size_t)(to - from), NULL, status); } - return serve_buffer(request, fce, compressed, contents, size, status); + return status; } static enum lwan_http_status dirlist_serve(struct lwan_request *request, void *data) { - const struct lwan_key_value *compressed = NULL; struct file_cache_entry *fce = data; struct dir_list_cache_data *dd = &fce->dir_list_cache_data; - const char *icon; - const void *contents; - size_t size; + const char *icon = lwan_request_get_query_param(request, "icon"); - icon = lwan_request_get_query_param(request, "icon"); if (!icon) { #if defined(HAVE_BROTLI) if (dd->brotli.len && (request->flags & REQUEST_ACCEPT_BROTLI)) { - compressed = &br_compression_hdr; - contents = dd->brotli.value; - size = dd->brotli.len; - } else + return serve_buffer(request, fce->mime_type, dd->brotli.value, + dd->brotli.len, br_compression_hdr, HTTP_OK); + } #endif + if (dd->deflated.len && (request->flags & REQUEST_ACCEPT_DEFLATE)) { - compressed = &deflate_compression_hdr; - contents = dd->deflated.value; - size = dd->deflated.len; - } else { - contents = lwan_strbuf_get_buffer(&dd->rendered); - size = lwan_strbuf_get_length(&dd->rendered); + return serve_buffer(request, fce->mime_type, dd->deflated.value, + dd->deflated.len, deflate_compression_hdr, + HTTP_OK); } - } else if (streq(icon, "back")) { - contents = back_gif; - size = sizeof(back_gif); - request->response.mime_type = "image/gif"; - } else if (streq(icon, "file")) { - contents = file_gif; - size = sizeof(file_gif); - request->response.mime_type = "image/gif"; - } else if (streq(icon, "folder")) { - contents = folder_gif; - size = sizeof(folder_gif); - request->response.mime_type = "image/gif"; - } else { - return HTTP_NOT_FOUND; + + return serve_buffer( + request, fce->mime_type, lwan_strbuf_get_buffer(&dd->rendered), + lwan_strbuf_get_length(&dd->rendered), NULL, HTTP_OK); + } + + if (streq(icon, "back")) { + return serve_buffer(request, "image/gif", back_gif, sizeof(back_gif), + NULL, HTTP_OK); } - return serve_buffer(request, fce, compressed, contents, size, HTTP_OK); + if (streq(icon, "file")) { + return serve_buffer(request, "image/gif", file_gif, sizeof(file_gif), + NULL, HTTP_OK); + } + + if (streq(icon, "folder")) { + return serve_buffer(request, "image/gif", folder_gif, + sizeof(folder_gif), NULL, HTTP_OK); + } + + return HTTP_NOT_FOUND; } static enum lwan_http_status redir_serve(struct lwan_request *request, @@ -1314,11 +1268,13 @@ static enum lwan_http_status redir_serve(struct lwan_request *request, { struct file_cache_entry *fce = data; struct redir_cache_data *rd = &fce->redir_cache_data; - const struct lwan_key_value headers = {.key = "Location", - .value = rd->redir_to}; + const struct lwan_key_value headers[] = {{"Location", rd->redir_to}, {}}; + + lwan_strbuf_set_staticz(request->response.buffer, rd->redir_to); + request->response.mime_type = "text/plain"; + request->response.headers = headers; - return serve_buffer(request, fce, &headers, rd->redir_to, - strlen(rd->redir_to), HTTP_MOVED_PERMANENTLY); + return HTTP_MOVED_PERMANENTLY; } static enum lwan_http_status @@ -1344,13 +1300,17 @@ serve_files_handle_request(struct lwan_request *request, goto out; } - response->mime_type = fce->mime_type; - response->stream.callback = fce->funcs->serve; - response->stream.data = fce; + if (fce->funcs->serve == sendfile_serve) { + response->mime_type = fce->mime_type; + response->stream.callback = fce->funcs->serve; + response->stream.data = fce; + + request->flags |= RESPONSE_STREAM; - request->flags |= RESPONSE_STREAM; + return HTTP_OK; + } - return HTTP_OK; + return fce->funcs->serve(request, fce); out: response->stream.callback = NULL; From a1334b84b52d47be31dc93d110fd125051cd27b9 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 4 Dec 2019 21:56:42 -0800 Subject: [PATCH 1312/2505] Build fix after abb09dc0 --- src/lib/lwan-cache.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/lib/lwan-cache.c b/src/lib/lwan-cache.c index dbef64167..35154c762 100644 --- a/src/lib/lwan-cache.c +++ b/src/lib/lwan-cache.c @@ -270,9 +270,7 @@ static bool cache_pruner_job(void *data) struct timespec now; bool shutting_down = cache->flags & SHUTTING_DOWN; struct list_head queue; -#ifndef NDEBUG unsigned int evicted = 0; -#endif if (UNLIKELY(pthread_rwlock_trywrlock(&cache->queue.lock) == EBUSY)) return false; @@ -328,9 +326,7 @@ static bool cache_pruner_job(void *data) cache->cb.destroy_entry(node, cache->cb.context); } -#ifndef NDEBUG evicted++; -#endif } /* If local queue has been entirely processed, there's no need to From e0b71647cc7aa0bdaa7a50b428981a87ddf837d2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 4 Dec 2019 22:00:55 -0800 Subject: [PATCH 1313/2505] Cleanup serve_files_handle_request() by removing goto/label --- src/lib/lwan-mod-serve-files.c | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 632ca1052..b6ee86c9c 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -1283,22 +1283,17 @@ serve_files_handle_request(struct lwan_request *request, void *instance) { struct serve_files_priv *priv = instance; - enum lwan_http_status return_status; struct file_cache_entry *fce; struct cache_entry *ce; ce = cache_coro_get_and_ref_entry(priv->cache, request->conn->coro, request->url.value); - if (UNLIKELY(!ce)) { - return_status = HTTP_NOT_FOUND; - goto out; - } + if (UNLIKELY(!ce)) + return HTTP_NOT_FOUND; fce = (struct file_cache_entry *)ce; - if (client_has_fresh_content(request, fce->last_modified.integer)) { - return_status = HTTP_NOT_MODIFIED; - goto out; - } + if (client_has_fresh_content(request, fce->last_modified.integer)) + return HTTP_NOT_MODIFIED; if (fce->funcs->serve == sendfile_serve) { response->mime_type = fce->mime_type; @@ -1311,10 +1306,6 @@ serve_files_handle_request(struct lwan_request *request, } return fce->funcs->serve(request, fce); - -out: - response->stream.callback = NULL; - return return_status; } static const struct lwan_module module = { From 4c47b4c7ec268e395e4c37998ed5b9f1262a5a95 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 5 Dec 2019 19:30:12 -0800 Subject: [PATCH 1314/2505] Use string switch to determine which icon to serve --- src/lib/lwan-mod-serve-files.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index b6ee86c9c..7fa7fec23 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -1245,17 +1245,16 @@ static enum lwan_http_status dirlist_serve(struct lwan_request *request, lwan_strbuf_get_length(&dd->rendered), NULL, HTTP_OK); } - if (streq(icon, "back")) { + STRING_SWITCH(icon) { + case STR4_INT('b','a','c','k'): return serve_buffer(request, "image/gif", back_gif, sizeof(back_gif), NULL, HTTP_OK); - } - if (streq(icon, "file")) { + case STR4_INT('f','i','l','e'): return serve_buffer(request, "image/gif", file_gif, sizeof(file_gif), NULL, HTTP_OK); - } - if (streq(icon, "folder")) { + case STR4_INT('f','o','l','d'): return serve_buffer(request, "image/gif", folder_gif, sizeof(folder_gif), NULL, HTTP_OK); } From b70c7f220e9d95e3c22f8ed5599eddb5a96ee37f Mon Sep 17 00:00:00 2001 From: Ian Eyberg Date: Thu, 5 Dec 2019 10:11:50 -0800 Subject: [PATCH 1315/2505] adding link for nanos support --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 323e49896..e5668255f 100644 --- a/README.md +++ b/README.md @@ -679,6 +679,7 @@ Some other distribution channels were made available as well: * [Ubuntu](https://launchpad.net/lwan-unofficial) * [Alpine Linux](https://pkgs.alpinelinux.org/package/edge/testing/x86_64/lwan) * [NixOS](https://nixos.org/nixos/packages.html#lwan) +* It's also available as a package for the [Nanos unikernel](https://github.com/nanovms/nanos). Lwan has been also used as a benchmark: From f1f9d155b06e59b1b2b472b098089b87506cf14c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 7 Dec 2019 09:48:34 -0800 Subject: [PATCH 1316/2505] Remove last remnants of time_to_die in the source code --- src/lib/lwan-cache.c | 8 ++++---- src/lib/lwan-cache.h | 2 +- src/lib/lwan-thread.c | 2 +- src/lib/lwan-tq.c | 4 ++-- src/lib/lwan.h | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/lib/lwan-cache.c b/src/lib/lwan-cache.c index 35154c762..e10b5257f 100644 --- a/src/lib/lwan-cache.c +++ b/src/lib/lwan-cache.c @@ -202,12 +202,12 @@ struct cache_entry *cache_get_and_ref_entry(struct cache *cache, } if (!hash_add_unique(cache->hash.table, entry->key, entry)) { - struct timespec time_to_die; + struct timespec time_to_expire; - if (UNLIKELY(clock_gettime(monotonic_clock_id, &time_to_die) < 0)) + if (UNLIKELY(clock_gettime(monotonic_clock_id, &time_to_expire) < 0)) lwan_status_critical("clock_gettime"); - entry->time_to_die = time_to_die.tv_sec + cache->settings.time_to_live; + entry->time_to_expire = time_to_expire.tv_sec + cache->settings.time_to_live; if (LIKELY(!pthread_rwlock_wrlock(&cache->queue.lock))) { list_add_tail(&cache->queue.list, &entry->entries); @@ -301,7 +301,7 @@ static bool cache_pruner_job(void *data) list_for_each_safe(&queue, node, next, entries) { char *key = node->key; - if (now.tv_sec < node->time_to_die && LIKELY(!shutting_down)) + if (now.tv_sec < node->time_to_expire && LIKELY(!shutting_down)) break; list_del(&node->entries); diff --git a/src/lib/lwan-cache.h b/src/lib/lwan-cache.h index a46895602..d390f93fb 100644 --- a/src/lib/lwan-cache.h +++ b/src/lib/lwan-cache.h @@ -29,7 +29,7 @@ struct cache_entry { char *key; int refs; unsigned flags; - time_t time_to_die; + time_t time_to_expire; }; typedef struct cache_entry *(*cache_create_entry_cb)( diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index a62112b79..5183bf012 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -272,7 +272,7 @@ static ALWAYS_INLINE void spawn_coro(struct lwan_connection *conn, *conn = (struct lwan_connection) { .coro = coro_new(switcher, process_request_coro, conn), .flags = CONN_EVENTS_READ, - .time_to_die = tq->time + tq->keep_alive_timeout, + .time_to_expire = tq->time + tq->keep_alive_timeout, .thread = t, }; if (UNLIKELY(!conn->coro)) { diff --git a/src/lib/lwan-tq.c b/src/lib/lwan-tq.c index 55b99ee19..9d1bdbef6 100644 --- a/src/lib/lwan-tq.c +++ b/src/lib/lwan-tq.c @@ -64,7 +64,7 @@ void timeout_queue_move_to_last(struct timeout_queue *tq, /* CONN_IS_KEEP_ALIVE isn't checked here because non-keep-alive connections * are closed in the request processing coroutine after they have been * served. In practice, if this is called, it's a keep-alive connection. */ - conn->time_to_die = tq->time + tq->keep_alive_timeout; + conn->time_to_expire = tq->time + tq->keep_alive_timeout; timeout_queue_remove(tq, conn); timeout_queue_insert(tq, conn); @@ -100,7 +100,7 @@ void timeout_queue_expire_waiting(struct timeout_queue *tq) struct lwan_connection *conn = timeout_queue_idx_to_node(tq, tq->head.next); - if (conn->time_to_die > tq->time) + if (conn->time_to_expire > tq->time) return; timeout_queue_expire(tq, conn); diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 394954643..5eb0495be 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -320,10 +320,10 @@ struct lwan_connection { /* This structure is exactly 32-bytes on x86-64. If it is changed, * make sure the scheduler (lwan-thread.c) is updated as well. */ enum lwan_connection_flags flags; - unsigned int time_to_die; + unsigned int time_to_expire; struct coro *coro; struct lwan_thread *thread; - int prev, next; /* for death queue */ + int prev, next; /* for timeout queue */ }; struct lwan_proxy { From 05d781b889ccf93794a720b8c7138519899c5e7f Mon Sep 17 00:00:00 2001 From: "Leandro A. F. Pereira" Date: Thu, 12 Dec 2019 16:37:09 -0800 Subject: [PATCH 1317/2505] Create FUNDING.yml --- .github/FUNDING.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..d38101d81 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: lpereira From df8693b0a77005b814700769665f50ad80802db9 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 11 Dec 2019 19:01:34 -0800 Subject: [PATCH 1318/2505] Verify if paassword_file exists while reading config --- src/lib/lwan.c | 4 +++- src/scripts/testsuite.py | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index fddefc350..b183290ad 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -151,7 +151,9 @@ static void parse_listener_prefix_authorization(struct config *c, url_map->authorization.realm = strdup(l->value); } else if (streq(l->key, "password_file")) { free(url_map->authorization.password_file); - url_map->authorization.password_file = strdup(l->value); + url_map->authorization.password_file = realpath(l->value, NULL); + if (!url_map->authorization.password_file) + config_error(c, "Could not determine full path for password file: %s", l->value); } break; diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index de4b6eb55..1420bca4e 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -25,6 +25,7 @@ class LwanTest(unittest.TestCase): def setUp(self, env=None): + open('htpasswd', 'w').close() for spawn_try in range(20): self.lwan=subprocess.Popen([LWAN_PATH], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env) @@ -51,6 +52,10 @@ def tearDown(self): with self.lwan as l: l.communicate(timeout=1.0) l.kill() + try: + os.remove('htpasswd') + except FileNotFoundError: + pass def assertHttpResponseValid(self, request, status_code, content_type): self.assertEqual(request.status_code, status_code) From 1058e1a23ea0a4397b3aa1ce21ea5e51195cb00b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 11 Dec 2019 19:02:08 -0800 Subject: [PATCH 1319/2505] Move handle_request function pointer to the start of struct lwan_module --- src/lib/lwan.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 5eb0495be..2506f131b 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -352,16 +352,16 @@ struct lwan_request { }; struct lwan_module { + enum lwan_http_status (*handle_request)(struct lwan_request *request, + struct lwan_response *response, + void *instance); + void *(*create)(const char *prefix, void *args); void *(*create_from_hash)(const char *prefix, const struct hash *hash); void (*destroy)(void *instance); bool (*parse_conf)(void *instance, struct config *config); - enum lwan_http_status (*handle_request)(struct lwan_request *request, - struct lwan_response *response, - void *instance); - enum lwan_handler_flags flags; }; From b52c9f5e17542800a762f19bc9073bd8b3b95cb3 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 12 Dec 2019 17:57:04 -0800 Subject: [PATCH 1320/2505] Add AutoFDO profile for TWFB benchmark --- src/gcda/techempower.gcov | Bin 0 -> 21676 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/gcda/techempower.gcov diff --git a/src/gcda/techempower.gcov b/src/gcda/techempower.gcov new file mode 100644 index 0000000000000000000000000000000000000000..1c6e27f153e12de22388873bac73a7f89b0a4704 GIT binary patch literal 21676 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 Date: Sat, 14 Dec 2019 11:56:26 -0800 Subject: [PATCH 1321/2505] Simplify how alignment is stored in JSON structs --- src/samples/techempower/json.c | 2 +- src/samples/techempower/json.h | 39 +++++++++++++--------------------- 2 files changed, 16 insertions(+), 25 deletions(-) diff --git a/src/samples/techempower/json.c b/src/samples/techempower/json.c index 0b8d7c118..ac421ced7 100644 --- a/src/samples/techempower/json.c +++ b/src/samples/techempower/json.c @@ -496,7 +496,7 @@ static ptrdiff_t get_elem_size(const struct json_obj_descr *descr) for (i = 0; i < descr->object.sub_descr_len; i++) { ptrdiff_t s = get_elem_size(&descr->object.sub_descr[i]); - total += (ptrdiff_t)ROUND_UP(s, 1 << descr->object.sub_descr[i].align_shift); + total += (ptrdiff_t)ROUND_UP(s, descr->object.sub_descr[i].align); } return total; diff --git a/src/samples/techempower/json.h b/src/samples/techempower/json.h index e979418dc..f11dcc04d 100644 --- a/src/samples/techempower/json.h +++ b/src/samples/techempower/json.h @@ -55,8 +55,7 @@ enum json_tokens { struct json_obj_descr { const char *field_name; - - uint32_t align_shift; + uint32_t align; uint32_t field_name_len; uint32_t type; uint32_t offset; @@ -87,11 +86,6 @@ struct json_obj_descr { */ typedef int (*json_append_bytes_t)(const char *bytes, size_t len, void *data); -#define Z_ALIGN_SHIFT(type) \ - (__alignof__(type) == 1 \ - ? 0 \ - : __alignof__(type) == 2 ? 1 : __alignof__(type) == 4 ? 2 : 3) - /** * @brief Helper macro to declare a descriptor for supported primitive * values. @@ -116,7 +110,7 @@ typedef int (*json_append_bytes_t)(const char *bytes, size_t len, void *data); */ #define JSON_OBJ_DESCR_PRIM(struct_, field_name_, type_) \ { \ - .field_name = (#field_name_), .align_shift = Z_ALIGN_SHIFT(struct_), \ + .field_name = (#field_name_), .align = __alignof__(struct_), \ .field_name_len = sizeof(#field_name_) - 1, .type = type_, \ .offset = offsetof(struct_, field_name_), \ } @@ -149,7 +143,7 @@ typedef int (*json_append_bytes_t)(const char *bytes, size_t len, void *data); */ #define JSON_OBJ_DESCR_OBJECT(struct_, field_name_, sub_descr_) \ { \ - .field_name = (#field_name_), .align_shift = Z_ALIGN_SHIFT(struct_), \ + .field_name = (#field_name_), .align = __alignof__(struct_), \ .field_name_len = (sizeof(#field_name_) - 1), \ .type = JSON_TOK_OBJECT_START, \ .offset = offsetof(struct_, field_name_), \ @@ -188,13 +182,13 @@ typedef int (*json_append_bytes_t)(const char *bytes, size_t len, void *data); #define JSON_OBJ_DESCR_ARRAY(struct_, field_name_, max_len_, len_field_, \ elem_type_) \ { \ - .field_name = (#field_name_), .align_shift = Z_ALIGN_SHIFT(struct_), \ + .field_name = (#field_name_), .align = __alignof__(struct_), \ .field_name_len = sizeof(#field_name_) - 1, \ .type = JSON_TOK_LIST_START, .offset = offsetof(struct_, field_name_), \ .array = { \ .element_descr = \ &(struct json_obj_descr){ \ - .align_shift = Z_ALIGN_SHIFT(struct_), \ + .align = __alignof__(struct_), \ .type = elem_type_, \ .offset = offsetof(struct_, len_field_), \ }, \ @@ -244,13 +238,13 @@ typedef int (*json_append_bytes_t)(const char *bytes, size_t len, void *data); #define JSON_OBJ_DESCR_OBJ_ARRAY(struct_, field_name_, max_len_, len_field_, \ elem_descr_, elem_descr_len_) \ { \ - .field_name = (#field_name_), .align_shift = Z_ALIGN_SHIFT(struct_), \ + .field_name = (#field_name_), .align = __alignof__(struct_), \ .field_name_len = sizeof(#field_name_) - 1, \ .type = JSON_TOK_LIST_START, .offset = offsetof(struct_, field_name_), \ .array = { \ .element_descr = \ &(struct json_obj_descr){ \ - .align_shift = Z_ALIGN_SHIFT(struct_), \ + .align = __alignof__(struct_), \ .type = JSON_TOK_OBJECT_START, \ .offset = offsetof(struct_, len_field_), \ .object = \ @@ -314,13 +308,13 @@ typedef int (*json_append_bytes_t)(const char *bytes, size_t len, void *data); #define JSON_OBJ_DESCR_ARRAY_ARRAY(struct_, field_name_, max_len_, len_field_, \ elem_descr_, elem_descr_len_) \ { \ - .field_name = (#field_name_), .align_shift = Z_ALIGN_SHIFT(struct_), \ + .field_name = (#field_name_), .align = __alignof__(struct_), \ .field_name_len = sizeof(#field_name_) - 1, \ .type = JSON_TOK_LIST_START, .offset = offsetof(struct_, field_name_), \ .array = { \ .element_descr = \ &(struct json_obj_descr){ \ - .align_shift = Z_ALIGN_SHIFT(struct_), \ + .align = __alignof__(struct_), \ .type = JSON_TOK_LIST_START, \ .offset = offsetof(struct_, len_field_), \ .object = \ @@ -353,8 +347,7 @@ typedef int (*json_append_bytes_t)(const char *bytes, size_t len, void *data); #define JSON_OBJ_DESCR_PRIM_NAMED(struct_, json_field_name_, \ struct_field_name_, type_) \ { \ - .field_name = (json_field_name_), \ - .align_shift = Z_ALIGN_SHIFT(struct_), \ + .field_name = (json_field_name_), .align = __alignof__(struct_), \ .field_name_len = sizeof(json_field_name_) - 1, .type = type_, \ .offset = offsetof(struct_, struct_field_name_), \ } @@ -378,8 +371,7 @@ typedef int (*json_append_bytes_t)(const char *bytes, size_t len, void *data); #define JSON_OBJ_DESCR_OBJECT_NAMED(struct_, json_field_name_, \ struct_field_name_, sub_descr_) \ { \ - .field_name = (json_field_name_), \ - .align_shift = Z_ALIGN_SHIFT(struct_), \ + .field_name = (json_field_name_), .align = __alignof__(struct_), \ .field_name_len = (sizeof(json_field_name_) - 1), \ .type = JSON_TOK_OBJECT_START, \ .offset = offsetof(struct_, struct_field_name_), \ @@ -414,15 +406,14 @@ typedef int (*json_append_bytes_t)(const char *bytes, size_t len, void *data); struct_field_name_, max_len_, len_field_, \ elem_type_) \ { \ - .field_name = (json_field_name_), \ - .align_shift = Z_ALIGN_SHIFT(struct_), \ + .field_name = (json_field_name_), .align = __alignof__(struct_), \ .field_name_len = sizeof(json_field_name_) - 1, \ .type = JSON_TOK_LIST_START, \ .offset = offsetof(struct_, struct_field_name_), \ .array = { \ .element_descr = \ &(struct json_obj_descr){ \ - .align_shift = Z_ALIGN_SHIFT(struct_), \ + .align = __alignof__(struct_), \ .type = elem_type_, \ .offset = offsetof(struct_, len_field_), \ }, \ @@ -480,13 +471,13 @@ typedef int (*json_append_bytes_t)(const char *bytes, size_t len, void *data); struct_, json_field_name_, struct_field_name_, max_len_, len_field_, \ elem_descr_, elem_descr_len_) \ { \ - .field_name = json_field_name_, .align_shift = Z_ALIGN_SHIFT(struct_), \ + .field_name = json_field_name_, .align = __alignof__(struct_), \ .field_name_len = sizeof(json_field_name_) - 1, \ .type = JSON_TOK_LIST_START, \ .offset = offsetof(struct_, struct_field_name_), \ .element_descr = \ &(struct json_obj_descr){ \ - .align_shift = Z_ALIGN_SHIFT(struct_), \ + .align = __alignof__(struct_), \ .type = JSON_TOK_OBJECT_START, \ .offset = offsetof(struct_, len_field_), \ .object = \ From 15a924995d9e6326896f260f0ba7a4d0cce70c96 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 17 Dec 2019 08:11:21 -0800 Subject: [PATCH 1322/2505] Minor consistency cleanup in lwan_array_append_heap() --- src/lib/lwan-array.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-array.c b/src/lib/lwan-array.c index 296521cb6..8026f197b 100644 --- a/src/lib/lwan-array.c +++ b/src/lib/lwan-array.c @@ -84,7 +84,7 @@ void *lwan_array_append_heap(struct lwan_array *a, size_t element_size) a->base = new_base; } - return ((unsigned char *)a->base) + a->elements++ * element_size; + return ((char *)a->base) + a->elements++ * element_size; } void *lwan_array_append_inline(struct lwan_array *a, From 95bdde87c9c86922991dd548a9e9a7cc8bfaec90 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 18 Dec 2019 18:15:34 -0800 Subject: [PATCH 1323/2505] Reduce memory usage after 1ddaed371f --- src/lib/lwan-private.h | 2 ++ src/lib/lwan-socket.c | 4 ++-- src/lib/lwan-thread.c | 18 +++++++++++++----- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 069bbff16..127fe225d 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -24,6 +24,8 @@ #include "lwan.h" +int lwan_socket_get_backlog_size(void); + struct lwan_fd_watch *lwan_watch_fd(struct lwan *l, int fd, uint32_t events, diff --git a/src/lib/lwan-socket.c b/src/lib/lwan-socket.c index fdacda223..b84520e0a 100644 --- a/src/lib/lwan-socket.c +++ b/src/lib/lwan-socket.c @@ -35,7 +35,7 @@ #include "int-to-str.h" #include "sd-daemon.h" -static int get_backlog_size(void) +int lwan_socket_get_backlog_size(void) { int backlog = SOMAXCONN; @@ -152,7 +152,7 @@ static sa_family_t parse_listener(char *listener, char **node, char **port) static int listen_addrinfo(int fd, const struct addrinfo *addr) { - if (listen(fd, get_backlog_size()) < 0) + if (listen(fd, lwan_socket_get_backlog_size()) < 0) lwan_status_critical_perror("listen"); char host_buf[NI_MAXHOST], serv_buf[NI_MAXSERV]; diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 5183bf012..8e541c7a8 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -39,7 +39,12 @@ #include "lwan-tq.h" #include "list.h" -static ALWAYS_INLINE int min(const int a, const int b) { return a < b ? a : b; } +#define LWAN_MIN(a_, b_) \ + ({ \ + __typeof__(a_) __a__ = (a_); \ + __typeof__(b_) __b__ = (b_); \ + __a__ > __b__ ? __b__ : __a__; \ + }) static void lwan_strbuf_free_defer(void *data) { @@ -388,7 +393,7 @@ static void *thread_io_loop(void *data) struct lwan_thread *t = data; int epoll_fd = t->epoll_fd; const int read_pipe_fd = t->pipe_fd[0]; - const int max_events = min((int)t->lwan->thread.max_fd, 1024); + const int max_events = LWAN_MIN((int)t->lwan->thread.max_fd, 1024); struct lwan *lwan = t->lwan; struct epoll_event *events; struct coro_switcher switcher; @@ -447,7 +452,8 @@ static void *thread_io_loop(void *data) return NULL; } -static void create_thread(struct lwan *l, struct lwan_thread *thread) +static void create_thread(struct lwan *l, struct lwan_thread *thread, + const size_t n_queue_fds) { int ignore; pthread_attr_t attr; @@ -492,7 +498,6 @@ static void create_thread(struct lwan *l, struct lwan_thread *thread) if (pthread_attr_destroy(&attr)) lwan_status_critical_perror("pthread_attr_destroy"); - size_t n_queue_fds = thread->lwan->thread.max_fd; if (spsc_queue_init(&thread->pending_fds, n_queue_fds) < 0) { lwan_status_critical("Could not initialize pending fd " "queue width %zu elements", n_queue_fds); @@ -650,8 +655,11 @@ void lwan_thread_init(struct lwan *l) if (!l->thread.threads) lwan_status_critical("Could not allocate memory for threads"); + const size_t n_queue_fds = LWAN_MIN(l->thread.max_fd / l->thread.count, + (size_t)(2 * lwan_socket_get_backlog_size())); + lwan_status_info("Pending client file descriptor queue has %zu items", n_queue_fds); for (short i = 0; i < l->thread.count; i++) - create_thread(l, &l->thread.threads[i]); + create_thread(l, &l->thread.threads[i], n_queue_fds); const unsigned int total_conns = l->thread.max_fd * l->thread.count; #ifdef __x86_64__ From 43459e0a6f94b8087fb3413ecf07637a61d245b3 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 18 Dec 2019 18:16:12 -0800 Subject: [PATCH 1324/2505] Warn if reuse_port option is used on a non-supported system --- src/lib/lwan-socket.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/lwan-socket.c b/src/lib/lwan-socket.c index b84520e0a..cc5f49572 100644 --- a/src/lib/lwan-socket.c +++ b/src/lib/lwan-socket.c @@ -200,6 +200,9 @@ static int bind_and_listen_addrinfos(struct addrinfo *addrs, bool reuse_port) #ifdef SO_REUSEPORT SET_SOCKET_OPTION_MAY_FAIL(SOL_SOCKET, SO_REUSEPORT, (int[]){reuse_port}); +#else + if (reuse_port) + lwan_status_warning("reuse_port not supported by the OS"); #endif if (!bind(fd, addr->ai_addr, addr->ai_addrlen)) From 24a0c0e790bce1f88fcd71eae4d740219defa8d8 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 18 Dec 2019 18:16:30 -0800 Subject: [PATCH 1325/2505] Perform some sanity checks for the MIME-type table --- src/lib/lwan-tables.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index 11cd6a74f..3ffa10b79 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -84,6 +84,15 @@ void lwan_tables_init(void) } mime_entries_initialized = true; + + assert(streq(lwan_determine_mime_type_for_file_name(".mkv"), + "video/x-matroska")); + assert(streq(lwan_determine_mime_type_for_file_name(".bz2"), + "application/x-bzip2")); + assert(streq(lwan_determine_mime_type_for_file_name(".xml"), + "application/xml")); + assert(streq(lwan_determine_mime_type_for_file_name(".nosuchext"), + "application/octet-stream")); } void From a410065f98a372f110523441fe37e4bc27780f3a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 18 Dec 2019 20:10:29 -0800 Subject: [PATCH 1326/2505] Simplify coro_swapcontext() in x86-64 --- src/lib/lwan-coro.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 5913c2c68..094373952 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -141,8 +141,7 @@ asm(".text\n\t" "movq %rsi,56(%rdi)\n\t" "movq (%rsp),%rcx\n\t" "movq %rcx,64(%rdi)\n\t" - "leaq 0x8(%rsp),%rcx\n\t" - "movq %rcx,72(%rdi)\n\t" + "movq %rsp,72(%rdi)\n\t" "movq 72(%rsi),%rsp\n\t" "movq 0(%rsi),%rbx\n\t" "movq 8(%rsi),%rbp\n\t" From fade14287e0aa4a98018173e9ed079cf65547c06 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 18 Dec 2019 18:13:46 -0800 Subject: [PATCH 1327/2505] Improve coroutine context switch performance by ~10% ----------------------------------------------------- Benchmark Time CPU Iterations ----------------------------------------------------- coroswitch-new 22.0 ns 22.0 ns 31761461 coroswitch-old 24.4 ns 24.3 ns 28819430 --- src/lib/lwan-coro.c | 33 ++++++++++++--------------------- src/lib/lwan-coro.h | 1 - 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 094373952..68c1053f7 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -183,33 +183,23 @@ asm(".text\n\t" #define coro_swapcontext(cur, oth) swapcontext(cur, oth) #endif -#ifndef __x86_64__ +__attribute__((used)) static void coro_entry_point(struct coro *coro, coro_function_t func, void *data) { int return_value = func(coro, data); coro_yield(coro, return_value); } -#else -static_assert(offsetof(struct coro, yield_value) == 616, - "yield_value at the correct offset (coro_defer array should be " - "inlinefirst)"); -void __attribute__((noinline, visibility("internal"))) -coro_entry_point(struct coro *coro, coro_function_t func, void *data); +#ifdef __x86_64__ +void __attribute__((noinline, visibility("internal"))) coro_entry_point_x86_64(); + asm(".text\n\t" ".p2align 4\n\t" - ASM_ROUTINE(coro_entry_point) - "pushq %rbx\n\t" - "movq %rdi, %rbx\n\t" /* coro = rdi */ - "movq %rsi, %rdx\n\t" /* func = rsi */ - "movq %r15, %rsi\n\t" /* data = r15 */ - "call *%rdx\n\t" /* eax = func(coro, data) */ - "movq (%rbx), %rsi\n\t" - "movl %eax, 616(%rbx)\n\t" /* coro->yield_value = eax */ - "popq %rbx\n\t" - "leaq 0x50(%rsi), %rdi\n\t" /* get coro context from coro */ - "jmp " ASM_SYMBOL(coro_swapcontext) "\n\t"); + ASM_ROUTINE(coro_entry_point_x86_64) + "mov %r15, %rdx\n\t" + "jmp coro_entry_point\n\t" +); #endif void coro_deferred_run(struct coro *coro, size_t generation) @@ -252,7 +242,7 @@ void coro_reset(struct coro *coro, coro_function_t func, void *data) coro->context[5 /* R15 */] = (uintptr_t)data; coro->context[6 /* RDI */] = (uintptr_t)coro; coro->context[7 /* RSI */] = (uintptr_t)func; - coro->context[8 /* RIP */] = (uintptr_t)coro_entry_point; + coro->context[8 /* RIP */] = (uintptr_t)coro_entry_point_x86_64; /* Ensure stack is properly aligned: it should be aligned to a * 16-bytes boundary so SSE will work properly, but should be @@ -327,7 +317,6 @@ ALWAYS_INLINE int coro_resume(struct coro *coro) #endif coro_swapcontext(&coro->switcher->caller, &coro->context); - memcpy(&coro->context, &coro->switcher->callee, sizeof(coro->context)); return coro->yield_value; } @@ -343,8 +332,10 @@ ALWAYS_INLINE int coro_resume_value(struct coro *coro, int value) inline int coro_yield(struct coro *coro, int value) { assert(coro); + coro->yield_value = value; - coro_swapcontext(&coro->switcher->callee, &coro->switcher->caller); + coro_swapcontext(&coro->context, &coro->switcher->caller); + return coro->yield_value; } diff --git a/src/lib/lwan-coro.h b/src/lib/lwan-coro.h index 922834a4a..e6c9d061e 100644 --- a/src/lib/lwan-coro.h +++ b/src/lib/lwan-coro.h @@ -38,7 +38,6 @@ typedef int (*coro_function_t)(struct coro *coro, void *data); struct coro_switcher { coro_context caller; - coro_context callee; }; struct coro * From bda9bc7ca476ca159e65ea589be534c604134e48 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 18 Dec 2019 21:50:05 -0800 Subject: [PATCH 1328/2505] Fix visibility of coro_entry_point() --- src/lib/lwan-coro.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 68c1053f7..bf07ebe9c 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -183,8 +183,8 @@ asm(".text\n\t" #define coro_swapcontext(cur, oth) swapcontext(cur, oth) #endif -__attribute__((used)) -static void +__attribute__((used, visibility("internal"))) +void coro_entry_point(struct coro *coro, coro_function_t func, void *data) { int return_value = func(coro, data); @@ -192,7 +192,7 @@ coro_entry_point(struct coro *coro, coro_function_t func, void *data) } #ifdef __x86_64__ -void __attribute__((noinline, visibility("internal"))) coro_entry_point_x86_64(); +void __attribute__((visibility("internal"))) coro_entry_point_x86_64(); asm(".text\n\t" ".p2align 4\n\t" From 82b8d98ffbdeb542b8e7ea6da09726d6b7690a0b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 18 Dec 2019 21:52:39 -0800 Subject: [PATCH 1329/2505] Get rid of timeouts_reset(): only used by timeouts_close() And timeouts_close() frees the timeouts struct, so there's no point in cleaning anything up before freeing the struct. --- src/lib/timeout.c | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/src/lib/timeout.c b/src/lib/timeout.c index 9363f83cf..4ebe81a8f 100644 --- a/src/lib/timeout.c +++ b/src/lib/timeout.c @@ -187,37 +187,8 @@ struct timeouts *timeouts_open(timeout_error_t *error) return NULL; } -static void timeouts_reset(struct timeouts *T) -{ - struct list_head reset; - struct timeout *to; - unsigned i, j; - - list_head_init(&reset); - - for (i = 0; i < N_ELEMENTS(T->wheel); i++) { - for (j = 0; j < N_ELEMENTS(T->wheel[i]); j++) { - list_append_list(&reset, &T->wheel[i][j]); - list_head_init(&T->wheel[i][j]); - } - } - - list_append_list(&reset, &T->expired); - list_head_init(&T->expired); - - list_for_each (&reset, to, tqe) { - to->pending = NULL; - } -} - void timeouts_close(struct timeouts *T) { - /* - * NOTE: Delete installed timeouts so timeout_pending() and - * timeout_expired() worked as expected. - */ - timeouts_reset(T); - free(T); } From 4cfe6d890f59f961c883effe4796c2c34b6435bc Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 18 Dec 2019 21:59:41 -0800 Subject: [PATCH 1330/2505] Revert "Simplify coro_swapcontext() in x86-64" This reverts commit a410065f98a372f110523441fe37e4bc27780f3a. --- src/lib/lwan-coro.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index bf07ebe9c..d4112059e 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -141,7 +141,8 @@ asm(".text\n\t" "movq %rsi,56(%rdi)\n\t" "movq (%rsp),%rcx\n\t" "movq %rcx,64(%rdi)\n\t" - "movq %rsp,72(%rdi)\n\t" + "leaq 0x8(%rsp),%rcx\n\t" + "movq %rcx,72(%rdi)\n\t" "movq 72(%rsi),%rsp\n\t" "movq 0(%rsi),%rbx\n\t" "movq 8(%rsi),%rbp\n\t" From 839b877c53984f04da7fee55d0f59ed9709fc7a5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 18 Dec 2019 22:07:43 -0800 Subject: [PATCH 1331/2505] Fix build on macOS --- src/lib/lwan-coro.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index d4112059e..5ca616877 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -199,7 +199,7 @@ asm(".text\n\t" ".p2align 4\n\t" ASM_ROUTINE(coro_entry_point_x86_64) "mov %r15, %rdx\n\t" - "jmp coro_entry_point\n\t" + "jmp " ASM_SYMBOL(coro_entry_point) "\n\t" ); #endif From 7c4454921d3ecb97bb177e612a9e0910ef2a9301 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 19 Dec 2019 08:06:33 -0800 Subject: [PATCH 1332/2505] Try to force a tail call for coro_yield() in coro_entry_point() Before: 000000000001e400 : 1e400: 53 push %rbx 1e401: 48 89 fb mov %rdi,%rbx 1e404: 49 89 f0 mov %rsi,%r8 1e407: 48 89 d6 mov %rdx,%rsi 1e40a: 41 ff d0 callq *%r8 1e40d: 48 8b 33 mov (%rbx),%rsi 1e410: 48 8d 7b 08 lea 0x8(%rbx),%rdi 1e414: 89 83 68 02 00 00 mov %eax,0x268(%rbx) 1e41a: e8 81 c9 fe ff callq ada0 1e41f: 90 nop After: 000000000001e400 : 1e400: 53 push %rbx 1e401: 49 89 f0 mov %rsi,%r8 1e404: 48 89 fb mov %rdi,%rbx 1e407: 48 89 d6 mov %rdx,%rsi 1e40a: 41 ff d0 callq *%r8 1e40d: 48 8b 33 mov (%rbx),%rsi 1e410: 48 8d 7b 08 lea 0x8(%rbx),%rdi 1e414: 89 83 68 02 00 00 mov %eax,0x268(%rbx) 1e41a: 5b pop %rbx 1e41b: e9 80 c9 fe ff jmpq ada0 --- src/lib/lwan-coro.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 5ca616877..bd24364db 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -188,8 +188,7 @@ __attribute__((used, visibility("internal"))) void coro_entry_point(struct coro *coro, coro_function_t func, void *data) { - int return_value = func(coro, data); - coro_yield(coro, return_value); + return (void)coro_yield(coro, func(coro, data)); } #ifdef __x86_64__ From 870ddd48aa1ccc5067a8b6972df173e9310bd569 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 19 Dec 2019 08:43:57 -0800 Subject: [PATCH 1333/2505] Simplify timeout_slot() --- src/lib/timeout.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/lib/timeout.c b/src/lib/timeout.c index 4ebe81a8f..6a6797b0a 100644 --- a/src/lib/timeout.c +++ b/src/lib/timeout.c @@ -222,11 +222,7 @@ static inline int timeout_wheel(timeout_t timeout) static inline int timeout_slot(int wheel, timeout_t expires) { - const timeout_t wheel_mask = (timeout_t)WHEEL_MASK; - const timeout_t slot = - wheel_mask & ((expires >> (wheel * WHEEL_BIT)) - !!wheel); - - return (int)slot; + return (int)(WHEEL_MASK & ((expires >> (wheel * WHEEL_BIT)) - !!wheel)); } static void From 04e78ecac1b4b494d695f232217c471c4440c2ba Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 19 Dec 2019 18:11:10 -0800 Subject: [PATCH 1334/2505] Minor cleanups in configdump --- src/bin/tools/configdump.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/bin/tools/configdump.c b/src/bin/tools/configdump.c index bbc3fd876..4e9eb6081 100644 --- a/src/bin/tools/configdump.c +++ b/src/bin/tools/configdump.c @@ -80,7 +80,6 @@ dump(struct config *config, int indent_level) int main(int argc, char *argv[]) { struct config *config; - int indent_level = 0; if (argc < 2) { lwan_status_critical("Usage: %s /path/to/config/file.conf", argv[0]); @@ -94,7 +93,7 @@ int main(int argc, char *argv[]) return 1; } - dump(config, indent_level); + dump(config, 0); config_close(config); From a282c7eb9ba3e391739a036ffe36543c4ea3861d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 19 Dec 2019 18:11:29 -0800 Subject: [PATCH 1335/2505] Poison the requested, not the aligned, size when using the BPA --- src/lib/lwan-coro.c | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index bd24364db..d027da959 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -90,11 +90,9 @@ struct coro { int yield_value; - /* The bump pointer allocator is disabled for fuzzing builds as that - * will make it more difficult to find overflows. - * See: https://blog.fuzzing-project.org/65-When-your-Memory-Allocator-hides-Security-Bugs.html - */ struct { + /* This allocator is instrumented on debug builds using asan and/or valgrind, if + * enabled during configuration time. See coro_malloc_bump_ptr() for details. */ void *ptr; size_t remaining; } bump_ptr_alloc; @@ -192,6 +190,8 @@ coro_entry_point(struct coro *coro, coro_function_t func, void *data) } #ifdef __x86_64__ +/* See comment in coro_reset() for an explanation of why this routine is + * necessary. */ void __attribute__((visibility("internal"))) coro_entry_point_x86_64(); asm(".text\n\t" @@ -406,27 +406,47 @@ static void instrument_bpa_free(void *ptr, void *size) } #endif +#if defined(INSTRUMENT_FOR_ASAN) || defined(INSTRUMENT_FOR_VALGRIND) +static inline void *coro_malloc_bump_ptr(struct coro *coro, + size_t aligned_size, + size_t requested_size) +#else static inline void *coro_malloc_bump_ptr(struct coro *coro, size_t aligned_size) +#endif { void *ptr = coro->bump_ptr_alloc.ptr; coro->bump_ptr_alloc.remaining -= aligned_size; coro->bump_ptr_alloc.ptr = (char *)ptr + aligned_size; + /* This instrumentation is desirable to find buffer overflows, but it's not + * cheap. Enable it only in debug builds (for Valgrind) or when using + * address sanitizer (always the case when fuzz-testing on OSS-Fuzz). See: + * https://blog.fuzzing-project.org/65-When-your-Memory-Allocator-hides-Security-Bugs.html + */ + #if defined(INSTRUMENT_FOR_VALGRIND) - VALGRIND_MALLOCLIKE_BLOCK(ptr, aligned_size, 0, 0); + VALGRIND_MALLOCLIKE_BLOCK(ptr, requested_size, 0, 0); #endif #if defined(INSTRUMENT_FOR_ASAN) - __asan_unpoison_memory_region(ptr, aligned_size); + __asan_unpoison_memory_region(ptr, requested_size); #endif #if defined(INSTRUMENT_FOR_VALGRIND) || defined(INSTRUMENT_FOR_ASAN) coro_defer2(coro, instrument_bpa_free, ptr, - (void *)(uintptr_t)aligned_size); + (void *)(uintptr_t)requested_size); #endif return ptr; } +#if defined(INSTRUMENT_FOR_ASAN) || defined(INSTRUMENT_FOR_VALGRIND) +#define CORO_MALLOC_BUMP_PTR(coro_, aligned_size_, requested_size_) \ + coro_malloc_bump_ptr(coro_, aligned_size_, requested_size_) +#else +#define CORO_MALLOC_BUMP_PTR(coro_, aligned_size_, requested_size_) \ + coro_malloc_bump_ptr(coro_, aligned_size_) +#endif + void *coro_malloc(struct coro *coro, size_t size) { /* The bump pointer allocator can't be in the generic coro_malloc_full() @@ -440,7 +460,7 @@ void *coro_malloc(struct coro *coro, size_t size) (size + sizeof(void *) - 1ul) & ~(sizeof(void *) - 1ul); if (LIKELY(coro->bump_ptr_alloc.remaining >= aligned_size)) - return coro_malloc_bump_ptr(coro, aligned_size); + return CORO_MALLOC_BUMP_PTR(coro, aligned_size, size); /* This will allocate as many "bump pointer arenas" as necessary; the * old ones will be freed automatically as each allocations coro_defers @@ -464,7 +484,7 @@ void *coro_malloc(struct coro *coro, size_t size) coro_defer(coro, free, coro->bump_ptr_alloc.ptr); /* Avoid checking if there's still space in the arena again. */ - return coro_malloc_bump_ptr(coro, aligned_size); + return CORO_MALLOC_BUMP_PTR(coro, aligned_size, size); } return coro_malloc_full(coro, size, free); From a8c3809eea9a318c1e2dd77d7f677bb09d5f999c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 19 Dec 2019 18:12:17 -0800 Subject: [PATCH 1336/2505] Always pass length of params/results arrays to DB access layer Should fix some issues with the database tests. --- src/samples/techempower/database.c | 50 +++++++++++++-------------- src/samples/techempower/database.h | 24 ++++++++----- src/samples/techempower/techempower.c | 35 +++++++++++-------- 3 files changed, 62 insertions(+), 47 deletions(-) diff --git a/src/samples/techempower/database.c b/src/samples/techempower/database.c index f8363196b..7196365f8 100644 --- a/src/samples/techempower/database.c +++ b/src/samples/techempower/database.c @@ -30,7 +30,7 @@ struct db_stmt { bool (*bind)(const struct db_stmt *stmt, struct db_row *rows, size_t n_rows); - bool (*step)(const struct db_stmt *stmt, struct db_row *row); + bool (*step)(const struct db_stmt *stmt, struct db_row *row, size_t n_cols); void (*finalize)(struct db_stmt *stmt); }; @@ -66,9 +66,8 @@ static bool db_stmt_bind_mysql(const struct db_stmt *stmt, if (!stmt_mysql->param_bind) { stmt_mysql->param_bind = calloc(n_rows, sizeof(MYSQL_BIND)); - if (!stmt_mysql->param_bind) { + if (!stmt_mysql->param_bind) return false; - } } else { mysql_stmt_reset(stmt_mysql->stmt); } @@ -92,7 +91,9 @@ static bool db_stmt_bind_mysql(const struct db_stmt *stmt, return !mysql_stmt_bind_param(stmt_mysql->stmt, stmt_mysql->param_bind); } -static bool db_stmt_step_mysql(const struct db_stmt *stmt, struct db_row *row) +static bool db_stmt_step_mysql(const struct db_stmt *stmt, + struct db_row *row, + size_t n_rows) { struct db_stmt_mysql *stmt_mysql = (struct db_stmt_mysql *)stmt; @@ -103,10 +104,6 @@ static bool db_stmt_step_mysql(const struct db_stmt *stmt, struct db_row *row) } if (!stmt_mysql->result_bind) { - size_t n_rows = 0; - for (struct db_row *r = row; r->kind != '\0'; r++) - n_rows++; - if (!n_rows) return false; @@ -115,12 +112,8 @@ static bool db_stmt_step_mysql(const struct db_stmt *stmt, struct db_row *row) if (!stmt_mysql->result_bind) return false; - stmt_mysql->param_bind = - calloc(n_rows, sizeof(*stmt_mysql->param_bind)); - if (!stmt_mysql->param_bind) { - free(stmt_mysql->result_bind); - return false; - } + free(stmt_mysql->param_bind); + stmt_mysql->param_bind = NULL; MYSQL_BIND *result = stmt_mysql->result_bind; for (size_t r = 0; r < n_rows; r++) { @@ -131,7 +124,7 @@ static bool db_stmt_step_mysql(const struct db_stmt *stmt, struct db_row *row) result[r].buffer_type = MYSQL_TYPE_LONG; result[r].buffer = &row[r].u.i; } else { - return false; + goto out; } result[r].is_null = false; @@ -139,10 +132,16 @@ static bool db_stmt_step_mysql(const struct db_stmt *stmt, struct db_row *row) } if (mysql_stmt_bind_result(stmt_mysql->stmt, result)) - return false; + goto out; } return mysql_stmt_fetch(stmt_mysql->stmt) == 0; + +out: + free(stmt_mysql->result_bind); + stmt_mysql->result_bind = NULL; + + return false; } static void db_stmt_finalize_mysql(struct db_stmt *stmt) @@ -248,16 +247,13 @@ static bool db_stmt_bind_sqlite(const struct db_stmt *stmt, { const struct db_stmt_sqlite *stmt_sqlite = (const struct db_stmt_sqlite *)stmt; - const struct db_row *rows_1_based = rows - 1; int ret; sqlite3_reset(stmt_sqlite->sqlite); sqlite3_clear_bindings(stmt_sqlite->sqlite); for (size_t row = 1; row <= n_rows; row++) { - const struct db_row *r = &rows_1_based[row]; - if (r->kind == '\0') - break; + const struct db_row *r = &rows[row - 1]; if (r->kind == 's') { ret = sqlite3_bind_text(stmt_sqlite->sqlite, (int)row, r->u.s, -1, @@ -276,7 +272,9 @@ static bool db_stmt_bind_sqlite(const struct db_stmt *stmt, return true; } -static bool db_stmt_step_sqlite(const struct db_stmt *stmt, struct db_row *row) +static bool db_stmt_step_sqlite(const struct db_stmt *stmt, + struct db_row *row, + size_t n_cols) { const struct db_stmt_sqlite *stmt_sqlite = (const struct db_stmt_sqlite *)stmt; @@ -284,8 +282,9 @@ static bool db_stmt_step_sqlite(const struct db_stmt *stmt, struct db_row *row) if (sqlite3_step(stmt_sqlite->sqlite) != SQLITE_ROW) return false; - int column_id = 0; - for (struct db_row *r = row; r->kind != '\0'; r++, column_id++) { + for (int column_id = 0; column_id < (int)n_cols; column_id++) { + struct db_row *r = &row[column_id]; + if (r->kind == 'i') { r->u.i = sqlite3_column_int(stmt_sqlite->sqlite, column_id); } else if (r->kind == 's') { @@ -372,9 +371,10 @@ db_stmt_bind(const struct db_stmt *stmt, struct db_row *rows, size_t n_rows) return stmt->bind(stmt, rows, n_rows); } -inline bool db_stmt_step(const struct db_stmt *stmt, struct db_row *row) +inline bool +db_stmt_step(const struct db_stmt *stmt, struct db_row *row, size_t n_cols) { - return stmt->step(stmt, row); + return stmt->step(stmt, row, n_cols); } inline void db_stmt_finalize(struct db_stmt *stmt) { stmt->finalize(stmt); } diff --git a/src/samples/techempower/database.h b/src/samples/techempower/database.h index e439d6ce0..553c0c6f4 100644 --- a/src/samples/techempower/database.h +++ b/src/samples/techempower/database.h @@ -14,7 +14,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #pragma once @@ -33,13 +34,20 @@ struct db_row { size_t buffer_length; }; -bool db_stmt_bind(const struct db_stmt *stmt, struct db_row *rows, size_t n_rows); -bool db_stmt_step(const struct db_stmt *stmt, struct db_row *row); +bool db_stmt_bind(const struct db_stmt *stmt, + struct db_row *rows, + size_t n_rows); +bool db_stmt_step(const struct db_stmt *stmt, + struct db_row *row, + size_t n_cols); void db_stmt_finalize(struct db_stmt *stmt); void db_disconnect(struct db *db); -struct db_stmt *db_prepare_stmt(const struct db *db, const char *sql, - const size_t sql_len); - -struct db *db_connect_sqlite(const char *path, bool read_only, const char *pragmas[]); -struct db *db_connect_mysql(const char *host, const char *user, const char *pass, const char *database); +struct db_stmt * +db_prepare_stmt(const struct db *db, const char *sql, const size_t sql_len); +struct db * +db_connect_sqlite(const char *path, bool read_only, const char *pragmas[]); +struct db *db_connect_mysql(const char *host, + const char *user, + const char *pass, + const char *database); diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index 883af1484..27f6b83ce 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -180,17 +180,21 @@ LWAN_HANDLER(json) static bool db_query(struct db_stmt *stmt, struct db_row rows[], + size_t n_rows, struct db_row results[], + size_t n_cols, struct db_json *out) { int id = rand() % 10000; + assert(n_rows >= 1); + rows[0].u.i = id; - if (UNLIKELY(!db_stmt_bind(stmt, rows, 1))) + if (UNLIKELY(!db_stmt_bind(stmt, rows, n_rows))) return false; - if (UNLIKELY(!db_stmt_step(stmt, results))) + if (UNLIKELY(!db_stmt_step(stmt, results, n_cols))) return false; out->id = id; @@ -201,8 +205,8 @@ static bool db_query(struct db_stmt *stmt, LWAN_HANDLER(db) { - struct db_row rows[1] = {{.kind = 'i'}}; - struct db_row results[] = {{.kind = 'i'}, {.kind = '\0'}}; + struct db_row rows[] = {{.kind = 'i'}}; + struct db_row results[] = {{.kind = 'i'}}; struct db_stmt *stmt = db_prepare_stmt(get_db(), random_number_query, sizeof(random_number_query) - 1); struct db_json db_json; @@ -212,7 +216,8 @@ LWAN_HANDLER(db) return HTTP_INTERNAL_ERROR; } - bool queried = db_query(stmt, rows, results, &db_json); + bool queried = db_query(stmt, rows, N_ELEMENTS(rows), results, + N_ELEMENTS(results), &db_json); db_stmt_finalize(stmt); @@ -245,10 +250,11 @@ LWAN_HANDLER(queries) return HTTP_INTERNAL_ERROR; struct queries_json qj = {.queries_len = (size_t)queries}; - struct db_row rows[1] = {{.kind = 'i'}}; - struct db_row results[] = {{.kind = 'i'}, {.kind = '\0'}}; + struct db_row rows[] = {{.kind = 'i'}}; + struct db_row results[] = {{.kind = 'i'}}; for (long i = 0; i < queries; i++) { - if (!db_query(stmt, rows, results, &qj.queries[i])) + if (!db_query(stmt, rows, N_ELEMENTS(rows), results, + N_ELEMENTS(results), &qj.queries[i])) goto out; } @@ -322,12 +328,13 @@ static int fortune_list_generator(struct coro *coro, void *data) fortune_array_init(&fortunes); - struct db_row results[] = {{.kind = 'i'}, - {.kind = 's', - .u.s = fortune_buffer, - .buffer_length = sizeof(fortune_buffer)}, - {.kind = '\0'}}; - while (db_stmt_step(stmt, results)) { + struct db_row results[] = { + {.kind = 'i'}, + {.kind = 's', + .u.s = fortune_buffer, + .buffer_length = sizeof(fortune_buffer)}, + }; + while (db_stmt_step(stmt, results, N_ELEMENTS(results))) { if (!append_fortune(coro, &fortunes, results[0].u.i, results[1].u.s)) goto out; } From 9e24206fe363562126bcffc411178d3dd03af598 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 19 Dec 2019 18:13:13 -0800 Subject: [PATCH 1337/2505] Cleanup declaration of fortune_desc --- src/samples/techempower/techempower.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index 27f6b83ce..aaab6c99f 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -106,14 +106,14 @@ static int fortune_list_generator(struct coro *coro, void *data); #undef TPL_STRUCT #define TPL_STRUCT struct Fortune -static const struct lwan_var_descriptor fortune_item_desc[] = { - TPL_VAR_INT(item.id), - TPL_VAR_STR_ESCAPE(item.message), - TPL_VAR_SENTINEL, -}; - static const struct lwan_var_descriptor fortune_desc[] = { - TPL_VAR_SEQUENCE(item, fortune_list_generator, fortune_item_desc), + TPL_VAR_SEQUENCE(item, + fortune_list_generator, + ((const struct lwan_var_descriptor[]){ + TPL_VAR_INT(item.id), + TPL_VAR_STR_ESCAPE(item.message), + TPL_VAR_SENTINEL, + })), TPL_VAR_SENTINEL, }; From 1727cf5e655c7ebd37cbad239da79b3ffaab3a12 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 19 Dec 2019 21:34:59 -0800 Subject: [PATCH 1338/2505] Fix spurious errors in the TWFB queries benchmark There's no id = 0 in the database, so both MySQL and SQLite would return 'no data found', causing the request handler to error out prematurely. --- src/samples/techempower/techempower.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index aaab6c99f..ef121195b 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -185,7 +185,7 @@ static bool db_query(struct db_stmt *stmt, size_t n_cols, struct db_json *out) { - int id = rand() % 10000; + const int id = (rand() % 10000) + 1; assert(n_rows >= 1); From aa1748bc62f22d3bdbcd65b3d9e86e463d915358 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 19 Dec 2019 21:36:43 -0800 Subject: [PATCH 1339/2505] Clean ups in the DAL for the TWFB benchmark --- src/samples/techempower/database.c | 44 ++++++++++++++++-------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/samples/techempower/database.c b/src/samples/techempower/database.c index 7196365f8..e7d723ae5 100644 --- a/src/samples/techempower/database.c +++ b/src/samples/techempower/database.c @@ -68,14 +68,11 @@ static bool db_stmt_bind_mysql(const struct db_stmt *stmt, stmt_mysql->param_bind = calloc(n_rows, sizeof(MYSQL_BIND)); if (!stmt_mysql->param_bind) return false; - } else { - mysql_stmt_reset(stmt_mysql->stmt); } - for (size_t row = 0; row < n_rows; row++) { - if (rows[row].kind == '\0') - break; + mysql_stmt_reset(stmt_mysql->stmt); + for (size_t row = 0; row < n_rows; row++) { MYSQL_BIND *param = &stmt_mysql->param_bind[row]; if (rows[row].kind == 's') { param->buffer_type = MYSQL_TYPE_STRING; @@ -84,6 +81,7 @@ static bool db_stmt_bind_mysql(const struct db_stmt *stmt, param->buffer_type = MYSQL_TYPE_LONG; param->buffer = &rows[row].u.i; } + param->is_null = false; param->length = 0; } @@ -135,7 +133,10 @@ static bool db_stmt_step_mysql(const struct db_stmt *stmt, goto out; } - return mysql_stmt_fetch(stmt_mysql->stmt) == 0; + if (!mysql_stmt_fetch(stmt_mysql->stmt)) + return true; + + lwan_status_error("Got error from MySQL: %s", mysql_stmt_error(stmt_mysql->stmt)); out: free(stmt_mysql->result_bind); @@ -249,31 +250,30 @@ static bool db_stmt_bind_sqlite(const struct db_stmt *stmt, (const struct db_stmt_sqlite *)stmt; int ret; - sqlite3_reset(stmt_sqlite->sqlite); sqlite3_clear_bindings(stmt_sqlite->sqlite); - for (size_t row = 1; row <= n_rows; row++) { - const struct db_row *r = &rows[row - 1]; + for (size_t row = 0; row < n_rows; row++) { + const struct db_row *r = &rows[row]; + int sql_row = (int)row + 1; if (r->kind == 's') { - ret = sqlite3_bind_text(stmt_sqlite->sqlite, (int)row, r->u.s, -1, + ret = sqlite3_bind_text(stmt_sqlite->sqlite, sql_row, r->u.s, -1, NULL); - if (ret != SQLITE_OK) - return false; } else if (r->kind == 'i') { - ret = sqlite3_bind_int(stmt_sqlite->sqlite, (int)row, r->u.i); - if (ret != SQLITE_OK) - return false; + ret = sqlite3_bind_int(stmt_sqlite->sqlite, sql_row, r->u.i); } else { return false; } + + if (ret != SQLITE_OK) + return false; } return true; } static bool db_stmt_step_sqlite(const struct db_stmt *stmt, - struct db_row *row, + struct db_row *columns, size_t n_cols) { const struct db_stmt_sqlite *stmt_sqlite = @@ -283,18 +283,20 @@ static bool db_stmt_step_sqlite(const struct db_stmt *stmt, return false; for (int column_id = 0; column_id < (int)n_cols; column_id++) { - struct db_row *r = &row[column_id]; + struct db_row *c = &columns[column_id]; - if (r->kind == 'i') { - r->u.i = sqlite3_column_int(stmt_sqlite->sqlite, column_id); - } else if (r->kind == 's') { - r->u.s = + if (c->kind == 'i') { + c->u.i = sqlite3_column_int(stmt_sqlite->sqlite, column_id); + } else if (c->kind == 's') { + c->u.s = (char *)sqlite3_column_text(stmt_sqlite->sqlite, column_id); } else { return false; } } + sqlite3_reset(stmt_sqlite->sqlite); + return true; } From a1cf8c9e2711b0aac34fab44b08d82df02fd4e32 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 20 Dec 2019 06:54:44 -0800 Subject: [PATCH 1340/2505] Revert "Clean ups in the DAL for the TWFB benchmark" This reverts commit aa1748bc62f22d3bdbcd65b3d9e86e463d915358. It was causing an infinite loop in the Fortunes benchmark and I don't feel like debugging this right now. I should add TWFB testing to the test suite. --- src/samples/techempower/database.c | 44 ++++++++++++++---------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/src/samples/techempower/database.c b/src/samples/techempower/database.c index e7d723ae5..7196365f8 100644 --- a/src/samples/techempower/database.c +++ b/src/samples/techempower/database.c @@ -68,11 +68,14 @@ static bool db_stmt_bind_mysql(const struct db_stmt *stmt, stmt_mysql->param_bind = calloc(n_rows, sizeof(MYSQL_BIND)); if (!stmt_mysql->param_bind) return false; + } else { + mysql_stmt_reset(stmt_mysql->stmt); } - mysql_stmt_reset(stmt_mysql->stmt); - for (size_t row = 0; row < n_rows; row++) { + if (rows[row].kind == '\0') + break; + MYSQL_BIND *param = &stmt_mysql->param_bind[row]; if (rows[row].kind == 's') { param->buffer_type = MYSQL_TYPE_STRING; @@ -81,7 +84,6 @@ static bool db_stmt_bind_mysql(const struct db_stmt *stmt, param->buffer_type = MYSQL_TYPE_LONG; param->buffer = &rows[row].u.i; } - param->is_null = false; param->length = 0; } @@ -133,10 +135,7 @@ static bool db_stmt_step_mysql(const struct db_stmt *stmt, goto out; } - if (!mysql_stmt_fetch(stmt_mysql->stmt)) - return true; - - lwan_status_error("Got error from MySQL: %s", mysql_stmt_error(stmt_mysql->stmt)); + return mysql_stmt_fetch(stmt_mysql->stmt) == 0; out: free(stmt_mysql->result_bind); @@ -250,30 +249,31 @@ static bool db_stmt_bind_sqlite(const struct db_stmt *stmt, (const struct db_stmt_sqlite *)stmt; int ret; + sqlite3_reset(stmt_sqlite->sqlite); sqlite3_clear_bindings(stmt_sqlite->sqlite); - for (size_t row = 0; row < n_rows; row++) { - const struct db_row *r = &rows[row]; - int sql_row = (int)row + 1; + for (size_t row = 1; row <= n_rows; row++) { + const struct db_row *r = &rows[row - 1]; if (r->kind == 's') { - ret = sqlite3_bind_text(stmt_sqlite->sqlite, sql_row, r->u.s, -1, + ret = sqlite3_bind_text(stmt_sqlite->sqlite, (int)row, r->u.s, -1, NULL); + if (ret != SQLITE_OK) + return false; } else if (r->kind == 'i') { - ret = sqlite3_bind_int(stmt_sqlite->sqlite, sql_row, r->u.i); + ret = sqlite3_bind_int(stmt_sqlite->sqlite, (int)row, r->u.i); + if (ret != SQLITE_OK) + return false; } else { return false; } - - if (ret != SQLITE_OK) - return false; } return true; } static bool db_stmt_step_sqlite(const struct db_stmt *stmt, - struct db_row *columns, + struct db_row *row, size_t n_cols) { const struct db_stmt_sqlite *stmt_sqlite = @@ -283,20 +283,18 @@ static bool db_stmt_step_sqlite(const struct db_stmt *stmt, return false; for (int column_id = 0; column_id < (int)n_cols; column_id++) { - struct db_row *c = &columns[column_id]; + struct db_row *r = &row[column_id]; - if (c->kind == 'i') { - c->u.i = sqlite3_column_int(stmt_sqlite->sqlite, column_id); - } else if (c->kind == 's') { - c->u.s = + if (r->kind == 'i') { + r->u.i = sqlite3_column_int(stmt_sqlite->sqlite, column_id); + } else if (r->kind == 's') { + r->u.s = (char *)sqlite3_column_text(stmt_sqlite->sqlite, column_id); } else { return false; } } - sqlite3_reset(stmt_sqlite->sqlite); - return true; } From 99581f799c6f19915bc15cbcce1154807bbd4d74 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 21 Dec 2019 22:44:58 -0800 Subject: [PATCH 1341/2505] Move websocket-related things to lwan-websocket.c --- src/lib/CMakeLists.txt | 5 +- src/lib/lwan-response.c | 186 ---------------------------------- src/lib/lwan-websocket.c | 208 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 211 insertions(+), 188 deletions(-) create mode 100644 src/lib/lwan-websocket.c diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index e6af20dca..024ce713b 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -31,7 +31,9 @@ set(SOURCES lwan-template.c lwan-thread.c lwan-time.c + lwan-tq.c lwan-trie.c + lwan-websocket.c missing.c missing-pthread.c murmur3.c @@ -39,9 +41,8 @@ set(SOURCES queue.c realpathat.c sd-daemon.c - timeout.c sha1.c - lwan-tq.c + timeout.c ) if (HAVE_LUA) diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 0d92dc16b..5ecc8fefd 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -509,189 +509,3 @@ void lwan_response_send_event(struct lwan_request *request, const char *event) lwan_strbuf_reset(request->response.buffer); coro_yield(request->conn->coro, CONN_CORO_WANT_WRITE); } - -enum ws_opcode { - WS_OPCODE_CONTINUATION = 0, - WS_OPCODE_TEXT = 1, - WS_OPCODE_BINARY = 2, - WS_OPCODE_CLOSE = 8, - WS_OPCODE_PING = 9, - WS_OPCODE_PONG = 10, -}; - -static void write_websocket_frame(struct lwan_request *request, - unsigned char header_byte, - char *msg, - size_t len) -{ - struct iovec vec[4]; - uint8_t net_len_byte, opcode_byte; - uint16_t net_len_short; - uint64_t net_len_long; - size_t last = 0; - - vec[last++] = (struct iovec){.iov_base = &header_byte, .iov_len = 1}; - - if (len <= 125) { - net_len_byte = (uint8_t)len; - - vec[last++] = (struct iovec){.iov_base = &net_len_byte, .iov_len = 1}; - } else if (len <= 65535) { - net_len_short = htons((uint16_t)len); - opcode_byte = 0x7e; - - vec[last++] = (struct iovec){.iov_base = &opcode_byte, .iov_len = 1}; - vec[last++] = (struct iovec){.iov_base = &net_len_short, .iov_len = 2}; - } else { - net_len_long = htobe64((uint64_t)len); - opcode_byte = 0x7f; - - vec[last++] = (struct iovec){.iov_base = &opcode_byte, .iov_len = 1}; - vec[last++] = (struct iovec){.iov_base = &net_len_long, .iov_len = 8}; - } - - vec[last++] = (struct iovec){.iov_base = msg, .iov_len = len}; - - lwan_writev(request, vec, last); -} - -void lwan_response_websocket_write(struct lwan_request *request) -{ - size_t len = lwan_strbuf_get_length(request->response.buffer); - char *msg = lwan_strbuf_get_buffer(request->response.buffer); - /* FIXME: does it make a difference if we use WS_OPCODE_TEXT or - * WS_OPCODE_BINARY? */ - unsigned char header = 0x80 | WS_OPCODE_TEXT; - - if (!(request->conn->flags & CONN_IS_WEBSOCKET)) - return; - - write_websocket_frame(request, header, msg, len); - lwan_strbuf_reset(request->response.buffer); -} - -static void send_websocket_pong(struct lwan_request *request, size_t len) -{ - size_t generation; - char *temp; - - if (UNLIKELY(len > 125)) { - lwan_status_debug("Received PING opcode with length %zu." - "Max is 125. Aborting connection.", - len); - goto abort; - } - - generation = coro_deferred_get_generation(request->conn->coro); - - temp = coro_malloc(request->conn->coro, len); - if (UNLIKELY(!temp)) - goto abort; - - lwan_recv(request, temp, len, 0); - write_websocket_frame(request, WS_OPCODE_PONG, temp, len); - - coro_deferred_run(request->conn->coro, generation); - - return; - -abort: - coro_yield(request->conn->coro, CONN_CORO_ABORT); - __builtin_unreachable(); -} - -bool lwan_response_websocket_read(struct lwan_request *request) -{ - uint16_t header; - uint64_t len_frame; - char *msg; - bool continuation = false; - bool fin; - - if (!(request->conn->flags & CONN_IS_WEBSOCKET)) - return false; - - lwan_strbuf_reset(request->response.buffer); - -next_frame: - lwan_recv(request, &header, sizeof(header), 0); - - fin = (header & 0x8000); - - switch ((enum ws_opcode)((header & 0xf00) >> 8)) { - case WS_OPCODE_CONTINUATION: - continuation = true; - break; - case WS_OPCODE_TEXT: - case WS_OPCODE_BINARY: - break; - case WS_OPCODE_CLOSE: - request->conn->flags &= ~CONN_IS_WEBSOCKET; - break; - case WS_OPCODE_PING: - /* FIXME: handling PING packets here doesn't seem ideal; they won't be - * handled, for instance, if the user never receives data from the - * websocket. */ - send_websocket_pong(request, header & 0x7f); - goto next_frame; - default: - lwan_status_debug( - "Received unexpected WebSockets opcode: 0x%x, ignoring", - (header & 0xf00) >> 8); - goto next_frame; - } - - switch (header & 0x7f) { - default: - len_frame = (uint64_t)(header & 0x7f); - break; - case 0x7e: - lwan_recv(request, &len_frame, 2, 0); - len_frame = (uint64_t)ntohs((uint16_t)len_frame); - break; - case 0x7f: - lwan_recv(request, &len_frame, 8, 0); - len_frame = be64toh(len_frame); - break; - } - - size_t cur_len = lwan_strbuf_get_length(request->response.buffer); - - if (UNLIKELY(!lwan_strbuf_grow_by(request->response.buffer, len_frame))) { - coro_yield(request->conn->coro, CONN_CORO_ABORT); - __builtin_unreachable(); - } - - msg = lwan_strbuf_get_buffer(request->response.buffer) + cur_len; - - if (LIKELY(header & 0x80)) { - /* Payload is masked; should always be true on Client->Server comms but - * don't assume this is always the case. */ - union { - char as_char[4]; - uint32_t as_int; - } masks; - struct iovec vec[] = { - {.iov_base = masks.as_char, .iov_len = sizeof(masks.as_char)}, - {.iov_base = msg, .iov_len = len_frame}, - }; - - lwan_readv(request, vec, N_ELEMENTS(vec)); - - if (masks.as_int != 0x00000000) { - for (uint64_t i = 0; i < len_frame; i++) - msg[i] ^= masks.as_char[i % sizeof(masks)]; - } - } else { - lwan_recv(request, msg, len_frame, 0); - } - - if (continuation && !fin) { - coro_yield(request->conn->coro, CONN_CORO_WANT_READ); - continuation = false; - - goto next_frame; - } - - return request->conn->flags & CONN_IS_WEBSOCKET; -} diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c new file mode 100644 index 000000000..7f9f02988 --- /dev/null +++ b/src/lib/lwan-websocket.c @@ -0,0 +1,208 @@ +/* + * lwan - simple web server + * Copyright (c) 2019 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include "lwan-private.h" +#include "lwan-io-wrappers.h" + +enum ws_opcode { + WS_OPCODE_CONTINUATION = 0, + WS_OPCODE_TEXT = 1, + WS_OPCODE_BINARY = 2, + WS_OPCODE_CLOSE = 8, + WS_OPCODE_PING = 9, + WS_OPCODE_PONG = 10, +}; + +static void write_websocket_frame(struct lwan_request *request, + unsigned char header_byte, + char *msg, + size_t len) +{ + struct iovec vec[4]; + uint8_t net_len_byte, opcode_byte; + uint16_t net_len_short; + uint64_t net_len_long; + size_t last = 0; + + vec[last++] = (struct iovec){.iov_base = &header_byte, .iov_len = 1}; + + if (len <= 125) { + net_len_byte = (uint8_t)len; + + vec[last++] = (struct iovec){.iov_base = &net_len_byte, .iov_len = 1}; + } else if (len <= 65535) { + net_len_short = htons((uint16_t)len); + opcode_byte = 0x7e; + + vec[last++] = (struct iovec){.iov_base = &opcode_byte, .iov_len = 1}; + vec[last++] = (struct iovec){.iov_base = &net_len_short, .iov_len = 2}; + } else { + net_len_long = htobe64((uint64_t)len); + opcode_byte = 0x7f; + + vec[last++] = (struct iovec){.iov_base = &opcode_byte, .iov_len = 1}; + vec[last++] = (struct iovec){.iov_base = &net_len_long, .iov_len = 8}; + } + + vec[last++] = (struct iovec){.iov_base = msg, .iov_len = len}; + + lwan_writev(request, vec, last); +} + +void lwan_response_websocket_write(struct lwan_request *request) +{ + size_t len = lwan_strbuf_get_length(request->response.buffer); + char *msg = lwan_strbuf_get_buffer(request->response.buffer); + /* FIXME: does it make a difference if we use WS_OPCODE_TEXT or + * WS_OPCODE_BINARY? */ + unsigned char header = 0x80 | WS_OPCODE_TEXT; + + if (!(request->conn->flags & CONN_IS_WEBSOCKET)) + return; + + write_websocket_frame(request, header, msg, len); + lwan_strbuf_reset(request->response.buffer); +} + +static void send_websocket_pong(struct lwan_request *request, size_t len) +{ + size_t generation; + char *temp; + + if (UNLIKELY(len > 125)) { + lwan_status_debug("Received PING opcode with length %zu." + "Max is 125. Aborting connection.", + len); + goto abort; + } + + generation = coro_deferred_get_generation(request->conn->coro); + + temp = coro_malloc(request->conn->coro, len); + if (UNLIKELY(!temp)) + goto abort; + + lwan_recv(request, temp, len, 0); + write_websocket_frame(request, WS_OPCODE_PONG, temp, len); + + coro_deferred_run(request->conn->coro, generation); + + return; + +abort: + coro_yield(request->conn->coro, CONN_CORO_ABORT); + __builtin_unreachable(); +} + +bool lwan_response_websocket_read(struct lwan_request *request) +{ + uint16_t header; + uint64_t len_frame; + char *msg; + bool continuation = false; + bool fin; + + if (!(request->conn->flags & CONN_IS_WEBSOCKET)) + return false; + + lwan_strbuf_reset(request->response.buffer); + +next_frame: + lwan_recv(request, &header, sizeof(header), 0); + + fin = (header & 0x8000); + + switch ((enum ws_opcode)((header & 0xf00) >> 8)) { + case WS_OPCODE_CONTINUATION: + continuation = true; + break; + case WS_OPCODE_TEXT: + case WS_OPCODE_BINARY: + break; + case WS_OPCODE_CLOSE: + request->conn->flags &= ~CONN_IS_WEBSOCKET; + break; + case WS_OPCODE_PING: + /* FIXME: handling PING packets here doesn't seem ideal; they won't be + * handled, for instance, if the user never receives data from the + * websocket. */ + send_websocket_pong(request, header & 0x7f); + goto next_frame; + default: + lwan_status_debug( + "Received unexpected WebSockets opcode: 0x%x, ignoring", + (header & 0xf00) >> 8); + goto next_frame; + } + + switch (header & 0x7f) { + default: + len_frame = (uint64_t)(header & 0x7f); + break; + case 0x7e: + lwan_recv(request, &len_frame, 2, 0); + len_frame = (uint64_t)ntohs((uint16_t)len_frame); + break; + case 0x7f: + lwan_recv(request, &len_frame, 8, 0); + len_frame = be64toh(len_frame); + break; + } + + size_t cur_len = lwan_strbuf_get_length(request->response.buffer); + + if (UNLIKELY(!lwan_strbuf_grow_by(request->response.buffer, len_frame))) { + coro_yield(request->conn->coro, CONN_CORO_ABORT); + __builtin_unreachable(); + } + + msg = lwan_strbuf_get_buffer(request->response.buffer) + cur_len; + + if (LIKELY(header & 0x80)) { + /* Payload is masked; should always be true on Client->Server comms but + * don't assume this is always the case. */ + union { + char as_char[4]; + uint32_t as_int; + } masks; + struct iovec vec[] = { + {.iov_base = masks.as_char, .iov_len = sizeof(masks.as_char)}, + {.iov_base = msg, .iov_len = len_frame}, + }; + + lwan_readv(request, vec, N_ELEMENTS(vec)); + + if (masks.as_int != 0x00000000) { + for (uint64_t i = 0; i < len_frame; i++) + msg[i] ^= masks.as_char[i % sizeof(masks)]; + } + } else { + lwan_recv(request, msg, len_frame, 0); + } + + if (continuation && !fin) { + coro_yield(request->conn->coro, CONN_CORO_WANT_READ); + continuation = false; + + goto next_frame; + } + + return request->conn->flags & CONN_IS_WEBSOCKET; +} From 414993de80497c0da375cb66eaedb76beaeb95d5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 22 Dec 2019 20:35:50 -0800 Subject: [PATCH 1342/2505] Remove one branch from prepare_for_response() Make the parsing of the Accept-Encoding header lazy and conditional on compressed data being available for that particular encoding, and only happen inside the file serving module. --- src/lib/lwan-mod-serve-files.c | 19 ++++++++++++------- src/lib/lwan-request.c | 14 +++++++++++--- src/lib/lwan.h | 5 ++++- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 7fa7fec23..6ef4ae2c8 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -1109,6 +1109,12 @@ compute_range(struct lwan_request *request, off_t *from, off_t *to, off_t size) return HTTP_PARTIAL_CONTENT; } +static inline bool accepts_encoding(struct lwan_request *request, + const enum lwan_request_flags encoding) +{ + return lwan_request_get_accept_encoding(request) & encoding; +} + static enum lwan_http_status sendfile_serve(struct lwan_request *request, void *data) { @@ -1122,7 +1128,7 @@ static enum lwan_http_status sendfile_serve(struct lwan_request *request, size_t size; int fd; - if (sd->compressed.size && (request->flags & REQUEST_ACCEPT_GZIP)) { + if (sd->compressed.size && accepts_encoding(request, REQUEST_ACCEPT_GZIP)) { from = 0; to = (off_t)sd->compressed.size; @@ -1189,20 +1195,20 @@ static enum lwan_http_status mmap_serve(struct lwan_request *request, struct mmap_cache_data *md = &fce->mmap_cache_data; #if defined(HAVE_ZSTD) - if (md->zstd.len && (request->flags & REQUEST_ACCEPT_ZSTD)) { + if (md->zstd.len && accepts_encoding(request, REQUEST_ACCEPT_ZSTD)) { return serve_buffer(request, fce->mime_type, md->zstd.value, md->zstd.len, zstd_compression_hdr, HTTP_OK); } #endif #if defined(HAVE_BROTLI) - if (md->brotli.len && (request->flags & REQUEST_ACCEPT_BROTLI)) { + if (md->brotli.len && accepts_encoding(request, REQUEST_ACCEPT_BROTLI)) { return serve_buffer(request, fce->mime_type, md->brotli.value, md->brotli.len, br_compression_hdr, HTTP_OK); } #endif - if (md->deflated.len && (request->flags & REQUEST_ACCEPT_DEFLATE)) { + if (md->deflated.len && accepts_encoding(request, REQUEST_ACCEPT_DEFLATE)) { return serve_buffer(request, fce->mime_type, md->deflated.value, md->deflated.len, deflate_compression_hdr, HTTP_OK); } @@ -1228,13 +1234,13 @@ static enum lwan_http_status dirlist_serve(struct lwan_request *request, if (!icon) { #if defined(HAVE_BROTLI) - if (dd->brotli.len && (request->flags & REQUEST_ACCEPT_BROTLI)) { + if (dd->brotli.len && accepts_encoding(request, REQUEST_ACCEPT_BROTLI)) { return serve_buffer(request, fce->mime_type, dd->brotli.value, dd->brotli.len, br_compression_hdr, HTTP_OK); } #endif - if (dd->deflated.len && (request->flags & REQUEST_ACCEPT_DEFLATE)) { + if (dd->deflated.len && accepts_encoding(request, REQUEST_ACCEPT_DEFLATE)) { return serve_buffer(request, fce->mime_type, dd->deflated.value, dd->deflated.len, deflate_compression_hdr, HTTP_OK); @@ -1312,7 +1318,6 @@ static const struct lwan_module module = { .create_from_hash = serve_files_create_from_hash, .destroy = serve_files_destroy, .handle_request = serve_files_handle_request, - .flags = HANDLER_PARSE_ACCEPT_ENCODING, }; LWAN_REGISTER_MODULE(serve_files, &module); diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index e93f92e6d..6b5f1c510 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1333,9 +1333,6 @@ static enum lwan_http_status prepare_for_response(struct lwan_url_map *url_map, request->url.len--; } - if (url_map->flags & HANDLER_PARSE_ACCEPT_ENCODING) - parse_accept_encoding(request); - if (lwan_request_get_method(request) == REQUEST_METHOD_POST) { enum lwan_http_status status; @@ -1655,6 +1652,17 @@ lwan_request_get_post_params(struct lwan_request *request) return &request->helper->post_params; } +ALWAYS_INLINE enum lwan_request_flags +lwan_request_get_accept_encoding(struct lwan_request *request) +{ + if (!(request->flags & REQUEST_PARSED_ACCEPT_ENCODING)) { + parse_accept_encoding(request); + request->flags |= REQUEST_PARSED_ACCEPT_ENCODING; + } + + return request->flags & REQUEST_ACCEPT_MASK; +} + #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION static int useless_coro_for_fuzzing(struct coro *c __attribute__((unused)), void *data __attribute__((unused))) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 2506f131b..a9c1352c0 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -199,7 +199,6 @@ enum lwan_handler_flags { HANDLER_MUST_AUTHORIZE = 1 << 1, HANDLER_CAN_REWRITE_URL = 1 << 2, HANDLER_DATA_IS_HASH_TABLE = 1 << 3, - HANDLER_PARSE_ACCEPT_ENCODING = 1 << 4, HANDLER_PARSE_MASK = HANDLER_HAS_POST_DATA, }; @@ -225,6 +224,8 @@ enum lwan_request_flags { REQUEST_ACCEPT_GZIP = 1 << 4, REQUEST_ACCEPT_BROTLI = 1 << 5, REQUEST_ACCEPT_ZSTD = 1 << 6, + REQUEST_ACCEPT_MASK = 1 << 3 | 1 << 4 | 1 << 5 | 1 << 6, + REQUEST_IS_HTTP_1_0 = 1 << 7, REQUEST_ALLOW_PROXY_REQS = 1 << 8, REQUEST_PROXIED = 1 << 9, @@ -550,6 +551,8 @@ const struct lwan_key_value_array * lwan_request_get_query_params(struct lwan_request *request); const struct lwan_key_value_array * lwan_request_get_post_params(struct lwan_request *request); +enum lwan_request_flags +lwan_request_get_accept_encoding(struct lwan_request *request); enum lwan_http_status lwan_request_websocket_upgrade(struct lwan_request *request); From dd9c4757b234a58dde08325a1c452a8359bfad78 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 28 Dec 2019 00:03:35 -0800 Subject: [PATCH 1343/2505] Instrument coro_malloc() with Valgrind's memcheck.h header Instead of using the VALGRIND_FREELIKE_BLOCK() and VALGRIND_MALLOCLIKE_BLOCK() macros, which do not map 1:1 to the BPA, use the memcheck-specific macros VALGRIND_MAKE_MEM_NOACCESS() and VALGRIND_MAKE_MEM_UNDEFINED(), which work in a similar manner to ASan's poisoning of memory regions. --- src/lib/lwan-coro.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index d027da959..0e1fb46bf 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -35,6 +35,7 @@ #if !defined(NDEBUG) && defined(HAVE_VALGRIND) #define INSTRUMENT_FOR_VALGRIND #include +#include #endif #if defined(__clang__) @@ -397,7 +398,7 @@ void *coro_malloc_full(struct coro *coro, static void instrument_bpa_free(void *ptr, void *size) { #if defined(INSTRUMENT_FOR_VALGRIND) - VALGRIND_FREELIKE_BLOCK(ptr, 0); + VALGRIND_MAKE_MEM_NOACCESS(ptr, (size_t)(uintptr_t)size); #endif #if defined(INSTRUMENT_FOR_ASAN) @@ -426,7 +427,7 @@ static inline void *coro_malloc_bump_ptr(struct coro *coro, size_t aligned_size) */ #if defined(INSTRUMENT_FOR_VALGRIND) - VALGRIND_MALLOCLIKE_BLOCK(ptr, requested_size, 0, 0); + VALGRIND_MAKE_MEM_UNDEFINED(ptr, requested_size); #endif #if defined(INSTRUMENT_FOR_ASAN) __asan_unpoison_memory_region(ptr, requested_size); @@ -471,16 +472,16 @@ void *coro_malloc(struct coro *coro, size_t size) if (UNLIKELY(!coro->bump_ptr_alloc.ptr)) return NULL; + coro->bump_ptr_alloc.remaining = CORO_BUMP_PTR_ALLOC_SIZE; + #if defined(INSTRUMENT_FOR_ASAN) - /* There's apparently no way to poison the whole BPA arena with Valgrind - * like it's possible with ASAN (short of registering the BPA arena as - * a mempool, but that also doesn't really map 1:1 to how this works). - */ __asan_poison_memory_region(coro->bump_ptr_alloc.ptr, CORO_BUMP_PTR_ALLOC_SIZE); #endif - - coro->bump_ptr_alloc.remaining = CORO_BUMP_PTR_ALLOC_SIZE; +#if defined(INSTRUMENT_FOR_VALGRIND) + VALGRIND_MAKE_MEM_NOACCESS(coro->bump_ptr_alloc.ptr, + CORO_BUMP_PTR_ALLOC_SIZE); +#endif coro_defer(coro, free, coro->bump_ptr_alloc.ptr); /* Avoid checking if there's still space in the arena again. */ From 49acb4dcba47ad3de28bd5d6f12a6ec8c73b1465 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 28 Dec 2019 09:39:42 -0800 Subject: [PATCH 1344/2505] Disable semantic interposition and align functions on 32-byte boundary (Just experiments for now, might revert later.) --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7863c87d8..a8ac34a77 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -220,6 +220,8 @@ else () endif () if (${CMAKE_BUILD_TYPE} MATCHES "Rel") + enable_c_flag_if_avail(-falign-functions=32 C_FLAGS_REL HAVE_ALIGN_FNS) + enable_c_flag_if_avail(-fno-semantic-interposition C_FLAGS_REL HAVE_NO_SEMANTIC_INTERPOSITION) enable_c_flag_if_avail(-malign-data=abi C_FLAGS_REL HAVE_ALIGN_DATA) enable_c_flag_if_avail(-fno-asynchronous-unwind-tables C_FLAGS_REL HAVE_NO_ASYNC_UNWIND_TABLES) From f6ae24b2121de6b7bd311379aa55aa4192b4522e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 28 Dec 2019 11:16:30 -0800 Subject: [PATCH 1345/2505] Fix build on macOS --- src/lib/lwan-websocket.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index 7f9f02988..47d2743fb 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -18,6 +18,8 @@ * USA. */ +#include + #include "lwan-private.h" #include "lwan-io-wrappers.h" From 991e19255573b2e7fac1c646c065788d81fe2366 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 29 Dec 2019 23:22:42 -0800 Subject: [PATCH 1346/2505] Correctly generate TWFB queries results with an array (not object) --- src/samples/techempower/json.c | 13 ++++- src/samples/techempower/json.h | 22 +++++++ src/samples/techempower/techempower.c | 83 ++++++++++++++++----------- 3 files changed, 82 insertions(+), 36 deletions(-) diff --git a/src/samples/techempower/json.c b/src/samples/techempower/json.c index ac421ced7..c9a999589 100644 --- a/src/samples/techempower/json.c +++ b/src/samples/techempower/json.c @@ -811,6 +811,16 @@ bool_encode(const bool *value, json_append_bytes_t append_bytes, void *data) return append_bytes("false", 5, data); } +int json_arr_encode(const struct json_obj_descr *descr, + const void *val, + json_append_bytes_t append_bytes, + void *data) +{ + void *ptr = (char *)val + descr->offset; + + return arr_encode(descr->array.element_descr, ptr, val, append_bytes, data); +} + static int encode(const struct json_obj_descr *descr, const void *val, json_append_bytes_t append_bytes, @@ -825,7 +835,8 @@ static int encode(const struct json_obj_descr *descr, case JSON_TOK_STRING: return str_encode(ptr, append_bytes, data); case JSON_TOK_LIST_START: - return arr_encode(descr->array.element_descr, ptr, val, append_bytes, data); + return arr_encode(descr->array.element_descr, ptr, val, + append_bytes, data); case JSON_TOK_OBJECT_START: return json_obj_encode(descr->object.sub_descr, descr->object.sub_descr_len, ptr, append_bytes, diff --git a/src/samples/techempower/json.h b/src/samples/techempower/json.h index f11dcc04d..f04863f64 100644 --- a/src/samples/techempower/json.h +++ b/src/samples/techempower/json.h @@ -618,6 +618,28 @@ int json_obj_encode(const struct json_obj_descr *descr, json_append_bytes_t append_bytes, void *data); +/** + * @brief Encodes an array using an arbitrary writer function + * + * @param descr Pointer to the descriptor array + * + * @param descr_len Number of elements in the descriptor array + * + * @param val Struct holding the values + * + * @param append_bytes Function to append bytes to the output + * + * @param data Data pointer to be passed to the append_bytes callback + * function. + * + * @return 0 if object has been successfully encoded. A negative value + * indicates an error. + */ +int json_arr_encode(const struct json_obj_descr *descr, + const void *val, + json_append_bytes_t append_bytes, + void *data); + #ifdef __cplusplus } #endif diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index ef121195b..08f054879 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -49,30 +49,6 @@ struct db_connection_params { static struct db_connection_params db_connection_params; -static struct db *get_db(void) -{ - static __thread struct db *database; - - if (!database) { - switch (db_connection_params.type) { - case DB_CONN_MYSQL: - database = db_connect_mysql(db_connection_params.mysql.hostname, - db_connection_params.mysql.user, - db_connection_params.mysql.password, - db_connection_params.mysql.database); - break; - case DB_CONN_SQLITE: - database = db_connect_sqlite(db_connection_params.sqlite.path, true, - db_connection_params.sqlite.pragmas); - break; - } - if (!database) - lwan_status_critical("Could not connect to the database"); - } - - return database; -} - static const char hello_world[] = "Hello, World!"; static const char random_number_query[] = "SELECT randomNumber FROM world WHERE id=?"; @@ -139,14 +115,37 @@ struct queries_json { struct db_json queries[500]; size_t queries_len; }; -static const struct json_obj_descr queries_json_desc[] = { +static const struct json_obj_descr queries_array_desc = JSON_OBJ_DESCR_OBJ_ARRAY(struct queries_json, queries, 500, queries_len, db_json_desc, - N_ELEMENTS(db_json_desc)), -}; + N_ELEMENTS(db_json_desc)); + +static struct db *get_db(void) +{ + static __thread struct db *database; + + if (!database) { + switch (db_connection_params.type) { + case DB_CONN_MYSQL: + database = db_connect_mysql(db_connection_params.mysql.hostname, + db_connection_params.mysql.user, + db_connection_params.mysql.password, + db_connection_params.mysql.database); + break; + case DB_CONN_SQLITE: + database = db_connect_sqlite(db_connection_params.sqlite.path, true, + db_connection_params.sqlite.pragmas); + break; + } + if (!database) + lwan_status_critical("Could not connect to the database"); + } + + return database; +} static int append_to_strbuf(const char *bytes, size_t len, void *data) { @@ -155,10 +154,11 @@ static int append_to_strbuf(const char *bytes, size_t len, void *data) return lwan_strbuf_append_str(strbuf, bytes, len) ? 0 : -EINVAL; } -static enum lwan_http_status json_response(struct lwan_response *response, - const struct json_obj_descr *descr, - size_t descr_len, - const void *data) +static enum lwan_http_status +json_response_obj(struct lwan_response *response, + const struct json_obj_descr *descr, + size_t descr_len, + const void *data) { lwan_strbuf_grow_to(response->buffer, 128); @@ -170,11 +170,25 @@ static enum lwan_http_status json_response(struct lwan_response *response, return HTTP_OK; } +static enum lwan_http_status +json_response_arr(struct lwan_response *response, + const struct json_obj_descr *descr, + const void *data) +{ + lwan_strbuf_grow_to(response->buffer, 128); + + if (json_arr_encode(descr, data, append_to_strbuf, response->buffer) < 0) + return HTTP_INTERNAL_ERROR; + + response->mime_type = "application/json"; + return HTTP_OK; +} + LWAN_HANDLER(json) { struct hello_world_json j = {.message = hello_world}; - return json_response(response, hello_world_json_desc, + return json_response_obj(response, hello_world_json_desc, N_ELEMENTS(hello_world_json_desc), &j); } @@ -224,7 +238,7 @@ LWAN_HANDLER(db) if (!queried) return HTTP_INTERNAL_ERROR; - return json_response(response, db_json_desc, N_ELEMENTS(db_json_desc), + return json_response_obj(response, db_json_desc, N_ELEMENTS(db_json_desc), &db_json); } @@ -258,8 +272,7 @@ LWAN_HANDLER(queries) goto out; } - ret = json_response(response, queries_json_desc, - N_ELEMENTS(queries_json_desc), &qj); + ret = json_response_arr(response, &queries_array_desc, &qj); out: db_stmt_finalize(stmt); From b71a465bf48fea221b4d8290a12802626fac7ffb Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 3 Jan 2020 17:49:08 -0800 Subject: [PATCH 1347/2505] Provide LWAN_MIN() and LWAN_MAX() macros These macros are implemented using a statement expression, so parameters are copied as such as they're evaluated only once. It's implemented as a macro so there's no need to implement one for every type (__typeof__ is used to declare the argument copies in the statement expression scope). --- src/bin/fuzz/template_fuzzer.cc | 6 ++---- src/lib/lwan-private.h | 14 ++++++++++++++ src/lib/lwan-request.c | 7 +------ src/lib/lwan-strbuf.c | 7 +------ src/lib/lwan-thread.c | 7 ------- src/lib/timeout.c | 14 +++----------- 6 files changed, 21 insertions(+), 34 deletions(-) diff --git a/src/bin/fuzz/template_fuzzer.cc b/src/bin/fuzz/template_fuzzer.cc index 0f1c2f758..9f3b24ecb 100644 --- a/src/bin/fuzz/template_fuzzer.cc +++ b/src/bin/fuzz/template_fuzzer.cc @@ -3,7 +3,7 @@ #include extern "C" { -#include "lwan.h" +#include "lwan-private.h" #include "lwan-template.h" } @@ -52,14 +52,12 @@ static const struct lwan_var_descriptor file_list_desc[] = { TPL_VAR_SENTINEL, }; -static size_t min(size_t a, size_t b) { return a > b ? b : a; } - extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { static char copy[32768]; struct lwan_tpl *tpl; - size = min(sizeof(copy) - 1, size); + size = LWAN_MIN(sizeof(copy) - 1, size); memcpy(copy, data, size); copy[size] = '\0'; diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 127fe225d..55484e5c2 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -24,6 +24,20 @@ #include "lwan.h" +#define LWAN_MIN(a_, b_) \ + ({ \ + __typeof__(a_) __a__ = (a_); \ + __typeof__(b_) __b__ = (b_); \ + __a__ > __b__ ? __b__ : __a__; \ + }) + +#define LWAN_MAX(a_, b_) \ + ({ \ + __typeof__(a_) __a__ = (a_); \ + __typeof__(b_) __b__ = (b_); \ + __a__ < __b__ ? __b__ : __a__; \ + }) + int lwan_socket_get_backlog_size(void); struct lwan_fd_watch *lwan_watch_fd(struct lwan *l, diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 6b5f1c510..4ec3634ea 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -974,16 +974,11 @@ post_data_finalizer(size_t total_read, return FINALIZER_TRY_AGAIN; } -static ALWAYS_INLINE int max(int a, int b) -{ - return (a > b) ? a : b; -} - static ALWAYS_INLINE int calculate_n_packets(size_t total) { /* 740 = 1480 (a common MTU) / 2, so that Lwan'll optimistically error out * after ~2x number of expected packets to fully read the request body.*/ - return max(5, (int)(total / 740)); + return LWAN_MAX(5, (int)(total / 740)); } static const char *is_dir(const char *v) diff --git a/src/lib/lwan-strbuf.c b/src/lib/lwan-strbuf.c index 507538afc..b863cba6f 100644 --- a/src/lib/lwan-strbuf.c +++ b/src/lib/lwan-strbuf.c @@ -39,15 +39,10 @@ static inline size_t align_size(size_t unaligned_size) return aligned_size; } -static ALWAYS_INLINE size_t max(size_t one, size_t another) -{ - return (one > another) ? one : another; -} - static bool grow_buffer_if_needed(struct lwan_strbuf *s, size_t size) { if (s->flags & STATIC) { - const size_t aligned_size = align_size(max(size + 1, s->used)); + const size_t aligned_size = align_size(LWAN_MAX(size + 1, s->used)); if (UNLIKELY(!aligned_size)) return false; diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 8e541c7a8..3da80f566 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -39,13 +39,6 @@ #include "lwan-tq.h" #include "list.h" -#define LWAN_MIN(a_, b_) \ - ({ \ - __typeof__(a_) __a__ = (a_); \ - __typeof__(b_) __b__ = (b_); \ - __a__ > __b__ ? __b__ : __a__; \ - }) - static void lwan_strbuf_free_defer(void *data) { lwan_strbuf_free((struct lwan_strbuf *)data); diff --git a/src/lib/timeout.c b/src/lib/timeout.c index 6a6797b0a..7012b4829 100644 --- a/src/lib/timeout.c +++ b/src/lib/timeout.c @@ -51,14 +51,6 @@ #define abstime_t timeout_t /* for documentation purposes */ #define reltime_t timeout_t /* "" */ -#if !defined MIN -#define MIN(a, b) (((a) < (b)) ? (a) : (b)) -#endif - -#if !defined MAX -#define MAX(a, b) (((a) > (b)) ? (a) : (b)) -#endif - /* * B I T M A N I P U L A T I O N R O U T I N E S * @@ -217,7 +209,7 @@ static inline reltime_t timeout_rem(struct timeouts *T, struct timeout *to) static inline int timeout_wheel(timeout_t timeout) { /* must be called with timeout != 0, so fls input is nonzero */ - return (fls(MIN(timeout, TIMEOUT_MAX)) - 1) / WHEEL_BIT; + return (fls(LWAN_MIN(timeout, TIMEOUT_MAX)) - 1) / WHEEL_BIT; } static inline int timeout_slot(int wheel, timeout_t expires) @@ -330,7 +322,7 @@ void timeouts_update(struct timeouts *T, abstime_t curtime) break; /* break if we didn't wrap around end of wheel */ /* if we're continuing, the next wheel must tick at least once */ - elapsed = MAX(elapsed, (WHEEL_LEN << (wheel * WHEEL_BIT))); + elapsed = LWAN_MAX(elapsed, (WHEEL_LEN << (wheel * WHEEL_BIT))); } T->curtime = curtime; @@ -386,7 +378,7 @@ static timeout_t timeouts_int(struct timeouts *T) _timeout -= relmask & T->curtime; /* reduce by how much lower wheels have progressed */ - timeout = MIN(_timeout, timeout); + timeout = LWAN_MIN(_timeout, timeout); } relmask <<= WHEEL_BIT; From c6f4036a44a1db83bd54c97caccc2933305ab5dc Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 3 Jan 2020 17:50:43 -0800 Subject: [PATCH 1348/2505] Remove #include from lwan-response.c It was needed for the websockets code, which has been moved to its own file. --- src/lib/lwan-response.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 5ecc8fefd..f7406d3c8 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -25,7 +25,6 @@ #include #include #include -#include #include "lwan-private.h" From e42b16556d2aba0966d911eb2358bec27450f0f2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 3 Jan 2020 17:51:11 -0800 Subject: [PATCH 1349/2505] Remove one branch from lwan_response() RESPONSE_SENT_HEADERS is still checked, but in an assertion, so this condition is checked at least in debug versions of the library. --- src/lib/lwan-response.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index f7406d3c8..d37ab1253 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -140,6 +140,8 @@ void lwan_response(struct lwan_request *request, enum lwan_http_status status) const struct lwan_response *response = &request->response; char headers[DEFAULT_HEADERS_SIZE]; + assert(!(request->flags & RESPONSE_SENT_HEADERS)); + if (UNLIKELY(request->flags & RESPONSE_CHUNKED_ENCODING)) { /* Send last, 0-sized chunk */ lwan_strbuf_reset(response->buffer); @@ -148,11 +150,6 @@ void lwan_response(struct lwan_request *request, enum lwan_http_status status) return; } - if (UNLIKELY(request->flags & RESPONSE_SENT_HEADERS)) { - lwan_status_debug("Headers already sent, ignoring call"); - return; - } - if (UNLIKELY(!response->mime_type)) { /* Requests without a MIME Type are errors from handlers that should just be handled by lwan_default_response(). */ From 3941cca8b25cf5273b9aa33bc865d42425b2bbfa Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 3 Jan 2020 17:52:00 -0800 Subject: [PATCH 1350/2505] Miscellaneous cleanups in lwan_response() Try to force tail calls when deferring to lwan_default_response(). --- src/lib/lwan-response.c | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index d37ab1253..34a8555fc 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -153,8 +153,7 @@ void lwan_response(struct lwan_request *request, enum lwan_http_status status) if (UNLIKELY(!response->mime_type)) { /* Requests without a MIME Type are errors from handlers that should just be handled by lwan_default_response(). */ - lwan_default_response(request, status); - return; + return lwan_default_response(request, status); } log_request(request, status); @@ -176,15 +175,11 @@ void lwan_response(struct lwan_request *request, enum lwan_http_status status) size_t header_len = lwan_prepare_response_header(request, status, headers, sizeof(headers)); - if (UNLIKELY(!header_len)) { - lwan_default_response(request, HTTP_INTERNAL_ERROR); - return; - } + if (UNLIKELY(!header_len)) + return lwan_default_response(request, HTTP_INTERNAL_ERROR); - if (!has_response_body(lwan_request_get_method(request), status)) { - lwan_send(request, headers, header_len, 0); - return; - } + if (!has_response_body(lwan_request_get_method(request), status)) + return (void)lwan_send(request, headers, header_len, 0); char *resp_buf = lwan_strbuf_get_buffer(response->buffer); const size_t resp_len = lwan_strbuf_get_length(response->buffer); @@ -193,15 +188,15 @@ void lwan_response(struct lwan_request *request, enum lwan_http_status status) * so use send() for responses small enough to fit the headers * buffer. On Linux, this is ~10% faster. */ memcpy(headers + header_len, resp_buf, resp_len); - lwan_send(request, headers, header_len + resp_len, 0); - } else { - struct iovec response_vec[] = { - {.iov_base = headers, .iov_len = header_len}, - {.iov_base = resp_buf, .iov_len = resp_len}, - }; - - lwan_writev(request, response_vec, N_ELEMENTS(response_vec)); + return (void)lwan_send(request, headers, header_len + resp_len, 0); } + + struct iovec response_vec[] = { + {.iov_base = headers, .iov_len = header_len}, + {.iov_base = resp_buf, .iov_len = resp_len}, + }; + + return (void)lwan_writev(request, response_vec, N_ELEMENTS(response_vec)); } void lwan_default_response(struct lwan_request *request, From 33646bc8f47ebb21841ea11bf7509155bc60ea30 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 3 Jan 2020 17:53:02 -0800 Subject: [PATCH 1351/2505] Get rid of the union in struct lwan_strbuf Doesn't change anything in the end, but the code ends up being slightly simpler this way. --- src/lib/lwan-strbuf.c | 36 ++++++++++++++++++------------------ src/lib/lwan-strbuf.h | 7 ++----- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/lib/lwan-strbuf.c b/src/lib/lwan-strbuf.c index b863cba6f..ff3def0cc 100644 --- a/src/lib/lwan-strbuf.c +++ b/src/lib/lwan-strbuf.c @@ -50,11 +50,11 @@ static bool grow_buffer_if_needed(struct lwan_strbuf *s, size_t size) if (UNLIKELY(!buffer)) return false; - memcpy(buffer, s->value.static_buffer, s->used); + memcpy(buffer, s->buffer, s->used); buffer[s->used + 1] = '\0'; s->flags &= ~STATIC; - s->value.buffer = buffer; + s->buffer = buffer; s->capacity = aligned_size; return true; @@ -65,11 +65,11 @@ static bool grow_buffer_if_needed(struct lwan_strbuf *s, size_t size) if (UNLIKELY(!aligned_size)) return false; - char *buffer = realloc(s->value.buffer, aligned_size); + char *buffer = realloc(s->buffer, aligned_size); if (UNLIKELY(!buffer)) return false; - s->value.buffer = buffer; + s->buffer = buffer; s->capacity = aligned_size; } @@ -85,7 +85,7 @@ bool lwan_strbuf_init_with_size(struct lwan_strbuf *s, size_t size) *s = (struct lwan_strbuf) { .used = 0, .capacity = 0, - .value.static_buffer = "", + .buffer = "", .flags = STATIC, }; } else { @@ -94,7 +94,7 @@ bool lwan_strbuf_init_with_size(struct lwan_strbuf *s, size_t size) if (UNLIKELY(!grow_buffer_if_needed(s, size))) return false; - s->value.buffer[0] = '\0'; + s->buffer[0] = '\0'; } return true; @@ -135,7 +135,7 @@ ALWAYS_INLINE struct lwan_strbuf *lwan_strbuf_new_static(const char *str, *s = (struct lwan_strbuf) { .flags = STATIC | DYNAMICALLY_ALLOCATED, - .value.static_buffer = str, + .buffer = (char *)str, .used = size, .capacity = size, }; @@ -148,7 +148,7 @@ void lwan_strbuf_free(struct lwan_strbuf *s) if (UNLIKELY(!s)) return; if (!(s->flags & STATIC)) - free(s->value.buffer); + free(s->buffer); if (s->flags & DYNAMICALLY_ALLOCATED) free(s); } @@ -158,8 +158,8 @@ bool lwan_strbuf_append_char(struct lwan_strbuf *s, const char c) if (UNLIKELY(!grow_buffer_if_needed(s, s->used + 2))) return false; - s->value.buffer[s->used++] = c; - s->value.buffer[s->used] = '\0'; + s->buffer[s->used++] = c; + s->buffer[s->used] = '\0'; return true; } @@ -169,9 +169,9 @@ bool lwan_strbuf_append_str(struct lwan_strbuf *s1, const char *s2, size_t sz) if (UNLIKELY(!grow_buffer_if_needed(s1, s1->used + sz + 2))) return false; - memcpy(s1->value.buffer + s1->used, s2, sz); + memcpy(s1->buffer + s1->used, s2, sz); s1->used += sz; - s1->value.buffer[s1->used] = '\0'; + s1->buffer[s1->used] = '\0'; return true; } @@ -179,9 +179,9 @@ bool lwan_strbuf_append_str(struct lwan_strbuf *s1, const char *s2, size_t sz) bool lwan_strbuf_set_static(struct lwan_strbuf *s1, const char *s2, size_t sz) { if (!(s1->flags & STATIC)) - free(s1->value.buffer); + free(s1->buffer); - s1->value.static_buffer = s2; + s1->buffer = (char *)s2; s1->used = s1->capacity = sz; s1->flags |= STATIC; @@ -193,9 +193,9 @@ bool lwan_strbuf_set(struct lwan_strbuf *s1, const char *s2, size_t sz) if (UNLIKELY(!grow_buffer_if_needed(s1, sz + 1))) return false; - memcpy(s1->value.buffer, s2, sz); + memcpy(s1->buffer, s2, sz); s1->used = sz; - s1->value.buffer[sz] = '\0'; + s1->buffer[sz] = '\0'; return true; } @@ -260,10 +260,10 @@ bool lwan_strbuf_grow_by(struct lwan_strbuf *s, size_t offset) void lwan_strbuf_reset(struct lwan_strbuf *s) { if (s->flags & STATIC) { - s->value.static_buffer = ""; + s->buffer = ""; s->capacity = 0; } else { - s->value.buffer[0] = '\0'; + s->buffer[0] = '\0'; } s->used = 0; diff --git a/src/lib/lwan-strbuf.h b/src/lib/lwan-strbuf.h index d68ff2cd2..0488830c8 100644 --- a/src/lib/lwan-strbuf.h +++ b/src/lib/lwan-strbuf.h @@ -25,10 +25,7 @@ #include struct lwan_strbuf { - union { - char *buffer; - const char *static_buffer; - } value; + char *buffer; size_t capacity, used; unsigned int flags; }; @@ -80,5 +77,5 @@ static inline size_t lwan_strbuf_get_length(const struct lwan_strbuf *s) static inline char *lwan_strbuf_get_buffer(const struct lwan_strbuf *s) { - return s->value.buffer; + return s->buffer; } From 3c7f13b85e6c18bc6248808ce640d303c762ba8b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 3 Jan 2020 17:57:10 -0800 Subject: [PATCH 1352/2505] Pass columns signatures in a string to the DAL stmt::step() method --- src/samples/techempower/database.c | 66 +++++++++++++++++---------- src/samples/techempower/database.h | 6 +-- src/samples/techempower/techempower.c | 28 ++++-------- 3 files changed, 53 insertions(+), 47 deletions(-) diff --git a/src/samples/techempower/database.c b/src/samples/techempower/database.c index 7196365f8..e8e2c4588 100644 --- a/src/samples/techempower/database.c +++ b/src/samples/techempower/database.c @@ -18,10 +18,12 @@ * USA. */ +#include #include #include #include #include +#include #include "database.h" #include "lwan-status.h" @@ -30,7 +32,7 @@ struct db_stmt { bool (*bind)(const struct db_stmt *stmt, struct db_row *rows, size_t n_rows); - bool (*step)(const struct db_stmt *stmt, struct db_row *row, size_t n_cols); + bool (*step)(const struct db_stmt *stmt, const char *signature, va_list ap); void (*finalize)(struct db_stmt *stmt); }; @@ -92,8 +94,8 @@ static bool db_stmt_bind_mysql(const struct db_stmt *stmt, } static bool db_stmt_step_mysql(const struct db_stmt *stmt, - struct db_row *row, - size_t n_rows) + const char *signature, + va_list ap) { struct db_stmt_mysql *stmt_mysql = (struct db_stmt_mysql *)stmt; @@ -104,11 +106,11 @@ static bool db_stmt_step_mysql(const struct db_stmt *stmt, } if (!stmt_mysql->result_bind) { - if (!n_rows) + if (*signature == '\0') return false; stmt_mysql->result_bind = - calloc(n_rows, sizeof(*stmt_mysql->result_bind)); + calloc(strlen(signature), sizeof(*stmt_mysql->result_bind)); if (!stmt_mysql->result_bind) return false; @@ -116,19 +118,23 @@ static bool db_stmt_step_mysql(const struct db_stmt *stmt, stmt_mysql->param_bind = NULL; MYSQL_BIND *result = stmt_mysql->result_bind; - for (size_t r = 0; r < n_rows; r++) { - if (row[r].kind == 's') { + for (size_t r = 0; signature[r]; r++) { + switch (signature[r]) { + case 's': result[r].buffer_type = MYSQL_TYPE_STRING; - result[r].buffer = row[r].u.s; - } else if (row[r].kind == 'i') { + result[r].buffer = va_arg(ap, char *); + result[r].buffer_length = va_arg(ap, size_t); + break; + case 'i': result[r].buffer_type = MYSQL_TYPE_LONG; - result[r].buffer = &row[r].u.i; - } else { + result[r].buffer = va_arg(ap, long *); + result[r].buffer_length = 0; + break; + default: goto out; } result[r].is_null = false; - result[r].buffer_length = row[r].buffer_length; } if (mysql_stmt_bind_result(stmt_mysql->stmt, result)) @@ -273,8 +279,8 @@ static bool db_stmt_bind_sqlite(const struct db_stmt *stmt, } static bool db_stmt_step_sqlite(const struct db_stmt *stmt, - struct db_row *row, - size_t n_cols) + const char *signature, + va_list ap) { const struct db_stmt_sqlite *stmt_sqlite = (const struct db_stmt_sqlite *)stmt; @@ -282,15 +288,18 @@ static bool db_stmt_step_sqlite(const struct db_stmt *stmt, if (sqlite3_step(stmt_sqlite->sqlite) != SQLITE_ROW) return false; - for (int column_id = 0; column_id < (int)n_cols; column_id++) { - struct db_row *r = &row[column_id]; - - if (r->kind == 'i') { - r->u.i = sqlite3_column_int(stmt_sqlite->sqlite, column_id); - } else if (r->kind == 's') { - r->u.s = - (char *)sqlite3_column_text(stmt_sqlite->sqlite, column_id); - } else { + for (int r = 0; signature[r]; r++) { + switch (signature[r]) { + case 'i': + *va_arg(ap, long *) = sqlite3_column_int(stmt_sqlite->sqlite, r); + break; + case 's': { + char *out = va_arg(ap, char *); + size_t bufsize = va_arg(ap, size_t); + strncpy(out, sqlite3_column_text(stmt_sqlite->sqlite, r), bufsize); + break; + } + default: return false; } } @@ -372,9 +381,16 @@ db_stmt_bind(const struct db_stmt *stmt, struct db_row *rows, size_t n_rows) } inline bool -db_stmt_step(const struct db_stmt *stmt, struct db_row *row, size_t n_cols) +db_stmt_step(const struct db_stmt *stmt, const char *signature, ...) { - return stmt->step(stmt, row, n_cols); + va_list ap; + bool ret; + + va_start(ap, signature); + ret = stmt->step(stmt, signature, ap); + va_end(ap); + + return ret; } inline void db_stmt_finalize(struct db_stmt *stmt) { stmt->finalize(stmt); } diff --git a/src/samples/techempower/database.h b/src/samples/techempower/database.h index 553c0c6f4..0af4f79a9 100644 --- a/src/samples/techempower/database.h +++ b/src/samples/techempower/database.h @@ -37,9 +37,9 @@ struct db_row { bool db_stmt_bind(const struct db_stmt *stmt, struct db_row *rows, size_t n_rows); -bool db_stmt_step(const struct db_stmt *stmt, - struct db_row *row, - size_t n_cols); + +bool db_stmt_step(const struct db_stmt *stmt, const char *signature, ...); + void db_stmt_finalize(struct db_stmt *stmt); void db_disconnect(struct db *db); struct db_stmt * diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index 08f054879..f27d3301c 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -195,8 +195,6 @@ LWAN_HANDLER(json) static bool db_query(struct db_stmt *stmt, struct db_row rows[], size_t n_rows, - struct db_row results[], - size_t n_cols, struct db_json *out) { const int id = (rand() % 10000) + 1; @@ -208,11 +206,12 @@ static bool db_query(struct db_stmt *stmt, if (UNLIKELY(!db_stmt_bind(stmt, rows, n_rows))) return false; - if (UNLIKELY(!db_stmt_step(stmt, results, n_cols))) + long random_number; + if (UNLIKELY(!db_stmt_step(stmt, "i", &random_number))) return false; out->id = id; - out->randomNumber = results[0].u.i; + out->randomNumber = (int)random_number; return true; } @@ -220,7 +219,6 @@ static bool db_query(struct db_stmt *stmt, LWAN_HANDLER(db) { struct db_row rows[] = {{.kind = 'i'}}; - struct db_row results[] = {{.kind = 'i'}}; struct db_stmt *stmt = db_prepare_stmt(get_db(), random_number_query, sizeof(random_number_query) - 1); struct db_json db_json; @@ -230,8 +228,7 @@ LWAN_HANDLER(db) return HTTP_INTERNAL_ERROR; } - bool queried = db_query(stmt, rows, N_ELEMENTS(rows), results, - N_ELEMENTS(results), &db_json); + bool queried = db_query(stmt, rows, N_ELEMENTS(rows), &db_json); db_stmt_finalize(stmt); @@ -264,11 +261,9 @@ LWAN_HANDLER(queries) return HTTP_INTERNAL_ERROR; struct queries_json qj = {.queries_len = (size_t)queries}; - struct db_row rows[] = {{.kind = 'i'}}; struct db_row results[] = {{.kind = 'i'}}; for (long i = 0; i < queries; i++) { - if (!db_query(stmt, rows, N_ELEMENTS(rows), results, - N_ELEMENTS(results), &qj.queries[i])) + if (!db_query(stmt, results, N_ELEMENTS(results), &qj.queries[i])) goto out; } @@ -330,7 +325,6 @@ static bool append_fortune(struct coro *coro, static int fortune_list_generator(struct coro *coro, void *data) { static const char fortune_query[] = "SELECT * FROM Fortune"; - char fortune_buffer[256]; struct Fortune *fortune = data; struct fortune_array fortunes; struct db_stmt *stmt; @@ -341,14 +335,10 @@ static int fortune_list_generator(struct coro *coro, void *data) fortune_array_init(&fortunes); - struct db_row results[] = { - {.kind = 'i'}, - {.kind = 's', - .u.s = fortune_buffer, - .buffer_length = sizeof(fortune_buffer)}, - }; - while (db_stmt_step(stmt, results, N_ELEMENTS(results))) { - if (!append_fortune(coro, &fortunes, results[0].u.i, results[1].u.s)) + long id; + char fortune_buffer[256]; + while (db_stmt_step(stmt, "is", &id, &fortune_buffer, sizeof(fortune_buffer))) { + if (!append_fortune(coro, &fortunes, (int)id, fortune_buffer)) goto out; } From 3509be27e129995a3c63e642ed11ffd2553c2b6d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 3 Jan 2020 17:58:04 -0800 Subject: [PATCH 1353/2505] Minor cleanups in techempower.c --- src/samples/techempower/techempower.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index f27d3301c..18a044e80 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -31,7 +31,7 @@ enum db_connect_type { DB_CONN_MYSQL, DB_CONN_SQLITE }; -struct db_connection_params { +static struct db_connection_params { enum db_connect_type type; union { struct { @@ -45,9 +45,7 @@ struct db_connection_params { const char **pragmas; } sqlite; }; -}; - -static struct db_connection_params db_connection_params; +} db_connection_params; static const char hello_world[] = "Hello, World!"; static const char random_number_query[] = @@ -397,7 +395,8 @@ int main(void) .mysql.user = getenv("MYSQL_USER"), .mysql.password = getenv("MYSQL_PASS"), .mysql.hostname = getenv("MYSQL_HOST"), - .mysql.database = getenv("MYSQL_DB")}; + .mysql.database = getenv("MYSQL_DB"), + }; if (!db_connection_params.mysql.user) lwan_status_critical("No MySQL user provided"); From add8aa83110bc948a41f829c91db46c155e298b5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 3 Jan 2020 17:59:16 -0800 Subject: [PATCH 1354/2505] Simplify status message formatting when using colors --- src/lib/lwan-status.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-status.c b/src/lib/lwan-status.c index f96078fd7..f77a2eeee 100644 --- a/src/lib/lwan-status.c +++ b/src/lib/lwan-status.c @@ -132,6 +132,8 @@ static long gettid_cached(void) } #endif +#define FORMAT_WITH_COLOR(fmt, color) "\033[" color "m" fmt "\033[0m" + static void #ifdef NDEBUG status_out(enum lwan_status_type type, const char *fmt, va_list values) @@ -152,10 +154,10 @@ status_out(const char *file, #ifndef NDEBUG char *base_name = basename(strdupa(file)); - if (use_colors) { - printf("\033[32;1m%ld\033[0m", gettid_cached()); - printf(" \033[3m%s:%d\033[0m", base_name, line); - printf(" \033[33m%s()\033[0m ", func); + if (LIKELY(use_colors)) { + printf(FORMAT_WITH_COLOR("%ld ", "32;1"), gettid_cached()); + printf(FORMAT_WITH_COLOR("%s:%d ", "3"), base_name, line); + printf(FORMAT_WITH_COLOR("%s() ", "33"), func); } else { printf("%ld %s:%d %s() ", gettid_cached(), base_name, line, func); } @@ -179,6 +181,8 @@ status_out(const char *file, errno = saved_errno; } +#undef FORMAT_WITH_COLOR + #ifdef NDEBUG #define IMPLEMENT_FUNCTION(fn_name_, type_) \ void lwan_status_##fn_name_(const char *fmt, ...) \ From c730cb855e09992277c5d96531aa0231121c2cc4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 3 Jan 2020 17:59:38 -0800 Subject: [PATCH 1355/2505] Sprinkle LIKELY() and UNLIKELY() macros in status functions --- src/lib/lwan-status.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib/lwan-status.c b/src/lib/lwan-status.c index f77a2eeee..e3bb32d41 100644 --- a/src/lib/lwan-status.c +++ b/src/lib/lwan-status.c @@ -166,7 +166,7 @@ status_out(const char *file, fwrite_unlocked(start.value, start.len, 1, stdout); vprintf(fmt, values); - if (type & STATUS_PERROR) { + if (UNLIKELY(type & STATUS_PERROR)) { char errbuf[64]; char *errmsg = strerror_thunk_r(saved_errno, errbuf, sizeof(errbuf) - 1); @@ -187,13 +187,13 @@ status_out(const char *file, #define IMPLEMENT_FUNCTION(fn_name_, type_) \ void lwan_status_##fn_name_(const char *fmt, ...) \ { \ - if (!quiet) { \ + if (LIKELY(!quiet)) { \ va_list values; \ va_start(values, fmt); \ status_out(type_, fmt, values); \ va_end(values); \ } \ - if ((type_)&STATUS_CRITICAL) \ + if (UNLIKELY((type_)&STATUS_CRITICAL)) \ exit(1); \ } #else @@ -202,13 +202,13 @@ status_out(const char *file, const char *func, const char *fmt, \ ...) \ { \ - if (!quiet) { \ + if (LIKELY(!quiet)) { \ va_list values; \ va_start(values, fmt); \ status_out(file, line, func, type_, fmt, values); \ va_end(values); \ } \ - if ((type_)&STATUS_CRITICAL) \ + if (UNLIKELY((type_)&STATUS_CRITICAL)) \ abort(); \ } From 8656640b2b9d1894f520f1d1bbb34bd9ebfaa287 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Tue, 14 Jan 2020 22:25:27 +0100 Subject: [PATCH 1356/2505] Fix minor typos and consistency issues --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index e5668255f..740584250 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ optimizations, etc): ~/lwan/build$ cmake .. -DCMAKE_BUILD_TYPE=Release -If you'd like to enable optimiations but still use a debugger, use this instead: +If you'd like to enable optimizations but still use a debugger, use this instead: ~/lwan/build$ cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo @@ -101,7 +101,7 @@ This will generate a few binaries: - `src/bin/lwan/lwan`: The main Lwan executable. May be executed with `--help` for guidance. - `src/bin/testrunner/testrunner`: Contains code to execute the test suite. - `src/samples/freegeoip/freegeoip`: [FreeGeoIP sample implementation](https://freegeoip.lwan.ws). Requires SQLite. - - `src/samples/techempower/techempower`: Code for the Techempower Web Framework benchmark. Requires SQLite and MySQL libraries. + - `src/samples/techempower/techempower`: Code for the TechEmpower Web Framework benchmark. Requires SQLite and MySQL libraries. - `src/samples/clock/clock`: [Clock sample](https://time.lwan.ws). Generates a GIF file that always shows the local time. - `src/bin/tools/mimegen`: Builds the extension-MIME type table. Used during build process. - `src/bin/tools/bin2hex`: Generates a C file from a binary file, suitable for use with #include. @@ -360,7 +360,7 @@ threads, but the state will be available only for the amount of time specified in the `cache_period` configuration option. There's no need to have one instance of the Lua module for each endpoint; a -single script, embeded in the configuration file or otherwise, can service +single script, embedded in the configuration file or otherwise, can service many different endpoints. Scripts are supposed to implement functions with the following signature: `handle_${METHOD}_${ENDPOINT}(req)`, where `${METHOD}` can be a HTTP method (i.e. `get`, `post`, `head`, etc.), and @@ -559,7 +559,7 @@ be exported to `liblwan.so`. Lwan tries to maintain a source history that's as flat as possible, devoid of merge commits. This means that pull requests should be rebased on top of the -current master before they can be merged; sometimes this can be made +current master before they can be merged; sometimes this can be done automatically by the GitHub interface, sometimes they need some manual work to fix conflicts. It is appreciated if the contributor fixes these conflicts when asked. @@ -572,7 +572,7 @@ matter if it is your legal name or a nickname, but it should be enough to credit you) and a valid email address. There's no need to add `Signed-off-by` lines, even though it's fine to send commits with them. -If a change is requested in a pull request, you have three choices: +If a change is requested in a pull request, you have two choices: - *Reply asking for clarification.* Maybe the intentions were not clear enough, and whoever asked for changes didn't fully understand what you were trying to @@ -583,17 +583,17 @@ squash or fixup commits; don't add your fixes on top of your tree. Do not creat another pull request just to accomodate the changes. After rewriting the history locally, force-push to your PR branch; the PR will update automatically with your changes. Rewriting the history of development branches is fine, and -force-pushing them is normal and expected. +force-pushing them is normal and expected It is not enforced, but it is recommended to create smaller commits. How commits are split in Lwan is pretty much arbitrary, so please take a look at -the commit history to get the idea on how the division should be made. Git +the commit history to get an idea on how the division should be made. Git offers a plethora of commands to achieve this result: the already mentioned interactive rebase, the `-p` option to `git add`, and `git commit --amend` are good examples. Commit messages should have one line of summary (~72 chars), followed by an -empty line, followed by paragraphs of 80-lines explaining the change. The +empty line, followed by paragraphs of 80-char lines explaining the change. The paragraphs explaining the changes are usually not necessary if the summary is good enough. Try to [write good commit messages](https://chris.beams.io/posts/git-commit/). From d0b3894015ad85f3d86d233b87d230ee81efc919 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Tue, 14 Jan 2020 22:33:14 +0100 Subject: [PATCH 1357/2505] Rewrite example: optional forward slash between rewrite base and pattern There can be a forward slash between the endpoint of the rewrite module and the patterns, so we make this explicit in the example. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 740584250..9a780d183 100644 --- a/README.md +++ b/README.md @@ -415,11 +415,11 @@ example, where two patterns are specified: ``` rewrite /some/base/endpoint { pattern posts/(%d+) { - # Matches /some/base/endpointposts/2600 + # Matches /some/base/endpointposts/2600 and /some/base/endpoint/posts/2600 rewrite_as = /cms/view-post?id=%1 } pattern imgur/(%a+)/(%g+) { - # Matches /some/base/endpointimgur/gif/mpT94Ld + # Matches /some/base/endpointimgur/gif/mpT94Ld and /some/base/endpoint/imgur/gif/mpT94Ld redirect_to = https://i.imgur.com/%2.%1 } } From c7322d1e341afd5edfcc776474cf80e23e23ee20 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 14 Jan 2020 21:41:52 -0800 Subject: [PATCH 1358/2505] Minor cleanups in database access layer --- src/samples/techempower/database.c | 34 ++++++++++++++++++------------ 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/samples/techempower/database.c b/src/samples/techempower/database.c index e8e2c4588..19767399a 100644 --- a/src/samples/techempower/database.c +++ b/src/samples/techempower/database.c @@ -74,18 +74,22 @@ static bool db_stmt_bind_mysql(const struct db_stmt *stmt, mysql_stmt_reset(stmt_mysql->stmt); } - for (size_t row = 0; row < n_rows; row++) { - if (rows[row].kind == '\0') - break; - + for (size_t row = 0; row < n_rows && rows[row].kind; row++) { MYSQL_BIND *param = &stmt_mysql->param_bind[row]; - if (rows[row].kind == 's') { + + switch (rows[row].kind) { + case 's': param->buffer_type = MYSQL_TYPE_STRING; param->buffer = rows[row].u.s; - } else if (rows[row].kind == 'i') { + break; + case 'i': param->buffer_type = MYSQL_TYPE_LONG; param->buffer = &rows[row].u.i; + break; + default: + return false; } + param->is_null = false; param->length = 0; } @@ -253,26 +257,28 @@ static bool db_stmt_bind_sqlite(const struct db_stmt *stmt, { const struct db_stmt_sqlite *stmt_sqlite = (const struct db_stmt_sqlite *)stmt; - int ret; sqlite3_reset(stmt_sqlite->sqlite); sqlite3_clear_bindings(stmt_sqlite->sqlite); for (size_t row = 1; row <= n_rows; row++) { const struct db_row *r = &rows[row - 1]; + int ret; - if (r->kind == 's') { + switch (r->kind) { + case 's': ret = sqlite3_bind_text(stmt_sqlite->sqlite, (int)row, r->u.s, -1, NULL); - if (ret != SQLITE_OK) - return false; - } else if (r->kind == 'i') { + break; + case 'i': ret = sqlite3_bind_int(stmt_sqlite->sqlite, (int)row, r->u.i); - if (ret != SQLITE_OK) - return false; - } else { + break; + default: return false; } + + if (ret != SQLITE_OK) + return false; } return true; From a79cdb8ec4c5c554b28766e630853614eb87d44c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 14 Jan 2020 21:44:55 -0800 Subject: [PATCH 1359/2505] Remove some branches when encoding JSON objects and array In both cases, the branches were there to avoid generating a trailing `,' after the last item. For objects, take advantage of the fact that the element order doesn't matter, so loop through the 2nd to the last key/value pair first, always appending a comma to the output, then append the first key/value pair. For arrays, loop through n_elements-1, always adding the comma, and then write the next element. --- src/samples/techempower/json.c | 108 +++++++++++++++++++++------------ 1 file changed, 69 insertions(+), 39 deletions(-) diff --git a/src/samples/techempower/json.c b/src/samples/techempower/json.c index c9a999589..871e94e59 100644 --- a/src/samples/techempower/json.c +++ b/src/samples/techempower/json.c @@ -717,10 +717,10 @@ static int arr_encode(const struct json_obj_descr *elem_descr, { ptrdiff_t elem_size = get_elem_size(elem_descr); /* - * NOTE: Since an element descriptor's offset isn't meaningful - * (array elements occur at multiple offsets in `val'), we use - * its space in elem_descr to store the offset to the field - * containing the number of elements. + * NOTE: Since an element descriptor's offset isn't meaningful (array + * elements occur at multiple offsets in `val'), we use its space in + * elem_descr to store the offset to the field containing the number of + * elements. */ size_t n_elem = *(size_t *)((char *)val + elem_descr->offset); size_t i; @@ -731,36 +731,42 @@ static int arr_encode(const struct json_obj_descr *elem_descr, return ret; } - for (i = 0; i < n_elem; i++) { - /* - * Though "field" points at the next element in the - * array which we need to encode, the value in - * elem_descr->offset is actually the offset of the - * length field in the "parent" struct containing the - * array. - * - * To patch things up, we lie to encode() about where - * the field is by exactly the amount it will offset - * it. This is a size optimization for struct - * json_obj_descr: the alternative is to keep a - * separate field next to element_descr which is an - * offset to the length field in the parent struct, - * but that would add a size_t to every descriptor. - */ - ret = encode(elem_descr, (char *)field - elem_descr->offset, - append_bytes, data); - if (ret < 0) { - return ret; - } + if (n_elem >= 1) { + n_elem--; + + for (i = 0; i < n_elem; i++) { + /* + * Though "field" points at the next element in the array which + * we need to encode, the value in elem_descr->offset is + * actually the offset of the length field in the "parent" + * struct containing the array. + * + * To patch things up, we lie to encode() about where the field + * is by exactly the amount it will offset it. This is a size + * optimization for struct json_obj_descr: the alternative is to + * keep a separate field next to element_descr which is an + * offset to the length field in the parent struct, but that + * would add a size_t to every descriptor. + */ + ret = encode(elem_descr, (char *)field - elem_descr->offset, + append_bytes, data); + if (ret < 0) { + return ret; + } - if (i < n_elem - 1) { ret = append_bytes(",", 1, data); if (ret < 0) { return ret; } + + field = (char *)field + elem_size; } - field = (char *)field + elem_size; + ret = encode(elem_descr, (char *)field - elem_descr->offset, + append_bytes, data); + if (ret < 0) { + return ret; + } } return append_bytes("]", 1, data); @@ -848,6 +854,31 @@ static int encode(const struct json_obj_descr *descr, } } +static int encode_key_value(const struct json_obj_descr *descr, + const void *val, + json_append_bytes_t append_bytes, + void *data) +{ + int ret; + + ret = str_encode((const char **)&descr->field_name, append_bytes, data); + if (ret < 0) { + return ret; + } + + ret = append_bytes(":", 1, data); + if (ret < 0) { + return ret; + } + + ret = encode(descr, val, append_bytes, data); + if (ret < 0) { + return ret; + } + + return ret; +} + int json_obj_encode(const struct json_obj_descr *descr, size_t descr_len, const void *val, @@ -862,29 +893,28 @@ int json_obj_encode(const struct json_obj_descr *descr, return ret; } - for (i = 0; i < descr_len; i++) { - ret = - str_encode((const char **)&descr[i].field_name, append_bytes, data); + /* To avoid checking if we're encoding the last element on each iteration of + * this loop, start at the second descriptor, and always write the comma. + * Then, after the loop, encode the first descriptor. If the descriptor + * array has only 1 element, this loop won't run. This is fine since order + * isn't important for objects, and we save some branches. */ + for (i = 1; i < descr_len; i++) { + ret = encode_key_value(&descr[i], val, append_bytes, data); if (ret < 0) { return ret; } - ret = append_bytes(":", 1, data); + ret = append_bytes(",", 1, data); if (ret < 0) { return ret; } + } - ret = encode(&descr[i], val, append_bytes, data); + if (descr_len) { + ret = encode_key_value(&descr[0], val, append_bytes, data); if (ret < 0) { return ret; } - - if (i < descr_len - 1) { - ret = append_bytes(",", 1, data); - if (ret < 0) { - return ret; - } - } } return append_bytes("}", 1, data); From ec1fab1fe5cd95b9f475e0c3ac89cfa619728346 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 14 Jan 2020 23:02:28 -0800 Subject: [PATCH 1360/2505] Use int_to_string() to serialize numbers with the JSON library This potentially reduces the number of divisions required to convert an integer to a string. --- src/samples/techempower/json.c | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/samples/techempower/json.c b/src/samples/techempower/json.c index 871e94e59..0e72e59ad 100644 --- a/src/samples/techempower/json.c +++ b/src/samples/techempower/json.c @@ -15,6 +15,7 @@ #include #include "json.h" +#include "int-to-str.h" struct token { enum json_tokens type; @@ -793,18 +794,11 @@ str_encode(const char **str, json_append_bytes_t append_bytes, void *data) static int num_encode(const int32_t *num, json_append_bytes_t append_bytes, void *data) { - char buf[3 * sizeof(int32_t)]; - int ret; - - ret = snprintf(buf, sizeof(buf), "%d", *num); - if (ret < 0) { - return ret; - } - if (ret >= (int)sizeof(buf)) { - return -ENOMEM; - } + char buf[INT_TO_STR_BUFFER_SIZE]; + size_t len; + char *as_string = int_to_string(*num, buf, &len); - return append_bytes(buf, (size_t)ret, data); + return append_bytes(as_string, len, data); } static int From fe90962e6b182becec770fb83f922b7ff6465cdd Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 14 Jan 2020 23:17:56 -0800 Subject: [PATCH 1361/2505] Reduce calls to append_bytes() when encoding JSON objects By giving a hint to the JSON encoder that the keys do not need to be encoded (e.g. because we know beforehand that they don't require any special treatment), we can reduce calls to append_bytes() in hot paths. The TWFB benchmark implements append_bytes() by append a character at a time to a strbuf, which is inneficient: it's writing two bytes at a time (the desired char + terminating NUL), and updating the internal bookkeeping data every time for every character. If there's no need to encode the key, just a single memory copy+bookkeeping information is needed for each key. In the "queries" benchmark, if requesting for 500 queries, this reduces the number of calls to append_bytes() from 13001 to 7001. This is almost 12KiB of data per request that does not need to be written byte-per-byte. --- src/samples/techempower/json.c | 65 ++++++++++++++++----------- src/samples/techempower/json.h | 36 +++++++++++---- src/samples/techempower/techempower.c | 7 +-- 3 files changed, 69 insertions(+), 39 deletions(-) diff --git a/src/samples/techempower/json.c b/src/samples/techempower/json.c index 0e72e59ad..c66a3ee14 100644 --- a/src/samples/techempower/json.c +++ b/src/samples/techempower/json.c @@ -708,13 +708,15 @@ ssize_t json_escape(char *str, size_t *len, size_t buf_size) static int encode(const struct json_obj_descr *descr, const void *val, json_append_bytes_t append_bytes, - void *data); + void *data, + bool encode_key); static int arr_encode(const struct json_obj_descr *elem_descr, const void *field, const void *val, json_append_bytes_t append_bytes, - void *data) + void *data, + bool encode_key) { ptrdiff_t elem_size = get_elem_size(elem_descr); /* @@ -750,7 +752,7 @@ static int arr_encode(const struct json_obj_descr *elem_descr, * would add a size_t to every descriptor. */ ret = encode(elem_descr, (char *)field - elem_descr->offset, - append_bytes, data); + append_bytes, data, encode_key); if (ret < 0) { return ret; } @@ -764,7 +766,7 @@ static int arr_encode(const struct json_obj_descr *elem_descr, } ret = encode(elem_descr, (char *)field - elem_descr->offset, - append_bytes, data); + append_bytes, data, encode_key); if (ret < 0) { return ret; } @@ -774,7 +776,7 @@ static int arr_encode(const struct json_obj_descr *elem_descr, } static int -str_encode(const char **str, json_append_bytes_t append_bytes, void *data) +str_encode(const char **str, json_append_bytes_t append_bytes, void *data, bool encode) { int ret; @@ -783,7 +785,11 @@ str_encode(const char **str, json_append_bytes_t append_bytes, void *data) return ret; } - ret = json_escape_internal(*str, append_bytes, data); + if (encode) { + ret = json_escape_internal(*str, append_bytes, data); + } else { + ret = append_bytes(*str, strlen(*str), data); + } if (!ret) { return append_bytes("\"", 1, data); } @@ -811,20 +817,23 @@ bool_encode(const bool *value, json_append_bytes_t append_bytes, void *data) return append_bytes("false", 5, data); } -int json_arr_encode(const struct json_obj_descr *descr, - const void *val, - json_append_bytes_t append_bytes, - void *data) +int json_arr_encode_full(const struct json_obj_descr *descr, + const void *val, + json_append_bytes_t append_bytes, + void *data, + bool encode_key) { void *ptr = (char *)val + descr->offset; - return arr_encode(descr->array.element_descr, ptr, val, append_bytes, data); + return arr_encode(descr->array.element_descr, ptr, val, append_bytes, data, + encode_key); } static int encode(const struct json_obj_descr *descr, const void *val, json_append_bytes_t append_bytes, - void *data) + void *data, + bool encode_key) { void *ptr = (char *)val + descr->offset; @@ -833,14 +842,14 @@ static int encode(const struct json_obj_descr *descr, case JSON_TOK_TRUE: return bool_encode(ptr, append_bytes, data); case JSON_TOK_STRING: - return str_encode(ptr, append_bytes, data); + return str_encode(ptr, append_bytes, data, true); case JSON_TOK_LIST_START: return arr_encode(descr->array.element_descr, ptr, val, - append_bytes, data); + append_bytes, data, encode_key); case JSON_TOK_OBJECT_START: - return json_obj_encode(descr->object.sub_descr, - descr->object.sub_descr_len, ptr, append_bytes, - data); + return json_obj_encode_full(descr->object.sub_descr, + descr->object.sub_descr_len, ptr, append_bytes, + data, encode_key); case JSON_TOK_NUMBER: return num_encode(ptr, append_bytes, data); default: @@ -851,11 +860,12 @@ static int encode(const struct json_obj_descr *descr, static int encode_key_value(const struct json_obj_descr *descr, const void *val, json_append_bytes_t append_bytes, - void *data) + void *data, + bool encode_key) { int ret; - ret = str_encode((const char **)&descr->field_name, append_bytes, data); + ret = str_encode((const char **)&descr->field_name, append_bytes, data, encode_key); if (ret < 0) { return ret; } @@ -865,7 +875,7 @@ static int encode_key_value(const struct json_obj_descr *descr, return ret; } - ret = encode(descr, val, append_bytes, data); + ret = encode(descr, val, append_bytes, data, encode_key); if (ret < 0) { return ret; } @@ -873,11 +883,12 @@ static int encode_key_value(const struct json_obj_descr *descr, return ret; } -int json_obj_encode(const struct json_obj_descr *descr, - size_t descr_len, - const void *val, - json_append_bytes_t append_bytes, - void *data) +int json_obj_encode_full(const struct json_obj_descr *descr, + size_t descr_len, + const void *val, + json_append_bytes_t append_bytes, + void *data, + bool encode_key) { size_t i; int ret; @@ -893,7 +904,7 @@ int json_obj_encode(const struct json_obj_descr *descr, * array has only 1 element, this loop won't run. This is fine since order * isn't important for objects, and we save some branches. */ for (i = 1; i < descr_len; i++) { - ret = encode_key_value(&descr[i], val, append_bytes, data); + ret = encode_key_value(&descr[i], val, append_bytes, data, encode_key); if (ret < 0) { return ret; } @@ -905,7 +916,7 @@ int json_obj_encode(const struct json_obj_descr *descr, } if (descr_len) { - ret = encode_key_value(&descr[0], val, append_bytes, data); + ret = encode_key_value(&descr[0], val, append_bytes, data, encode_key); if (ret < 0) { return ret; } diff --git a/src/samples/techempower/json.h b/src/samples/techempower/json.h index f04863f64..757bbe457 100644 --- a/src/samples/techempower/json.h +++ b/src/samples/techempower/json.h @@ -612,12 +612,22 @@ int json_obj_encode_buf(const struct json_obj_descr *descr, * @return 0 if object has been successfully encoded. A negative value * indicates an error. */ -int json_obj_encode(const struct json_obj_descr *descr, - size_t descr_len, - const void *val, - json_append_bytes_t append_bytes, - void *data); +int json_obj_encode_full(const struct json_obj_descr *descr, + size_t descr_len, + const void *val, + json_append_bytes_t append_bytes, + void *data, + bool encode_key); +static inline int json_obj_encode(const struct json_obj_descr *descr, + size_t descr_len, + const void *val, + json_append_bytes_t append_bytes, + void *data) +{ + return json_obj_encode_full(descr, descr_len, val, append_bytes, data, + true); +} /** * @brief Encodes an array using an arbitrary writer function * @@ -635,10 +645,18 @@ int json_obj_encode(const struct json_obj_descr *descr, * @return 0 if object has been successfully encoded. A negative value * indicates an error. */ -int json_arr_encode(const struct json_obj_descr *descr, - const void *val, - json_append_bytes_t append_bytes, - void *data); +int json_arr_encode_full(const struct json_obj_descr *descr, + const void *val, + json_append_bytes_t append_bytes, + void *data, + bool encode_key); +static inline int json_arr_encode(const struct json_obj_descr *descr, + const void *val, + json_append_bytes_t append_bytes, + void *data) +{ + return json_arr_encode_full(descr, val, append_bytes, data, true); +} #ifdef __cplusplus } diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index 18a044e80..87cb65bff 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -160,8 +160,8 @@ json_response_obj(struct lwan_response *response, { lwan_strbuf_grow_to(response->buffer, 128); - if (json_obj_encode(descr, descr_len, data, append_to_strbuf, - response->buffer) < 0) + if (json_obj_encode_full(descr, descr_len, data, append_to_strbuf, + response->buffer, false) < 0) return HTTP_INTERNAL_ERROR; response->mime_type = "application/json"; @@ -175,7 +175,8 @@ json_response_arr(struct lwan_response *response, { lwan_strbuf_grow_to(response->buffer, 128); - if (json_arr_encode(descr, data, append_to_strbuf, response->buffer) < 0) + if (json_arr_encode_full(descr, data, append_to_strbuf, response->buffer, + false) < 0) return HTTP_INTERNAL_ERROR; response->mime_type = "application/json"; From f77b0563f36a2666cb19284db11804863165b095 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 15 Jan 2020 19:50:10 -0800 Subject: [PATCH 1362/2505] Use UNLIKELY() macros in the JSON library --- src/samples/techempower/json.c | 55 +++++++++++++++++----------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/src/samples/techempower/json.c b/src/samples/techempower/json.c index c66a3ee14..e1aaa9897 100644 --- a/src/samples/techempower/json.c +++ b/src/samples/techempower/json.c @@ -15,6 +15,7 @@ #include #include "json.h" +#include "lwan.h" #include "int-to-str.h" struct token { @@ -109,7 +110,7 @@ static void *lexer_string(struct lexer *lexer) while (true) { int chr = next(lexer); - if (chr == '\0') { + if (UNLIKELY(chr == '\0')) { emit(lexer, JSON_TOK_ERROR); return NULL; } @@ -126,19 +127,19 @@ static void *lexer_string(struct lexer *lexer) case 't': continue; case 'u': - if (!isxdigit(next(lexer))) { + if (UNLIKELY(!isxdigit(next(lexer)))) { goto error; } - if (!isxdigit(next(lexer))) { + if (UNLIKELY(!isxdigit(next(lexer)))) { goto error; } - if (!isxdigit(next(lexer))) { + if (UNLIKELY(!isxdigit(next(lexer)))) { goto error; } - if (!isxdigit(next(lexer))) { + if (UNLIKELY(!isxdigit(next(lexer)))) { goto error; } @@ -167,7 +168,7 @@ static void *lexer_string(struct lexer *lexer) static int accept_run(struct lexer *lexer, const char *run) { for (; *run; run++) { - if (next(lexer) != *run) { + if (UNLIKELY(next(lexer) != *run)) { return -EINVAL; } } @@ -200,7 +201,7 @@ static void *lexer_boolean(struct lexer *lexer) static void *lexer_null(struct lexer *lexer) { - if (accept_run(lexer, "ull") < 0) { + if (UNLIKELY(accept_run(lexer, "ull") < 0)) { emit(lexer, JSON_TOK_ERROR); return NULL; } @@ -250,7 +251,7 @@ static void *lexer_json(struct lexer *lexer) case 'f': return lexer_boolean; case '-': - if (isdigit(peek(lexer))) { + if (LIKELY(isdigit(peek(lexer)))) { return lexer_number; } @@ -261,7 +262,7 @@ static void *lexer_json(struct lexer *lexer) continue; } - if (isdigit(chr)) { + if (LIKELY(isdigit(chr))) { return lexer_number; } @@ -286,11 +287,11 @@ static int obj_init(struct json_obj *json, char *data, size_t len) lexer_init(&json->lexer, data, len); - if (!lexer_next(&json->lexer, &token)) { + if (UNLIKELY(!lexer_next(&json->lexer, &token))) { return -EINVAL; } - if (token.type != JSON_TOK_OBJECT_START) { + if (UNLIKELY(token.type != JSON_TOK_OBJECT_START)) { return -EINVAL; } @@ -316,7 +317,7 @@ static int obj_next(struct json_obj *json, struct json_obj_key_value *kv) { struct token token; - if (!lexer_next(&json->lexer, &token)) { + if (UNLIKELY(!lexer_next(&json->lexer, &token))) { return -EINVAL; } @@ -329,11 +330,11 @@ static int obj_next(struct json_obj *json, struct json_obj_key_value *kv) return 0; case JSON_TOK_COMMA: - if (!lexer_next(&json->lexer, &token)) { + if (UNLIKELY(!lexer_next(&json->lexer, &token))) { return -EINVAL; } - if (token.type != JSON_TOK_STRING) { + if (UNLIKELY(token.type != JSON_TOK_STRING)) { return -EINVAL; } @@ -347,16 +348,16 @@ static int obj_next(struct json_obj *json, struct json_obj_key_value *kv) } /* Match : after key */ - if (!lexer_next(&json->lexer, &token)) { + if (UNLIKELY(!lexer_next(&json->lexer, &token))) { return -EINVAL; } - if (token.type != JSON_TOK_COLON) { + if (UNLIKELY(token.type != JSON_TOK_COLON)) { return -EINVAL; } /* Match value */ - if (!lexer_next(&json->lexer, &kv->value)) { + if (UNLIKELY(!lexer_next(&json->lexer, &kv->value))) { return -EINVAL; } @@ -365,7 +366,7 @@ static int obj_next(struct json_obj *json, struct json_obj_key_value *kv) static int arr_next(struct json_obj *json, struct token *value) { - if (!lexer_next(&json->lexer, value)) { + if (UNLIKELY(!lexer_next(&json->lexer, value))) { return -EINVAL; } @@ -374,7 +375,7 @@ static int arr_next(struct json_obj *json, struct token *value) } if (value->type == JSON_TOK_COMMA) { - if (!lexer_next(&json->lexer, value)) { + if (UNLIKELY(!lexer_next(&json->lexer, value))) { return -EINVAL; } } @@ -527,11 +528,11 @@ static int arr_parse(struct json_obj *obj, return 0; } - if (field == last_elem) { + if (UNLIKELY(field == last_elem)) { return -ENOSPC; } - if (decode_value(obj, elem_descr, &value, field, val) < 0) { + if (UNLIKELY(decode_value(obj, elem_descr, &value, field, val) < 0)) { return -EINVAL; } @@ -576,7 +577,7 @@ static int obj_parse(struct json_obj *obj, /* Store the decoded value */ ret = decode_value(obj, &descr[i], &kv.value, decode_field, val); - if (ret < 0) { + if (UNLIKELY(UNLIKELY(ret < 0))) { return ret; } @@ -600,7 +601,7 @@ int json_obj_parse(char *payload, assert(descr_len < (sizeof(ret) * CHAR_BIT - 1)); ret = obj_init(&obj, payload, len); - if (ret < 0) { + if (UNLIKELY(ret < 0)) { return ret; } @@ -678,7 +679,7 @@ ssize_t json_escape(char *str, size_t *len, size_t buf_size) return 0; } - if (escaped_len >= buf_size) { + if (UNLIKELY(escaped_len >= buf_size)) { return -ENOMEM; } @@ -781,7 +782,7 @@ str_encode(const char **str, json_append_bytes_t append_bytes, void *data, bool int ret; ret = append_bytes("\"", 1, data); - if (ret < 0) { + if (UNLIKELY(ret < 0)) { return ret; } @@ -935,7 +936,7 @@ static int append_bytes_to_buf(const char *bytes, size_t len, void *data) { struct appender *appender = data; - if (len > appender->size - appender->used) { + if (UNLIKELY(len > appender->size - appender->used)) { return -ENOMEM; } @@ -976,7 +977,7 @@ ssize_t json_calc_encoded_len(const struct json_obj_descr *descr, int ret; ret = json_obj_encode(descr, descr_len, val, measure_bytes, &total); - if (ret < 0) { + if (UNLIKELY(ret < 0)) { return ret; } From 9dd8e8ad2d1561ea790ede5c81f1e056b8cceb0a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 15 Jan 2020 19:50:24 -0800 Subject: [PATCH 1363/2505] Simplify parsing of true/false tokens --- src/samples/techempower/json.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/samples/techempower/json.c b/src/samples/techempower/json.c index e1aaa9897..b12387d16 100644 --- a/src/samples/techempower/json.c +++ b/src/samples/techempower/json.c @@ -178,17 +178,17 @@ static int accept_run(struct lexer *lexer, const char *run) static void *lexer_boolean(struct lexer *lexer) { - backup(lexer); + /* Already matched either `t' or `f' at this point */ switch (next(lexer)) { - case 't': - if (!accept_run(lexer, "rue")) { + case 'r': + if (LIKELY(!accept_run(lexer, "ue"))) { emit(lexer, JSON_TOK_TRUE); return lexer_json; } break; - case 'f': - if (!accept_run(lexer, "alse")) { + case 'a': + if (LIKELY(!accept_run(lexer, "lse"))) { emit(lexer, JSON_TOK_FALSE); return lexer_json; } From 6a9144e02bf301554c140f491482b3f2b4983a0c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 15 Jan 2020 19:50:44 -0800 Subject: [PATCH 1364/2505] Simplify JSON array encoding Perform a comparison with 0 and return early early. --- src/samples/techempower/json.c | 67 +++++++++++++++++----------------- 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/src/samples/techempower/json.c b/src/samples/techempower/json.c index b12387d16..020b449a7 100644 --- a/src/samples/techempower/json.c +++ b/src/samples/techempower/json.c @@ -730,47 +730,48 @@ static int arr_encode(const struct json_obj_descr *elem_descr, size_t i; int ret; + if (UNLIKELY(!n_elem)) { + return append_bytes("[]", 2, data); + } + ret = append_bytes("[", 1, data); - if (ret < 0) { + if (UNLIKELY(ret < 0)) { return ret; } - if (n_elem >= 1) { - n_elem--; - - for (i = 0; i < n_elem; i++) { - /* - * Though "field" points at the next element in the array which - * we need to encode, the value in elem_descr->offset is - * actually the offset of the length field in the "parent" - * struct containing the array. - * - * To patch things up, we lie to encode() about where the field - * is by exactly the amount it will offset it. This is a size - * optimization for struct json_obj_descr: the alternative is to - * keep a separate field next to element_descr which is an - * offset to the length field in the parent struct, but that - * would add a size_t to every descriptor. - */ - ret = encode(elem_descr, (char *)field - elem_descr->offset, - append_bytes, data, encode_key); - if (ret < 0) { - return ret; - } - - ret = append_bytes(",", 1, data); - if (ret < 0) { - return ret; - } - - field = (char *)field + elem_size; - } - + n_elem--; + for (i = 0; i < n_elem; i++) { + /* + * Though "field" points at the next element in the array which we + * need to encode, the value in elem_descr->offset is actually the + * offset of the length field in the "parent" struct containing the + * array. + * + * To patch things up, we lie to encode() about where the field is + * by exactly the amount it will offset it. This is a size + * optimization for struct json_obj_descr: the alternative is to + * keep a separate field next to element_descr which is an offset to + * the length field in the parent struct, but that would add a + * size_t to every descriptor. + */ ret = encode(elem_descr, (char *)field - elem_descr->offset, append_bytes, data, encode_key); - if (ret < 0) { + if (UNLIKELY(ret < 0)) { + return ret; + } + + ret = append_bytes(",", 1, data); + if (UNLIKELY(ret < 0)) { return ret; } + + field = (char *)field + elem_size; + } + + ret = encode(elem_descr, (char *)field - elem_descr->offset, + append_bytes, data, encode_key); + if (UNLIKELY(ret < 0)) { + return ret; } return append_bytes("]", 1, data); From 1209418baa9ae361299a6388f7e82180f3f978b9 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 15 Jan 2020 19:51:25 -0800 Subject: [PATCH 1365/2505] Tail call value encoding when encoding key/value for JSON objects --- src/samples/techempower/json.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/samples/techempower/json.c b/src/samples/techempower/json.c index 020b449a7..e170204a6 100644 --- a/src/samples/techempower/json.c +++ b/src/samples/techempower/json.c @@ -868,21 +868,16 @@ static int encode_key_value(const struct json_obj_descr *descr, int ret; ret = str_encode((const char **)&descr->field_name, append_bytes, data, encode_key); - if (ret < 0) { + if (UNLIKELY(ret < 0)) { return ret; } ret = append_bytes(":", 1, data); - if (ret < 0) { - return ret; - } - - ret = encode(descr, val, append_bytes, data, encode_key); - if (ret < 0) { + if (UNLIKELY(ret < 0)) { return ret; } - return ret; + return encode(descr, val, append_bytes, data, encode_key); } int json_obj_encode_full(const struct json_obj_descr *descr, From bc5566a07b0dba029086ae97ab1550611f529ac0 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 15 Jan 2020 19:51:47 -0800 Subject: [PATCH 1366/2505] Simplify JSON object encoding Perform a zero-check before proceeding with the rest of the encoding procedure. --- src/samples/techempower/json.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/samples/techempower/json.c b/src/samples/techempower/json.c index e170204a6..3d892966d 100644 --- a/src/samples/techempower/json.c +++ b/src/samples/techempower/json.c @@ -890,8 +890,13 @@ int json_obj_encode_full(const struct json_obj_descr *descr, size_t i; int ret; + if (UNLIKELY(!descr_len)) { + /* Code below assumes at least one descr, so return early. */ + return append_bytes("{}", 2, data); + } + ret = append_bytes("{", 1, data); - if (ret < 0) { + if (UNLIKELY(ret < 0)) { return ret; } @@ -902,21 +907,19 @@ int json_obj_encode_full(const struct json_obj_descr *descr, * isn't important for objects, and we save some branches. */ for (i = 1; i < descr_len; i++) { ret = encode_key_value(&descr[i], val, append_bytes, data, encode_key); - if (ret < 0) { + if (UNLIKELY(ret < 0)) { return ret; } ret = append_bytes(",", 1, data); - if (ret < 0) { + if (UNLIKELY(ret < 0)) { return ret; } } - if (descr_len) { - ret = encode_key_value(&descr[0], val, append_bytes, data, encode_key); - if (ret < 0) { - return ret; - } + ret = encode_key_value(&descr[0], val, append_bytes, data, encode_key); + if (UNLIKELY(ret < 0)) { + return ret; } return append_bytes("}", 1, data); From 590d69aaf60ad9590eaeea1fe48aee2606d3cca7 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 16 Jan 2020 08:46:19 -0800 Subject: [PATCH 1367/2505] Empty arrays and objects are uncommon in JSON No need to have a special case for these, so making two append_bytes() calls when we encounter either an object or an array that's empty is more than fine. --- src/samples/techempower/json.c | 107 ++++++++++++++++----------------- 1 file changed, 53 insertions(+), 54 deletions(-) diff --git a/src/samples/techempower/json.c b/src/samples/techempower/json.c index 3d892966d..ca674d6d3 100644 --- a/src/samples/techempower/json.c +++ b/src/samples/techempower/json.c @@ -1,5 +1,6 @@ /* * Copyright (c) 2017 Intel Corporation + * Copyright (c) 2020 Leandro A. F. Pereira * * SPDX-License-Identifier: Apache-2.0 */ @@ -730,48 +731,46 @@ static int arr_encode(const struct json_obj_descr *elem_descr, size_t i; int ret; - if (UNLIKELY(!n_elem)) { - return append_bytes("[]", 2, data); - } - ret = append_bytes("[", 1, data); if (UNLIKELY(ret < 0)) { return ret; } - n_elem--; - for (i = 0; i < n_elem; i++) { - /* - * Though "field" points at the next element in the array which we - * need to encode, the value in elem_descr->offset is actually the - * offset of the length field in the "parent" struct containing the - * array. - * - * To patch things up, we lie to encode() about where the field is - * by exactly the amount it will offset it. This is a size - * optimization for struct json_obj_descr: the alternative is to - * keep a separate field next to element_descr which is an offset to - * the length field in the parent struct, but that would add a - * size_t to every descriptor. - */ - ret = encode(elem_descr, (char *)field - elem_descr->offset, - append_bytes, data, encode_key); - if (UNLIKELY(ret < 0)) { - return ret; + if (LIKELY(n_elem)) { + n_elem--; + for (i = 0; i < n_elem; i++) { + /* + * Though "field" points at the next element in the array which we + * need to encode, the value in elem_descr->offset is actually the + * offset of the length field in the "parent" struct containing the + * array. + * + * To patch things up, we lie to encode() about where the field is + * by exactly the amount it will offset it. This is a size + * optimization for struct json_obj_descr: the alternative is to + * keep a separate field next to element_descr which is an offset to + * the length field in the parent struct, but that would add a + * size_t to every descriptor. + */ + ret = encode(elem_descr, (char *)field - elem_descr->offset, + append_bytes, data, encode_key); + if (UNLIKELY(ret < 0)) { + return ret; + } + + ret = append_bytes(",", 1, data); + if (UNLIKELY(ret < 0)) { + return ret; + } + + field = (char *)field + elem_size; } - ret = append_bytes(",", 1, data); + ret = encode(elem_descr, (char *)field - elem_descr->offset, + append_bytes, data, encode_key); if (UNLIKELY(ret < 0)) { return ret; } - - field = (char *)field + elem_size; - } - - ret = encode(elem_descr, (char *)field - elem_descr->offset, - append_bytes, data, encode_key); - if (UNLIKELY(ret < 0)) { - return ret; } return append_bytes("]", 1, data); @@ -867,7 +866,8 @@ static int encode_key_value(const struct json_obj_descr *descr, { int ret; - ret = str_encode((const char **)&descr->field_name, append_bytes, data, encode_key); + ret = str_encode((const char **)&descr->field_name, append_bytes, data, + encode_key); if (UNLIKELY(ret < 0)) { return ret; } @@ -887,41 +887,40 @@ int json_obj_encode_full(const struct json_obj_descr *descr, void *data, bool encode_key) { - size_t i; int ret; - if (UNLIKELY(!descr_len)) { - /* Code below assumes at least one descr, so return early. */ - return append_bytes("{}", 2, data); - } - ret = append_bytes("{", 1, data); if (UNLIKELY(ret < 0)) { return ret; } - /* To avoid checking if we're encoding the last element on each iteration of - * this loop, start at the second descriptor, and always write the comma. - * Then, after the loop, encode the first descriptor. If the descriptor - * array has only 1 element, this loop won't run. This is fine since order - * isn't important for objects, and we save some branches. */ - for (i = 1; i < descr_len; i++) { - ret = encode_key_value(&descr[i], val, append_bytes, data, encode_key); - if (UNLIKELY(ret < 0)) { - return ret; + if (LIKELY(descr_len)) { + /* To avoid checking if we're encoding the last element on each + * iteration of this loop, start at the second descriptor, and always + * write the comma. Then, after the loop, encode the first descriptor. + * If the descriptor array has only 1 element, this loop won't run. This + * is fine since order isn't important for objects, and we save some + * branches. */ + + for (size_t i = 1; i < descr_len; i++) { + ret = encode_key_value(&descr[i], val, append_bytes, data, + encode_key); + if (UNLIKELY(ret < 0)) { + return ret; + } + + ret = append_bytes(",", 1, data); + if (UNLIKELY(ret < 0)) { + return ret; + } } - ret = append_bytes(",", 1, data); + ret = encode_key_value(&descr[0], val, append_bytes, data, encode_key); if (UNLIKELY(ret < 0)) { return ret; } } - ret = encode_key_value(&descr[0], val, append_bytes, data, encode_key); - if (UNLIKELY(ret < 0)) { - return ret; - } - return append_bytes("}", 1, data); } From aa5a9bfa29141de412f6a2c6c52fd936ad358dcc Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 17 Jan 2020 18:21:15 -0800 Subject: [PATCH 1368/2505] For the JSON encoder, s/encode_key/escape_key/g --- src/samples/techempower/json.c | 34 +++++++++++++++++----------------- src/samples/techempower/json.h | 4 ++-- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/samples/techempower/json.c b/src/samples/techempower/json.c index ca674d6d3..dff7c3f71 100644 --- a/src/samples/techempower/json.c +++ b/src/samples/techempower/json.c @@ -711,14 +711,14 @@ static int encode(const struct json_obj_descr *descr, const void *val, json_append_bytes_t append_bytes, void *data, - bool encode_key); + bool escape_key); static int arr_encode(const struct json_obj_descr *elem_descr, const void *field, const void *val, json_append_bytes_t append_bytes, void *data, - bool encode_key) + bool escape_key) { ptrdiff_t elem_size = get_elem_size(elem_descr); /* @@ -753,7 +753,7 @@ static int arr_encode(const struct json_obj_descr *elem_descr, * size_t to every descriptor. */ ret = encode(elem_descr, (char *)field - elem_descr->offset, - append_bytes, data, encode_key); + append_bytes, data, escape_key); if (UNLIKELY(ret < 0)) { return ret; } @@ -767,7 +767,7 @@ static int arr_encode(const struct json_obj_descr *elem_descr, } ret = encode(elem_descr, (char *)field - elem_descr->offset, - append_bytes, data, encode_key); + append_bytes, data, escape_key); if (UNLIKELY(ret < 0)) { return ret; } @@ -777,7 +777,7 @@ static int arr_encode(const struct json_obj_descr *elem_descr, } static int -str_encode(const char **str, json_append_bytes_t append_bytes, void *data, bool encode) +str_encode(const char **str, json_append_bytes_t append_bytes, void *data, bool escape) { int ret; @@ -786,7 +786,7 @@ str_encode(const char **str, json_append_bytes_t append_bytes, void *data, bool return ret; } - if (encode) { + if (escape) { ret = json_escape_internal(*str, append_bytes, data); } else { ret = append_bytes(*str, strlen(*str), data); @@ -822,19 +822,19 @@ int json_arr_encode_full(const struct json_obj_descr *descr, const void *val, json_append_bytes_t append_bytes, void *data, - bool encode_key) + bool escape_key) { void *ptr = (char *)val + descr->offset; return arr_encode(descr->array.element_descr, ptr, val, append_bytes, data, - encode_key); + escape_key); } static int encode(const struct json_obj_descr *descr, const void *val, json_append_bytes_t append_bytes, void *data, - bool encode_key) + bool escape_key) { void *ptr = (char *)val + descr->offset; @@ -846,11 +846,11 @@ static int encode(const struct json_obj_descr *descr, return str_encode(ptr, append_bytes, data, true); case JSON_TOK_LIST_START: return arr_encode(descr->array.element_descr, ptr, val, - append_bytes, data, encode_key); + append_bytes, data, escape_key); case JSON_TOK_OBJECT_START: return json_obj_encode_full(descr->object.sub_descr, descr->object.sub_descr_len, ptr, append_bytes, - data, encode_key); + data, escape_key); case JSON_TOK_NUMBER: return num_encode(ptr, append_bytes, data); default: @@ -862,12 +862,12 @@ static int encode_key_value(const struct json_obj_descr *descr, const void *val, json_append_bytes_t append_bytes, void *data, - bool encode_key) + bool escape_key) { int ret; ret = str_encode((const char **)&descr->field_name, append_bytes, data, - encode_key); + escape_key); if (UNLIKELY(ret < 0)) { return ret; } @@ -877,7 +877,7 @@ static int encode_key_value(const struct json_obj_descr *descr, return ret; } - return encode(descr, val, append_bytes, data, encode_key); + return encode(descr, val, append_bytes, data, escape_key); } int json_obj_encode_full(const struct json_obj_descr *descr, @@ -885,7 +885,7 @@ int json_obj_encode_full(const struct json_obj_descr *descr, const void *val, json_append_bytes_t append_bytes, void *data, - bool encode_key) + bool escape_key) { int ret; @@ -904,7 +904,7 @@ int json_obj_encode_full(const struct json_obj_descr *descr, for (size_t i = 1; i < descr_len; i++) { ret = encode_key_value(&descr[i], val, append_bytes, data, - encode_key); + escape_key); if (UNLIKELY(ret < 0)) { return ret; } @@ -915,7 +915,7 @@ int json_obj_encode_full(const struct json_obj_descr *descr, } } - ret = encode_key_value(&descr[0], val, append_bytes, data, encode_key); + ret = encode_key_value(&descr[0], val, append_bytes, data, escape_key); if (UNLIKELY(ret < 0)) { return ret; } diff --git a/src/samples/techempower/json.h b/src/samples/techempower/json.h index 757bbe457..ac6445c50 100644 --- a/src/samples/techempower/json.h +++ b/src/samples/techempower/json.h @@ -617,7 +617,7 @@ int json_obj_encode_full(const struct json_obj_descr *descr, const void *val, json_append_bytes_t append_bytes, void *data, - bool encode_key); + bool escape_key); static inline int json_obj_encode(const struct json_obj_descr *descr, size_t descr_len, const void *val, @@ -649,7 +649,7 @@ int json_arr_encode_full(const struct json_obj_descr *descr, const void *val, json_append_bytes_t append_bytes, void *data, - bool encode_key); + bool escape_key); static inline int json_arr_encode(const struct json_obj_descr *descr, const void *val, json_append_bytes_t append_bytes, From 3ff902899d346cd3048779c5c499c34f6416d3d4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 18 Jan 2020 14:46:39 -0800 Subject: [PATCH 1369/2505] Get rid of compiler warning when compiling db_stmt_step_sqlite() --- src/samples/techempower/database.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/samples/techempower/database.c b/src/samples/techempower/database.c index 19767399a..1a0153b24 100644 --- a/src/samples/techempower/database.c +++ b/src/samples/techempower/database.c @@ -302,7 +302,11 @@ static bool db_stmt_step_sqlite(const struct db_stmt *stmt, case 's': { char *out = va_arg(ap, char *); size_t bufsize = va_arg(ap, size_t); - strncpy(out, sqlite3_column_text(stmt_sqlite->sqlite, r), bufsize); + + strncpy(out, + (const char *)sqlite3_column_text(stmt_sqlite->sqlite, r), + bufsize); + break; } default: From bdc3852b187e86721e0768c170cd79a95a4e9775 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 18 Jan 2020 14:47:34 -0800 Subject: [PATCH 1370/2505] sqlite3_open_v2() requires SQLITE_OPEN_READWRITE to open a R/W DB --- src/samples/techempower/database.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/samples/techempower/database.c b/src/samples/techempower/database.c index 1a0153b24..e1ff94042 100644 --- a/src/samples/techempower/database.c +++ b/src/samples/techempower/database.c @@ -364,7 +364,7 @@ db_connect_sqlite(const char *path, bool read_only, const char *pragmas[]) if (!db_sqlite) return NULL; - int flags = read_only ? SQLITE_OPEN_READONLY : 0; + int flags = read_only ? SQLITE_OPEN_READONLY : SQLITE_OPEN_READWRITE; int ret = sqlite3_open_v2(path, &db_sqlite->sqlite, flags, NULL); if (ret != SQLITE_OK) { free(db_sqlite); From 31f6b2993547a7e400ee7f69745a5a2a00ebb9b4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 19 Jan 2020 16:32:56 -0800 Subject: [PATCH 1371/2505] Revert "Remove one branch from lwan_response()" This reverts commit e42b16556d2aba0966d911eb2358bec27450f0f2. Some tests were failing and this is the cause; I don't have time to debug this right now. --- src/lib/lwan-response.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 34a8555fc..85971a5f8 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -140,8 +140,6 @@ void lwan_response(struct lwan_request *request, enum lwan_http_status status) const struct lwan_response *response = &request->response; char headers[DEFAULT_HEADERS_SIZE]; - assert(!(request->flags & RESPONSE_SENT_HEADERS)); - if (UNLIKELY(request->flags & RESPONSE_CHUNKED_ENCODING)) { /* Send last, 0-sized chunk */ lwan_strbuf_reset(response->buffer); @@ -150,6 +148,11 @@ void lwan_response(struct lwan_request *request, enum lwan_http_status status) return; } + if (UNLIKELY(request->flags & RESPONSE_SENT_HEADERS)) { + lwan_status_debug("Headers already sent, ignoring call"); + return; + } + if (UNLIKELY(!response->mime_type)) { /* Requests without a MIME Type are errors from handlers that should just be handled by lwan_default_response(). */ From 6d7d6fdd0c268e690b57311d9d121004974b2738 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 19 Jan 2020 16:36:55 -0800 Subject: [PATCH 1372/2505] Get rid of `p' temporary variable in parse_headers() --- src/lib/lwan-request.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 4ec3634ea..16dedb71c 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -546,10 +546,8 @@ static bool parse_headers(struct lwan_request_parser_helper *helper, size_t n_headers = 0; char *next_header; - for (char *p = buffer + 1;;) { - char *next_chr = p; - - next_header = memchr(next_chr, '\r', (size_t)(buffer_end - p)); + for (char *next_chr = buffer + 1;;) { + next_header = memchr(next_chr, '\r', (size_t)(buffer_end - next_chr)); if (UNLIKELY(!next_header)) return false; @@ -575,8 +573,8 @@ static bool parse_headers(struct lwan_request_parser_helper *helper, return false; } - p = next_header + HEADER_TERMINATOR_LEN; - if (UNLIKELY(p >= buffer_end)) + next_chr = next_header + HEADER_TERMINATOR_LEN; + if (UNLIKELY(next_chr >= buffer_end)) return false; } From ae031fcafd30256066029a21a42f56927edcddb0 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 21 Jan 2020 18:57:03 -0800 Subject: [PATCH 1373/2505] Add image/gif MIME type to the fast path --- src/bin/tools/mimegen.c | 2 ++ src/lib/lwan-tables.c | 19 +++++++------------ 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/bin/tools/mimegen.c b/src/bin/tools/mimegen.c index 7f8650fc9..08d6d85e5 100644 --- a/src/bin/tools/mimegen.c +++ b/src/bin/tools/mimegen.c @@ -182,6 +182,8 @@ static bool is_builtin_mime_type(const char *mime) return true; if (streq(mime, "image/jpeg")) return true; + if (streq(mime, "image/gif")) + return true; if (streq(mime, "image/png")) return true; if (streq(mime, "text/html")) diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index 3ffa10b79..1803df498 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -117,18 +117,13 @@ lwan_determine_mime_type_for_file_name(const char *file_name) goto fallback; STRING_SWITCH_L(last_dot) { - case STR4_INT_L('.','j','p','g'): - return "image/jpeg"; - case STR4_INT_L('.','p','n','g'): - return "image/png"; - case STR4_INT_L('.','h','t','m'): - return "text/html"; - case STR4_INT_L('.','c','s','s'): - return "text/css"; - case STR4_INT_L('.','t','x','t'): - return "text/plain"; - case STR4_INT_L('.','j','s',0x20): - return "application/javascript"; + case STR4_INT_L('.','g','i','f'): return "image/gif"; + case STR4_INT_L('.','j','p','g'): return "image/jpeg"; + case STR4_INT_L('.','p','n','g'): return "image/png"; + case STR4_INT_L('.','h','t','m'): return "text/html"; + case STR4_INT_L('.','c','s','s'): return "text/css"; + case STR4_INT_L('.','t','x','t'): return "text/plain"; + case STR4_INT_L('.','j','s',' '): return "application/javascript"; } if (LIKELY(*last_dot)) { From fe5ab8c89a139447e23cc23e77894a16565cca31 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 21 Jan 2020 18:59:43 -0800 Subject: [PATCH 1374/2505] Use a helper function to simplify serve_bufer() with struct lwan_value --- src/bin/tools/bin2hex.c | 14 ++++++-- src/lib/lwan-mod-serve-files.c | 65 ++++++++++++++++++---------------- 2 files changed, 46 insertions(+), 33 deletions(-) diff --git a/src/bin/tools/bin2hex.c b/src/bin/tools/bin2hex.c index dc7b3cf0e..3b4795c9d 100644 --- a/src/bin/tools/bin2hex.c +++ b/src/bin/tools/bin2hex.c @@ -26,6 +26,8 @@ #include #include +#include "lwan.h" + static int bin2hex(const char *path, const char *identifier) { int fd = open(path, O_RDONLY | O_CLOEXEC); @@ -51,7 +53,13 @@ static int bin2hex(const char *path, const char *identifier) for (i = 0; i < st.st_size; i++) printf("0x%02x, ", ptr[i] & 0xff); - printf("};\n\n"); + printf("\n};\n"); + + printf("static const struct lwan_value %s_value = {.value = (char *)%s, .len = " + "sizeof(%s)};\n", + identifier, identifier, identifier); + + printf("\n"); munmap(ptr, (size_t)st.st_size); @@ -63,8 +71,7 @@ int main(int argc, char *argv[]) int arg; if (argc < 2) { - fprintf(stderr, "Usage: %s /path/to/file file_identifier [...]\n", - argv[0]); + fprintf(stderr, "Usage: %s /path/to/file file_identifier [...]\n", argv[0]); return 1; } @@ -75,6 +82,7 @@ int main(int argc, char *argv[]) printf("/* Auto generated by %s, do not edit. */\n", argv[0]); printf("#pragma once\n\n"); + printf("#include \"lwan.h\"\n"); for (arg = 1; arg < argc; arg += 2) { const char *path = argv[arg]; diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 6ef4ae2c8..cd58a2dde 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -1188,29 +1188,39 @@ static enum lwan_http_status serve_buffer(struct lwan_request *request, return status_code; } -static enum lwan_http_status mmap_serve(struct lwan_request *request, - void *data) +static ALWAYS_INLINE enum lwan_http_status +serve_value(struct lwan_request *request, + const char *mime_type, + const struct lwan_value *value, + const struct lwan_key_value *headers, + enum lwan_http_status status_code) +{ + return serve_buffer(request, mime_type, value->value, value->len, headers, + status_code); +} + +static enum lwan_http_status mmap_serve(struct lwan_request *request, void *data) { struct file_cache_entry *fce = data; struct mmap_cache_data *md = &fce->mmap_cache_data; #if defined(HAVE_ZSTD) if (md->zstd.len && accepts_encoding(request, REQUEST_ACCEPT_ZSTD)) { - return serve_buffer(request, fce->mime_type, md->zstd.value, - md->zstd.len, zstd_compression_hdr, HTTP_OK); + return serve_value(request, fce->mime_type, &md->zstd, zstd_compression_hdr, + HTTP_OK); } #endif #if defined(HAVE_BROTLI) if (md->brotli.len && accepts_encoding(request, REQUEST_ACCEPT_BROTLI)) { - return serve_buffer(request, fce->mime_type, md->brotli.value, - md->brotli.len, br_compression_hdr, HTTP_OK); + return serve_value(request, fce->mime_type, &md->brotli, br_compression_hdr, + HTTP_OK); } #endif if (md->deflated.len && accepts_encoding(request, REQUEST_ACCEPT_DEFLATE)) { - return serve_buffer(request, fce->mime_type, md->deflated.value, - md->deflated.len, deflate_compression_hdr, HTTP_OK); + return serve_value(request, fce->mime_type, &md->deflated, + deflate_compression_hdr, HTTP_OK); } off_t from, to; @@ -1218,15 +1228,14 @@ static enum lwan_http_status mmap_serve(struct lwan_request *request, compute_range(request, &from, &to, (off_t)md->uncompressed.len); if (status == HTTP_OK || status == HTTP_PARTIAL_CONTENT) { return serve_buffer(request, fce->mime_type, - (char *)md->uncompressed.value + from, - (size_t)(to - from), NULL, status); + (char *)md->uncompressed.value + from, (size_t)(to - from), + NULL, status); } return status; } -static enum lwan_http_status dirlist_serve(struct lwan_request *request, - void *data) +static enum lwan_http_status dirlist_serve(struct lwan_request *request, void *data) { struct file_cache_entry *fce = data; struct dir_list_cache_data *dd = &fce->dir_list_cache_data; @@ -1235,34 +1244,30 @@ static enum lwan_http_status dirlist_serve(struct lwan_request *request, if (!icon) { #if defined(HAVE_BROTLI) if (dd->brotli.len && accepts_encoding(request, REQUEST_ACCEPT_BROTLI)) { - return serve_buffer(request, fce->mime_type, dd->brotli.value, - dd->brotli.len, br_compression_hdr, HTTP_OK); + return serve_value(request, fce->mime_type, &dd->brotli, br_compression_hdr, + HTTP_OK); } #endif if (dd->deflated.len && accepts_encoding(request, REQUEST_ACCEPT_DEFLATE)) { - return serve_buffer(request, fce->mime_type, dd->deflated.value, - dd->deflated.len, deflate_compression_hdr, - HTTP_OK); + return serve_value(request, fce->mime_type, &dd->deflated, + deflate_compression_hdr, HTTP_OK); } - return serve_buffer( - request, fce->mime_type, lwan_strbuf_get_buffer(&dd->rendered), - lwan_strbuf_get_length(&dd->rendered), NULL, HTTP_OK); + return serve_buffer(request, fce->mime_type, + lwan_strbuf_get_buffer(&dd->rendered), + lwan_strbuf_get_length(&dd->rendered), NULL, HTTP_OK); } - STRING_SWITCH(icon) { - case STR4_INT('b','a','c','k'): - return serve_buffer(request, "image/gif", back_gif, sizeof(back_gif), - NULL, HTTP_OK); + STRING_SWITCH (icon) { + case STR4_INT('b', 'a', 'c', 'k'): + return serve_value(request, "image/gif", &back_gif_value, NULL, HTTP_OK); - case STR4_INT('f','i','l','e'): - return serve_buffer(request, "image/gif", file_gif, sizeof(file_gif), - NULL, HTTP_OK); + case STR4_INT('f', 'i', 'l', 'e'): + return serve_value(request, "image/gif", &file_gif_value, NULL, HTTP_OK); - case STR4_INT('f','o','l','d'): - return serve_buffer(request, "image/gif", folder_gif, - sizeof(folder_gif), NULL, HTTP_OK); + case STR4_INT('f', 'o', 'l', 'd'): + return serve_value(request, "image/gif", &folder_gif_value, NULL, HTTP_OK); } return HTTP_NOT_FOUND; From 8ed219e3d1f3f6cd321af6be3f32d08a863e162e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 21 Jan 2020 19:04:45 -0800 Subject: [PATCH 1375/2505] Use case insensitive comparison when looking for MIME types --- src/lib/lwan-tables.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index 1803df498..4e3896802 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -87,12 +87,16 @@ void lwan_tables_init(void) assert(streq(lwan_determine_mime_type_for_file_name(".mkv"), "video/x-matroska")); - assert(streq(lwan_determine_mime_type_for_file_name(".bz2"), - "application/x-bzip2")); assert(streq(lwan_determine_mime_type_for_file_name(".xml"), "application/xml")); assert(streq(lwan_determine_mime_type_for_file_name(".nosuchext"), "application/octet-stream")); + assert(streq(lwan_determine_mime_type_for_file_name(".gif"), + "image/gif")); + assert(streq(lwan_determine_mime_type_for_file_name(".JS"), + "application/javascript")); + assert(streq(lwan_determine_mime_type_for_file_name(".BZ2"), + "application/x-bzip2")); } void @@ -106,7 +110,7 @@ compare_mime_entry(const void *a, const void *b) const char *exta = (const char *)a; const char *extb = (const char *)b; - return strncmp(exta, extb, 8); + return strncasecmp(exta, extb, 8); } const char * From ff6efed8531df9ebf29a779b06821848b94dbb96 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 23 Jan 2020 08:33:09 -0800 Subject: [PATCH 1376/2505] Fix detection of secure_getenv() --- CMakeLists.txt | 1 + src/cmake/lwan-build-config.h.cmake | 1 + 2 files changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index a8ac34a77..f166055a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -142,6 +142,7 @@ check_function_exists(posix_fadvise HAVE_POSIX_FADVISE) check_function_exists(getentropy HAVE_GETENTROPY) check_function_exists(fwrite_unlocked HAVE_FWRITE_UNLOCKED) check_function_exists(gettid HAVE_GETTID) +check_function_exists(secure_getenv HAVE_SECURE_GETENV) # This is available on -ldl in glibc, but some systems (such as OpenBSD) # will bundle these in the C library. This isn't required for glibc anyway, diff --git a/src/cmake/lwan-build-config.h.cmake b/src/cmake/lwan-build-config.h.cmake index adb3809f6..d2669a6f9 100644 --- a/src/cmake/lwan-build-config.h.cmake +++ b/src/cmake/lwan-build-config.h.cmake @@ -43,6 +43,7 @@ #cmakedefine HAVE_GETENTROPY #cmakedefine HAVE_FWRITE_UNLOCKED #cmakedefine HAVE_GETTID +#cmakedefine HAVE_SECURE_GETENV /* Compiler builtins for specific CPU instruction support */ #cmakedefine HAVE_BUILTIN_CLZLL From ccf426d585fb1fd93f2af108d46cbdd0608ccbd3 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 23 Jan 2020 21:00:21 -0800 Subject: [PATCH 1377/2505] Add coro_memdup() to duplicate a memory buffer --- src/lib/lwan-coro.c | 7 +++++++ src/lib/lwan-coro.h | 1 + 2 files changed, 8 insertions(+) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 0e1fb46bf..7fcec8a6b 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -526,3 +526,10 @@ char *coro_printf(struct coro *coro, const char *fmt, ...) coro_defer(coro, free, tmp_str); return tmp_str; } + +void *coro_memdup(struct coro *coro, const void *src, size_t len) +{ + void *ptr = coro_malloc(coro, len); + + return LIKELY(ptr) ? memcpy(ptr, src, len) : NULL; +} diff --git a/src/lib/lwan-coro.h b/src/lib/lwan-coro.h index e6c9d061e..2dd8cc294 100644 --- a/src/lib/lwan-coro.h +++ b/src/lib/lwan-coro.h @@ -68,3 +68,4 @@ char *coro_strdup(struct coro *coro, const char *str); char *coro_strndup(struct coro *coro, const char *str, size_t len); char *coro_printf(struct coro *coro, const char *fmt, ...) __attribute__((format(printf, 2, 3))); +void *coro_memdup(struct coro *coro, const void *src, size_t len); From b136942ed10126d8912c5442e225d9d8ec80b12f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 23 Jan 2020 21:01:17 -0800 Subject: [PATCH 1378/2505] Implement coro_strdup() and coro_strndup() in terms of coro_strdup() --- src/lib/lwan-coro.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 7fcec8a6b..d4c368610 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -494,20 +494,17 @@ void *coro_malloc(struct coro *coro, size_t size) char *coro_strndup(struct coro *coro, const char *str, size_t max_len) { const size_t len = strnlen(str, max_len) + 1; - /* FIXME: Can we ask the bump ptr allocator to allocate without aligning - * this to 8-byte boundary? */ - char *dup = coro_malloc(coro, len); + char *dup = coro_memdup(coro, str, len); - if (LIKELY(dup)) { - memcpy(dup, str, len); + if (LIKELY(dup)) dup[len - 1] = '\0'; - } + return dup; } char *coro_strdup(struct coro *coro, const char *str) { - return coro_strndup(coro, str, SSIZE_MAX - 1); + return coro_memdup(coro, str, strlen(str) + 1); } char *coro_printf(struct coro *coro, const char *fmt, ...) From d518dfb79a39464b2f044b9af62046787462abe4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 23 Jan 2020 21:01:34 -0800 Subject: [PATCH 1379/2505] Use coro_strdup() to prepare temporary response headers In some cases, this was even wrong, as a reference to an out of scope object was used to build the response. --- src/lib/lwan-http-authorize.c | 17 +++++++---------- src/lib/lwan-mod-redirect.c | 16 ++++------------ src/lib/lwan-mod-serve-files.c | 8 +++++--- 3 files changed, 16 insertions(+), 25 deletions(-) diff --git a/src/lib/lwan-http-authorize.c b/src/lib/lwan-http-authorize.c index 0cf5e3194..e7ef3ca67 100644 --- a/src/lib/lwan-http-authorize.c +++ b/src/lib/lwan-http-authorize.c @@ -185,7 +185,6 @@ bool lwan_http_authorize(struct lwan_request *request, { static const char authenticate_tmpl[] = "Basic realm=\"%s\""; static const size_t basic_len = sizeof("Basic ") - 1; - struct lwan_key_value *headers; const char *authorization = lwan_request_get_header(request, "Authorization"); @@ -197,15 +196,13 @@ bool lwan_http_authorize(struct lwan_request *request, return true; } - headers = coro_malloc(request->conn->coro, 2 * sizeof(*headers)); - if (UNLIKELY(!headers)) - return false; - - headers[0].key = "WWW-Authenticate"; - headers[0].value = - coro_printf(request->conn->coro, authenticate_tmpl, realm); - headers[1].key = headers[1].value = NULL; + const struct lwan_key_value headers[] = { + {"WWW-Authenticate", + coro_printf(request->conn->coro, authenticate_tmpl, realm)}, + {}, + }; + request->response.headers = + coro_memdup(request->conn->coro, headers, sizeof(headers)); - request->response.headers = headers; return false; } diff --git a/src/lib/lwan-mod-redirect.c b/src/lib/lwan-mod-redirect.c index 81baf7415..eb9edd142 100644 --- a/src/lib/lwan-mod-redirect.c +++ b/src/lib/lwan-mod-redirect.c @@ -35,20 +35,12 @@ redirect_handle_request(struct lwan_request *request, void *instance) { struct redirect_priv *priv = instance; - struct lwan_key_value *headers; + struct lwan_key_value headers[] = {{"Location", priv->to}, {}}; - headers = coro_malloc(request->conn->coro, sizeof(*headers) * 2); - if (UNLIKELY(!headers)) - return HTTP_INTERNAL_ERROR; + response->headers = + coro_memdup(request->conn->coro, headers, sizeof(headers)); - headers[0].key = "Location"; - headers[0].value = priv->to; - headers[1].key = NULL; - headers[1].value = NULL; - - response->headers = headers; - - return priv->code; + return response->headers ? priv->code : HTTP_INTERNAL_ERROR; } static void *redirect_create(const char *prefix __attribute__((unused)), diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index cd58a2dde..5b1b1eaf7 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -1278,13 +1278,15 @@ static enum lwan_http_status redir_serve(struct lwan_request *request, { struct file_cache_entry *fce = data; struct redir_cache_data *rd = &fce->redir_cache_data; - const struct lwan_key_value headers[] = {{"Location", rd->redir_to}, {}}; + struct lwan_key_value headers[] = {{"Location", rd->redir_to}, {}}; lwan_strbuf_set_staticz(request->response.buffer, rd->redir_to); request->response.mime_type = "text/plain"; - request->response.headers = headers; + request->response.headers = + coro_memdup(request->conn->coro, headers, sizeof(headers)); - return HTTP_MOVED_PERMANENTLY; + return request->response.headers ? HTTP_MOVED_PERMANENTLY + : HTTP_INTERNAL_ERROR; } static enum lwan_http_status From 639a9db416d7571a8e3c433a6910e068cf7b0c47 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 25 Jan 2020 08:01:50 -0800 Subject: [PATCH 1380/2505] Avoid redirects for directories when building auto index --- src/lib/lwan-mod-serve-files.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 5b1b1eaf7..68a4c982f 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -164,6 +164,7 @@ struct file_list { const char *unit; const char *zebra_class; + const char *slash_if_dir; } file_list; }; @@ -241,6 +242,7 @@ static const struct lwan_var_descriptor file_list_desc[] = { TPL_VAR_INT(file_list.size), TPL_VAR_STR(file_list.unit), TPL_VAR_STR(file_list.zebra_class), + TPL_VAR_STR(file_list.slash_if_dir), TPL_VAR_SENTINEL, })), TPL_VAR_SENTINEL, @@ -278,7 +280,7 @@ static const char *directory_list_tpl_str = " \n" " {{{file_list.name}}}\n" + "href=\"{{rel_path}}/{{{file_list.name}}}{{file_list.slash_if_dir}}\">{{{file_list.name}}}\n" " {{file_list.type}}\n" " {{file_list.size}}{{file_list.unit}}\n" " \n" @@ -322,11 +324,13 @@ static int directory_list_generator(struct coro *coro, void *data) fl->file_list.icon = "folder"; fl->file_list.icon_alt = "DIR"; fl->file_list.type = "directory"; + fl->file_list.slash_if_dir = "/"; } else if (S_ISREG(st.st_mode)) { fl->file_list.icon = "file"; fl->file_list.icon_alt = "FILE"; fl->file_list.type = lwan_determine_mime_type_for_file_name(entry->d_name); + fl->file_list.slash_if_dir = ""; } else { continue; } From 2519406e5ace95ea31063b5a087beeb367f2d4e6 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 25 Jan 2020 08:02:24 -0800 Subject: [PATCH 1381/2505] Use get_rel_path() to calculate the relative path for redirects --- src/lib/lwan-mod-serve-files.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 68a4c982f..74b418fa9 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -736,7 +736,7 @@ static bool redir_init(struct file_cache_entry *ce, { struct redir_cache_data *rd = &ce->redir_cache_data; - return asprintf(&rd->redir_to, "%s/", full_path + priv->root_path_len) >= 0; + return asprintf(&rd->redir_to, "%s/", get_rel_path(full_path, priv)) >= 0; } static const struct cache_funcs *get_funcs(struct serve_files_priv *priv, From b655182b3bfaaa6233da9bcc763945debc15e7f5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 25 Jan 2020 09:23:47 -0800 Subject: [PATCH 1382/2505] Better check to determine if XSI or glibc strerror_r() should be used --- src/lib/lwan-status.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-status.c b/src/lib/lwan-status.c index e3bb32d41..e7cbe0f9a 100644 --- a/src/lib/lwan-status.c +++ b/src/lib/lwan-status.c @@ -105,9 +105,9 @@ static inline struct lwan_value end_color(void) static inline char *strerror_thunk_r(int error_number, char *buffer, size_t len) { -#ifdef __GLIBC__ +#if defined(__GLIBC__) && defined(_GNU_SOURCE) return strerror_r(error_number, buffer, len); -#else +#else /* XSI-compliant strerror_r() */ if (!strerror_r(error_number, buffer, len)) return buffer; return "Unknown"; From d85fe26ecea0df896ffe5f41995ee86b0d619c6c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 25 Jan 2020 09:42:06 -0800 Subject: [PATCH 1383/2505] Remove some branches when validating a timestamp --- src/lib/lwan-time.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/lib/lwan-time.c b/src/lib/lwan-time.c index 8f70c5dc5..66bb99009 100644 --- a/src/lib/lwan-time.c +++ b/src/lib/lwan-time.c @@ -25,9 +25,10 @@ #include "lwan-private.h" #include "int-to-str.h" -static int parse_2_digit_num(const char *str, const char end_chr, int min, int max) +static int +parse_2_digit_num(const char *str, const char end_chr, unsigned int max) { - int val; + unsigned int val; if (UNLIKELY(!lwan_char_isdigit(*str))) return -EINVAL; @@ -36,13 +37,13 @@ static int parse_2_digit_num(const char *str, const char end_chr, int min, int m if (UNLIKELY(*(str + 2) != end_chr)) return -EINVAL; - val = (*str - '0') * 10; - val += *(str + 1) - '0'; + val = (unsigned int)((*str - '0') * 10); + val += (unsigned int)(*(str + 1) - '0'); - if (UNLIKELY(val < min || val > max)) + if (UNLIKELY(val > max)) return -EINVAL; - return val; + return (int)val; } int lwan_parse_rfc_time(const char in[static 30], time_t *out) @@ -65,8 +66,8 @@ int lwan_parse_rfc_time(const char in[static 30], time_t *out) } str += 5; - tm.tm_mday = parse_2_digit_num(str, ' ', 1, 31); - if (UNLIKELY(tm.tm_mday < 0)) + tm.tm_mday = parse_2_digit_num(str, ' ', 31); + if (UNLIKELY(tm.tm_mday <= 1)) return -EINVAL; str += 3; @@ -95,11 +96,11 @@ int lwan_parse_rfc_time(const char in[static 30], time_t *out) return -EINVAL; str += 5; - tm.tm_hour = parse_2_digit_num(str, ':', 0, 23); + tm.tm_hour = parse_2_digit_num(str, ':', 23); str += 3; - tm.tm_min = parse_2_digit_num(str, ':', 0, 59); + tm.tm_min = parse_2_digit_num(str, ':', 59); str += 3; - tm.tm_sec = parse_2_digit_num(str, ' ', 0, 59); + tm.tm_sec = parse_2_digit_num(str, ' ', 59); str += 3; STRING_SWITCH(str) { @@ -108,11 +109,10 @@ int lwan_parse_rfc_time(const char in[static 30], time_t *out) *out = timegm(&tm); - if (UNLIKELY(*out == (time_t)-1)) - return -EINVAL; - - return 0; + if (LIKELY(*out > 0)) + return 0; + /* Fallthrough */ default: return -EINVAL; } From 45f6ab6b316bef0a581defc3e43c37051eb6e33a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 26 Jan 2020 13:27:19 -0800 Subject: [PATCH 1384/2505] Avoid relying on locale information for extension lookup Ensure that the MIME-Type table has extensions in lower case, and convert the extension passed to lwan_determine_mime_type_for_file_name() to lower case just once before calling into bsearch(). --- src/bin/tools/mimegen.c | 28 +++++++++++++++++----------- src/lib/lwan-tables.c | 8 ++++++-- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/bin/tools/mimegen.c b/src/bin/tools/mimegen.c index 08d6d85e5..8bdf08419 100644 --- a/src/bin/tools/mimegen.c +++ b/src/bin/tools/mimegen.c @@ -17,6 +17,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include #include #include #include @@ -70,18 +71,16 @@ static int output_append_padded(struct output *output, const char *str) { size_t str_len = strlen(str); - if (str_len <= 8) { - int r = output_append_full(output, str, str_len); - if (r < 0) - return r; + assert(str_len <= 8); - if (str_len != 8) - return output_append_full(output, "\0\0\0\0\0\0\0\0", 8 - str_len); + int r = output_append_full(output, str, str_len); + if (r < 0) + return r; - return 0; - } + if (str_len != 8) + return output_append_full(output, "\0\0\0\0\0\0\0\0", 8 - str_len); - return -EINVAL; + return 0; } static int output_append(struct output *output, const char *str) @@ -94,7 +93,7 @@ static int compare_ext(const void *a, const void *b) const char **exta = (const char **)a; const char **extb = (const char **)b; - return strcmp(*exta, *extb); + return strcasecmp(*exta, *extb); } static char *strend(char *str, char ch) @@ -298,7 +297,14 @@ int main(int argc, char *argv[]) return 1; } for (i = 0; i < hash_get_count(ext_mime); i++) { - if (output_append_padded(&output, exts[i]) < 0) { + char ext_lower[9] = {0}; + + strncpy(ext_lower, exts[i], 8); + + for (char *p = ext_copy; *p; p++) + *p |= 0x20; + + if (output_append_padded(&output, ext_lower) < 0) { fprintf(stderr, "Could not append to output\n"); return 1; } diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index 4e3896802..1046c5b16 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -110,7 +110,7 @@ compare_mime_entry(const void *a, const void *b) const char *exta = (const char *)a; const char *extb = (const char *)b; - return strncasecmp(exta, extb, 8); + return strncmp(exta, extb, 8); } const char * @@ -131,7 +131,11 @@ lwan_determine_mime_type_for_file_name(const char *file_name) } if (LIKELY(*last_dot)) { - char *extension, *key = last_dot + 1; + char *key = strndupa(last_dot + 1, 8); + char *extension; + + for (char *p = key; *p; p++) + *p |= 0x20; extension = bsearch(key, uncompressed_mime_entries, MIME_ENTRIES, 8, compare_mime_entry); From 1130a9a5f52c6b01be0da88b0e50895fd7f4016d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 26 Jan 2020 16:39:45 -0800 Subject: [PATCH 1385/2505] Fix build after 45f6ab6b3 --- src/bin/tools/mimegen.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/tools/mimegen.c b/src/bin/tools/mimegen.c index 8bdf08419..9c99d6871 100644 --- a/src/bin/tools/mimegen.c +++ b/src/bin/tools/mimegen.c @@ -301,7 +301,7 @@ int main(int argc, char *argv[]) strncpy(ext_lower, exts[i], 8); - for (char *p = ext_copy; *p; p++) + for (char *p = ext_lower; *p; p++) *p |= 0x20; if (output_append_padded(&output, ext_lower) < 0) { From f8e03ceb67b43d4109b20e91cfbf79fb334a96f3 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 27 Jan 2020 18:29:45 -0800 Subject: [PATCH 1386/2505] Avoid alloca() in lwan_determine_mime_type_for_file_name() --- src/lib/lwan-tables.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index 1046c5b16..74e05d971 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -131,9 +131,11 @@ lwan_determine_mime_type_for_file_name(const char *file_name) } if (LIKELY(*last_dot)) { - char *key = strndupa(last_dot + 1, 8); + char key[9]; char *extension; + strncpy(key, last_dot + 1, 8); + key[8] = '\0'; for (char *p = key; *p; p++) *p |= 0x20; From 65a1cee0fa327a0a2cbfe4ce51149bfb8a414d4e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 27 Jan 2020 18:30:04 -0800 Subject: [PATCH 1387/2505] Minor cleanups in cache_entry_unref() --- src/lib/lwan-cache.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan-cache.c b/src/lib/lwan-cache.c index e10b5257f..d5a669c77 100644 --- a/src/lib/lwan-cache.c +++ b/src/lib/lwan-cache.c @@ -240,13 +240,18 @@ void cache_entry_unref(struct cache *cache, struct cache_entry *entry) { assert(entry); + /* FIXME: There's a race condition in this function: if the cache is + * destroyed while there are either temporary or floating entries, + * calling the destroy_entry callback function will dereference + * deallocated memory. */ + if (entry->flags & TEMPORARY) { /* FREE_KEY_ON_DESTROY is set on elements that never got into the * hash table, so their keys are never destroyed automatically. */ if (entry->flags & FREE_KEY_ON_DESTROY) free(entry->key); - goto destroy_entry; + return cache->cb.destroy_entry(entry, cache->cb.context); } if (ATOMIC_DEC(entry->refs)) @@ -255,11 +260,7 @@ void cache_entry_unref(struct cache *cache, struct cache_entry *entry) /* FLOATING entries without references won't be picked up by the pruner * job, so destroy them right here. */ if (entry->flags & FLOATING) { -destroy_entry: - /* FIXME: There's a race condition here: if the cache is destroyed - * while there are cache items floating around, this will dereference - * deallocated memory. */ - cache->cb.destroy_entry(entry, cache->cb.context); + return cache->cb.destroy_entry(entry, cache->cb.context); } } From c00a4be2a8485f9354097d457d0961c53e37b307 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 27 Jan 2020 19:16:15 -0800 Subject: [PATCH 1388/2505] Add a note about mimalloc also being supported as an alt. malloc() --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9a780d183..34967a00b 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,8 @@ with, specify one of the following options to the CMake invocation line: - `-DSANITIZER=thread` selects the Thread Sanitizer. Alternative memory allocators can be selected as well. Lwan currently -supports [TCMalloc](https://github.com/gperftools/gperftools) and +supports [TCMalloc](https://github.com/gperftools/gperftools), +[mimalloc](https://github.com/microsoft/mimalloc), and [jemalloc](http://jemalloc.net/) out of the box. To use either one of them, pass `-DALTERNATIVE_MALLOC=ON` to the CMake invocation line. From 43b63f13dfcb74e6cc0440f5cbb1f9b53563fe39 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 28 Jan 2020 18:50:45 -0800 Subject: [PATCH 1389/2505] Cleanup lwan_format_rfc_time() by using auxiliary function --- src/lib/lwan-time.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan-time.c b/src/lib/lwan-time.c index 66bb99009..b676529a2 100644 --- a/src/lib/lwan-time.c +++ b/src/lib/lwan-time.c @@ -118,6 +118,12 @@ int lwan_parse_rfc_time(const char in[static 30], time_t *out) } } +static inline char * +append_two_digits(char *p, unsigned int digits) +{ + return mempcpy(p, uint_to_string_2_digits(digits), 2); +} + int lwan_format_rfc_time(const time_t in, char out[static 30]) { static const char *weekdays = "Sun,Mon,Tue,Wed,Thu,Fri,Sat,"; @@ -131,21 +137,21 @@ int lwan_format_rfc_time(const time_t in, char out[static 30]) p = mempcpy(out, weekdays + tm.tm_wday * 4, 4); *p++ = ' '; - p = mempcpy(p, uint_to_string_2_digits((unsigned int)tm.tm_mday), 2); + p = append_two_digits(p, (unsigned int)tm.tm_mday); *p++ = ' '; p = mempcpy(p, months + tm.tm_mon * 4, 4); tm.tm_year += 1900; - p = mempcpy(p, uint_to_string_2_digits((unsigned int)tm.tm_year / 100), 2); - p = mempcpy(p, uint_to_string_2_digits((unsigned int)tm.tm_year % 100), 2); + p = append_two_digits(p, (unsigned int)tm.tm_year / 100); + p = append_two_digits(p, (unsigned int)tm.tm_year % 100); *p++ = ' '; - p = mempcpy(p, uint_to_string_2_digits((unsigned int)tm.tm_hour), 2); + p = append_two_digits(p, (unsigned int)tm.tm_hour); *p++ = ':'; - p = mempcpy(p, uint_to_string_2_digits((unsigned int)tm.tm_min), 2); + p = append_two_digits(p, (unsigned int)tm.tm_min); *p++ = ':'; - p = mempcpy(p, uint_to_string_2_digits((unsigned int)tm.tm_sec), 2); + p = append_two_digits(p, (unsigned int)tm.tm_sec); memcpy(p, " GMT", 5); From e56af084478d4da7c8493e0f51ed65c4a6a4b734 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 28 Jan 2020 18:51:22 -0800 Subject: [PATCH 1390/2505] Remove most branches while serializing JSON in the TWFB benchmark The likelyhood of append_bytes() failing is very minimal, and the benchmark doesn't care about which particular error caused the serialization to fail; it only cares if it failed. So, instead of branching for each and every call to see if it failed, perform a bitwise OR with the return value of every call. This way, if all calls succeed (i.e. return value is 0), the overall serialization value is also success (0). Otherwise, it's going to be a bitwise OR of all negative errno values returned by the user-provided append_bytes() function, which may or may not be useful (if they return always the same value, or if they return something different than a negative errno -- e.g. one bit set per kind of error). Since this is mostly for benchmarking purposes, this optimization is perfectly fine. --- src/samples/techempower/json.c | 87 ++++++++-------------------------- 1 file changed, 21 insertions(+), 66 deletions(-) diff --git a/src/samples/techempower/json.c b/src/samples/techempower/json.c index dff7c3f71..da5d013f2 100644 --- a/src/samples/techempower/json.c +++ b/src/samples/techempower/json.c @@ -729,12 +729,7 @@ static int arr_encode(const struct json_obj_descr *elem_descr, */ size_t n_elem = *(size_t *)((char *)val + elem_descr->offset); size_t i; - int ret; - - ret = append_bytes("[", 1, data); - if (UNLIKELY(ret < 0)) { - return ret; - } + int ret = append_bytes("[", 1, data); if (LIKELY(n_elem)) { n_elem--; @@ -752,50 +747,33 @@ static int arr_encode(const struct json_obj_descr *elem_descr, * the length field in the parent struct, but that would add a * size_t to every descriptor. */ - ret = encode(elem_descr, (char *)field - elem_descr->offset, - append_bytes, data, escape_key); - if (UNLIKELY(ret < 0)) { - return ret; - } + ret |= encode(elem_descr, (char *)field - elem_descr->offset, + append_bytes, data, escape_key); - ret = append_bytes(",", 1, data); - if (UNLIKELY(ret < 0)) { - return ret; - } + ret |= append_bytes(",", 1, data); field = (char *)field + elem_size; } - ret = encode(elem_descr, (char *)field - elem_descr->offset, - append_bytes, data, escape_key); - if (UNLIKELY(ret < 0)) { - return ret; - } + ret |= encode(elem_descr, (char *)field - elem_descr->offset, + append_bytes, data, escape_key); } - return append_bytes("]", 1, data); + return ret | append_bytes("]", 1, data); } static int str_encode(const char **str, json_append_bytes_t append_bytes, void *data, bool escape) { - int ret; - - ret = append_bytes("\"", 1, data); - if (UNLIKELY(ret < 0)) { - return ret; - } + int ret = append_bytes("\"", 1, data); if (escape) { - ret = json_escape_internal(*str, append_bytes, data); + ret |= json_escape_internal(*str, append_bytes, data); } else { - ret = append_bytes(*str, strlen(*str), data); - } - if (!ret) { - return append_bytes("\"", 1, data); + ret |= append_bytes(*str, strlen(*str), data); } - return ret; + return ret | append_bytes("\"", 1, data); } static int @@ -864,20 +842,12 @@ static int encode_key_value(const struct json_obj_descr *descr, void *data, bool escape_key) { - int ret; + int ret = str_encode((const char **)&descr->field_name, append_bytes, data, + escape_key); - ret = str_encode((const char **)&descr->field_name, append_bytes, data, - escape_key); - if (UNLIKELY(ret < 0)) { - return ret; - } + ret |= append_bytes(":", 1, data); - ret = append_bytes(":", 1, data); - if (UNLIKELY(ret < 0)) { - return ret; - } - - return encode(descr, val, append_bytes, data, escape_key); + return ret | encode(descr, val, append_bytes, data, escape_key); } int json_obj_encode_full(const struct json_obj_descr *descr, @@ -887,12 +857,7 @@ int json_obj_encode_full(const struct json_obj_descr *descr, void *data, bool escape_key) { - int ret; - - ret = append_bytes("{", 1, data); - if (UNLIKELY(ret < 0)) { - return ret; - } + int ret = append_bytes("{", 1, data); if (LIKELY(descr_len)) { /* To avoid checking if we're encoding the last element on each @@ -903,25 +868,15 @@ int json_obj_encode_full(const struct json_obj_descr *descr, * branches. */ for (size_t i = 1; i < descr_len; i++) { - ret = encode_key_value(&descr[i], val, append_bytes, data, - escape_key); - if (UNLIKELY(ret < 0)) { - return ret; - } - - ret = append_bytes(",", 1, data); - if (UNLIKELY(ret < 0)) { - return ret; - } + ret |= encode_key_value(&descr[i], val, append_bytes, data, + escape_key); + ret |= append_bytes(",", 1, data); } - ret = encode_key_value(&descr[0], val, append_bytes, data, escape_key); - if (UNLIKELY(ret < 0)) { - return ret; - } + ret |= encode_key_value(&descr[0], val, append_bytes, data, escape_key); } - return append_bytes("}", 1, data); + return ret | append_bytes("}", 1, data); } struct appender { From f669b7ddb7bab18a685e7daac47b0bf48b446ff3 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 28 Jan 2020 18:57:58 -0800 Subject: [PATCH 1391/2505] Add bounds-check asserts to some lwan_array operations --- src/lib/lwan-array.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib/lwan-array.h b/src/lib/lwan-array.h index 96c9d6a3c..16729da68 100644 --- a/src/lib/lwan-array.h +++ b/src/lib/lwan-array.h @@ -20,6 +20,7 @@ #pragma once +#include #include #include "lwan-coro.h" @@ -118,11 +119,16 @@ struct lwan_array *coro_lwan_array_new(struct coro *coro, bool inline_first); __attribute__((unused)) static inline size_t array_type_##_get_elem_index( \ const struct array_type_ *array, element_type_ *elem) \ { \ + assert(elem >= (element_type_ *)array->base.base); \ + assert(elem < \ + (element_type_ *)array->base.base + array->base.elements); \ return (size_t)(elem - (element_type_ *)array->base.base); \ } \ __attribute__((unused)) static inline element_type_ \ *array_type_##_get_elem(const struct array_type_ *array, size_t index) \ { \ + assert(index <= /* Legal to have a ptr to 1 past end */ \ + array->base.elements); \ return &((element_type_ *)array->base.base)[index]; \ } \ __attribute__((unused)) static inline size_t array_type_##_len( \ From 09f14932a9f8b740c3887a04d596075ad8084d9b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 28 Jan 2020 18:58:43 -0800 Subject: [PATCH 1392/2505] Slightly cleanup serving of lwan_value structs in serve_files module --- src/lib/lwan-mod-serve-files.c | 51 +++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 74b418fa9..2890fb4e1 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -1203,28 +1203,38 @@ serve_value(struct lwan_request *request, status_code); } -static enum lwan_http_status mmap_serve(struct lwan_request *request, void *data) +static ALWAYS_INLINE enum lwan_http_status +serve_value_ok(struct lwan_request *request, + const char *mime_type, + const struct lwan_value *value, + const struct lwan_key_value *headers) +{ + return serve_value(request, mime_type, value, headers, HTTP_OK); +} + +static enum lwan_http_status mmap_serve(struct lwan_request *request, + void *data) { struct file_cache_entry *fce = data; struct mmap_cache_data *md = &fce->mmap_cache_data; #if defined(HAVE_ZSTD) if (md->zstd.len && accepts_encoding(request, REQUEST_ACCEPT_ZSTD)) { - return serve_value(request, fce->mime_type, &md->zstd, zstd_compression_hdr, - HTTP_OK); + return serve_value_ok(request, fce->mime_type, &md->zstd, + zstd_compression_hdr); } #endif #if defined(HAVE_BROTLI) if (md->brotli.len && accepts_encoding(request, REQUEST_ACCEPT_BROTLI)) { - return serve_value(request, fce->mime_type, &md->brotli, br_compression_hdr, - HTTP_OK); + return serve_value_ok(request, fce->mime_type, &md->brotli, + br_compression_hdr); } #endif if (md->deflated.len && accepts_encoding(request, REQUEST_ACCEPT_DEFLATE)) { - return serve_value(request, fce->mime_type, &md->deflated, - deflate_compression_hdr, HTTP_OK); + return serve_value_ok(request, fce->mime_type, &md->deflated, + deflate_compression_hdr); } off_t from, to; @@ -1232,14 +1242,15 @@ static enum lwan_http_status mmap_serve(struct lwan_request *request, void *data compute_range(request, &from, &to, (off_t)md->uncompressed.len); if (status == HTTP_OK || status == HTTP_PARTIAL_CONTENT) { return serve_buffer(request, fce->mime_type, - (char *)md->uncompressed.value + from, (size_t)(to - from), - NULL, status); + (char *)md->uncompressed.value + from, + (size_t)(to - from), NULL, status); } return status; } -static enum lwan_http_status dirlist_serve(struct lwan_request *request, void *data) +static enum lwan_http_status dirlist_serve(struct lwan_request *request, + void *data) { struct file_cache_entry *fce = data; struct dir_list_cache_data *dd = &fce->dir_list_cache_data; @@ -1248,30 +1259,30 @@ static enum lwan_http_status dirlist_serve(struct lwan_request *request, void *d if (!icon) { #if defined(HAVE_BROTLI) if (dd->brotli.len && accepts_encoding(request, REQUEST_ACCEPT_BROTLI)) { - return serve_value(request, fce->mime_type, &dd->brotli, br_compression_hdr, - HTTP_OK); + return serve_value_ok(request, fce->mime_type, &dd->brotli, + br_compression_hdr); } #endif if (dd->deflated.len && accepts_encoding(request, REQUEST_ACCEPT_DEFLATE)) { - return serve_value(request, fce->mime_type, &dd->deflated, - deflate_compression_hdr, HTTP_OK); + return serve_value_ok(request, fce->mime_type, &dd->deflated, + deflate_compression_hdr); } - return serve_buffer(request, fce->mime_type, - lwan_strbuf_get_buffer(&dd->rendered), - lwan_strbuf_get_length(&dd->rendered), NULL, HTTP_OK); + return serve_buffer( + request, fce->mime_type, lwan_strbuf_get_buffer(&dd->rendered), + lwan_strbuf_get_length(&dd->rendered), NULL, HTTP_OK); } STRING_SWITCH (icon) { case STR4_INT('b', 'a', 'c', 'k'): - return serve_value(request, "image/gif", &back_gif_value, NULL, HTTP_OK); + return serve_value_ok(request, "image/gif", &back_gif_value, NULL); case STR4_INT('f', 'i', 'l', 'e'): - return serve_value(request, "image/gif", &file_gif_value, NULL, HTTP_OK); + return serve_value_ok(request, "image/gif", &file_gif_value, NULL); case STR4_INT('f', 'o', 'l', 'd'): - return serve_value(request, "image/gif", &folder_gif_value, NULL, HTTP_OK); + return serve_value_ok(request, "image/gif", &folder_gif_value, NULL); } return HTTP_NOT_FOUND; From 37473235d928d58a5d98268348325ec67690b988 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 28 Jan 2020 19:51:46 -0800 Subject: [PATCH 1393/2505] Declare `i' inside for statement in arr_encode() --- src/samples/techempower/json.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/samples/techempower/json.c b/src/samples/techempower/json.c index da5d013f2..cef0ec740 100644 --- a/src/samples/techempower/json.c +++ b/src/samples/techempower/json.c @@ -728,12 +728,11 @@ static int arr_encode(const struct json_obj_descr *elem_descr, * elements. */ size_t n_elem = *(size_t *)((char *)val + elem_descr->offset); - size_t i; int ret = append_bytes("[", 1, data); if (LIKELY(n_elem)) { n_elem--; - for (i = 0; i < n_elem; i++) { + for (size_t i = 0; i < n_elem; i++) { /* * Though "field" points at the next element in the array which we * need to encode, the value in elem_descr->offset is actually the From efad0528590f50121124757993cde1548b6978fa Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 29 Jan 2020 18:29:30 -0800 Subject: [PATCH 1394/2505] Add basic tests for the TWFB benchmark Since some work is being put into the benchmark to squeeze the performance a little bit before the next round, adding these tests to safeguard that those changes won't break anything. So far, only the "Plain Text", "JSON", "DB", and "Queries" benchmarks are being tested. "Fortunes" still needs a test. --- src/samples/techempower/techempower.c | 10 +++++ src/scripts/testsuite.py | 57 +++++++++++++++++++++++++-- 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index 87cb65bff..75da4b2d4 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -374,14 +374,24 @@ LWAN_HANDLER(fortunes) return HTTP_OK; } +LWAN_HANDLER(quit_lwan) +{ + exit(0); + return HTTP_OK; +} + int main(void) { static const struct lwan_url_map url_map[] = { + /* Routes for the TWFB benchmark: */ {.prefix = "/json", .handler = LWAN_HANDLER_REF(json)}, {.prefix = "/db", .handler = LWAN_HANDLER_REF(db)}, {.prefix = "/queries", .handler = LWAN_HANDLER_REF(queries)}, {.prefix = "/plaintext", .handler = LWAN_HANDLER_REF(plaintext)}, {.prefix = "/fortunes", .handler = LWAN_HANDLER_REF(fortunes)}, + /* Routes for the test harness: */ + {.prefix = "/quit-lwan", .handler = LWAN_HANDLER_REF(quit_lwan)}, + {.prefix = "/hello", .handler = LWAN_HANDLER_REF(plaintext)}, {.prefix = NULL}, }; struct lwan l; diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index 1420bca4e..642daac96 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -14,6 +14,7 @@ import time import unittest import string +import shutil LWAN_PATH = './build/src/bin/testrunner/testrunner' for arg in sys.argv[1:]: @@ -24,11 +25,11 @@ print('Using', LWAN_PATH, 'for lwan') class LwanTest(unittest.TestCase): - def setUp(self, env=None): + def setUp(self, env=None, alt_lwan_path=None): open('htpasswd', 'w').close() for spawn_try in range(20): - self.lwan=subprocess.Popen([LWAN_PATH], stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, env=env) + self.lwan=subprocess.Popen([LWAN_PATH if alt_lwan_path is None else alt_lwan_path], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env) for request_try in range(20): try: requests.get('http://127.0.0.1:8080/hello') @@ -72,6 +73,56 @@ def assertResponsePlain(self, request, status_code=200): self.assertHttpResponseValid(request, status_code, 'text/plain') +class TestTWFB(LwanTest): + def setUp(self, env=None): + harness_path = './build/src/samples/techempower/techempower' + + if not os.path.exists(harness_path): + raise Exception('Could not find TWFB benchmark harness') + + super().setUp(env, alt_lwan_path=harness_path) + shutil.copyfile('./src/samples/techempower/techempower.db', './techempower.db') + + def tearDown(self): + super().tearDown() + os.remove('techempower.db') + + def test_plaintext(self): + r = requests.get('http://127.0.0.1:8080/plaintext') + + self.assertResponsePlain(r) + self.assertEqual(r.text, 'Hello, World!') + + def assertSingleQueryResultIsValid(self, single): + self.assertTrue(isinstance(single, dict)) + self.assertEqual({'randomNumber', 'id'}, set(single.keys())) + self.assertEqual(type(single['randomNumber']), type(0)) + self.assertEqual(type(single['id']), type(0)) + self.assertTrue(0 <= single['randomNumber'] <= 9999) + self.assertTrue(1 <= single['id'] <= 10000) + + def test_json(self): + r = requests.get('http://127.0.0.1:8080/json') + + self.assertHttpResponseValid(r, 200, 'application/json') + self.assertEqual(r.json(), {'message': 'Hello, World!'}) + + def test_single_query(self): + r = requests.get('http://127.0.0.1:8080/db') + + self.assertHttpResponseValid(r, 200, 'application/json') + self.assertSingleQueryResultIsValid(r.json()) + + def test_multiple_queries(self): + for queries in (1, 10, 100, 500, 1000, 0, -1): + r = requests.get('http://127.0.0.1:8080/queries?queries=%d' % queries) + + self.assertHttpResponseValid(r, 200, 'application/json') + self.assertTrue(isinstance(r.json(), list)) + self.assertEqual(len(r.json()), min(500, max(queries, 1))) + for query in r.json(): + self.assertSingleQueryResultIsValid(query) + class TestPost(LwanTest): def test_will_it_blend(self): r = requests.post('http://127.0.0.1:8080/post/blend', json={'will-it-blend': True}) From 10bfc8cb78c653cc32a2d0c18249671dfda93a5f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 29 Jan 2020 18:44:13 -0800 Subject: [PATCH 1395/2505] Make it possible to have nested LWAN_MIN/LWAN_MAX macro expansions --- src/lib/lwan-private.h | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 55484e5c2..a462f79a2 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -24,20 +24,28 @@ #include "lwan.h" -#define LWAN_MIN(a_, b_) \ +#define LWAN_CONCAT(a_, b_) a_ ## b_ +#define LWAN_TMP_ID_DETAIL(n_) LWAN_CONCAT(lwan_tmp_id, n_) +#define LWAN_TMP_ID LWAN_TMP_ID_DETAIL(__COUNTER__) + +#define LWAN_MIN_DETAIL(a_, b_, name_a_, name_b_) \ ({ \ - __typeof__(a_) __a__ = (a_); \ - __typeof__(b_) __b__ = (b_); \ - __a__ > __b__ ? __b__ : __a__; \ + __typeof__(a_) name_a_ = (a_); \ + __typeof__(b_) name_b_ = (b_); \ + name_a_ > name_b_ ? name_b_ : name_a_; \ }) -#define LWAN_MAX(a_, b_) \ +#define LWAN_MAX_DETAIL(a_, b_, name_a_, name_b_) \ ({ \ - __typeof__(a_) __a__ = (a_); \ - __typeof__(b_) __b__ = (b_); \ - __a__ < __b__ ? __b__ : __a__; \ + __typeof__(a_) name_a_ = (a_); \ + __typeof__(b_) name_b_ = (b_); \ + name_a_ < name_b_ ? name_b_ : name_a_; \ }) +#define LWAN_MIN(a_, b_) LWAN_MIN_DETAIL(a_, b_, LWAN_TMP_ID, LWAN_TMP_ID) + +#define LWAN_MAX(a_, b_) LWAN_MAX_DETAIL(a_, b_, LWAN_TMP_ID, LWAN_TMP_ID) + int lwan_socket_get_backlog_size(void); struct lwan_fd_watch *lwan_watch_fd(struct lwan *l, From 1a012c957cc6ef3ba82831e6123175696fcc2252 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 29 Jan 2020 18:45:02 -0800 Subject: [PATCH 1396/2505] Use nested LWAN_MIN/LWAN_MAX macro to calculate max # of queries --- src/samples/techempower/techempower.c | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index 75da4b2d4..db7e4c2cc 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -22,7 +22,7 @@ #include #include -#include "lwan.h" +#include "lwan-private.h" #include "lwan-config.h" #include "lwan-template.h" @@ -244,15 +244,9 @@ LWAN_HANDLER(queries) const char *queries_str = lwan_request_get_query_param(request, "queries"); long queries; - if (LIKELY(queries_str)) { - queries = parse_long(queries_str, -1); - if (UNLIKELY(queries <= 0)) - queries = 1; - else if (UNLIKELY(queries > 500)) - queries = 500; - } else { - queries = 1; - } + queries = LIKELY(queries_str) + ? LWAN_MIN(500, LWAN_MAX(1, parse_long(queries_str, -1))) + : 1; struct db_stmt *stmt = db_prepare_stmt(get_db(), random_number_query, sizeof(random_number_query) - 1); From 3c4e5e380b5593baffc13328880bb66525c734a1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 29 Jan 2020 18:50:48 -0800 Subject: [PATCH 1397/2505] Ensure `testsuite` target works as expected after efad0528 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f166055a9..e933d6693 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -326,7 +326,7 @@ if (LUA_FOUND AND PYTHONINTERP_FOUND) COMMAND ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/src/scripts/testsuite.py -v ${CMAKE_BINARY_DIR}/src/bin/testrunner/testrunner - DEPENDS testrunner + DEPENDS testrunner techempower WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMENT "Running test suite.") From 2f53ef1c8456738d7e7e489c101c8c1d0e6c75bc Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 29 Jan 2020 19:22:41 -0800 Subject: [PATCH 1398/2505] Fix tests in the build bot --- CMakeLists.txt | 3 +-- src/scripts/testsuite.py | 25 ++++++++++++++----------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e933d6693..f5cce17e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -324,8 +324,7 @@ find_package(PythonInterp 3) if (LUA_FOUND AND PYTHONINTERP_FOUND) add_custom_target(testsuite COMMAND ${PYTHON_EXECUTABLE} - ${PROJECT_SOURCE_DIR}/src/scripts/testsuite.py -v - ${CMAKE_BINARY_DIR}/src/bin/testrunner/testrunner + ${PROJECT_SOURCE_DIR}/src/scripts/testsuite.py -v ${CMAKE_BINARY_DIR} DEPENDS testrunner techempower WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMENT "Running test suite.") diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index 642daac96..0c294348e 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -16,19 +16,27 @@ import string import shutil -LWAN_PATH = './build/src/bin/testrunner/testrunner' +BUILD_DIR = './build' for arg in sys.argv[1:]: if not arg.startswith('-') and os.path.exists(arg): - LWAN_PATH = arg + BUILD_DIR = arg sys.argv.remove(arg) -print('Using', LWAN_PATH, 'for lwan') +def get_harness_path(harness): + return { + 'testrunner': os.path.join(BUILD_DIR, './src/bin/testrunner/testrunner'), + 'techempower': os.path.join(BUILD_DIR, './src/samples/techempower/techempower'), + }[harness] + +print('Using paths:') +print(' testrunner:', get_harness_path('testrunner')) +print(' techempower:', get_harness_path('techempower')) class LwanTest(unittest.TestCase): - def setUp(self, env=None, alt_lwan_path=None): + def setUp(self, env=None, harness='testrunner'): open('htpasswd', 'w').close() for spawn_try in range(20): - self.lwan=subprocess.Popen([LWAN_PATH if alt_lwan_path is None else alt_lwan_path], + self.lwan=subprocess.Popen([get_harness_path(harness)], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env) for request_try in range(20): try: @@ -75,12 +83,7 @@ def assertResponsePlain(self, request, status_code=200): class TestTWFB(LwanTest): def setUp(self, env=None): - harness_path = './build/src/samples/techempower/techempower' - - if not os.path.exists(harness_path): - raise Exception('Could not find TWFB benchmark harness') - - super().setUp(env, alt_lwan_path=harness_path) + super().setUp(env, harness='techempower') shutil.copyfile('./src/samples/techempower/techempower.db', './techempower.db') def tearDown(self): From 19e1c6b6fc73973107b631c63f8196a68300d718 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 29 Jan 2020 21:33:41 -0800 Subject: [PATCH 1399/2505] Add test for Fortunes benchmark --- src/scripts/testsuite.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index 0c294348e..a809a8a36 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -3,18 +3,19 @@ # performs certain system calls. This should speed up the mmap tests # considerably and make it possible to perform more low-level tests. +import hashlib import os import random import re import requests +import shutil import signal import socket +import string import subprocess import sys import time import unittest -import string -import shutil BUILD_DIR = './build' for arg in sys.argv[1:]: @@ -104,6 +105,13 @@ def assertSingleQueryResultIsValid(self, single): self.assertTrue(0 <= single['randomNumber'] <= 9999) self.assertTrue(1 <= single['id'] <= 10000) + def test_fortunes(self): + r = requests.get('http://127.0.0.1:8080/fortunes') + + self.assertHttpResponseValid(r, 200, 'text/html; charset=UTF-8') + self.assertEqual(hashlib.md5(bytes(r.text, 'utf-8')).hexdigest(), + '352e66abf97b5a07c76a8b3c9e3e6339') + def test_json(self): r = requests.get('http://127.0.0.1:8080/json') From 9338c55a9fcbc188398fd917a9dcc44235369d88 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 30 Jan 2020 08:04:34 -0800 Subject: [PATCH 1400/2505] Test if missing `queries` parameter yields expected result --- src/scripts/testsuite.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index a809a8a36..b38d751ae 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -125,15 +125,20 @@ def test_single_query(self): self.assertSingleQueryResultIsValid(r.json()) def test_multiple_queries(self): - for queries in (1, 10, 100, 500, 1000, 0, -1): - r = requests.get('http://127.0.0.1:8080/queries?queries=%d' % queries) - + def assertMultipleQueriesValid(r, queries): self.assertHttpResponseValid(r, 200, 'application/json') self.assertTrue(isinstance(r.json(), list)) self.assertEqual(len(r.json()), min(500, max(queries, 1))) for query in r.json(): self.assertSingleQueryResultIsValid(query) + for queries in (1, 10, 100, 500, 1000, 0, -1): + r = requests.get('http://127.0.0.1:8080/queries?queries=%d' % queries) + assertMultipleQueriesValid(r, queries) + + r = requests.get('http://127.0.0.1:8080/queries') + assertMultipleQueriesValid(r, 1) + class TestPost(LwanTest): def test_will_it_blend(self): r = requests.post('http://127.0.0.1:8080/post/blend', json={'will-it-blend': True}) From 461b3be3708a882a459412bf7ed0197a94c5d85a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 30 Jan 2020 18:26:46 -0800 Subject: [PATCH 1401/2505] Simplify LWAN_MIN/LWAN_MAX macros by providing one impl for both --- src/lib/lwan-private.h | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index a462f79a2..27c0ec320 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -28,23 +28,16 @@ #define LWAN_TMP_ID_DETAIL(n_) LWAN_CONCAT(lwan_tmp_id, n_) #define LWAN_TMP_ID LWAN_TMP_ID_DETAIL(__COUNTER__) -#define LWAN_MIN_DETAIL(a_, b_, name_a_, name_b_) \ +#define LWAN_MIN_MAX_DETAIL(a_, b_, name_a_, name_b_, op_) \ ({ \ - __typeof__(a_) name_a_ = (a_); \ - __typeof__(b_) name_b_ = (b_); \ - name_a_ > name_b_ ? name_b_ : name_a_; \ + const __typeof__((a_) + 0) name_a_ = (a_); \ + const __typeof__((b_) + 0) name_b_ = (b_); \ + name_a_ op_ name_b_ ? name_b_ : name_a_; \ }) -#define LWAN_MAX_DETAIL(a_, b_, name_a_, name_b_) \ - ({ \ - __typeof__(a_) name_a_ = (a_); \ - __typeof__(b_) name_b_ = (b_); \ - name_a_ < name_b_ ? name_b_ : name_a_; \ - }) - -#define LWAN_MIN(a_, b_) LWAN_MIN_DETAIL(a_, b_, LWAN_TMP_ID, LWAN_TMP_ID) +#define LWAN_MIN(a_, b_) LWAN_MIN_MAX_DETAIL(a_, b_, LWAN_TMP_ID, LWAN_TMP_ID, >) -#define LWAN_MAX(a_, b_) LWAN_MAX_DETAIL(a_, b_, LWAN_TMP_ID, LWAN_TMP_ID) +#define LWAN_MAX(a_, b_) LWAN_MIN_MAX_DETAIL(a_, b_, LWAN_TMP_ID, LWAN_TMP_ID, <) int lwan_socket_get_backlog_size(void); From c4d4f1402709bc1e5621e590ea891732256ce4df Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 30 Jan 2020 18:27:23 -0800 Subject: [PATCH 1402/2505] Sort extensions in fast path (lwan_determine_mime_type_for_file_name()) --- src/lib/lwan-tables.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index 74e05d971..613d83617 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -121,13 +121,13 @@ lwan_determine_mime_type_for_file_name(const char *file_name) goto fallback; STRING_SWITCH_L(last_dot) { + case STR4_INT_L('.','c','s','s'): return "text/css"; case STR4_INT_L('.','g','i','f'): return "image/gif"; + case STR4_INT_L('.','h','t','m'): return "text/html"; case STR4_INT_L('.','j','p','g'): return "image/jpeg"; + case STR4_INT_L('.','j','s',' '): return "application/javascript"; case STR4_INT_L('.','p','n','g'): return "image/png"; - case STR4_INT_L('.','h','t','m'): return "text/html"; - case STR4_INT_L('.','c','s','s'): return "text/css"; case STR4_INT_L('.','t','x','t'): return "text/plain"; - case STR4_INT_L('.','j','s',' '): return "application/javascript"; } if (LIKELY(*last_dot)) { From 5c6a56ca6a22fc1505343e3b97821feaac2365c8 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 31 Jan 2020 17:52:14 -0800 Subject: [PATCH 1403/2505] Fix coverage build --- src/bin/testrunner/CMakeLists.txt | 3 ++- src/samples/CMakeLists.txt | 3 ++- src/samples/techempower/CMakeLists.txt | 8 ++++++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/bin/testrunner/CMakeLists.txt b/src/bin/testrunner/CMakeLists.txt index 8fba9996b..ea8a86025 100644 --- a/src/bin/testrunner/CMakeLists.txt +++ b/src/bin/testrunner/CMakeLists.txt @@ -12,9 +12,10 @@ if (${CMAKE_BUILD_TYPE} MATCHES "Coverage") if (PYTHONINTERP_FOUND) setup_target_for_coverage(generate-coverage - "${PYTHON_EXECUTABLE} ${CMAKE_SOURCE_DIR}/src/scripts/testsuite.py $ -v" + "${PYTHON_EXECUTABLE} ${CMAKE_SOURCE_DIR}/src/scripts/testsuite.py -v ${CMAKE_BINARY_DIR}" coverage ) + add_dependencies(generate-coverage testrunner) message(STATUS "Python found; generate-coverage target enabled") else () message(STATUS "Python not found; coverage report disabled") diff --git a/src/samples/CMakeLists.txt b/src/samples/CMakeLists.txt index 031b2b4f8..3735043df 100644 --- a/src/samples/CMakeLists.txt +++ b/src/samples/CMakeLists.txt @@ -1,8 +1,9 @@ if (NOT ${CMAKE_BUILD_TYPE} MATCHES "Coverage") add_subdirectory(freegeoip) - add_subdirectory(techempower) add_subdirectory(hello) add_subdirectory(hello-no-meta) add_subdirectory(clock) add_subdirectory(websocket) endif() + +add_subdirectory(techempower) diff --git a/src/samples/techempower/CMakeLists.txt b/src/samples/techempower/CMakeLists.txt index 064808498..b220c2ca9 100644 --- a/src/samples/techempower/CMakeLists.txt +++ b/src/samples/techempower/CMakeLists.txt @@ -34,6 +34,14 @@ if (MYSQL_INCLUDE_DIR AND SQLITE_FOUND) ) include_directories(${SQLITE_INCLUDE_DIRS}) include_directories(BEFORE ${CMAKE_BINARY_DIR}) + + if (${CMAKE_BUILD_TYPE} MATCHES "Coverage") + find_package(PythonInterp 3) + + if (PYTHONINTERP_FOUND) + add_dependencies(generate-coverage techempower) + endif() + endif () endif () endif () From a60f3cbd0a803b7e72c7faae130fd8485fc09fb5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 31 Jan 2020 20:06:44 -0800 Subject: [PATCH 1404/2505] Reduce branches while appending strings to generate JSON --- src/samples/techempower/techempower.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index db7e4c2cc..0406d7731 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -149,7 +149,7 @@ static int append_to_strbuf(const char *bytes, size_t len, void *data) { struct lwan_strbuf *strbuf = data; - return lwan_strbuf_append_str(strbuf, bytes, len) ? 0 : -EINVAL; + return !lwan_strbuf_append_str(strbuf, bytes, len); } static enum lwan_http_status @@ -161,7 +161,7 @@ json_response_obj(struct lwan_response *response, lwan_strbuf_grow_to(response->buffer, 128); if (json_obj_encode_full(descr, descr_len, data, append_to_strbuf, - response->buffer, false) < 0) + response->buffer, false) != 0) return HTTP_INTERNAL_ERROR; response->mime_type = "application/json"; @@ -176,7 +176,7 @@ json_response_arr(struct lwan_response *response, lwan_strbuf_grow_to(response->buffer, 128); if (json_arr_encode_full(descr, data, append_to_strbuf, response->buffer, - false) < 0) + false) != 0) return HTTP_INTERNAL_ERROR; response->mime_type = "application/json"; From f0478a7cad27cf341ef9612b06db50a9479f5152 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 31 Jan 2020 20:08:43 -0800 Subject: [PATCH 1405/2505] Simplify fortune_compare() --- src/samples/techempower/techempower.c | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index 0406d7731..5cc726474 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -281,16 +281,8 @@ static int fortune_compare(const void *a, const void *b) { const struct Fortune *fortune_a = (const struct Fortune *)a; const struct Fortune *fortune_b = (const struct Fortune *)b; - size_t a_len = strlen(fortune_a->item.message); - size_t b_len = strlen(fortune_b->item.message); - if (!a_len || !b_len) - return a_len > b_len; - - size_t min_len = a_len < b_len ? a_len : b_len; - - int cmp = memcmp(fortune_a->item.message, fortune_b->item.message, min_len); - return cmp == 0 ? -(int)(ssize_t)min_len : cmp; + return strcmp(fortune_a->item.message, fortune_b->item.message); } static bool append_fortune(struct coro *coro, From 3cdb43962bf5e54eb9e7727e8569c02dd962e36f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 31 Jan 2020 20:24:39 -0800 Subject: [PATCH 1406/2505] Reduce number of arguments for auxiliary db_query() function --- src/samples/techempower/techempower.c | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index 5cc726474..182bbcc64 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -191,18 +191,12 @@ LWAN_HANDLER(json) N_ELEMENTS(hello_world_json_desc), &j); } -static bool db_query(struct db_stmt *stmt, - struct db_row rows[], - size_t n_rows, - struct db_json *out) +static bool db_query(struct db_stmt *stmt, struct db_json *out) { const int id = (rand() % 10000) + 1; - assert(n_rows >= 1); - - rows[0].u.i = id; - - if (UNLIKELY(!db_stmt_bind(stmt, rows, n_rows))) + struct db_row row = { .kind = 'i', .u.i = id }; + if (UNLIKELY(!db_stmt_bind(stmt, &row, 1))) return false; long random_number; @@ -217,7 +211,6 @@ static bool db_query(struct db_stmt *stmt, LWAN_HANDLER(db) { - struct db_row rows[] = {{.kind = 'i'}}; struct db_stmt *stmt = db_prepare_stmt(get_db(), random_number_query, sizeof(random_number_query) - 1); struct db_json db_json; @@ -227,7 +220,7 @@ LWAN_HANDLER(db) return HTTP_INTERNAL_ERROR; } - bool queried = db_query(stmt, rows, N_ELEMENTS(rows), &db_json); + bool queried = db_query(stmt, &db_json); db_stmt_finalize(stmt); @@ -254,9 +247,8 @@ LWAN_HANDLER(queries) return HTTP_INTERNAL_ERROR; struct queries_json qj = {.queries_len = (size_t)queries}; - struct db_row results[] = {{.kind = 'i'}}; for (long i = 0; i < queries; i++) { - if (!db_query(stmt, results, N_ELEMENTS(results), &qj.queries[i])) + if (!db_query(stmt, &qj.queries[i])) goto out; } From 5208a56523c610e030aa54af42ad51f7ac21d71f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 1 Feb 2020 09:03:14 -0800 Subject: [PATCH 1407/2505] Unpoison BPA arenas before freeing Leaving the arenas poisoned causes spurious violations when fuzzing. Unpoisoning them before freeing means we'll only rely on asan's and valgrind's malloc()/free() checkers to detect UAF in the BPA arena. --- src/lib/lwan-coro.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index d4c368610..1502a4b1c 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -448,6 +448,19 @@ static inline void *coro_malloc_bump_ptr(struct coro *coro, size_t aligned_size) coro_malloc_bump_ptr(coro_, aligned_size_) #endif +#if defined(INSTRUMENT_FOR_ASAN) || defined(INSTRUMENT_FOR_VALGRIND) +static void unpoison_and_free(void *ptr) +{ +#if defined(INSTRUMENT_FOR_VALGRIND) + VALGRIND_MAKE_MEM_UNDEFINED(ptr, CORO_BUMP_PTR_ALLOC_SIZE); +#endif +#if defined(INSTRUMENT_FOR_ASAN) + __asan_unpoison_memory_region(ptr, CORO_BUMP_PTR_ALLOC_SIZE); +#endif + free(ptr); +} +#endif + void *coro_malloc(struct coro *coro, size_t size) { /* The bump pointer allocator can't be in the generic coro_malloc_full() @@ -482,7 +495,12 @@ void *coro_malloc(struct coro *coro, size_t size) VALGRIND_MAKE_MEM_NOACCESS(coro->bump_ptr_alloc.ptr, CORO_BUMP_PTR_ALLOC_SIZE); #endif + +#if defined(INSTRUMENT_FOR_ASAN) || defined(INSTRUMENT_FOR_VALGRIND) + coro_defer(coro, unpoison_and_free, coro->bump_ptr_alloc.ptr); +#else coro_defer(coro, free, coro->bump_ptr_alloc.ptr); +#endif /* Avoid checking if there's still space in the arena again. */ return CORO_MALLOC_BUMP_PTR(coro, aligned_size, size); From ebe874317d5317bc4ddbe7690dad9f8e15bf8da3 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 1 Feb 2020 10:39:16 -0800 Subject: [PATCH 1408/2505] Use field_name_len instead of calling strlen() while encoding keys --- src/samples/techempower/json.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/samples/techempower/json.c b/src/samples/techempower/json.c index cef0ec740..a83aaaced 100644 --- a/src/samples/techempower/json.c +++ b/src/samples/techempower/json.c @@ -761,15 +761,19 @@ static int arr_encode(const struct json_obj_descr *elem_descr, return ret | append_bytes("]", 1, data); } -static int -str_encode(const char **str, json_append_bytes_t append_bytes, void *data, bool escape) +static int str_encode(const char **str, + const size_t str_len, + json_append_bytes_t append_bytes, + void *data, + bool escape) { int ret = append_bytes("\"", 1, data); if (escape) { + assert(str_len == 0); ret |= json_escape_internal(*str, append_bytes, data); } else { - ret |= append_bytes(*str, strlen(*str), data); + ret |= append_bytes(*str, str_len, data); } return ret | append_bytes("\"", 1, data); @@ -820,7 +824,7 @@ static int encode(const struct json_obj_descr *descr, case JSON_TOK_TRUE: return bool_encode(ptr, append_bytes, data); case JSON_TOK_STRING: - return str_encode(ptr, append_bytes, data, true); + return str_encode(ptr, 0, append_bytes, data, true); case JSON_TOK_LIST_START: return arr_encode(descr->array.element_descr, ptr, val, append_bytes, data, escape_key); @@ -841,8 +845,8 @@ static int encode_key_value(const struct json_obj_descr *descr, void *data, bool escape_key) { - int ret = str_encode((const char **)&descr->field_name, append_bytes, data, - escape_key); + int ret = str_encode((const char **)&descr->field_name, + descr->field_name_len, append_bytes, data, escape_key); ret |= append_bytes(":", 1, data); From b57303b448d86ab2b8e401dfbb5aa45897427fa7 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 1 Feb 2020 10:52:26 -0800 Subject: [PATCH 1409/2505] Reduce number of append_bytes() calls while encoding keys in JSON Pre-encode the key with quotes and the colon following it in the field declaration macros, so that only one call to append_bytes() per key is necessary while encoding a key-value pair. This also removes the branch on "encode_key" while encoding arbitrary strings. --- src/samples/techempower/json.c | 24 ++-- src/samples/techempower/json.h | 194 ++++++++++++++++----------------- 2 files changed, 108 insertions(+), 110 deletions(-) diff --git a/src/samples/techempower/json.c b/src/samples/techempower/json.c index a83aaaced..0fe57a0c3 100644 --- a/src/samples/techempower/json.c +++ b/src/samples/techempower/json.c @@ -762,19 +762,12 @@ static int arr_encode(const struct json_obj_descr *elem_descr, } static int str_encode(const char **str, - const size_t str_len, json_append_bytes_t append_bytes, - void *data, - bool escape) + void *data) { int ret = append_bytes("\"", 1, data); - if (escape) { - assert(str_len == 0); - ret |= json_escape_internal(*str, append_bytes, data); - } else { - ret |= append_bytes(*str, str_len, data); - } + ret |= json_escape_internal(*str, append_bytes, data); return ret | append_bytes("\"", 1, data); } @@ -824,7 +817,7 @@ static int encode(const struct json_obj_descr *descr, case JSON_TOK_TRUE: return bool_encode(ptr, append_bytes, data); case JSON_TOK_STRING: - return str_encode(ptr, 0, append_bytes, data, true); + return str_encode(ptr, append_bytes, data); case JSON_TOK_LIST_START: return arr_encode(descr->array.element_descr, ptr, val, append_bytes, data, escape_key); @@ -845,10 +838,15 @@ static int encode_key_value(const struct json_obj_descr *descr, void *data, bool escape_key) { - int ret = str_encode((const char **)&descr->field_name, - descr->field_name_len, append_bytes, data, escape_key); + int ret; - ret |= append_bytes(":", 1, data); + if (!escape_key) { + ret = append_bytes(descr->field_name + descr->field_name_len, + descr->field_name_len + 3 /* 3=len('"":') */, data); + } else { + ret = str_encode((const char **)&descr->field_name, append_bytes, data); + ret |= append_bytes(":", 1, data); + } return ret | encode(descr, val, append_bytes, data, escape_key); } diff --git a/src/samples/techempower/json.h b/src/samples/techempower/json.h index ac6445c50..daa02c986 100644 --- a/src/samples/techempower/json.h +++ b/src/samples/techempower/json.h @@ -86,6 +86,10 @@ struct json_obj_descr { */ typedef int (*json_append_bytes_t)(const char *bytes, size_t len, void *data); +#define JSON_FIELD_NAME(field_name_) \ + .field_name = #field_name_ "\"" #field_name_ "\":", \ + .field_name_len = sizeof(#field_name_) - 1 + /** * @brief Helper macro to declare a descriptor for supported primitive * values. @@ -110,9 +114,9 @@ typedef int (*json_append_bytes_t)(const char *bytes, size_t len, void *data); */ #define JSON_OBJ_DESCR_PRIM(struct_, field_name_, type_) \ { \ - .field_name = (#field_name_), .align = __alignof__(struct_), \ - .field_name_len = sizeof(#field_name_) - 1, .type = type_, \ - .offset = offsetof(struct_, field_name_), \ + JSON_FIELD_NAME(field_name_), \ + .align = __alignof__(struct_), .type = type_, \ + .offset = offsetof(struct_, field_name_), \ } /** @@ -143,14 +147,13 @@ typedef int (*json_append_bytes_t)(const char *bytes, size_t len, void *data); */ #define JSON_OBJ_DESCR_OBJECT(struct_, field_name_, sub_descr_) \ { \ - .field_name = (#field_name_), .align = __alignof__(struct_), \ - .field_name_len = (sizeof(#field_name_) - 1), \ - .type = JSON_TOK_OBJECT_START, \ - .offset = offsetof(struct_, field_name_), \ - .object = { \ - .sub_descr = sub_descr_, \ - .sub_descr_len = ARRAY_SIZE(sub_descr_), \ - }, \ + JSON_FIELD_NAME(field_name_), \ + .align = __alignof__(struct_), .type = JSON_TOK_OBJECT_START, \ + .offset = offsetof(struct_, field_name_), \ + .object = { \ + .sub_descr = sub_descr_, \ + .sub_descr_len = ARRAY_SIZE(sub_descr_), \ + }, \ } /** @@ -182,18 +185,18 @@ typedef int (*json_append_bytes_t)(const char *bytes, size_t len, void *data); #define JSON_OBJ_DESCR_ARRAY(struct_, field_name_, max_len_, len_field_, \ elem_type_) \ { \ - .field_name = (#field_name_), .align = __alignof__(struct_), \ - .field_name_len = sizeof(#field_name_) - 1, \ - .type = JSON_TOK_LIST_START, .offset = offsetof(struct_, field_name_), \ - .array = { \ - .element_descr = \ - &(struct json_obj_descr){ \ - .align = __alignof__(struct_), \ - .type = elem_type_, \ - .offset = offsetof(struct_, len_field_), \ - }, \ - .n_elements = (max_len_), \ - }, \ + JSON_FIELD_NAME(field_name_), \ + .align = __alignof__(struct_), .type = JSON_TOK_LIST_START, \ + .offset = offsetof(struct_, field_name_), \ + .array = { \ + .element_descr = \ + &(struct json_obj_descr){ \ + .align = __alignof__(struct_), \ + .type = elem_type_, \ + .offset = offsetof(struct_, len_field_), \ + }, \ + .n_elements = (max_len_), \ + }, \ } /** @@ -238,23 +241,23 @@ typedef int (*json_append_bytes_t)(const char *bytes, size_t len, void *data); #define JSON_OBJ_DESCR_OBJ_ARRAY(struct_, field_name_, max_len_, len_field_, \ elem_descr_, elem_descr_len_) \ { \ - .field_name = (#field_name_), .align = __alignof__(struct_), \ - .field_name_len = sizeof(#field_name_) - 1, \ - .type = JSON_TOK_LIST_START, .offset = offsetof(struct_, field_name_), \ - .array = { \ - .element_descr = \ - &(struct json_obj_descr){ \ - .align = __alignof__(struct_), \ - .type = JSON_TOK_OBJECT_START, \ - .offset = offsetof(struct_, len_field_), \ - .object = \ - { \ - .sub_descr = elem_descr_, \ - .sub_descr_len = elem_descr_len_, \ - }, \ - }, \ - .n_elements = (max_len_), \ - }, \ + JSON_FIELD_NAME(field_name_), \ + .align = __alignof__(struct_), .type = JSON_TOK_LIST_START, \ + .offset = offsetof(struct_, field_name_), \ + .array = { \ + .element_descr = \ + &(struct json_obj_descr){ \ + .align = __alignof__(struct_), \ + .type = JSON_TOK_OBJECT_START, \ + .offset = offsetof(struct_, len_field_), \ + .object = \ + { \ + .sub_descr = elem_descr_, \ + .sub_descr_len = elem_descr_len_, \ + }, \ + }, \ + .n_elements = (max_len_), \ + }, \ } /** @@ -308,23 +311,23 @@ typedef int (*json_append_bytes_t)(const char *bytes, size_t len, void *data); #define JSON_OBJ_DESCR_ARRAY_ARRAY(struct_, field_name_, max_len_, len_field_, \ elem_descr_, elem_descr_len_) \ { \ - .field_name = (#field_name_), .align = __alignof__(struct_), \ - .field_name_len = sizeof(#field_name_) - 1, \ - .type = JSON_TOK_LIST_START, .offset = offsetof(struct_, field_name_), \ - .array = { \ - .element_descr = \ - &(struct json_obj_descr){ \ - .align = __alignof__(struct_), \ - .type = JSON_TOK_LIST_START, \ - .offset = offsetof(struct_, len_field_), \ - .object = \ - { \ - .sub_descr = elem_descr_, \ - .sub_descr_len = elem_descr_len_, \ - }, \ - }, \ - .n_elements = (max_len_), \ - }, \ + JSON_FIELD_NAME(field_name_), \ + .align = __alignof__(struct_), .type = JSON_TOK_LIST_START, \ + .offset = offsetof(struct_, field_name_), \ + .array = { \ + .element_descr = \ + &(struct json_obj_descr){ \ + .align = __alignof__(struct_), \ + .type = JSON_TOK_LIST_START, \ + .offset = offsetof(struct_, len_field_), \ + .object = \ + { \ + .sub_descr = elem_descr_, \ + .sub_descr_len = elem_descr_len_, \ + }, \ + }, \ + .n_elements = (max_len_), \ + }, \ } /** @@ -347,9 +350,9 @@ typedef int (*json_append_bytes_t)(const char *bytes, size_t len, void *data); #define JSON_OBJ_DESCR_PRIM_NAMED(struct_, json_field_name_, \ struct_field_name_, type_) \ { \ - .field_name = (json_field_name_), .align = __alignof__(struct_), \ - .field_name_len = sizeof(json_field_name_) - 1, .type = type_, \ - .offset = offsetof(struct_, struct_field_name_), \ + JSON_FIELD_NAME(json_field_name_), \ + .align = __alignof__(struct_), .type = type_, \ + .offset = offsetof(struct_, struct_field_name_), \ } /** @@ -371,14 +374,13 @@ typedef int (*json_append_bytes_t)(const char *bytes, size_t len, void *data); #define JSON_OBJ_DESCR_OBJECT_NAMED(struct_, json_field_name_, \ struct_field_name_, sub_descr_) \ { \ - .field_name = (json_field_name_), .align = __alignof__(struct_), \ - .field_name_len = (sizeof(json_field_name_) - 1), \ - .type = JSON_TOK_OBJECT_START, \ - .offset = offsetof(struct_, struct_field_name_), \ - .object = { \ - .sub_descr = sub_descr_, \ - .sub_descr_len = ARRAY_SIZE(sub_descr_), \ - }, \ + JSON_FIELD_NAME(json_field_name_), \ + .align = __alignof__(struct_), .type = JSON_TOK_OBJECT_START, \ + .offset = offsetof(struct_, struct_field_name_), \ + .object = { \ + .sub_descr = sub_descr_, \ + .sub_descr_len = ARRAY_SIZE(sub_descr_), \ + }, \ } /** @@ -406,19 +408,18 @@ typedef int (*json_append_bytes_t)(const char *bytes, size_t len, void *data); struct_field_name_, max_len_, len_field_, \ elem_type_) \ { \ - .field_name = (json_field_name_), .align = __alignof__(struct_), \ - .field_name_len = sizeof(json_field_name_) - 1, \ - .type = JSON_TOK_LIST_START, \ - .offset = offsetof(struct_, struct_field_name_), \ - .array = { \ - .element_descr = \ - &(struct json_obj_descr){ \ - .align = __alignof__(struct_), \ - .type = elem_type_, \ - .offset = offsetof(struct_, len_field_), \ - }, \ - .n_elements = (max_len_), \ - }, \ + JSON_FIELD_NAME(json_field_name_), \ + .align = __alignof__(struct_), .type = JSON_TOK_LIST_START, \ + .offset = offsetof(struct_, struct_field_name_), \ + .array = { \ + .element_descr = \ + &(struct json_obj_descr){ \ + .align = __alignof__(struct_), \ + .type = elem_type_, \ + .offset = offsetof(struct_, len_field_), \ + }, \ + .n_elements = (max_len_), \ + }, \ } /** @@ -471,22 +472,21 @@ typedef int (*json_append_bytes_t)(const char *bytes, size_t len, void *data); struct_, json_field_name_, struct_field_name_, max_len_, len_field_, \ elem_descr_, elem_descr_len_) \ { \ - .field_name = json_field_name_, .align = __alignof__(struct_), \ - .field_name_len = sizeof(json_field_name_) - 1, \ - .type = JSON_TOK_LIST_START, \ - .offset = offsetof(struct_, struct_field_name_), \ - .element_descr = \ - &(struct json_obj_descr){ \ - .align = __alignof__(struct_), \ - .type = JSON_TOK_OBJECT_START, \ - .offset = offsetof(struct_, len_field_), \ - .object = \ - { \ - .sub_descr = elem_descr_, \ - .sub_descr_len = elem_descr_len_, \ - }, \ - }, \ - .n_elements = (max_len_), \ + JSON_FIELD_NAME(json_field_name_), \ + .align = __alignof__(struct_), .type = JSON_TOK_LIST_START, \ + .offset = offsetof(struct_, struct_field_name_), \ + .element_descr = \ + &(struct json_obj_descr){ \ + .align = __alignof__(struct_), \ + .type = JSON_TOK_OBJECT_START, \ + .offset = offsetof(struct_, len_field_), \ + .object = \ + { \ + .sub_descr = elem_descr_, \ + .sub_descr_len = elem_descr_len_, \ + }, \ + }, \ + .n_elements = (max_len_), \ } /** From 435d495268950d35e5d4b718e79f75553b08ce69 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 1 Feb 2020 17:39:28 -0800 Subject: [PATCH 1410/2505] Remove some branches from json_escape_internal() --- src/samples/techempower/json.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/samples/techempower/json.c b/src/samples/techempower/json.c index 0fe57a0c3..f2e9b1640 100644 --- a/src/samples/techempower/json.c +++ b/src/samples/techempower/json.c @@ -638,15 +638,15 @@ static int json_escape_internal(const char *str, const char *cur; int ret = 0; - for (cur = str; ret == 0 && *cur; cur++) { + for (cur = str; *cur; cur++) { char escaped = escape_as(*cur); if (escaped) { char bytes[2] = {'\\', escaped}; - ret = append_bytes(bytes, 2, data); + ret |= append_bytes(bytes, 2, data); } else { - ret = append_bytes(cur, 1, data); + ret |= append_bytes(cur, 1, data); } } From 546d3f12d25bb9964a075eb7b4724484dd71f61d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 1 Feb 2020 17:45:47 -0800 Subject: [PATCH 1411/2505] Batch calls to append_bytes() in json_escape_internal() (Cuts calls to escape "Hello, World!" from 13 to just 1.) --- src/samples/techempower/json.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/samples/techempower/json.c b/src/samples/techempower/json.c index f2e9b1640..00013dcd9 100644 --- a/src/samples/techempower/json.c +++ b/src/samples/techempower/json.c @@ -636,20 +636,27 @@ static int json_escape_internal(const char *str, void *data) { const char *cur; + const char *unescaped; int ret = 0; - for (cur = str; *cur; cur++) { + for (cur = unescaped = str; *cur; cur++) { char escaped = escape_as(*cur); if (escaped) { char bytes[2] = {'\\', escaped}; + if (unescaped - cur) { + ret |= append_bytes(unescaped, (size_t)(cur - unescaped), data); + unescaped = cur + 1; + } + ret |= append_bytes(bytes, 2, data); - } else { - ret |= append_bytes(cur, 1, data); } } + if (unescaped - cur) + return ret | append_bytes(unescaped, (size_t)(cur - unescaped), data); + return ret; } From f765d4962c70cf593ce1068eb860e829851be99b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 2 Feb 2020 12:24:52 -0800 Subject: [PATCH 1412/2505] Reduce number of strbuf reallocations while serializing JSON arrays For the TWFB "queries" benchmark, it's better to grow the string buffer to the size it'll eventually be, before serializing the data in JSON. This avoids various reallocations and copies during this time. --- src/samples/techempower/techempower.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index 182bbcc64..32b9b08f4 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -158,8 +158,6 @@ json_response_obj(struct lwan_response *response, size_t descr_len, const void *data) { - lwan_strbuf_grow_to(response->buffer, 128); - if (json_obj_encode_full(descr, descr_len, data, append_to_strbuf, response->buffer, false) != 0) return HTTP_INTERNAL_ERROR; @@ -173,8 +171,6 @@ json_response_arr(struct lwan_response *response, const struct json_obj_descr *descr, const void *data) { - lwan_strbuf_grow_to(response->buffer, 128); - if (json_arr_encode_full(descr, data, append_to_strbuf, response->buffer, false) != 0) return HTTP_INTERNAL_ERROR; @@ -252,6 +248,11 @@ LWAN_HANDLER(queries) goto out; } + /* Avoid reallocations/copies while building response. Each response + * has ~32bytes. 500 queries (max) should be less than 16384 bytes, + * so this is a good approximation. */ + lwan_strbuf_grow_to(response->buffer, 32 * queries); + ret = json_response_arr(response, &queries_array_desc, &qj); out: From 402c120fd049bf27dddb9eafe664fc5b4e479be0 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 2 Feb 2020 19:29:07 -0800 Subject: [PATCH 1413/2505] Remove compiler warning after f765d4962c --- src/samples/techempower/techempower.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index 32b9b08f4..856269515 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -251,7 +251,7 @@ LWAN_HANDLER(queries) /* Avoid reallocations/copies while building response. Each response * has ~32bytes. 500 queries (max) should be less than 16384 bytes, * so this is a good approximation. */ - lwan_strbuf_grow_to(response->buffer, 32 * queries); + lwan_strbuf_grow_to(response->buffer, (size_t)(32l * queries)); ret = json_response_arr(response, &queries_array_desc, &qj); From e3c03b6a975a7206a1b6e5d78a99abf5e7be1647 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 2 Feb 2020 19:29:28 -0800 Subject: [PATCH 1414/2505] Cleanups in db_query() --- src/samples/techempower/techempower.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index 856269515..c93e2d8a6 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -184,14 +184,12 @@ LWAN_HANDLER(json) struct hello_world_json j = {.message = hello_world}; return json_response_obj(response, hello_world_json_desc, - N_ELEMENTS(hello_world_json_desc), &j); + N_ELEMENTS(hello_world_json_desc), &j); } static bool db_query(struct db_stmt *stmt, struct db_json *out) { - const int id = (rand() % 10000) + 1; - - struct db_row row = { .kind = 'i', .u.i = id }; + struct db_row row = {.kind = 'i', .u.i = (rand() % 10000) + 1}; if (UNLIKELY(!db_stmt_bind(stmt, &row, 1))) return false; @@ -199,7 +197,7 @@ static bool db_query(struct db_stmt *stmt, struct db_json *out) if (UNLIKELY(!db_stmt_step(stmt, "i", &random_number))) return false; - out->id = id; + out->id = row.u.i; out->randomNumber = (int)random_number; return true; From fcc6da98b64c524c6e3ad7ca73d11d094142e52b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 6 Feb 2020 23:03:30 -0800 Subject: [PATCH 1415/2505] Add some TWFB benchmarks in Lua --- src/lib/lwan-mod-lua.h | 2 +- src/samples/techempower/json.lua | 400 +++++++++++++++++++++++ src/samples/techempower/techempower.c | 14 +- src/samples/techempower/techempower.conf | 24 ++ src/scripts/testsuite.py | 16 + 5 files changed, 442 insertions(+), 14 deletions(-) create mode 100644 src/samples/techempower/json.lua diff --git a/src/lib/lwan-mod-lua.h b/src/lib/lwan-mod-lua.h index cb6319d2f..43e48106a 100644 --- a/src/lib/lwan-mod-lua.h +++ b/src/lib/lwan-mod-lua.h @@ -32,7 +32,7 @@ LWAN_MODULE_FORWARD_DECL(lua); #define LUA(default_type_) \ .module = LWAN_MODULE_REF(lua), \ - .args = ((struct lwan_lua[]) {{ \ + .args = ((struct lwan_lua_settings[]) {{ \ .default_type = default_type_ \ }}), \ .flags = 0 diff --git a/src/samples/techempower/json.lua b/src/samples/techempower/json.lua new file mode 100644 index 000000000..098e7b29a --- /dev/null +++ b/src/samples/techempower/json.lua @@ -0,0 +1,400 @@ +-- +-- json.lua +-- +-- Copyright (c) 2019 rxi +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. +-- + +local json = { _version = "0.1.2" } + +------------------------------------------------------------------------------- +-- Encode +------------------------------------------------------------------------------- + +local encode + +local escape_char_map = { + [ "\\" ] = "\\\\", + [ "\"" ] = "\\\"", + [ "\b" ] = "\\b", + [ "\f" ] = "\\f", + [ "\n" ] = "\\n", + [ "\r" ] = "\\r", + [ "\t" ] = "\\t", +} + +local escape_char_map_inv = { [ "\\/" ] = "/" } +for k, v in pairs(escape_char_map) do + escape_char_map_inv[v] = k +end + + +local function escape_char(c) + return escape_char_map[c] or string.format("\\u%04x", c:byte()) +end + + +local function encode_nil(val) + return "null" +end + + +local function encode_table(val, stack) + local res = {} + stack = stack or {} + + -- Circular reference? + if stack[val] then error("circular reference") end + + stack[val] = true + + if rawget(val, 1) ~= nil or next(val) == nil then + -- Treat as array -- check keys are valid and it is not sparse + local n = 0 + for k in pairs(val) do + if type(k) ~= "number" then + error("invalid table: mixed or invalid key types") + end + n = n + 1 + end + if n ~= #val then + error("invalid table: sparse array") + end + -- Encode + for i, v in ipairs(val) do + table.insert(res, encode(v, stack)) + end + stack[val] = nil + return "[" .. table.concat(res, ",") .. "]" + + else + -- Treat as an object + for k, v in pairs(val) do + if type(k) ~= "string" then + error("invalid table: mixed or invalid key types") + end + table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) + end + stack[val] = nil + return "{" .. table.concat(res, ",") .. "}" + end +end + + +local function encode_string(val) + return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' +end + + +local function encode_number(val) + -- Check for NaN, -inf and inf + if val ~= val or val <= -math.huge or val >= math.huge then + error("unexpected number value '" .. tostring(val) .. "'") + end + return string.format("%.14g", val) +end + + +local type_func_map = { + [ "nil" ] = encode_nil, + [ "table" ] = encode_table, + [ "string" ] = encode_string, + [ "number" ] = encode_number, + [ "boolean" ] = tostring, +} + + +encode = function(val, stack) + local t = type(val) + local f = type_func_map[t] + if f then + return f(val, stack) + end + error("unexpected type '" .. t .. "'") +end + + +function json.encode(val) + return ( encode(val) ) +end + + +------------------------------------------------------------------------------- +-- Decode +------------------------------------------------------------------------------- + +local parse + +local function create_set(...) + local res = {} + for i = 1, select("#", ...) do + res[ select(i, ...) ] = true + end + return res +end + +local space_chars = create_set(" ", "\t", "\r", "\n") +local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") +local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") +local literals = create_set("true", "false", "null") + +local literal_map = { + [ "true" ] = true, + [ "false" ] = false, + [ "null" ] = nil, +} + + +local function next_char(str, idx, set, negate) + for i = idx, #str do + if set[str:sub(i, i)] ~= negate then + return i + end + end + return #str + 1 +end + + +local function decode_error(str, idx, msg) + local line_count = 1 + local col_count = 1 + for i = 1, idx - 1 do + col_count = col_count + 1 + if str:sub(i, i) == "\n" then + line_count = line_count + 1 + col_count = 1 + end + end + error( string.format("%s at line %d col %d", msg, line_count, col_count) ) +end + + +local function codepoint_to_utf8(n) + -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa + local f = math.floor + if n <= 0x7f then + return string.char(n) + elseif n <= 0x7ff then + return string.char(f(n / 64) + 192, n % 64 + 128) + elseif n <= 0xffff then + return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) + elseif n <= 0x10ffff then + return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, + f(n % 4096 / 64) + 128, n % 64 + 128) + end + error( string.format("invalid unicode codepoint '%x'", n) ) +end + + +local function parse_unicode_escape(s) + local n1 = tonumber( s:sub(3, 6), 16 ) + local n2 = tonumber( s:sub(9, 12), 16 ) + -- Surrogate pair? + if n2 then + return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) + else + return codepoint_to_utf8(n1) + end +end + + +local function parse_string(str, i) + local has_unicode_escape = false + local has_surrogate_escape = false + local has_escape = false + local last + for j = i + 1, #str do + local x = str:byte(j) + + if x < 32 then + decode_error(str, j, "control character in string") + end + + if last == 92 then -- "\\" (escape char) + if x == 117 then -- "u" (unicode escape sequence) + local hex = str:sub(j + 1, j + 5) + if not hex:find("%x%x%x%x") then + decode_error(str, j, "invalid unicode escape in string") + end + if hex:find("^[dD][89aAbB]") then + has_surrogate_escape = true + else + has_unicode_escape = true + end + else + local c = string.char(x) + if not escape_chars[c] then + decode_error(str, j, "invalid escape char '" .. c .. "' in string") + end + has_escape = true + end + last = nil + + elseif x == 34 then -- '"' (end of string) + local s = str:sub(i + 1, j - 1) + if has_surrogate_escape then + s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape) + end + if has_unicode_escape then + s = s:gsub("\\u....", parse_unicode_escape) + end + if has_escape then + s = s:gsub("\\.", escape_char_map_inv) + end + return s, j + 1 + + else + last = x + end + end + decode_error(str, i, "expected closing quote for string") +end + + +local function parse_number(str, i) + local x = next_char(str, i, delim_chars) + local s = str:sub(i, x - 1) + local n = tonumber(s) + if not n then + decode_error(str, i, "invalid number '" .. s .. "'") + end + return n, x +end + + +local function parse_literal(str, i) + local x = next_char(str, i, delim_chars) + local word = str:sub(i, x - 1) + if not literals[word] then + decode_error(str, i, "invalid literal '" .. word .. "'") + end + return literal_map[word], x +end + + +local function parse_array(str, i) + local res = {} + local n = 1 + i = i + 1 + while 1 do + local x + i = next_char(str, i, space_chars, true) + -- Empty / end of array? + if str:sub(i, i) == "]" then + i = i + 1 + break + end + -- Read token + x, i = parse(str, i) + res[n] = x + n = n + 1 + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "]" then break end + if chr ~= "," then decode_error(str, i, "expected ']' or ','") end + end + return res, i +end + + +local function parse_object(str, i) + local res = {} + i = i + 1 + while 1 do + local key, val + i = next_char(str, i, space_chars, true) + -- Empty / end of object? + if str:sub(i, i) == "}" then + i = i + 1 + break + end + -- Read key + if str:sub(i, i) ~= '"' then + decode_error(str, i, "expected string for key") + end + key, i = parse(str, i) + -- Read ':' delimiter + i = next_char(str, i, space_chars, true) + if str:sub(i, i) ~= ":" then + decode_error(str, i, "expected ':' after key") + end + i = next_char(str, i + 1, space_chars, true) + -- Read value + val, i = parse(str, i) + -- Set + res[key] = val + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "}" then break end + if chr ~= "," then decode_error(str, i, "expected '}' or ','") end + end + return res, i +end + + +local char_func_map = { + [ '"' ] = parse_string, + [ "0" ] = parse_number, + [ "1" ] = parse_number, + [ "2" ] = parse_number, + [ "3" ] = parse_number, + [ "4" ] = parse_number, + [ "5" ] = parse_number, + [ "6" ] = parse_number, + [ "7" ] = parse_number, + [ "8" ] = parse_number, + [ "9" ] = parse_number, + [ "-" ] = parse_number, + [ "t" ] = parse_literal, + [ "f" ] = parse_literal, + [ "n" ] = parse_literal, + [ "[" ] = parse_array, + [ "{" ] = parse_object, +} + + +parse = function(str, idx) + local chr = str:sub(idx, idx) + local f = char_func_map[chr] + if f then + return f(str, idx) + end + decode_error(str, idx, "unexpected character '" .. chr .. "'") +end + + +function json.decode(str) + if type(str) ~= "string" then + error("expected argument of type string, got " .. type(str)) + end + local res, idx = parse(str, next_char(str, 1, space_chars, true)) + idx = next_char(str, idx, space_chars, true) + if idx <= #str then + decode_error(str, idx, "trailing garbage") + end + return res +end + + +return json diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index c93e2d8a6..51f4666cc 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -25,6 +25,7 @@ #include "lwan-private.h" #include "lwan-config.h" #include "lwan-template.h" +#include "lwan-mod-lua.h" #include "database.h" #include "json.h" @@ -359,18 +360,6 @@ LWAN_HANDLER(quit_lwan) int main(void) { - static const struct lwan_url_map url_map[] = { - /* Routes for the TWFB benchmark: */ - {.prefix = "/json", .handler = LWAN_HANDLER_REF(json)}, - {.prefix = "/db", .handler = LWAN_HANDLER_REF(db)}, - {.prefix = "/queries", .handler = LWAN_HANDLER_REF(queries)}, - {.prefix = "/plaintext", .handler = LWAN_HANDLER_REF(plaintext)}, - {.prefix = "/fortunes", .handler = LWAN_HANDLER_REF(fortunes)}, - /* Routes for the test harness: */ - {.prefix = "/quit-lwan", .handler = LWAN_HANDLER_REF(quit_lwan)}, - {.prefix = "/hello", .handler = LWAN_HANDLER_REF(plaintext)}, - {.prefix = NULL}, - }; struct lwan l; lwan_init(&l); @@ -410,7 +399,6 @@ int main(void) if (!fortune_tpl) lwan_status_critical("Could not compile fortune templates"); - lwan_set_url_map(&l, url_map); lwan_main_loop(&l); lwan_tpl_free(fortune_tpl); diff --git a/src/samples/techempower/techempower.conf b/src/samples/techempower/techempower.conf index a0a46dfd3..981db296d 100644 --- a/src/samples/techempower/techempower.conf +++ b/src/samples/techempower/techempower.conf @@ -1,2 +1,26 @@ listener *:8080 { + # For main TWFB benchmarks + &plaintext /plaintext + &json /json + &db /db + &queries /queries + + # For Lua version of TWFB benchmarks + lua /lua. { + default_type = text/plain + cache period = 1h + script='''local json = require "json" + + function handle_get_plaintext(req) + req:set_response("Hello, World!") + end + + function handle_get_json(req) + req:set_response(json.encode({message="Hello, World!"})) + end''' + } + + # For test harness + &quit_lwan /quit-lwan + &plaintext /hello } diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index b38d751ae..d3236db28 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -86,10 +86,14 @@ class TestTWFB(LwanTest): def setUp(self, env=None): super().setUp(env, harness='techempower') shutil.copyfile('./src/samples/techempower/techempower.db', './techempower.db') + shutil.copyfile('./src/samples/techempower/techempower.conf', './techempower.conf') + shutil.copyfile('./src/samples/techempower/json.lua', './json.lua') def tearDown(self): super().tearDown() os.remove('techempower.db') + os.remove('techempower.conf') + os.remove('json.lua') def test_plaintext(self): r = requests.get('http://127.0.0.1:8080/plaintext') @@ -97,6 +101,12 @@ def test_plaintext(self): self.assertResponsePlain(r) self.assertEqual(r.text, 'Hello, World!') + def test_plaintext_lua(self): + r = requests.get('http://127.0.0.1:8080/lua.plaintext') + + self.assertResponsePlain(r) + self.assertEqual(r.text, 'Hello, World!') + def assertSingleQueryResultIsValid(self, single): self.assertTrue(isinstance(single, dict)) self.assertEqual({'randomNumber', 'id'}, set(single.keys())) @@ -118,6 +128,12 @@ def test_json(self): self.assertHttpResponseValid(r, 200, 'application/json') self.assertEqual(r.json(), {'message': 'Hello, World!'}) + def test_json_lua(self): + r = requests.get('http://127.0.0.1:8080/lua.json') + + self.assertHttpResponseValid(r, 200, 'application/json') + self.assertEqual(r.json(), {'message': 'Hello, World!'}) + def test_single_query(self): r = requests.get('http://127.0.0.1:8080/db') From 42f9d9b497280604ab8fb19bceff6651e184edc3 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 12 Feb 2020 19:16:43 -0800 Subject: [PATCH 1416/2505] Align coroutine swapping routines on 32-byte boundaries --- src/lib/lwan-coro.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 1502a4b1c..29cee4150 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -128,7 +128,7 @@ struct coro { void __attribute__((noinline, visibility("internal"))) coro_swapcontext(coro_context *current, coro_context *other); asm(".text\n\t" - ".p2align 4\n\t" + ".p2align 5\n\t" ASM_ROUTINE(coro_swapcontext) "movq %rbx,0(%rdi)\n\t" "movq %rbp,8(%rdi)\n\t" @@ -157,7 +157,7 @@ asm(".text\n\t" void __attribute__((noinline, visibility("internal"))) coro_swapcontext(coro_context *current, coro_context *other); asm(".text\n\t" - ".p2align 16\n\t" + ".p2align 5\n\t" ASM_ROUTINE(coro_swapcontext) "movl 0x4(%esp),%eax\n\t" "movl %ecx,0x1c(%eax)\n\t" /* ECX */ @@ -196,7 +196,7 @@ coro_entry_point(struct coro *coro, coro_function_t func, void *data) void __attribute__((visibility("internal"))) coro_entry_point_x86_64(); asm(".text\n\t" - ".p2align 4\n\t" + ".p2align 5\n\t" ASM_ROUTINE(coro_entry_point_x86_64) "mov %r15, %rdx\n\t" "jmp " ASM_SYMBOL(coro_entry_point) "\n\t" From 06ec231af032dd5c31c55e9360bc7bc581bcd453 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 12 Feb 2020 19:17:18 -0800 Subject: [PATCH 1417/2505] Cleanup status_out() prototype when building in debugging mode --- src/lib/lwan-status.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/lib/lwan-status.c b/src/lib/lwan-status.c index e7cbe0f9a..4b49ce9df 100644 --- a/src/lib/lwan-status.c +++ b/src/lib/lwan-status.c @@ -134,17 +134,15 @@ static long gettid_cached(void) #define FORMAT_WITH_COLOR(fmt, color) "\033[" color "m" fmt "\033[0m" -static void -#ifdef NDEBUG -status_out(enum lwan_status_type type, const char *fmt, va_list values) -#else -status_out(const char *file, - const int line, - const char *func, - enum lwan_status_type type, - const char *fmt, - va_list values) +static void status_out( +#ifndef NDEBUG + const char *file, + const int line, + const char *func, #endif + enum lwan_status_type type, + const char *fmt, + va_list values) { struct lwan_value start = start_color(type); struct lwan_value end = end_color(); From bb011a5056f314f41e864c707da9033f2434bc0e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 12 Feb 2020 19:17:51 -0800 Subject: [PATCH 1418/2505] Reuse the expression when batching append_bytes() calls on escape --- src/samples/techempower/json.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/samples/techempower/json.c b/src/samples/techempower/json.c index 00013dcd9..b102a105b 100644 --- a/src/samples/techempower/json.c +++ b/src/samples/techempower/json.c @@ -645,7 +645,7 @@ static int json_escape_internal(const char *str, if (escaped) { char bytes[2] = {'\\', escaped}; - if (unescaped - cur) { + if (cur - unescaped) { ret |= append_bytes(unescaped, (size_t)(cur - unescaped), data); unescaped = cur + 1; } @@ -654,8 +654,8 @@ static int json_escape_internal(const char *str, } } - if (unescaped - cur) - return ret | append_bytes(unescaped, (size_t)(cur - unescaped), data); + if (cur - unescaped) + ret |= append_bytes(unescaped, (size_t)(cur - unescaped), data); return ret; } From 2889502b134456dfe620f219ba6d56d922ed3379 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 12 Feb 2020 19:18:35 -0800 Subject: [PATCH 1419/2505] Document how keys are encoded in JSON descriptors --- src/samples/techempower/json.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/samples/techempower/json.c b/src/samples/techempower/json.c index b102a105b..3a20d6205 100644 --- a/src/samples/techempower/json.c +++ b/src/samples/techempower/json.c @@ -848,6 +848,10 @@ static int encode_key_value(const struct json_obj_descr *descr, int ret; if (!escape_key) { + /* Keys are encoded twice in the descriptor; once without quotes and + * the trailing comma, and one with. Doing it like so cuts some + * indirect calls to append_bytes(), which in turn also potentially + * cuts some branches in most implementations of it. */ ret = append_bytes(descr->field_name + descr->field_name_len, descr->field_name_len + 3 /* 3=len('"":') */, data); } else { From 194632fd6c7b1efac715c84fbce4094841db3c95 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 14 Feb 2020 19:57:27 -0800 Subject: [PATCH 1420/2505] Remove one branch per connection coroutine initialization String buffer initialization can be performed in a way that's never going to fail by copying a struct from rodata. --- src/lib/lwan-strbuf.c | 7 +------ src/lib/lwan-strbuf.h | 3 +++ src/lib/lwan-thread.c | 7 ++----- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/lib/lwan-strbuf.c b/src/lib/lwan-strbuf.c index ff3def0cc..2c71b09de 100644 --- a/src/lib/lwan-strbuf.c +++ b/src/lib/lwan-strbuf.c @@ -82,12 +82,7 @@ bool lwan_strbuf_init_with_size(struct lwan_strbuf *s, size_t size) return false; if (!size) { - *s = (struct lwan_strbuf) { - .used = 0, - .capacity = 0, - .buffer = "", - .flags = STATIC, - }; + *s = LWAN_STRBUF_STATIC_INIT; } else { memset(s, 0, sizeof(*s)); diff --git a/src/lib/lwan-strbuf.h b/src/lib/lwan-strbuf.h index 0488830c8..e0b7475c0 100644 --- a/src/lib/lwan-strbuf.h +++ b/src/lib/lwan-strbuf.h @@ -30,6 +30,9 @@ struct lwan_strbuf { unsigned int flags; }; +#define LWAN_STRBUF_STATIC_INIT \ + (struct lwan_strbuf) { .buffer = "", .flags = 1 << 0 } + bool lwan_strbuf_init_with_size(struct lwan_strbuf *buf, size_t size); bool lwan_strbuf_init(struct lwan_strbuf *buf); struct lwan_strbuf *lwan_strbuf_new_static(const char *str, size_t size); diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 3da80f566..84fc3e105 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -104,14 +104,12 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, struct lwan *lwan = conn->thread->lwan; int fd = lwan_connection_get_fd(lwan, conn); enum lwan_request_flags flags = lwan->config.request_flags; - struct lwan_strbuf strbuf; + struct lwan_strbuf strbuf = LWAN_STRBUF_STATIC_INIT; char request_buffer[DEFAULT_BUFFER_SIZE]; struct lwan_value buffer = {.value = request_buffer, .len = 0}; char *next_request = NULL; struct lwan_proxy proxy; - if (UNLIKELY(!lwan_strbuf_init(&strbuf))) - goto out; coro_defer(coro, lwan_strbuf_free_defer, &strbuf); const size_t init_gen = 1; /* 1 call to coro_defer() */ @@ -144,7 +142,7 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, } } else { graceful_close(lwan, conn, request_buffer); - goto out; + break; } lwan_strbuf_reset(&strbuf); @@ -153,7 +151,6 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, flags = request.flags & (REQUEST_PROXIED | REQUEST_ALLOW_CORS); } -out: coro_yield(coro, CONN_CORO_ABORT); __builtin_unreachable(); } From 4064949b6233fc4ce727ec8cbef3d8a88bd80467 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 21 Jan 2020 08:30:21 -0800 Subject: [PATCH 1421/2505] Log "Pending client file descr..." as a debug message rather than info --- src/lib/lwan-thread.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 84fc3e105..483ce5139 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -647,7 +647,7 @@ void lwan_thread_init(struct lwan *l) const size_t n_queue_fds = LWAN_MIN(l->thread.max_fd / l->thread.count, (size_t)(2 * lwan_socket_get_backlog_size())); - lwan_status_info("Pending client file descriptor queue has %zu items", n_queue_fds); + lwan_status_debug("Pending client file descriptor queue has %zu items", n_queue_fds); for (short i = 0; i < l->thread.count; i++) create_thread(l, &l->thread.threads[i], n_queue_fds); From 1f5c3e3b5b02757f1d3a9a41c2eedfc5327a28cc Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 20 Jan 2020 13:40:16 -0800 Subject: [PATCH 1422/2505] Remove fd_watcher With the async await stuff, this is not needed anymore. This simplifies the main accept loop: instead of blocking on epoll_wait(), it now blocks on accept4() -- which becomes non-blocking while a herd is passing, to maintain the behavior of nudging the worker threads only when the herd is gone. Without a coroutine to handle the incoming connections, the main loop should be a tiny wee little bit more efficient now. --- src/lib/lwan-private.h | 7 --- src/lib/lwan.c | 123 +++++++---------------------------------- src/lib/lwan.h | 6 -- 3 files changed, 20 insertions(+), 116 deletions(-) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 27c0ec320..d68bcd547 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -41,13 +41,6 @@ int lwan_socket_get_backlog_size(void); -struct lwan_fd_watch *lwan_watch_fd(struct lwan *l, - int fd, - uint32_t events, - coro_function_t coro_fn, - void *data); -void lwan_unwatch_fd(struct lwan *l, struct lwan_fd_watch *w); - void lwan_set_thread_name(const char *name); void lwan_response_init(struct lwan *l); diff --git a/src/lib/lwan.c b/src/lib/lwan.c index b183290ad..6df1c2be6 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -30,7 +31,6 @@ #include #include #include -#include #include #include @@ -521,18 +521,6 @@ static char *dup_or_null(const char *s) return s ? strdup(s) : NULL; } -static void lwan_fd_watch_init(struct lwan *l) -{ - l->epfd = epoll_create1(EPOLL_CLOEXEC); - if (l->epfd < 0) - lwan_status_critical_perror("epoll_create1"); -} - -static void lwan_fd_watch_shutdown(struct lwan *l) -{ - close(l->epfd); -} - void lwan_init_with_config(struct lwan *l, const struct lwan_config *config) { /* Load defaults */ @@ -591,7 +579,6 @@ void lwan_init_with_config(struct lwan *l, const struct lwan_config *config) lwan_thread_init(l); lwan_socket_init(l); lwan_http_authorize_init(); - lwan_fd_watch_init(l); } void lwan_shutdown(struct lwan *l) @@ -615,7 +602,6 @@ void lwan_shutdown(struct lwan *l) lwan_status_shutdown(l); lwan_http_authorize_shutdown(); lwan_readahead_shutdown(); - lwan_fd_watch_shutdown(l); } static ALWAYS_INLINE int schedule_client(struct lwan *l, int fd) @@ -682,78 +668,9 @@ accept_one(struct lwan *l, struct core_bitmap *cores) } } -static int accept_connection_coro(struct coro *coro, void *data) -{ - struct lwan *l = data; - struct core_bitmap cores = {}; - - while (coro_yield(coro, 1) & ~(EPOLLHUP | EPOLLRDHUP | EPOLLERR)) { - enum herd_accept ha; - - do { - ha = accept_one(l, &cores); - } while (ha == HERD_MORE); - - if (UNLIKELY(ha > HERD_MORE)) - break; - - for (size_t i = 0; i < N_ELEMENTS(cores.bitmap); i++) { - for (uint64_t c = cores.bitmap[i]; c; c ^= c & -c) { - size_t core = (size_t)__builtin_ctzl(c); - lwan_thread_nudge(&l->thread.threads[i * 64 + core]); - } - } - memset(&cores, 0, sizeof(cores)); - } - - return 0; -} - -struct lwan_fd_watch *lwan_watch_fd(struct lwan *l, - int fd, - uint32_t events, - coro_function_t coro_fn, - void *data) -{ - struct lwan_fd_watch *watch; - - watch = malloc(sizeof(*watch)); - if (!watch) - return NULL; - - watch->coro = coro_new(&l->switcher, coro_fn, data); - if (!watch->coro) - goto out; - - struct epoll_event ev = {.events = events, .data.ptr = watch->coro}; - if (epoll_ctl(l->epfd, EPOLL_CTL_ADD, fd, &ev) < 0) { - coro_free(watch->coro); - goto out; - } - - watch->fd = fd; - return watch; - -out: - free(watch); - return NULL; -} - -void lwan_unwatch_fd(struct lwan *l, struct lwan_fd_watch *w) -{ - if (l->main_socket != w->fd) { - if (epoll_ctl(l->epfd, EPOLL_CTL_DEL, w->fd, NULL) < 0) - lwan_status_perror("Could not unwatch fd %d", w->fd); - } - - coro_free(w->coro); - free(w); -} - void lwan_main_loop(struct lwan *l) { - struct epoll_event evs[16]; - struct lwan_fd_watch *watch; + struct core_bitmap cores = {}; assert(main_socket == -1); main_socket = l->main_socket; @@ -761,32 +678,32 @@ void lwan_main_loop(struct lwan *l) if (signal(SIGINT, sigint_handler) == SIG_ERR) lwan_status_critical("Could not set signal handler"); - watch = lwan_watch_fd(l, l->main_socket, EPOLLIN | EPOLLHUP | EPOLLRDHUP, - accept_connection_coro, l); - if (!watch) - lwan_status_critical("Could not watch main socket"); - lwan_status_info("Ready to serve"); while (true) { - int n_evs = epoll_wait(l->epfd, evs, N_ELEMENTS(evs), -1); + enum herd_accept ha; - if (UNLIKELY(n_evs < 0)) { - if (main_socket < 0) - break; - if (errno == EINTR || errno == EAGAIN) - continue; - lwan_status_perror("epoll_wait"); - break; + fcntl(l->main_socket, F_SETFL, 0); + ha = accept_one(l, &cores); + if (ha == HERD_MORE) { + fcntl(l->main_socket, F_SETFL, O_NONBLOCK); + + do { + ha = accept_one(l, &cores); + } while (ha == HERD_MORE); } - for (int i = 0; i < n_evs; i++) { - if (!coro_resume_value(evs[i].data.ptr, (int)evs[i].events)) - break; + if (UNLIKELY(ha > HERD_MORE)) + break; + + for (size_t i = 0; i < N_ELEMENTS(cores.bitmap); i++) { + for (uint64_t c = cores.bitmap[i]; c; c ^= c & -c) { + size_t core = (size_t)__builtin_ctzl(c); + lwan_thread_nudge(&l->thread.threads[i * 64 + core]); + } } + cores = (struct core_bitmap){}; } - - lwan_unwatch_fd(l, watch); } #ifdef CLOCK_MONOTONIC_COARSE diff --git a/src/lib/lwan.h b/src/lib/lwan.h index a9c1352c0..8e9d37b07 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -439,11 +439,6 @@ struct lwan_config { bool allow_post_temp_file; }; -struct lwan_fd_watch { - struct coro *coro; - int fd; -}; - struct lwan { struct lwan_trie url_map_trie; struct lwan_connection *conns; @@ -458,7 +453,6 @@ struct lwan { struct lwan_config config; struct coro_switcher switcher; int main_socket; - int epfd; unsigned short n_cpus; }; From bd5212aafe53862d55a17501de8dbd43447252f7 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 20 Jan 2020 11:14:29 -0800 Subject: [PATCH 1423/2505] Expand coroutine yield value to 64-bit This gives more space to yield pointers and other things that can be used to implement async/await. --- src/lib/lwan-coro.c | 8 ++++---- src/lib/lwan-coro.h | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 29cee4150..fa95811d3 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -89,7 +89,7 @@ struct coro { coro_context context; struct coro_defer_array defer; - int yield_value; + int64_t yield_value; struct { /* This allocator is instrumented on debug builds using asan and/or valgrind, if @@ -307,7 +307,7 @@ coro_new(struct coro_switcher *switcher, coro_function_t function, void *data) return coro; } -ALWAYS_INLINE int coro_resume(struct coro *coro) +ALWAYS_INLINE int64_t coro_resume(struct coro *coro) { assert(coro); @@ -322,7 +322,7 @@ ALWAYS_INLINE int coro_resume(struct coro *coro) return coro->yield_value; } -ALWAYS_INLINE int coro_resume_value(struct coro *coro, int value) +ALWAYS_INLINE int64_t coro_resume_value(struct coro *coro, int64_t value) { assert(coro); @@ -330,7 +330,7 @@ ALWAYS_INLINE int coro_resume_value(struct coro *coro, int value) return coro_resume(coro); } -inline int coro_yield(struct coro *coro, int value) +inline int64_t coro_yield(struct coro *coro, int64_t value) { assert(coro); diff --git a/src/lib/lwan-coro.h b/src/lib/lwan-coro.h index 2dd8cc294..b3cbe4286 100644 --- a/src/lib/lwan-coro.h +++ b/src/lib/lwan-coro.h @@ -21,11 +21,11 @@ #pragma once #include -#if defined(__x86_64__) #include + +#if defined(__x86_64__) typedef uintptr_t coro_context[10]; #elif defined(__i386__) -#include typedef uintptr_t coro_context[7]; #else #include @@ -46,9 +46,9 @@ void coro_free(struct coro *coro); void coro_reset(struct coro *coro, coro_function_t func, void *data); -int coro_resume(struct coro *coro); -int coro_resume_value(struct coro *coro, int value); -int coro_yield(struct coro *coro, int value); +int64_t coro_resume(struct coro *coro); +int64_t coro_resume_value(struct coro *coro, int64_t value); +int64_t coro_yield(struct coro *coro, int64_t value); void coro_defer(struct coro *coro, void (*func)(void *data), void *data); void coro_defer2(struct coro *coro, From 3ea69923283979b2da3c010f3d967e208a1265e6 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 20 Jan 2020 12:34:21 -0800 Subject: [PATCH 1424/2505] Allow a connection coroutine to wait on other file descriptors This sets up epoll to wait on an arbitrary file descriptor using EPOLLONESHOT; useful, for instance, if implementing asynchronous database access using the MariaDB client libraries in non-blocking mode. A sample program that proxies a TCP connection (and sends back the bytes received as chunks) to 127.0.0.1:6969 is provided to demonstrate that this works. --- src/lib/liblwan.sym | 12 ++-- src/lib/lwan-request.c | 83 +++++++++++++++++++++++++++ src/lib/lwan-thread.c | 80 +++++++++++++++++++++----- src/lib/lwan.h | 46 ++++++++++++--- src/samples/CMakeLists.txt | 1 + src/samples/asyncawait/CMakeLists.txt | 8 +++ src/samples/asyncawait/main.c | 76 ++++++++++++++++++++++++ 7 files changed, 275 insertions(+), 31 deletions(-) create mode 100644 src/samples/asyncawait/CMakeLists.txt create mode 100644 src/samples/asyncawait/main.c diff --git a/src/lib/liblwan.sym b/src/lib/liblwan.sym index 410cedb65..76f91a77b 100644 --- a/src/lib/liblwan.sym +++ b/src/lib/liblwan.sym @@ -9,9 +9,7 @@ global: lwan_get_config_path; lwan_get_default_config; - lwan_http_status_as_descriptive_string; - lwan_http_status_as_string; - lwan_http_status_as_string_with_code; + lwan_http_status_as_*; lwan_init; lwan_init_with_config; @@ -22,10 +20,7 @@ global: lwan_main_loop; - lwan_request_get_cookie; - lwan_request_get_post_param; - lwan_request_get_query_param; - lwan_request_get_remote_address; + lwan_request_get_*; lwan_request_sleep; lwan_response; @@ -83,6 +78,9 @@ global: lwan_module_info_*; + lwan_request_await_*; + lwan_request_async_*; + local: *; }; diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 16dedb71c..db2f1db8a 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1747,3 +1747,86 @@ __attribute__((used)) int fuzz_parse_http_request(const uint8_t *data, return 0; } #endif + +static inline int64_t +make_async_yield_value(int fd, enum lwan_connection_coro_yield event) +{ + return (int64_t)(((uint64_t)fd << 32 | event)); +} + +static inline void async_await_fd(struct coro *coro, + int fd, + enum lwan_connection_coro_yield events) +{ + assert(events >= CONN_CORO_ASYNC_AWAIT_READ && + events <= CONN_CORO_ASYNC_RESUME); + + coro_yield(coro, make_async_yield_value(fd, events)); + + /* FIXME: it should be possible to remove this second yield */ + return (void)coro_yield(coro, + make_async_yield_value(fd, CONN_CORO_ASYNC_RESUME)); +} + +void lwan_request_await_read(struct lwan_request *r, int fd) +{ + return async_await_fd(r->conn->coro, fd, CONN_CORO_ASYNC_AWAIT_READ); +} + +void lwan_request_await_write(struct lwan_request *r, int fd) +{ + return async_await_fd(r->conn->coro, fd, CONN_CORO_ASYNC_AWAIT_WRITE); +} + +void lwan_request_await_read_write(struct lwan_request *r, int fd) +{ + return async_await_fd(r->conn->coro, fd, CONN_CORO_ASYNC_AWAIT_READ_WRITE); +} + +ssize_t lwan_request_async_read(struct lwan_request *request, + int fd, + void *buf, + size_t len) +{ +await: + lwan_request_await_read(request, fd); + + while (true) { + ssize_t r = read(fd, buf, len); + + if (r < 0) { + switch (errno) { + case EINTR: + continue; + case EWOULDBLOCK: + goto await; + } + } + + return r; + } +} + +ssize_t lwan_request_async_write(struct lwan_request *request, + int fd, + const void *buf, + size_t len) +{ +await: + lwan_request_await_write(request, fd); + + while (true) { + ssize_t r = write(fd, buf, len); + + if (r < 0) { + switch (errno) { + case EINTR: + continue; + case EWOULDBLOCK: + goto await; + } + } + + return r; + } +} diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 483ce5139..c4fe6080f 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -161,23 +161,26 @@ static ALWAYS_INLINE uint32_t conn_flags_to_epoll_events(enum lwan_connection_flags flags) { static const uint32_t map[CONN_EVENTS_MASK + 1] = { - [0 /* Suspended by timer */] = EPOLLRDHUP, + [0 /* Suspended (timer or await) */] = EPOLLRDHUP, [CONN_EVENTS_WRITE] = EPOLLOUT | EPOLLRDHUP, [CONN_EVENTS_READ] = EPOLLIN | EPOLLRDHUP, [CONN_EVENTS_READ_WRITE] = EPOLLIN | EPOLLOUT | EPOLLRDHUP, + [CONN_EVENTS_ASYNC_READ] = EPOLLET | EPOLLIN | EPOLLRDHUP | EPOLLONESHOT, + [CONN_EVENTS_ASYNC_WRITE] = EPOLLET | EPOLLOUT | EPOLLRDHUP | EPOLLONESHOT, + [CONN_EVENTS_ASYNC_READ_WRITE] = EPOLLET | EPOLLIN | EPOLLOUT | EPOLLRDHUP | EPOLLONESHOT, }; return map[flags & CONN_EVENTS_MASK]; } #if defined(__linux__) -# define CONN_EVENTS_RESUME_TIMER CONN_EVENTS_READ_WRITE +# define CONN_EVENTS_RESUME CONN_EVENTS_READ_WRITE #else /* Kqueue doesn't like when you filter on both read and write, so * wait only on write when resuming a coro suspended by a timer. * The I/O wrappers should yield if trying to read without anything * in the buffer, changing the filter to only read, so this is OK. */ -# define CONN_EVENTS_RESUME_TIMER CONN_EVENTS_WRITE +# define CONN_EVENTS_RESUME CONN_EVENTS_WRITE #endif static void update_epoll_flags(int fd, @@ -187,6 +190,7 @@ static void update_epoll_flags(int fd, { static const enum lwan_connection_flags or_mask[CONN_CORO_MAX] = { [CONN_CORO_YIELD] = 0, + [CONN_CORO_WANT_READ_WRITE] = CONN_EVENTS_READ_WRITE, [CONN_CORO_WANT_READ] = CONN_EVENTS_READ, [CONN_CORO_WANT_WRITE] = CONN_EVENTS_WRITE, @@ -196,20 +200,32 @@ static void update_epoll_flags(int fd, * so unset both so that only EPOLLRDHUP (plus the implicitly-set ones) * are set. */ [CONN_CORO_SUSPEND_TIMER] = CONN_SUSPENDED_TIMER, + [CONN_CORO_SUSPEND_ASYNC_AWAIT] = CONN_SUSPENDED_ASYNC_AWAIT, /* Either EPOLLIN or EPOLLOUT have to be set here. There's no need to * know which event, because they were both cleared when the coro was * suspended. So set both flags here. This works because EPOLLET isn't * used. */ - [CONN_CORO_RESUME_TIMER] = CONN_EVENTS_RESUME_TIMER, + [CONN_CORO_RESUME] = CONN_EVENTS_RESUME, + + [CONN_CORO_ASYNC_AWAIT_READ] = CONN_EVENTS_ASYNC_READ, + [CONN_CORO_ASYNC_AWAIT_WRITE] = CONN_EVENTS_ASYNC_WRITE, + [CONN_CORO_ASYNC_AWAIT_READ_WRITE] = CONN_EVENTS_ASYNC_READ_WRITE, }; static const enum lwan_connection_flags and_mask[CONN_CORO_MAX] = { [CONN_CORO_YIELD] = ~0, + [CONN_CORO_WANT_READ_WRITE] = ~0, [CONN_CORO_WANT_READ] = ~CONN_EVENTS_WRITE, [CONN_CORO_WANT_WRITE] = ~CONN_EVENTS_READ, - [CONN_CORO_SUSPEND_TIMER] = ~CONN_EVENTS_READ_WRITE, - [CONN_CORO_RESUME_TIMER] = ~CONN_SUSPENDED_TIMER, + + [CONN_CORO_SUSPEND_TIMER] = ~(CONN_EVENTS_READ_WRITE | CONN_SUSPENDED_ASYNC_AWAIT), + [CONN_CORO_SUSPEND_ASYNC_AWAIT] = ~(CONN_EVENTS_READ_WRITE | CONN_SUSPENDED_TIMER), + [CONN_CORO_RESUME] = ~CONN_SUSPENDED, + + [CONN_CORO_ASYNC_AWAIT_READ] = ~0, + [CONN_CORO_ASYNC_AWAIT_WRITE] = ~0, + [CONN_CORO_ASYNC_AWAIT_READ_WRITE] = ~0, }; enum lwan_connection_flags prev_flags = conn->flags; @@ -228,19 +244,53 @@ static void update_epoll_flags(int fd, lwan_status_perror("epoll_ctl"); } -static ALWAYS_INLINE void -resume_coro(struct timeout_queue *tq, struct lwan_connection *conn, int epoll_fd) +static ALWAYS_INLINE void resume_coro(struct timeout_queue *tq, + struct lwan_connection *conn, + int epoll_fd) { assert(conn->coro); - enum lwan_connection_coro_yield yield_result = coro_resume(conn->coro); - if (yield_result == CONN_CORO_ABORT) { - timeout_queue_expire(tq, conn); - return; + int64_t from_coro = coro_resume(conn->coro); + enum lwan_connection_coro_yield yield_result = from_coro & 0xffffffff; + + if (UNLIKELY(yield_result == CONN_CORO_ABORT)) + return timeout_queue_expire(tq, conn); + + if (UNLIKELY(yield_result >= CONN_CORO_ASYNC)) { + assert(yield_result >= CONN_CORO_ASYNC_AWAIT_READ && + yield_result <= CONN_CORO_ASYNC_RESUME); + + static const enum lwan_connection_flags to_connection_flags[] = { + [CONN_CORO_ASYNC_AWAIT_READ] = CONN_EVENTS_ASYNC_READ, + [CONN_CORO_ASYNC_AWAIT_WRITE] = CONN_EVENTS_ASYNC_WRITE, + [CONN_CORO_ASYNC_AWAIT_READ_WRITE] = CONN_EVENTS_ASYNC_READ_WRITE, + [CONN_CORO_ASYNC_RESUME] = 0, + }; + struct epoll_event event = { + .events = + conn_flags_to_epoll_events(to_connection_flags[yield_result]), + .data.ptr = conn, + }; + int await_fd = (int)((uint64_t)from_coro >> 32); + int op; + + assert(event.events != 0); + assert(await_fd >= 0); + + if (yield_result == CONN_CORO_ASYNC_RESUME) { + op = EPOLL_CTL_DEL; + yield_result = CONN_CORO_RESUME; + } else { + op = EPOLL_CTL_ADD; + yield_result = CONN_CORO_SUSPEND_ASYNC_AWAIT; + } + + if (UNLIKELY(epoll_ctl(epoll_fd, op, await_fd, &event) < 0)) + lwan_status_perror("epoll_ctl"); } - update_epoll_flags(lwan_connection_get_fd(tq->lwan, conn), conn, epoll_fd, - yield_result); + return update_epoll_flags(lwan_connection_get_fd(tq->lwan, conn), conn, + epoll_fd, yield_result); } static void update_date_cache(struct lwan_thread *thread) @@ -326,7 +376,7 @@ static bool process_pending_timers(struct timeout_queue *tq, request = container_of(timeout, struct lwan_request, timeout); update_epoll_flags(request->fd, request->conn, epoll_fd, - CONN_CORO_RESUME_TIMER); + CONN_CORO_RESUME); } if (should_expire_timers) { diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 8e9d37b07..e0a7d81d3 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -262,30 +262,52 @@ enum lwan_connection_flags { * them to epoll events is smaller. See conn_flags_to_epoll_events(). */ CONN_EVENTS_READ = 1 << 0, CONN_EVENTS_WRITE = 1 << 1, - CONN_EVENTS_READ_WRITE = 1 << 0 | 1 << 1, - CONN_EVENTS_MASK = 1 << 0 | 1 << 1, + CONN_EVENTS_READ_WRITE = CONN_EVENTS_READ | CONN_EVENTS_WRITE, + CONN_EVENTS_ASYNC = 1 << 2, + CONN_EVENTS_ASYNC_READ = CONN_EVENTS_ASYNC | CONN_EVENTS_READ, + CONN_EVENTS_ASYNC_WRITE = CONN_EVENTS_ASYNC | CONN_EVENTS_WRITE, + CONN_EVENTS_ASYNC_READ_WRITE = CONN_EVENTS_ASYNC | CONN_EVENTS_READ_WRITE, + CONN_EVENTS_MASK = 1 << 0 | 1 << 1 | 1 << 2, - CONN_IS_KEEP_ALIVE = 1 << 2, - CONN_IS_UPGRADE = 1 << 3, - CONN_IS_WEBSOCKET = 1 << 4, + CONN_IS_KEEP_ALIVE = 1 << 3, + CONN_IS_UPGRADE = 1 << 4, + CONN_IS_WEBSOCKET = 1 << 5, /* This is only used to determine if timeout_del() is necessary when * the connection coro ends. */ - CONN_SUSPENDED_TIMER = 1 << 5, - CONN_HAS_REMOVE_SLEEP_DEFER = 1 << 6, + CONN_SUSPENDED_TIMER = 1 << 6, + CONN_HAS_REMOVE_SLEEP_DEFER = 1 << 7, - CONN_CORK = 1 << 7, + CONN_SUSPENDED_ASYNC_AWAIT = 1 << 8, + + CONN_SUSPENDED = CONN_SUSPENDED_TIMER | CONN_SUSPENDED_ASYNC_AWAIT, + + CONN_CORK = 1 << 9, }; enum lwan_connection_coro_yield { CONN_CORO_ABORT, + CONN_CORO_YIELD, + CONN_CORO_WANT_READ, CONN_CORO_WANT_WRITE, CONN_CORO_WANT_READ_WRITE, + CONN_CORO_SUSPEND_TIMER, - CONN_CORO_RESUME_TIMER, + CONN_CORO_SUSPEND_ASYNC_AWAIT, + CONN_CORO_RESUME, + + /* Group async stuff together to make it easier to check if a connection + * coroutine is yielding because of async reasons. */ + CONN_CORO_ASYNC_AWAIT_READ, + CONN_CORO_ASYNC_AWAIT_WRITE, + CONN_CORO_ASYNC_AWAIT_READ_WRITE, + CONN_CORO_ASYNC_RESUME, + CONN_CORO_MAX, + + CONN_CORO_ASYNC = CONN_CORO_ASYNC_AWAIT_READ, }; struct lwan_key_value { @@ -551,6 +573,12 @@ lwan_request_get_accept_encoding(struct lwan_request *request); enum lwan_http_status lwan_request_websocket_upgrade(struct lwan_request *request); +void lwan_request_await_read(struct lwan_request *r, int fd); +void lwan_request_await_write(struct lwan_request *r, int fd); +void lwan_request_await_read_write(struct lwan_request *r, int fd); +ssize_t lwan_request_async_read(struct lwan_request *r, int fd, void *buf, size_t len); +ssize_t lwan_request_async_write(struct lwan_request *r, int fd, const void *buf, size_t len); + #if defined(__cplusplus) } #endif diff --git a/src/samples/CMakeLists.txt b/src/samples/CMakeLists.txt index 3735043df..2a4a69766 100644 --- a/src/samples/CMakeLists.txt +++ b/src/samples/CMakeLists.txt @@ -4,6 +4,7 @@ if (NOT ${CMAKE_BUILD_TYPE} MATCHES "Coverage") add_subdirectory(hello-no-meta) add_subdirectory(clock) add_subdirectory(websocket) + add_subdirectory(asyncawait) endif() add_subdirectory(techempower) diff --git a/src/samples/asyncawait/CMakeLists.txt b/src/samples/asyncawait/CMakeLists.txt new file mode 100644 index 000000000..8f77fbac4 --- /dev/null +++ b/src/samples/asyncawait/CMakeLists.txt @@ -0,0 +1,8 @@ +add_executable(asyncawait + main.c +) + +target_link_libraries(asyncawait + ${LWAN_COMMON_LIBS} + ${ADDITIONAL_LIBRARIES} +) diff --git a/src/samples/asyncawait/main.c b/src/samples/asyncawait/main.c new file mode 100644 index 000000000..41605dce7 --- /dev/null +++ b/src/samples/asyncawait/main.c @@ -0,0 +1,76 @@ +/* + * lwan - simple web server + * Copyright (c) 2020 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include +#include +#include +#include +#include +#include + +#include "lwan.h" + +static void close_socket(void *data) { close((int)(intptr_t)data); } + +LWAN_HANDLER(asyncawait) +{ + int fd; + struct sockaddr_in addr; + + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) + return HTTP_INTERNAL_ERROR; + + coro_defer(request->conn->coro, close_socket, (void *)(intptr_t)fd); + + addr = (struct sockaddr_in){.sin_family = AF_INET, + .sin_addr.s_addr = inet_addr("127.0.0.1"), + .sin_port = htons(6969)}; + if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) + return HTTP_UNAVAILABLE; + + while (true) { + char buffer[128]; + ssize_t r = lwan_request_async_read(request, fd, buffer, sizeof(buffer)); + + lwan_strbuf_set_static(response->buffer, buffer, (size_t)r); + lwan_response_send_chunk(request); + } + + return HTTP_OK; +} + +int main(void) +{ + const struct lwan_url_map default_map[] = { + {.prefix = "/", .handler = LWAN_HANDLER_REF(asyncawait)}, + {}, + }; + struct lwan l; + + lwan_init(&l); + + lwan_set_url_map(&l, default_map); + lwan_main_loop(&l); + + lwan_shutdown(&l); + + return 0; +} From 23a65aa7e227760190a48f6bb8dcd0bd90e8ed39 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 26 Jan 2020 18:50:41 -0800 Subject: [PATCH 1425/2505] Set TCP_DEFER_ACCEPT in the main socket Since the main loop now blocks on accept4() again, set the TCP_DEFER_ACCEPT flag in an attempt to reduce latency. --- src/lib/lwan-socket.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/lwan-socket.c b/src/lib/lwan-socket.c index cc5f49572..14e8cec4c 100644 --- a/src/lib/lwan-socket.c +++ b/src/lib/lwan-socket.c @@ -264,6 +264,8 @@ void lwan_socket_init(struct lwan *l) SET_SOCKET_OPTION_MAY_FAIL(SOL_TCP, TCP_FASTOPEN, (int[]){5}); SET_SOCKET_OPTION_MAY_FAIL(SOL_TCP, TCP_QUICKACK, (int[]){0}); + SET_SOCKET_OPTION_MAY_FAIL(SOL_TCP, TCP_DEFER_ACCEPT, + (int[]){l->config.keep_alive_timeout}); #endif l->main_socket = fd; From 719e3cd8cb65b4f232d0d28cdaf90014561bb425 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 15 Feb 2020 07:27:56 -0800 Subject: [PATCH 1426/2505] Add preliminary SignalR Chat demo Inspired by demo by David Fowler and Damian Edwards in this presentation: https://www.youtube.com/watch?v=iL9nLAjCPtM This is not yet hooked up to the build system, and I don't even know if this builds. There are lots of issues with the WebSockets API in Lwan that I need to fix before this can become a nice demo. --- src/samples/chatr/CMakeLists.txt | 9 + src/samples/chatr/main.c | 520 +++++++++++++++++++++++++++++++ 2 files changed, 529 insertions(+) create mode 100644 src/samples/chatr/CMakeLists.txt create mode 100644 src/samples/chatr/main.c diff --git a/src/samples/chatr/CMakeLists.txt b/src/samples/chatr/CMakeLists.txt new file mode 100644 index 000000000..c0b9823a9 --- /dev/null +++ b/src/samples/chatr/CMakeLists.txt @@ -0,0 +1,9 @@ +add_executable(chatr + main.c + ../techempower/json.c +) + +target_link_libraries(chatr + ${LWAN_COMMON_LIBS} + ${ADDITIONAL_LIBRARIES} +) diff --git a/src/samples/chatr/main.c b/src/samples/chatr/main.c new file mode 100644 index 000000000..1e271c017 --- /dev/null +++ b/src/samples/chatr/main.c @@ -0,0 +1,520 @@ +/* + * lwan - simple web server + * Copyright (c) 2020 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include +#include + +#include "lwan.h" +#include "hash.h" +#include "ringbuffer.h" +#include "../techempower/json.h" + +struct sync_map { + struct hash *table; + pthread_mutex_t mutex; +}; + +#define SYNC_MAP_INITIALIZER(free_key_func_, free_value_func_) \ + (struct sync_map) \ + { \ + .mutex = PTHREAD_MUTEX_INITIALIZER, \ + .table = hash_str_new(free_key_func_, free_value_func_) \ + } + +DEFINE_RING_BUFFER_TYPE(msg_ring_buffer, char *, 32) +struct msg_ring { + struct msg_ring_buffer rb; + pthread_mutex_t mutex; +}; + +#define MSG_RING_INITIALIZER \ + (struct msg_ring) { .mutex = PTHREAD_MUTEX_INITIALIZER } + +struct available_transport { + const char *transport; + const char *transferFormats[4]; + size_t numTransferFormats; +}; +static const struct json_obj_descr available_transport_descr[] = { + JSON_OBJECT_DESCR_PRIM(struct available_transport, transport, JSON_TOK_STRING), + JSON_OBJECT_DESCR_ARRAY(struct available_transport, + transferFormats, + 1, + numTransferFormats, + JSON_TOK_STRING), +}; + +struct negotiate_response { + const char *connectionId; + struct available_transports availableTransports[4]; + size_t numAvailableTransports; +}; +static const struct json_obj_descr negotiate_response_descr[] = { + JSON_OBJ_DESCR_PRIM(struct negotiate_response, connectionId, JSON_TOK_STRING), + JSON_OBJ_DESCR_OBJ_ARRAY(struct negotiate_response, + availableTransports, + 4, + numAvailableTransports, + available_transport_descr, + ARRAY_SIZE(available_transport_descr)), +}; + +struct handshake_request { + const char *protocol; + int version; +}; +static const struct json_obj_descr handshake_request_descr[] = { + JSON_OBJ_DESCR_PRIM(struct handshake_request, protocol, JSON_TOK_STRING), + JSON_OBJ_DESCR_PRIM(struct handshake_request, versoin, JSON_TOK_NUMBER), +}; + +struct message { + int type; +}; +static const struct json_obj_descr message_descr[] = { + JSON_OBJ_DESCR_PRIM(struct message, type, JSON_TOK_NUMBER), +}; + +struct invocation_message { + int type; + const char *target; + const char *invocationId; + const char *arguments[10]; + size_t numArguments; +}; +static const struct json_obj_descr invocation_message[] = { + JSON_OBJ_DESCR_PRIM(struct invocation_message, target, JSON_TOK_STRING), + JSON_OBJ_DESCR_PRIM(struct invocation_message, invocationId, JSON_TOK_STRING), + JSON_OBJ_DESCR_ARRAY(struct invocation_message, + arguments, + 10, + numArguments, + JSON_TOK_STRING), + JSON_OBJ_DESCR_PRIM(struct invocation_message, type, JSON_TOK_NUMBER), +}; + +struct completion_message { + int type; + const char *invocationId; + const char *result; + const char *error; +}; +static const struct json_obj_descr invocation_message[] = { + JSON_OBJ_DESCR_PRIM(struct completion_message, type, JSON_TOK_NUMBER), + JSON_OBJ_DESCR_PRIM(struct completion_message, invocationId, JSON_TOK_STRING), + JSON_OBJ_DESCR_PRIM(struct completion_message, result, JSON_TOK_STRING), + JSON_OBJ_DESCR_PRIM(struct completion_message, error, JSON_TOK_STRING), +}; + +static bool msg_ring_try_put(struct msg_ring *ring, const char *msg) +{ + /* FIXME: find a way to use coro_strdup() here? should make cleanup easier */ + char *copy = strdup(msg); + bool ret; + + pthread_mutex_lock(&sync_map->mutex); + ret = msg_ring_buffer_try_put(&ring->rb, copy); + if (!ret) + free(copy); + pthread_mutex_unlock(&sync_map->mutex); + + return ret; +} + +static void msg_ring_consume(struct msg_ring *ring, + bool (*iter_func)(char *msg, void *data), + void *data) +{ + char *msg; + + pthread_mutex_lock(&ring->mutex); + + while ((msg = msg_ring_buffer_get_ptr_or_null(&ring->rb))) { + bool cont = iter(msg, data); + + free(msg); + if (!cont) + break; + } + + pthread_mutex_unlock(&ring->mutex); +} + +static bool free_ring_msg(char *msg, void *data) +{ + free(msg); + return true; +} + +static void msg_ring_free(struct msg_ring *ring) +{ + msg_ring_consume(ring, free_ring_msg, NULL); + pthread_mutex_destroy(&ring->mutex); +} + +static int sync_map_add(struct sync_map *sync_map, const char *key, const void *value) +{ + int ret; + + pthread_mutex_lock(&sync_map->mutex); + ret = hash_add(sync_map->table, key, value); + pthread_mutex_unlock(&sync_map->mutex); + + return ret; +} + +static const void *sync_map_find(struct sync_map *sync_map, const char *key) +{ + void *ret; + + pthread_mutex_lock(&sync_map->mutex); + ret = hash_find(sync_map->table, key); + pthread_mutex_unlock(&sync_map->mutex); + + return ret; +} + +static void +sync_map_range(struct sync_map *sync_map, + bool (*iter_func)(const char *key, void *value, void *data), + void *data) +{ + struct hash_iter iter; + const void *key; + void *value; + + pthread_mutex_lock(&sync_map->mutex); + hash_iter_init(&sync_map->table, &iter); + while (hash_iter_next(&iter, &key, &value)) { + if (!iter_func(key, value, data)) + break; + } + pthread_mutex_unlock(&sync_map->mutex); +} + +static const char *get_connection_id(char connection_id[static 17]) +{ + /* FIXME: use a better PRNG */ + /* FIXME: maybe base64? */ + static const char alphabet[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwvyz01234567890"; + + for (int i = 0; i < 16; i++) + connection_id[i] = alphabet[rand() % (sizeof(alphabet) - 1)]; + + connection_id[16] = '\0'; + + return connection_id; +} + +static int append_to_strbuf(const char *bytes, size_t len, void *data) +{ + struct lwan_strbuf *strbuf = data; + + return !lwan_strbuf_append_str(strbuf, bytes, len); +} + +LWAN_HANDLER(negotiate) +{ + char connection_id[17]; + + if (lwan_request_get_method(request) != REQUEST_METHOD_POST) + return HTTP_BAD_REQUEST; + + struct negotiate_response response = { + .connectionId = get_connection_id(connection_id), + .availableTransports = + (struct available_transport[]){ + { + .transport : "WebSockets", + .transferFormats : (const char *[]){"Text", "Binary"}, + .numTransferFormats : 2, + }, + }, + .numAvailableTransports = 1, + }; + + if (json_obj_encode_full(negotiate_response_descr, + ARRAY_SIZE(negotiate_response_descr), &response, + append_to_strbuf, response->buffer, false) != 0) + return HTTP_INTERNAL_ERROR; + + response->mime_type = "application/json"; + return HTTP_OK; +} + +static bool trim_record_separator(struct lwan_strbuf *buf) +{ + char *separator = + memrchr(lwan_strbuf_get_buffer(buf), 0x1e, lwan_strbuf_get_length(buf)); + + if (separator) { + buf->used = separator - buf->buffer; + *separator = '\0'; + return true; + } + + return false; +} + +static int parse_json(struct lwan_response *response, + const struct json_obj_descr *descr, + size_t descr_len, + void *data) +{ + if (!trim_record_separator(response->buffer)) + return -EINVAL; + + return json_obj_parse(lwan_strbuf_get_buffer(response->buffer), + lwan_strbuf_get_len(response->buffer), descr, + descr_len, data); +} + +static int send_json(struct lwan_response *request, + const struct json_obj_descr *descr, + size_t descr_len, + void *data) +{ + int ret = json_obj_encode_full(descr, descr_len, data, append_to_strbuf, + request->response->buffer, false); + if (ret == 0) { + lwan_strbuf_append_char(request->response->buffer, '\x1e'); + lwan_response_websocket_send(request); + } + + return ret; +} + +static bool send_error(struct lwan_request *request, const char *error) +{ + lwan_strbuf_set_printf(request->response->buffer, + "{\"error\":\"%s\"}\u001e", error); + lwan_response_websocket_send(request); + return false; +} + +static bool process_handshake(struct lwan_request *request, + struct lwan_response *response) +{ + if (!lwan_response_websocket_read(request)) + return false; + + struct handshake_request handshake; + int ret = parse_json(response, handshake_request_descr, + ARRAY_SIZE(handshake_request_descr), &handshake); + if (ret < 0) + return send_error(request, "Could not parse handshake JSON"); + if (!(ret & 1 << 0)) + return send_error(request, "Protocol not specified"); + if (!(ret & 1 << 1)) + return send_error(request, "Version not specified"); + + if (handshake.version != 0) + return send_error(request, "Only version 0 is supported"); + if (!streq(handshake.protocol, "json")) + return send_error(request, "Only `json' protocol supported"); + + lwan_strbuf_set_static(response->buffer, "{}\u001e", 3); + lwan_response_websocket_send(request); + + return true; +} + +static void handle_ping(struct lwan_request *request) +{ + lwan_strbuf_set_staticz("{\"type\":6}\u001e"); + lwan_response_websocket_send(request); +} + +static bool broadcast_msg(const void *key, void *value, void *data) +{ + struct msg_ring *messages = value; + + if (message->numArguments == 1) { + msg_ring_append(messages, message->arguments[0]); + return true; + } + + return false; +} + +static struct completion_message +handle_invocation_send(struct lwan_request *request, + struct invocation_message *message, + struct sync_map *clients) +{ + if (message->numArguments == 0) + return (struct completion_message){.error = "No arguments were passed"}; + + sync_map_range(clients, broadcast_msg, NULL); + + return (struct completion_message){ + .result = coro_printf(request->conn->coro, + "Got your message with %d arguments", + message->numArguments), + }; +} + +static bool send_completion_response(struct lwan_request *request, + const char *invocation_id, + struct completion_message completion) +{ + completion = (struct completion_message){ + .type = 3, + .invocationId = invocation_id, + .error = completion.error, + .result = completion.result, + }; + + return send_json(request, completion_message_descr, + ARRAY_SIZE(completion_message_descr), &completion) == 0; +} + +static bool handle_invocation(struct lwan_request *request, + struct sync_map *clients) +{ + struct invocation_message message; + int ret = parse_json(request->response, invocation_message_descr, + ARRAY_SIZE(invocation_message_descr), &message); + + if (ret < 0) + return send_error(request, "JSON could not be parsed"); + if (!(ret & 1 << 0)) + return send_error(request, "`target' not present or unparsable"); + if (!(ret & 1 << 1)) + return send_error(request, "`invocationId' not present or unparsable"); + if (!(ret & 1 << 2)) + return send_error(request, "`arguments' not present or unparsable"); + + if (streq(message.target, "send")) { + return send_completion_response( + request, message.invocationId, + handle_invocation_send(request, &message, clients)); + } + + return send_error(request, "Unknown target"); +} + +static bool hub_msg_ring_send_message(char *msg, void *data) +{ + struct lwan_request *request = data; + struct invocation_message invocation = { + .type = 1, + .target = "send", + .arguments[0] = msg, + .numArguments = 1, + }; + + return send_json(response, invocation_message_descr, + ARRAY_SIZE(invocation_message_descr), &invocation) == 0; +} + +static enum lwan_http_status +hub_connection_handler(struct lwan_request *request, + struct lwan_response *response, + const char *connection_id, + void *data) +{ + struct sync_map *clients = data; + struct msg_ring msg_ring = MSG_RING_INITIALIZER; + + if (sync_map_add(clients, connection_id, &msg_ring) != 0) { + send_error(request, "Could not register client ID"); + msg_ring_free(messages); + return HTTP_INTERNAL_ERROR; + } + coro_defer2(request->conn->coro, remove_client, clients, connection_id); + coro_defer(request->conn->coro, msg_ring_free, messages); + + while (true) { + /* FIXME: there's no way to specify that we want to either read from + * the websocket *or* get a message in the message ring buffer. */ + if (!lwan_response_websocket_read(request)) { + send_error(request, "Could not read from WebSocket"); + return HTTP_INTERNAL_ERROR; + } + + /* Big FIXMES: + * + * Ideally, messages would be refcounted to avoid duplication of + * messages lingering around. But this is fine for a sample app. + * + * Also, this is in the wrong "layer"; shouldn't be in the hub main + * loop. But the WebSockets API in Lwan isn't that great and lacks + * the facilities to perform this correctly. */ + msg_ring_consume(&msg_ring, hub_msg_ring_send_message, request); + + struct message message; + int ret = parse_json(response, message_descr, ARRAY_SIZE(message_descr), + &message); + if (ret < 0) + continue; + if (!(ret & 1 << 0)) /* `type` not present, ignore */ + continue; + + switch (message.type) { + case 1: + handle_invocation(request, clients); + break; + case 6: + handle_ping(request); + break; + } + } +} + +LWAN_HANDLER(chat) +{ + static const char handshake_response[] = "{}\u001e"; + const char *connection_id; + + if (lwan_request_websocket_upgrade(request) != HTTP_SWITCHING_PROTOCOLS) + return HTTP_BAD_REQUEST; + + connection_id = lwan_request_get_query_param(request, "id"); + if (!connecton_id || *connection_id == '\0') + connection_id = get_connection_id(coro_malloc(request->conn->coro, 17)); + + if (!process_handshake(request, response)) + return HTTP_BAD_REQUEST; + + return hub_connection_handler(request, response, connection_id, data); +} + +int main(void) +{ + struct sync_map clients = SYNC_MAP_INITIALIZER(NULL, NULL); + const struct lwan_url_map default_map[] = { + {.prefix = "/chat", .handler = LWAN_HANDLER_REF(chat), .data = &clients}, + {.prefix = "/chat/negotiate", .handler = LWAN_HANDLER_REF(negotiate)}, + {.prefix = "/", .module = SERVE_FILES("wwwroot")}, + {}, + }; + struct lwan l; + + lwan_init(&l); + + lwan_set_url_map(&l, default_map); + lwan_main_loop(&l); + + lwan_shutdown(&l); + + return 0; +} From 8d022751866f8408611ab1c39a7b5fb233028ffa Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 15 Feb 2020 07:39:13 -0800 Subject: [PATCH 1427/2505] Re-enable Fortunes benchmark after adding techempower.conf --- src/samples/techempower/techempower.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/src/samples/techempower/techempower.conf b/src/samples/techempower/techempower.conf index 981db296d..1d8262e10 100644 --- a/src/samples/techempower/techempower.conf +++ b/src/samples/techempower/techempower.conf @@ -4,6 +4,7 @@ listener *:8080 { &json /json &db /db &queries /queries + &fortunes /fortunes # For Lua version of TWFB benchmarks lua /lua. { From a4422b901386e852e30d9744241baac64c8fa973 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 18 Feb 2020 21:49:16 -0800 Subject: [PATCH 1428/2505] Waste less time while testing the cache infrastructure This cleans up a lot of stuff in testsuite.py that are unrelated to this optimization, but would be difficult to dissociate. Of note: * Setting ${REQUESTS_DEBUG} will enable verbose debugging for the requests module * testrunner.conf, test.lua, techempower.db, techempower.conf, and json.lua are copied from their respective locations to the current working directory before launching the test harness, and deleted afterwards * Old test runners that might have been left running from a previous execution are killed before each test is set up * Test harnesses are tested to see if they're actually executing before making a request to /hello to ensure they're actually up * The status code for the /hello request is now checked to assert it's actually 200 and avoid misconfiguration --- README.md | 1 + test.lua => src/bin/testrunner/test.lua | 0 .../bin/testrunner/testrunner.conf | 2 + src/lib/lwan-mod-serve-files.c | 6 +- src/lib/lwan-mod-serve-files.h | 3 + src/scripts/testsuite.py | 102 +++++++++++++----- 6 files changed, 83 insertions(+), 31 deletions(-) rename test.lua => src/bin/testrunner/test.lua (100%) rename testrunner.conf => src/bin/testrunner/testrunner.conf (98%) diff --git a/README.md b/README.md index 34967a00b..43d48184d 100644 --- a/README.md +++ b/README.md @@ -342,6 +342,7 @@ best to serve files in the fastest way possible according to some heuristics. | `auto_index_readme` | `bool` | `true` | Includes the contents of README files as part of the automatically generated directory index | | `directory_list_template` | `str` | `NULL` | Path to a Mustache template for the directory list; by default, use an internal template | | `read_ahead` | `int` | `131702` | Maximum amount of bytes to read ahead when caching open files. A value of `0` disables readahead. Readahead is performed by a low priority thread to not block the I/O threads while file extents are being read from the filesystem. | +| `cache_for` | `time` | `5s` | Time to keep file metadata (size, compressed contents, open file descriptor, etc.) in cache | #### Lua diff --git a/test.lua b/src/bin/testrunner/test.lua similarity index 100% rename from test.lua rename to src/bin/testrunner/test.lua diff --git a/testrunner.conf b/src/bin/testrunner/testrunner.conf similarity index 98% rename from testrunner.conf rename to src/bin/testrunner/testrunner.conf index 5fc1d7064..63bfc7b1d 100644 --- a/testrunner.conf +++ b/src/bin/testrunner/testrunner.conf @@ -121,5 +121,7 @@ listener *:8080 { # and serve that instead if `Accept-Encoding: gzip` is in the # request headers. serve precompressed files = true + + cache for = ${CACHE_FOR:5} } } diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 2890fb4e1..02289811e 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -953,8 +953,8 @@ static void *serve_files_create(const char *prefix, void *args) goto out_malloc; } - priv->cache = - cache_create(create_cache_entry, destroy_cache_entry, priv, 5); + priv->cache = cache_create(create_cache_entry, destroy_cache_entry, priv, + settings->cache_for); if (!priv->cache) { lwan_status_error("Couldn't create cache"); goto out_cache_create; @@ -1018,6 +1018,8 @@ static void *serve_files_create_from_hash(const char *prefix, (size_t)parse_long("read_ahead", SERVE_FILES_READ_AHEAD_BYTES), .auto_index_readme = parse_bool(hash_find(hash, "auto_index_readme"), true), + .cache_for = (time_t)parse_time_period(hash_find(hash, "cache_for"), + SERVE_FILES_CACHE_FOR), }; return serve_files_create(prefix, &settings); diff --git a/src/lib/lwan-mod-serve-files.h b/src/lib/lwan-mod-serve-files.h index 515a8fa7d..c3347e157 100644 --- a/src/lib/lwan-mod-serve-files.h +++ b/src/lib/lwan-mod-serve-files.h @@ -26,12 +26,14 @@ extern "C" { #include "lwan.h" #define SERVE_FILES_READ_AHEAD_BYTES (128 * 1024) +#define SERVE_FILES_CACHE_FOR 5 struct lwan_serve_files_settings { const char *root_path; const char *index_html; const char *directory_list_template; size_t read_ahead; + time_t cache_for; bool serve_precompressed_files; bool auto_index; bool auto_index_readme; @@ -49,6 +51,7 @@ LWAN_MODULE_FORWARD_DECL(serve_files); .directory_list_template = NULL, \ .auto_index = true, \ .auto_index_readme = true, \ + .cache_for = SERVE_FILES_CACHE_FOR, \ }}), \ .flags = (enum lwan_handler_flags)0 diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index d3236db28..4b40a8b94 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -16,6 +16,7 @@ import sys import time import unittest +import logging BUILD_DIR = './build' for arg in sys.argv[1:]: @@ -23,25 +24,61 @@ BUILD_DIR = arg sys.argv.remove(arg) -def get_harness_path(harness): - return { - 'testrunner': os.path.join(BUILD_DIR, './src/bin/testrunner/testrunner'), - 'techempower': os.path.join(BUILD_DIR, './src/samples/techempower/techempower'), - }[harness] - -print('Using paths:') -print(' testrunner:', get_harness_path('testrunner')) -print(' techempower:', get_harness_path('techempower')) +if os.getenv('REQUESTS_DEBUG'): + logging.basicConfig() + logging.getLogger().setLevel(logging.DEBUG) + requests_log = logging.getLogger("urllib3") + requests_log.setLevel(logging.DEBUG) + requests_log.propagate = True class LwanTest(unittest.TestCase): + harness_paths = { + 'testrunner': os.path.join(BUILD_DIR, 'src/bin/testrunner/testrunner'), + 'techempower': os.path.join(BUILD_DIR, 'src/samples/techempower/techempower'), + } + files_to_copy = { + 'testrunner': ('src/bin/testrunner/testrunner.conf', + 'src/bin/testrunner/test.lua'), + 'techempower': ('src/samples/techempower/techempower.db', + 'src/samples/techempower/techempower.conf', + 'src/samples/techempower/json.lua'), + } + + def ensureHighlander(self): + def pgrep(process_name): + try: + out = subprocess.check_output(('pgrep', process_name), universal_newlines=True) + return (int(pid) for pid in str(out).rstrip().split('\n')) + except subprocess.CalledProcessError: + yield from () + + for typ in self.harness_paths.keys(): + for pid in pgrep(typ): + os.kill(pid, 2) + def setUp(self, env=None, harness='testrunner'): + self.ensureHighlander() + + self.files_to_remove = [] + for file_to_copy in self.files_to_copy[harness]: + base = os.path.basename(file_to_copy) + shutil.copyfile(file_to_copy, base) + self.files_to_remove.append(base) + open('htpasswd', 'w').close() + self.files_to_remove.append('htpasswd') + for spawn_try in range(20): - self.lwan=subprocess.Popen([get_harness_path(harness)], - stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env) + self.lwan = subprocess.Popen([self.harness_paths[harness]], env=env, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + + if self.lwan.poll() is not None: + raise Exception('It seems that %s is not starting up' % harness) + for request_try in range(20): try: - requests.get('http://127.0.0.1:8080/hello') + r = requests.get('http://127.0.0.1:8080/hello') + self.assertEqual(r.status_code, 200) return except requests.ConnectionError: time.sleep(0.1) @@ -62,10 +99,14 @@ def tearDown(self): with self.lwan as l: l.communicate(timeout=1.0) l.kill() - try: - os.remove('htpasswd') - except FileNotFoundError: - pass + + self.ensureHighlander() + + for file_to_remove in self.files_to_remove: + try: + os.remove(file_to_remove) + except FileNotFoundError: + pass def assertHttpResponseValid(self, request, status_code, content_type): self.assertEqual(request.status_code, status_code) @@ -85,15 +126,6 @@ def assertResponsePlain(self, request, status_code=200): class TestTWFB(LwanTest): def setUp(self, env=None): super().setUp(env, harness='techempower') - shutil.copyfile('./src/samples/techempower/techempower.db', './techempower.db') - shutil.copyfile('./src/samples/techempower/techempower.conf', './techempower.conf') - shutil.copyfile('./src/samples/techempower/json.lua', './json.lua') - - def tearDown(self): - super().tearDown() - os.remove('techempower.db') - os.remove('techempower.conf') - os.remove('json.lua') def test_plaintext(self): r = requests.get('http://127.0.0.1:8080/plaintext') @@ -759,6 +791,17 @@ def test_read_env(self): self.assertEqual(r.text, 'Hello, %s!' % os.getenv('USER')) class TestCache(LwanTest): + def setUp(self): + self._cache_for = 3 + + new_environment = os.environ.copy() + new_environment.update({ + 'KEEP_ALIVE_TIMEOUT': '3', + 'CACHE_FOR': str(self._cache_for), + }) + + super().setUp(env=new_environment) + @classmethod def setUpClass(cls): if os.uname().sysname != 'Linux': @@ -780,8 +823,8 @@ def is_mmapped(self, f): def wait_munmap(self, f, timeout=20.0): while self.is_mmapped(f) and timeout >= 0: - time.sleep(0.1) - timeout -= 0.1 + time.sleep(0.05) + timeout -= 0.05 def test_cache_munmaps_conn_close(self): @@ -824,7 +867,8 @@ def test_cache_mmaps_once_even_after_timeout(self): requests.get('http://127.0.0.1:8080/100.html') self.assertEqual(self.count_mmaps('/100.html'), 1) - time.sleep(10) + # 10% over `cache_for` just to be conservative (job thread isn't that precise) + time.sleep(self._cache_for * 1.10) requests.get('http://127.0.0.1:8080/100.html') self.assertEqual(self.count_mmaps('/100.html'), 1) @@ -920,7 +964,7 @@ def test_custom_header_does_not_exist(self): class TestFuzzRegressionBase(SocketTest): def setUp(self): new_environment = os.environ.copy() - new_environment["KEEP_ALIVE_TIMEOUT"] = "0" + new_environment.update({'KEEP_ALIVE_TIMEOUT': '0'}) super(SocketTest, self).setUp(env=new_environment) def run_test(self, contents): From ce77553e425dd7a756ff8d593e88db170a4a5d7b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 18 Feb 2020 21:51:17 -0800 Subject: [PATCH 1429/2505] Document how Lwan finds configuration files --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 43d48184d..bb97bd90d 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,10 @@ Running ------- Set up the server by editing the provided `lwan.conf`; the format is -explained in details below. +explained in details below. (Lwan will try to find a configuration file +based in the executable name in the current directory; `testrunner.conf` +will be used for the `testrunner` binary, `lwan.conf` for the `lwan` binary, +and so on.) Configuration files are loaded from the current directory. If no changes are made to this file, running Lwan will serve static files located in @@ -214,6 +217,8 @@ playlist chiptune { } ``` +Some examples can be found in `lwan.conf` and `techempower.conf`. + #### Value types | Type | Description | From b4f2c3dcfebc991d76d2f5660b39374ea5db5ac6 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 18 Feb 2020 21:51:37 -0800 Subject: [PATCH 1430/2505] Fix TWFB JSON test (send correct Content-Type header) --- src/lib/lwan-lua.c | 18 ++++++++++++++---- src/samples/techempower/techempower.conf | 1 + 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index 701a0d293..2644d6641 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -148,20 +148,30 @@ LWAN_LUA_METHOD(ws_read) return 1; } -static bool append_key_value(lua_State *L, +static bool append_key_value(struct lwan_request *request, + lua_State *L, struct coro *coro, struct lwan_key_value_array *arr, char *key, int value_index) { struct lwan_key_value *kv; + size_t len; + const char *lua_value = lua_tolstring(L, value_index, &len); + char *value = coro_memdup(coro, lua_value, len + 1); + + if (!strcasecmp(key, "Content-Type")) { + request->response.mime_type = value; + + return value != NULL; + } kv = lwan_key_value_array_append(arr); if (!kv) return false; kv->key = key; - kv->value = coro_strdup(coro, lua_tostring(L, value_index)); + kv->value = value; return kv->value != NULL; } @@ -201,13 +211,13 @@ LWAN_LUA_METHOD(set_headers) goto out; if (lua_isstring(L, value_index)) { - if (!append_key_value(L, coro, headers, key, value_index)) + if (!append_key_value(request, L, coro, headers, key, value_index)) goto out; } else if (lua_istable(L, value_index)) { for (lua_pushnil(L); lua_next(L, value_index) != 0; lua_pop(L, 1)) { if (!lua_isstring(L, nested_value_index)) continue; - if (!append_key_value(L, coro, headers, key, + if (!append_key_value(request, L, coro, headers, key, nested_value_index)) goto out; } diff --git a/src/samples/techempower/techempower.conf b/src/samples/techempower/techempower.conf index 1d8262e10..1e343d7cc 100644 --- a/src/samples/techempower/techempower.conf +++ b/src/samples/techempower/techempower.conf @@ -17,6 +17,7 @@ listener *:8080 { end function handle_get_json(req) + req:set_headers({["Content-Type"]="application/json"}) req:set_response(json.encode({message="Hello, World!"})) end''' } From d722c3d75dd11b720036410acb724c7532b5242f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 18 Feb 2020 21:51:57 -0800 Subject: [PATCH 1431/2505] Minor cleanups in some tests --- src/scripts/testsuite.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index 4b40a8b94..458f3262a 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -426,7 +426,8 @@ def assertHasImage(name): self.assertTrue(readme in r.text) - self.assertTrue('' in r.text) + self.assertTrue(r.text.startswith('')) + self.assertTrue(r.text.endswith('\n')) def test_has_lwan_server_header(self): @@ -884,8 +885,8 @@ def test_proxy_version1(self): for request in range(5): sock.send(proxy + req if request == 0 else req) response = sock.recv(4096) - self.assertTrue(response.startswith('HTTP/1.1 200 OK'), response) - self.assertTrue('X-Proxy: 192.168.242.221' in response, response) + self.assertTrue(response.startswith('HTTP/1.1 200 OK')) + self.assertTrue('X-Proxy: 192.168.242.221' in response) def test_proxy_version2(self): proxy = ( @@ -901,8 +902,8 @@ def test_proxy_version2(self): for request in range(5): sock.send(proxy + req if request == 0 else req) response = sock.recv(4096) - self.assertTrue(response.startswith('HTTP/1.1 200 OK'), response) - self.assertTrue('X-Proxy: 1.2.3.4' in response, response) + self.assertTrue(response.startswith('HTTP/1.1 200 OK')) + self.assertTrue('X-Proxy: 1.2.3.4' in response) class TestPipelinedRequests(SocketTest): def test_pipelined_requests(self): From e484fe9801babc7a2ba6eb174d51c93410b8d3d5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 20 Feb 2020 20:38:50 -0800 Subject: [PATCH 1432/2505] Add guard region before coroutine stack in asan/valgrind builds This is mostly a precaution against misprogramming when running debug builds. I've been bitten by the stack size not being large enough way too many times, without any way to actually determine that was the cause of really weird crashes. By marking a small region before the stack as poisoned/no-access, this should catch these issues very quickly during testing. It's definitely not a security feature. --- src/lib/lwan-coro.c | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index fa95811d3..29caa17d0 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -102,6 +102,9 @@ struct coro { unsigned int vg_stack_id; #endif +#if defined(INSTRUMENT_FOR_ASAN) || defined(INSTRUMENT_FOR_VALGRIND) + unsigned char stack_poison[64]; +#endif unsigned char stack[] __attribute__((aligned(64))); }; @@ -286,7 +289,8 @@ void coro_reset(struct coro *coro, coro_function_t func, void *data) ALWAYS_INLINE struct coro * coro_new(struct coro_switcher *switcher, coro_function_t function, void *data) { - struct coro *coro = lwan_aligned_alloc(sizeof(struct coro) + CORO_STACK_MIN, 64); + struct coro *coro = + lwan_aligned_alloc(sizeof(struct coro) + CORO_STACK_MIN, 64); if (UNLIKELY(!coro)) return NULL; @@ -299,9 +303,21 @@ coro_new(struct coro_switcher *switcher, coro_function_t function, void *data) coro->switcher = switcher; coro_reset(coro, function, data); +#if defined(INSTRUMENT_FOR_ASAN) || defined(INSTRUMENT_FOR_VALGRIND) + memset(coro->stack_poison, 'X', sizeof(coro->stack_poison)); +#endif + +#if defined(INSTRUMENT_FOR_ASAN) + __asan_poison_memory_region(coro->stack_poison, + sizeof(coro->stack_poison)); +#endif + #if defined(INSTRUMENT_FOR_VALGRIND) - unsigned char *stack = coro->stack; - coro->vg_stack_id = VALGRIND_STACK_REGISTER(stack, stack + CORO_STACK_MIN); + VALGRIND_MAKE_MEM_NOACCESS(coro->stack_poison, + sizeof(coro->stack_poison)); + + coro->vg_stack_id = + VALGRIND_STACK_REGISTER(coro->stack, (char *)coro->stack + CORO_STACK_MIN); #endif return coro; @@ -343,11 +359,21 @@ inline int64_t coro_yield(struct coro *coro, int64_t value) void coro_free(struct coro *coro) { assert(coro); + + coro_deferred_run(coro, 0); + coro_defer_array_reset(&coro->defer); + +#if defined(INSTRUMENT_FOR_ASAN) + __asan_unpoison_memory_region(coro->stack_poison, + sizeof(coro->stack_poison)); +#endif + #if defined(INSTRUMENT_FOR_VALGRIND) VALGRIND_STACK_DEREGISTER(coro->vg_stack_id); + VALGRIND_MAKE_MEM_UNDEFINED(coro->stack_poison, + sizeof(coro->stack_poison)); #endif - coro_deferred_run(coro, 0); - coro_defer_array_reset(&coro->defer); + free(coro); } From 2e22c73fd8686adee6146efa234db48d3cd44c32 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 20 Feb 2020 20:43:40 -0800 Subject: [PATCH 1433/2505] Cleanup append_key_value() --- src/lib/lwan-lua.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index 2644d6641..de3c1af25 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -155,25 +155,24 @@ static bool append_key_value(struct lwan_request *request, char *key, int value_index) { - struct lwan_key_value *kv; size_t len; const char *lua_value = lua_tolstring(L, value_index, &len); char *value = coro_memdup(coro, lua_value, len + 1); if (!strcasecmp(key, "Content-Type")) { request->response.mime_type = value; + } else { + struct lwan_key_value *kv; - return value != NULL; - } - - kv = lwan_key_value_array_append(arr); - if (!kv) - return false; + kv = lwan_key_value_array_append(arr); + if (!kv) + return false; - kv->key = key; - kv->value = value; + kv->key = key; + kv->value = value; + } - return kv->value != NULL; + return value != NULL; } LWAN_LUA_METHOD(set_headers) From ac4495d71b37ace0cc54a49aa56c60aef28b18ea Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 20 Feb 2020 21:04:17 -0800 Subject: [PATCH 1434/2505] Remove one indirection per template engine VM instruction dispatch --- src/lib/lwan-template.c | 57 +++++++++++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index 908e336ef..e3adb51c0 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -97,7 +97,10 @@ static const char *lexeme_type_str[TOTAL_LEXEMES] = { #undef GENERATE_ARRAY_ITEM struct chunk { - enum action action; + union { + enum action action; + const void *instruction; + }; void *data; enum flags flags; }; @@ -107,6 +110,7 @@ DEFINE_ARRAY_TYPE(chunk_array, struct chunk) struct lwan_tpl { struct chunk_array chunks; size_t minimum_size; + bool dispatch_table_direct; }; struct symtab { @@ -1407,25 +1411,28 @@ lwan_tpl_compile_file(const char *filename, return tpl; } +static void +bake_direct_addresses(struct lwan_tpl *tpl, + const void *const dispatch_table[static ACTION_LAST]) +{ + struct chunk *iter; + + LWAN_ARRAY_FOREACH(&tpl->chunks, iter) { + if (iter->action == ACTION_APPLY_TPL) + bake_direct_addresses(iter->data, dispatch_table); + + iter->instruction = dispatch_table[iter->action]; + } + + tpl->dispatch_table_direct = true; +} + static const struct chunk *apply(struct lwan_tpl *tpl, const struct chunk *chunks, struct lwan_strbuf *buf, void *variables, const void *data) { - static const void *const dispatch_table[] = { - [ACTION_APPEND] = &&action_append, - [ACTION_APPEND_SMALL] = &&action_append_small, - [ACTION_VARIABLE] = &&action_variable, - [ACTION_VARIABLE_STR] = &&action_variable_str, - [ACTION_VARIABLE_STR_ESCAPE] = &&action_variable_str_escape, - [ACTION_IF_VARIABLE_NOT_EMPTY] = &&action_if_variable_not_empty, - [ACTION_END_IF_VARIABLE_NOT_EMPTY] = &&action_end_if_variable_not_empty, - [ACTION_APPLY_TPL] = &&action_apply_tpl, - [ACTION_START_ITER] = &&action_start_iter, - [ACTION_END_ITER] = &&action_end_iter, - [ACTION_LAST] = &&finalize, - }; struct coro_switcher switcher; struct coro *coro = NULL; const struct chunk *chunk = chunks; @@ -1433,6 +1440,24 @@ static const struct chunk *apply(struct lwan_tpl *tpl, if (UNLIKELY(!chunk)) return NULL; + if (!tpl->dispatch_table_direct) { + static const void *const dispatch_table[] = { + [ACTION_APPEND] = &&action_append, + [ACTION_APPEND_SMALL] = &&action_append_small, + [ACTION_VARIABLE] = &&action_variable, + [ACTION_VARIABLE_STR] = &&action_variable_str, + [ACTION_VARIABLE_STR_ESCAPE] = &&action_variable_str_escape, + [ACTION_IF_VARIABLE_NOT_EMPTY] = &&action_if_variable_not_empty, + [ACTION_END_IF_VARIABLE_NOT_EMPTY] = &&action_end_if_variable_not_empty, + [ACTION_APPLY_TPL] = &&action_apply_tpl, + [ACTION_START_ITER] = &&action_start_iter, + [ACTION_END_ITER] = &&action_end_iter, + [ACTION_LAST] = &&finalize, + }; + + bake_direct_addresses(tpl, dispatch_table); + } + #define RETURN_IF_NO_CHUNK(force_) \ do { \ if (force_ UNLIKELY(!chunk)) { \ @@ -1444,7 +1469,7 @@ static const struct chunk *apply(struct lwan_tpl *tpl, #define DISPATCH_ACTION(force_check_) \ do { \ RETURN_IF_NO_CHUNK(force_check_); \ - goto *dispatch_table[chunk->action]; \ + goto *chunk->instruction; \ } while (false) #define DISPATCH_NEXT_ACTION(force_check_) \ @@ -1452,7 +1477,7 @@ static const struct chunk *apply(struct lwan_tpl *tpl, RETURN_IF_NO_CHUNK(force_check_); \ \ chunk++; \ - goto *dispatch_table[chunk->action]; \ + goto *chunk->instruction; \ } while (false) #define DISPATCH_ACTION_FAST() DISPATCH_ACTION(0 &&) From a86c1de38e1afb96baa43cf5effbff4abf0eed35 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 20 Feb 2020 20:51:54 -0800 Subject: [PATCH 1435/2505] Coalesce Lua/Lwan tests for JSON and Plaintext benchmarks --- src/scripts/testsuite.py | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index 458f3262a..a18bef2ac 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -128,16 +128,11 @@ def setUp(self, env=None): super().setUp(env, harness='techempower') def test_plaintext(self): - r = requests.get('http://127.0.0.1:8080/plaintext') + for endpoint in ('lua.plaintext', 'plaintext'): + r = requests.get('http://127.0.0.1:8080/' + endpoint) - self.assertResponsePlain(r) - self.assertEqual(r.text, 'Hello, World!') - - def test_plaintext_lua(self): - r = requests.get('http://127.0.0.1:8080/lua.plaintext') - - self.assertResponsePlain(r) - self.assertEqual(r.text, 'Hello, World!') + self.assertResponsePlain(r) + self.assertEqual(r.text, 'Hello, World!') def assertSingleQueryResultIsValid(self, single): self.assertTrue(isinstance(single, dict)) @@ -155,16 +150,11 @@ def test_fortunes(self): '352e66abf97b5a07c76a8b3c9e3e6339') def test_json(self): - r = requests.get('http://127.0.0.1:8080/json') - - self.assertHttpResponseValid(r, 200, 'application/json') - self.assertEqual(r.json(), {'message': 'Hello, World!'}) + for endpoint in ('lua.json', 'json'): + r = requests.get('http://127.0.0.1:8080/' + endpoint) - def test_json_lua(self): - r = requests.get('http://127.0.0.1:8080/lua.json') - - self.assertHttpResponseValid(r, 200, 'application/json') - self.assertEqual(r.json(), {'message': 'Hello, World!'}) + self.assertHttpResponseValid(r, 200, 'application/json') + self.assertEqual(r.json(), {'message': 'Hello, World!'}) def test_single_query(self): r = requests.get('http://127.0.0.1:8080/db') From 7b5730537a18bdd36955ddc57578f068c7b23182 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 20 Feb 2020 21:33:24 -0800 Subject: [PATCH 1436/2505] Free memory leaks after ac4495d7 Forgot that the function to free a compiled template would look at the action in each chunk and perform special procedures to free every one of them. Since the action and instruction were sharing the same storage, those procedures were not executed anymore. --- src/lib/lwan-template.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index e3adb51c0..f44a0a1e4 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -97,12 +97,10 @@ static const char *lexeme_type_str[TOTAL_LEXEMES] = { #undef GENERATE_ARRAY_ITEM struct chunk { - union { - enum action action; - const void *instruction; - }; + const void *instruction; void *data; enum flags flags; + enum action action; }; DEFINE_ARRAY_TYPE(chunk_array, struct chunk) @@ -1417,7 +1415,7 @@ bake_direct_addresses(struct lwan_tpl *tpl, { struct chunk *iter; - LWAN_ARRAY_FOREACH(&tpl->chunks, iter) { + LWAN_ARRAY_FOREACH (&tpl->chunks, iter) { if (iter->action == ACTION_APPLY_TPL) bake_direct_addresses(iter->data, dispatch_table); From dcde40ec534f73687b4f1e1666030bd542b437a7 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 21 Feb 2020 08:29:30 -0800 Subject: [PATCH 1437/2505] Inline encode_key_value() in an attempt to hoist escape_key branch --- src/samples/techempower/json.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/samples/techempower/json.c b/src/samples/techempower/json.c index 3a20d6205..2ec99a6f9 100644 --- a/src/samples/techempower/json.c +++ b/src/samples/techempower/json.c @@ -839,11 +839,10 @@ static int encode(const struct json_obj_descr *descr, } } -static int encode_key_value(const struct json_obj_descr *descr, - const void *val, - json_append_bytes_t append_bytes, - void *data, - bool escape_key) +static inline int encode_key(const struct json_obj_descr *descr, + json_append_bytes_t append_bytes, + void *data, + bool escape_key) { int ret; @@ -859,7 +858,7 @@ static int encode_key_value(const struct json_obj_descr *descr, ret |= append_bytes(":", 1, data); } - return ret | encode(descr, val, append_bytes, data, escape_key); + return ret; } int json_obj_encode_full(const struct json_obj_descr *descr, @@ -880,12 +879,13 @@ int json_obj_encode_full(const struct json_obj_descr *descr, * branches. */ for (size_t i = 1; i < descr_len; i++) { - ret |= encode_key_value(&descr[i], val, append_bytes, data, - escape_key); + ret |= encode_key(&descr[i], append_bytes, data, escape_key); + ret |= encode(&descr[i], val, append_bytes, data, escape_key); ret |= append_bytes(",", 1, data); } - ret |= encode_key_value(&descr[0], val, append_bytes, data, escape_key); + ret |= encode_key(&descr[0], append_bytes, data, escape_key); + ret |= encode(&descr[0], val, append_bytes, data, escape_key); } return ret | append_bytes("}", 1, data); From 15d7cd714ab68a68abba867bfbb8826b6db41919 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 22 Feb 2020 09:47:34 -0800 Subject: [PATCH 1438/2505] Minor cleanup in config_fuzzer --- src/bin/fuzz/config_fuzzer.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/bin/fuzz/config_fuzzer.cc b/src/bin/fuzz/config_fuzzer.cc index b713615fd..1753f37d2 100644 --- a/src/bin/fuzz/config_fuzzer.cc +++ b/src/bin/fuzz/config_fuzzer.cc @@ -37,13 +37,12 @@ dump(struct config *config, int indent_level) extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { struct config *config; - int indent_level = 0; config = config_open_for_fuzzing(data, size); if (!config) return 1; - bool dumped = dump(config, indent_level); + bool dumped = dump(config, 0); config_close(config); From 84a0deed025904fbf515524b514525b908563e7e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 22 Feb 2020 09:47:55 -0800 Subject: [PATCH 1439/2505] Assert that an impossible cache entry flag combination won't happen --- src/lib/lwan-cache.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/lwan-cache.c b/src/lib/lwan-cache.c index d5a669c77..f61c9a9f3 100644 --- a/src/lib/lwan-cache.c +++ b/src/lib/lwan-cache.c @@ -260,6 +260,7 @@ void cache_entry_unref(struct cache *cache, struct cache_entry *entry) /* FLOATING entries without references won't be picked up by the pruner * job, so destroy them right here. */ if (entry->flags & FLOATING) { + assert(!(entry->flags & FREE_KEY_ON_DESTROY)); return cache->cb.destroy_entry(entry, cache->cb.context); } } From 1e2d86bfafa869acd6bb7ecb2e5926661df7e200 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 22 Feb 2020 09:48:40 -0800 Subject: [PATCH 1440/2505] Better comments in cache_pruner_job() --- src/lib/lwan-cache.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-cache.c b/src/lib/lwan-cache.c index f61c9a9f3..a91fb7eca 100644 --- a/src/lib/lwan-cache.c +++ b/src/lib/lwan-cache.c @@ -319,11 +319,16 @@ static bool cache_pruner_job(void *data) lwan_status_perror("pthread_rwlock_unlock"); if (ATOMIC_INC(node->refs) == 1) { + /* If the refcount was 0, and turned 1 after the increment, it means the item can + * be destroyed here. */ cache->cb.destroy_entry(node, cache->cb.context); } else { + /* If not, some other thread had references to this object. */ ATOMIC_BITWISE(&node->flags, or, FLOATING); - /* Decrement the reference and see if we were genuinely the last one - * holding it. If so, destroy the entry. */ + /* If in the time between the ref check above and setting the floating flag the + * thread holding the reference drops it, if our reference is 0 after dropping it, + * the pruner thread was the last thread holding the reference to this entry, so + * it's safe to destroy it at this point. */ if (!ATOMIC_DEC(node->refs)) cache->cb.destroy_entry(node, cache->cb.context); } From 7d7319b5a7e2f100d4b21fbfcccfd33b06fff0ed Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 22 Feb 2020 09:48:57 -0800 Subject: [PATCH 1441/2505] Add a little bit more error checking in config file parsing --- src/lib/lwan-config.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index c9ff02f62..98acad89c 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -639,9 +639,11 @@ static void *parse_config(struct parser *parser) case LEXEME_CLOSE_BRACKET: { struct config_line line = { .type = CONFIG_LINE_TYPE_SECTION_END }; - config_ring_buffer_try_put(&parser->items, &line); + if (config_ring_buffer_try_put(&parser->items, &line)) + return parse_config; - return parse_config; + lwan_status_error("Could not parse section end"); + return NULL; } case LEXEME_EOF: From 055a1fa7bdc82152d325a932284bc3448cbfb6ac Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 22 Feb 2020 10:16:51 -0800 Subject: [PATCH 1442/2505] Simplify framing of websocket writes --- src/lib/lwan-websocket.c | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index 47d2743fb..5af024617 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -37,35 +37,30 @@ static void write_websocket_frame(struct lwan_request *request, char *msg, size_t len) { - struct iovec vec[4]; - uint8_t net_len_byte, opcode_byte; - uint16_t net_len_short; - uint64_t net_len_long; - size_t last = 0; - - vec[last++] = (struct iovec){.iov_base = &header_byte, .iov_len = 1}; + uint8_t frame[9]; + size_t frame_len; if (len <= 125) { - net_len_byte = (uint8_t)len; - - vec[last++] = (struct iovec){.iov_base = &net_len_byte, .iov_len = 1}; + frame[0] = (uint8_t)len; + frame_len = 1; } else if (len <= 65535) { - net_len_short = htons((uint16_t)len); - opcode_byte = 0x7e; - - vec[last++] = (struct iovec){.iov_base = &opcode_byte, .iov_len = 1}; - vec[last++] = (struct iovec){.iov_base = &net_len_short, .iov_len = 2}; + frame[0] = 0x7e; + memcpy(frame + 1, &(uint16_t){htons((uint16_t)len)}, sizeof(uint16_t)); + frame_len = 3; } else { - net_len_long = htobe64((uint64_t)len); - opcode_byte = 0x7f; - - vec[last++] = (struct iovec){.iov_base = &opcode_byte, .iov_len = 1}; - vec[last++] = (struct iovec){.iov_base = &net_len_long, .iov_len = 8}; + frame[0] = 0x7f; + memcpy(frame + 1, &(uint64_t){htobe64((uint64_t)len)}, + sizeof(uint64_t)); + frame_len = 9; } - vec[last++] = (struct iovec){.iov_base = msg, .iov_len = len}; + struct iovec vec[] = { + {.iov_base = &header_byte, .iov_len = 1}, + {.iov_base = frame, .iov_len = frame_len}, + {.iov_base = msg, .iov_len = len}, + }; - lwan_writev(request, vec, last); + lwan_writev(request, vec, N_ELEMENTS(vec)); } void lwan_response_websocket_write(struct lwan_request *request) From ad92470c8a6f39fab5fe8bad3b45f86c22c97cae Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 22 Feb 2020 10:27:42 -0800 Subject: [PATCH 1443/2505] Unmask more bytes at a time when reading websocket frames --- src/lib/lwan-websocket.c | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index 5af024617..ab51c0cd0 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -175,20 +175,30 @@ bool lwan_response_websocket_read(struct lwan_request *request) if (LIKELY(header & 0x80)) { /* Payload is masked; should always be true on Client->Server comms but * don't assume this is always the case. */ - union { - char as_char[4]; - uint32_t as_int; - } masks; + uint32_t mask; struct iovec vec[] = { - {.iov_base = masks.as_char, .iov_len = sizeof(masks.as_char)}, + {.iov_base = &mask, .iov_len = sizeof(mask)}, {.iov_base = msg, .iov_len = len_frame}, }; lwan_readv(request, vec, N_ELEMENTS(vec)); - if (masks.as_int != 0x00000000) { - for (uint64_t i = 0; i < len_frame; i++) - msg[i] ^= masks.as_char[i % sizeof(masks)]; + if (mask) { + uint64_t i; + + for (i = 0; len_frame - i >= sizeof(mask); i += sizeof(mask)) { + uint32_t v; + + memcpy(&v, &msg[i], sizeof(v)); + v ^= mask; + memcpy(&msg[i], &v, sizeof(v)); + } + + switch (i & 3) { + case 3: msg[i + 2] ^= (char)((mask >> 16) & 0xff); /* fallthrough */ + case 2: msg[i + 1] ^= (char)((mask >> 8) & 0xff); /* fallthrough */ + case 1: msg[i + 0] ^= (char)(mask & 0xff); + } } } else { lwan_recv(request, msg, len_frame, 0); From 3828ddf0f902166680cce8eb70a1264a8f23cad0 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 22 Feb 2020 11:59:27 -0800 Subject: [PATCH 1444/2505] Simplify mmap_init() by closing file right after mmap() call --- src/lib/lwan-mod-serve-files.c | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 02289811e..9f9958913 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -476,7 +476,6 @@ static bool mmap_init(struct file_cache_entry *ce, struct mmap_cache_data *md = &ce->mmap_cache_data; const char *path = full_path + priv->root_path_len; int file_fd; - bool success; path += *path == '/'; @@ -486,10 +485,9 @@ static bool mmap_init(struct file_cache_entry *ce, md->uncompressed.value = mmap(NULL, (size_t)st->st_size, PROT_READ, MAP_SHARED, file_fd, 0); - if (UNLIKELY(md->uncompressed.value == MAP_FAILED)) { - success = false; - goto close_file; - } + close(file_fd); + if (UNLIKELY(md->uncompressed.value == MAP_FAILED)) + return false; lwan_madvise_queue(md->uncompressed.value, (size_t)st->st_size); @@ -505,12 +503,7 @@ static bool mmap_init(struct file_cache_entry *ce, ce->mime_type = lwan_determine_mime_type_for_file_name(full_path + priv->root_path_len); - success = true; - -close_file: - close(file_fd); - - return success; + return true; } static bool is_world_readable(mode_t mode) From e6e4774e0fe512097e02e2f9d23c2d0aa17cb8ad Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 24 Feb 2020 18:40:47 -0800 Subject: [PATCH 1445/2505] Use ints instead of short ints whenever possible --- src/lib/lwan-socket.c | 2 +- src/lib/lwan-thread.c | 10 +++++----- src/lib/lwan-tq.c | 34 +++++++++++++++++++--------------- src/lib/lwan-tq.h | 4 ++-- src/lib/lwan.c | 12 +++++++----- src/lib/lwan.h | 12 ++++++++---- 6 files changed, 42 insertions(+), 32 deletions(-) diff --git a/src/lib/lwan-socket.c b/src/lib/lwan-socket.c index 14e8cec4c..41df9eaf7 100644 --- a/src/lib/lwan-socket.c +++ b/src/lib/lwan-socket.c @@ -265,7 +265,7 @@ void lwan_socket_init(struct lwan *l) SET_SOCKET_OPTION_MAY_FAIL(SOL_TCP, TCP_FASTOPEN, (int[]){5}); SET_SOCKET_OPTION_MAY_FAIL(SOL_TCP, TCP_QUICKACK, (int[]){0}); SET_SOCKET_OPTION_MAY_FAIL(SOL_TCP, TCP_DEFER_ACCEPT, - (int[]){l->config.keep_alive_timeout}); + (int[]){(int)l->config.keep_alive_timeout}); #endif l->main_socket = fd; diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index c4fe6080f..66a486de6 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -317,7 +317,7 @@ static ALWAYS_INLINE void spawn_coro(struct lwan_connection *conn, *conn = (struct lwan_connection) { .coro = coro_new(switcher, process_request_coro, conn), .flags = CONN_EVENTS_READ, - .time_to_expire = tq->time + tq->keep_alive_timeout, + .time_to_expire = tq->current_time + tq->move_to_last_bump, .thread = t, }; if (UNLIKELY(!conn->coro)) { @@ -574,7 +574,7 @@ static bool read_cpu_topology(struct lwan *l, uint32_t siblings[]) { char path[PATH_MAX]; - for (unsigned short i = 0; i < l->n_cpus; i++) { + for (unsigned int i = 0; i < l->n_cpus; i++) { FILE *sib; uint32_t id, sibling; char separator; @@ -698,7 +698,7 @@ void lwan_thread_init(struct lwan *l) const size_t n_queue_fds = LWAN_MIN(l->thread.max_fd / l->thread.count, (size_t)(2 * lwan_socket_get_backlog_size())); lwan_status_debug("Pending client file descriptor queue has %zu items", n_queue_fds); - for (short i = 0; i < l->thread.count; i++) + for (unsigned int i = 0; i < l->thread.count; i++) create_thread(l, &l->thread.threads[i], n_queue_fds); const unsigned int total_conns = l->thread.max_fd * l->thread.count; @@ -737,7 +737,7 @@ void lwan_thread_shutdown(struct lwan *l) { lwan_status_debug("Shutting down threads"); - for (int i = 0; i < l->thread.count; i++) { + for (unsigned int i = 0; i < l->thread.count; i++) { struct lwan_thread *t = &l->thread.threads[i]; close(t->epoll_fd); @@ -747,7 +747,7 @@ void lwan_thread_shutdown(struct lwan *l) pthread_barrier_wait(&l->thread.barrier); pthread_barrier_destroy(&l->thread.barrier); - for (int i = 0; i < l->thread.count; i++) { + for (unsigned int i = 0; i < l->thread.count; i++) { struct lwan_thread *t = &l->thread.threads[i]; close(t->pipe_fd[0]); diff --git a/src/lib/lwan-tq.c b/src/lib/lwan-tq.c index 9d1bdbef6..e6c4618f0 100644 --- a/src/lib/lwan-tq.c +++ b/src/lib/lwan-tq.c @@ -35,8 +35,8 @@ timeout_queue_idx_to_node(struct timeout_queue *tq, int idx) return (idx < 0) ? &tq->head : &tq->conns[idx]; } -void timeout_queue_insert(struct timeout_queue *tq, - struct lwan_connection *new_node) +inline void timeout_queue_insert(struct timeout_queue *tq, + struct lwan_connection *new_node) { new_node->next = -1; new_node->prev = tq->head.prev; @@ -44,8 +44,8 @@ void timeout_queue_insert(struct timeout_queue *tq, tq->head.prev = prev->next = timeout_queue_node_to_idx(tq, new_node); } -static void timeout_queue_remove(struct timeout_queue *tq, - struct lwan_connection *node) +static inline void timeout_queue_remove(struct timeout_queue *tq, + struct lwan_connection *node) { struct lwan_connection *prev = timeout_queue_idx_to_node(tq, node->prev); struct lwan_connection *next = timeout_queue_idx_to_node(tq, node->next); @@ -64,7 +64,7 @@ void timeout_queue_move_to_last(struct timeout_queue *tq, /* CONN_IS_KEEP_ALIVE isn't checked here because non-keep-alive connections * are closed in the request processing coroutine after they have been * served. In practice, if this is called, it's a keep-alive connection. */ - conn->time_to_expire = tq->time + tq->keep_alive_timeout; + conn->time_to_expire = tq->current_time + tq->move_to_last_bump; timeout_queue_remove(tq, conn); timeout_queue_insert(tq, conn); @@ -72,15 +72,19 @@ void timeout_queue_move_to_last(struct timeout_queue *tq, void timeout_queue_init(struct timeout_queue *tq, const struct lwan *lwan) { - tq->lwan = lwan; - tq->conns = lwan->conns; - tq->time = 0; - tq->keep_alive_timeout = lwan->config.keep_alive_timeout; - tq->head.next = tq->head.prev = -1; - tq->timeout = (struct timeout){}; + *tq = (struct timeout_queue){ + .lwan = lwan, + .conns = lwan->conns, + .current_time = 0, + .move_to_last_bump = lwan->config.keep_alive_timeout, + .head.next = -1, + .head.prev = -1, + .timeout = (struct timeout){}, + }; } -void timeout_queue_expire(struct timeout_queue *tq, struct lwan_connection *conn) +void timeout_queue_expire(struct timeout_queue *tq, + struct lwan_connection *conn) { timeout_queue_remove(tq, conn); @@ -94,20 +98,20 @@ void timeout_queue_expire(struct timeout_queue *tq, struct lwan_connection *conn void timeout_queue_expire_waiting(struct timeout_queue *tq) { - tq->time++; + tq->current_time++; while (!timeout_queue_empty(tq)) { struct lwan_connection *conn = timeout_queue_idx_to_node(tq, tq->head.next); - if (conn->time_to_expire > tq->time) + if (conn->time_to_expire > tq->current_time) return; timeout_queue_expire(tq, conn); } /* Timeout queue exhausted: reset epoch */ - tq->time = 0; + tq->current_time = 0; } void timeout_queue_expire_all(struct timeout_queue *tq) diff --git a/src/lib/lwan-tq.h b/src/lib/lwan-tq.h index 43025b627..5abaf9441 100644 --- a/src/lib/lwan-tq.h +++ b/src/lib/lwan-tq.h @@ -25,8 +25,8 @@ struct timeout_queue { struct lwan_connection *conns; struct lwan_connection head; struct timeout timeout; - unsigned time; - unsigned short keep_alive_timeout; + unsigned int current_time; + unsigned int move_to_last_bump; }; void timeout_queue_init(struct timeout_queue *tq, const struct lwan *l); diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 6df1c2be6..b3d444cc0 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -363,7 +363,7 @@ static bool setup_from_config(struct lwan *lwan, const char *path) switch (line->type) { case CONFIG_LINE_TYPE_LINE: if (streq(line->key, "keep_alive_timeout")) { - lwan->config.keep_alive_timeout = (unsigned short)parse_long( + lwan->config.keep_alive_timeout = (unsigned int)parse_long( line->value, default_config.keep_alive_timeout); } else if (streq(line->key, "quiet")) { lwan->config.quiet = @@ -389,7 +389,7 @@ static bool setup_from_config(struct lwan *lwan, const char *path) if (n_threads < 0) config_error(conf, "Invalid number of threads: %ld", n_threads); - lwan->config.n_threads = (unsigned short int)n_threads; + lwan->config.n_threads = (unsigned int)n_threads; } else if (streq(line->key, "max_post_data_size")) { long max_post_data_size = parse_long( line->value, (long)default_config.max_post_data_size); @@ -498,15 +498,17 @@ static void allocate_connections(struct lwan *l, size_t max_open_files) memset(l->conns, 0, sz); } -static unsigned short int get_number_of_cpus(void) +static unsigned int get_number_of_cpus(void) { long n_online_cpus = sysconf(_SC_NPROCESSORS_ONLN); + if (UNLIKELY(n_online_cpus < 0)) { lwan_status_warning( "Could not get number of online CPUs, assuming 1 CPU"); return 1; } - return (unsigned short int)n_online_cpus; + + return (unsigned int)n_online_cpus; } void lwan_init(struct lwan *l) { lwan_init_with_config(l, &default_config); } @@ -552,7 +554,7 @@ void lwan_init_with_config(struct lwan *l, const struct lwan_config *config) if (l->thread.count == 1) l->thread.count = 2; } else if (l->config.n_threads > 3 * l->n_cpus) { - l->thread.count = (short unsigned int)(l->n_cpus * 3); + l->thread.count = l->n_cpus * 3; lwan_status_warning("%d threads requested, but only %d online CPUs; " "capping to %d threads", diff --git a/src/lib/lwan.h b/src/lib/lwan.h index e0a7d81d3..603bf2315 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -450,10 +450,13 @@ struct lwan_config { char *listener; char *error_template; char *config_file_path; + size_t max_post_data_size; - unsigned short keep_alive_timeout; + + unsigned int keep_alive_timeout; unsigned int expires; - unsigned short n_threads; + unsigned int n_threads; + bool quiet; bool reuse_port; bool proxy_protocol; @@ -469,14 +472,15 @@ struct lwan { pthread_barrier_t barrier; struct lwan_thread *threads; unsigned int max_fd; - unsigned short count; + unsigned int count; } thread; struct lwan_config config; struct coro_switcher switcher; + int main_socket; - unsigned short n_cpus; + unsigned int n_cpus; }; void lwan_set_url_map(struct lwan *l, const struct lwan_url_map *map); From ea0f85497a1b69d1d817c09eb2736e6a0063e366 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 24 Feb 2020 18:41:04 -0800 Subject: [PATCH 1446/2505] Cleanup assignment to `bool negate` in action_start_iter --- src/lib/lwan-template.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index f44a0a1e4..ff8a09ea1 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -1561,7 +1561,7 @@ action_apply_tpl: { coro = coro_new(&switcher, cd->descriptor->generator, variables); bool resumed = coro_resume_value(coro, 0); - bool negate = (chunk->flags & FLAGS_NEGATE) == FLAGS_NEGATE; + bool negate = chunk->flags & FLAGS_NEGATE; if (negate) resumed = !resumed; if (!resumed) { From fd3d877f8862ec508adc18df26efb8c6a0dc1f55 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 25 Feb 2020 20:56:52 -0800 Subject: [PATCH 1447/2505] strbuf flags should be opaque, so invert meaning of STATIC --- src/lib/lwan-strbuf.c | 26 +++++++++++++------------- src/lib/lwan-strbuf.h | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/lib/lwan-strbuf.c b/src/lib/lwan-strbuf.c index 2c71b09de..4bd5bda80 100644 --- a/src/lib/lwan-strbuf.c +++ b/src/lib/lwan-strbuf.c @@ -26,8 +26,8 @@ #include "lwan-private.h" -static const unsigned int STATIC = 1 << 0; -static const unsigned int DYNAMICALLY_ALLOCATED = 1 << 1; +static const unsigned int BUFFER_MALLOCD = 1 << 0; +static const unsigned int STRBUF_MALLOCD = 1 << 1; static inline size_t align_size(size_t unaligned_size) { @@ -41,7 +41,7 @@ static inline size_t align_size(size_t unaligned_size) static bool grow_buffer_if_needed(struct lwan_strbuf *s, size_t size) { - if (s->flags & STATIC) { + if (!(s->flags & BUFFER_MALLOCD)) { const size_t aligned_size = align_size(LWAN_MAX(size + 1, s->used)); if (UNLIKELY(!aligned_size)) return false; @@ -53,7 +53,7 @@ static bool grow_buffer_if_needed(struct lwan_strbuf *s, size_t size) memcpy(buffer, s->buffer, s->used); buffer[s->used + 1] = '\0'; - s->flags &= ~STATIC; + s->flags |= BUFFER_MALLOCD; s->buffer = buffer; s->capacity = aligned_size; @@ -110,7 +110,7 @@ struct lwan_strbuf *lwan_strbuf_new_with_size(size_t size) return NULL; } - s->flags |= DYNAMICALLY_ALLOCATED; + s->flags |= STRBUF_MALLOCD; return s; } @@ -129,7 +129,7 @@ ALWAYS_INLINE struct lwan_strbuf *lwan_strbuf_new_static(const char *str, return NULL; *s = (struct lwan_strbuf) { - .flags = STATIC | DYNAMICALLY_ALLOCATED, + .flags = STRBUF_MALLOCD, .buffer = (char *)str, .used = size, .capacity = size, @@ -142,9 +142,9 @@ void lwan_strbuf_free(struct lwan_strbuf *s) { if (UNLIKELY(!s)) return; - if (!(s->flags & STATIC)) + if (s->flags & BUFFER_MALLOCD) free(s->buffer); - if (s->flags & DYNAMICALLY_ALLOCATED) + if (s->flags & STRBUF_MALLOCD) free(s); } @@ -173,12 +173,12 @@ bool lwan_strbuf_append_str(struct lwan_strbuf *s1, const char *s2, size_t sz) bool lwan_strbuf_set_static(struct lwan_strbuf *s1, const char *s2, size_t sz) { - if (!(s1->flags & STATIC)) + if (s1->flags & BUFFER_MALLOCD) free(s1->buffer); s1->buffer = (char *)s2; s1->used = s1->capacity = sz; - s1->flags |= STATIC; + s1->flags &= ~BUFFER_MALLOCD; return true; } @@ -254,11 +254,11 @@ bool lwan_strbuf_grow_by(struct lwan_strbuf *s, size_t offset) void lwan_strbuf_reset(struct lwan_strbuf *s) { - if (s->flags & STATIC) { + if (s->flags & BUFFER_MALLOCD) { + s->buffer[0] = '\0'; + } else { s->buffer = ""; s->capacity = 0; - } else { - s->buffer[0] = '\0'; } s->used = 0; diff --git a/src/lib/lwan-strbuf.h b/src/lib/lwan-strbuf.h index e0b7475c0..ffcee5dce 100644 --- a/src/lib/lwan-strbuf.h +++ b/src/lib/lwan-strbuf.h @@ -31,7 +31,7 @@ struct lwan_strbuf { }; #define LWAN_STRBUF_STATIC_INIT \ - (struct lwan_strbuf) { .buffer = "", .flags = 1 << 0 } + (struct lwan_strbuf) { .buffer = "" } bool lwan_strbuf_init_with_size(struct lwan_strbuf *buf, size_t size); bool lwan_strbuf_init(struct lwan_strbuf *buf); From b61c4ff17a516c9045abdd614db191072f0fd19b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 26 Feb 2020 08:03:01 -0800 Subject: [PATCH 1448/2505] Mention ZSTD as an optional dependency --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index bb97bd90d..c371dec24 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ The build system will look for these libraries and enable/link if available. - [Lua 5.1](http://www.lua.org) or [LuaJIT 2.0](http://luajit.org) - [Valgrind](http://valgrind.org) - [Brotli](https://github.com/google/brotli) + - [ZSTD](https://github.com/facebook/zstd) - Alternative memory allocators can be used by passing `-DUSE_ALTERNATIVE_MALLOC` to CMake with the following values: - ["mimalloc"](https://github.com/microsoft/mimalloc) - ["jemalloc"](http://jemalloc.net/) From 7907aaa0a80f5a0b0fea209f393b239a85fbf073 Mon Sep 17 00:00:00 2001 From: TBK <858296+TBK@users.noreply.github.com> Date: Sat, 7 Mar 2020 16:15:11 +0100 Subject: [PATCH 1449/2505] README.md typo fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c371dec24..c50cef2e6 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,7 @@ pass `-DALTERNATIVE_MALLOC=ON` to the CMake invocation line. ### Tests - ~/lwan/build$ make teststuite + ~/lwan/build$ make testsuite This will compile the `testrunner` program and execute regression test suite in `src/scripts/testsuite.py`. From af6ac93fca641061a41d1c25ae74f87f1f1daea2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 28 Mar 2020 10:22:30 -0700 Subject: [PATCH 1450/2505] Do not error out if separator is at the end of the string Otherwise, requests with a Cookie header such as `foo=bar;` won't make the "foo" cookie available for request handlers. (Same thing happens for other usages of parse_key_values(), such as query strings ending in '&'.) --- src/lib/lwan-request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index db2f1db8a..0d5d52c19 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -368,7 +368,7 @@ static void parse_key_values(struct lwan_request *request, while (*ptr == ' ' || *ptr == separator) ptr++; if (UNLIKELY(*ptr == '\0')) - goto error; + break; key = ptr; ptr = strsep_char(key, end, separator); From f024d3bab686cd11ea7aa86f24a8d647b2b26587 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 9 Apr 2020 21:41:17 -0700 Subject: [PATCH 1451/2505] Enable sanitizer for both Debug and RelWithDebInfo build types --- CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f5cce17e1..610354ca4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -233,7 +233,9 @@ if (${CMAKE_BUILD_TYPE} MATCHES "Rel") enable_c_flag_if_avail(-ffat-lto-objects C_FLAGS_REL HAVE_LTO_FAT_OBJS) enable_c_flag_if_avail(-mcrc32 C_FLAGS_REL HAVE_BUILTIN_IA32_CRC32) -else () +endif () + +if (${CMAKE_BUILD_TYPE} MATCHES "Deb") option(SANITIZER "Use sanitizer (undefined, address, thread, none)" "none") if (${SANITIZER} MATCHES "(undefined|ub|ubsan)") From 8fb816e2b201a4afd3f7256304aa9cda3f14e50f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 9 Apr 2020 21:47:26 -0700 Subject: [PATCH 1452/2505] abortstr is unused when building in release mode --- src/lib/list.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/list.c b/src/lib/list.c index bb888af91..c27fcc2f3 100644 --- a/src/lib/list.c +++ b/src/lib/list.c @@ -87,6 +87,8 @@ struct list_node *list_check_node(const struct list_node *node, /* Check prev on head node. */ if (node->prev != p) return corrupt(abortstr, node, node, 0); +#else + (void)abortstr; #endif return (struct list_node *)node; From e575167ddb3464231284a31ddaa34dddda0c04da Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 9 Apr 2020 21:47:42 -0700 Subject: [PATCH 1453/2505] Use proper format specifier when building topology path --- src/lib/lwan-thread.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 66a486de6..fd7c5c36a 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -580,7 +580,7 @@ static bool read_cpu_topology(struct lwan *l, uint32_t siblings[]) char separator; snprintf(path, sizeof(path), - "/sys/devices/system/cpu/cpu%hd/topology/thread_siblings_list", + "/sys/devices/system/cpu/cpu%d/topology/thread_siblings_list", i); sib = fopen(path, "re"); From 96368ec118ed9adeb03a50f8c0b4b6f462dcb028 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 9 Apr 2020 21:48:41 -0700 Subject: [PATCH 1454/2505] Misc. cleanups in append_key_value() This mainly changes calls to `lua_isstring()` to `lua_type()`, which does not perform type coercion. --- src/lib/lwan-lua.c | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index de3c1af25..0cf35c4ff 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -178,9 +178,8 @@ static bool append_key_value(struct lwan_request *request, LWAN_LUA_METHOD(set_headers) { const int table_index = 2; - const int key_index = 1 + table_index; - const int value_index = 2 + table_index; - const int nested_value_index = value_index * 2 - table_index; + const int key_index = -2; + const int value_index = -1; struct lwan_key_value_array *headers; struct lwan_request *request = lwan_lua_get_request_from_userdata(L); struct coro *coro = request->conn->coro; @@ -196,33 +195,31 @@ LWAN_LUA_METHOD(set_headers) if (!headers) goto out; - lua_pushnil(L); - while (lua_next(L, table_index) != 0) { + for (lua_pushnil(L); lua_next(L, table_index) != 0; lua_pop(L, 1)) { char *key; - if (!lua_isstring(L, key_index)) { - lua_pop(L, 1); + if (lua_type(L, key_index) != LUA_TSTRING) continue; - } key = coro_strdup(request->conn->coro, lua_tostring(L, key_index)); if (!key) goto out; - if (lua_isstring(L, value_index)) { + switch (lua_type(L, value_index)) { + case LUA_TSTRING: if (!append_key_value(request, L, coro, headers, key, value_index)) goto out; - } else if (lua_istable(L, value_index)) { - for (lua_pushnil(L); lua_next(L, value_index) != 0; lua_pop(L, 1)) { - if (!lua_isstring(L, nested_value_index)) + break; + case LUA_TTABLE: + for (lua_pushnil(L); lua_next(L, value_index - 1) != 0; lua_pop(L, 1)) { + if (!lua_isstring(L, value_index)) continue; if (!append_key_value(request, L, coro, headers, key, - nested_value_index)) + value_index)) goto out; } + break; } - - lua_pop(L, 1); } kv = lwan_key_value_array_append(headers); From a75278f067189fc93ea41b25e5ec46f5eb95b217 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 9 Apr 2020 22:04:03 -0700 Subject: [PATCH 1455/2505] Fix use-after-free when using coroutine bump pointer allocator Even though we might not have executed all the deferred frees for all the arenas in this coro, force an allocation next time coro_malloc() is called. Otherwise, if the coroutine is reused for another request, another handler might try to write to freed memory. --- src/lib/lwan-coro.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 29caa17d0..f5aa1aa4d 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -221,6 +221,13 @@ void coro_deferred_run(struct coro *coro, size_t generation) } array->elements = generation; + + /* Even though we might not have executed all the deferred frees + * for all the arenas in this coro, force an allocation next time + * coro_malloc() is called. Otherwise, if the coroutine is reused + * for another request, another handler might try to write to freed + * memory. */ + coro->bump_ptr_alloc.remaining = 0; } ALWAYS_INLINE size_t coro_deferred_get_generation(const struct coro *coro) From 1665ed6aab97090a4e82d3cd66a9a42d2c57a0a1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 10 Apr 2020 19:22:14 -0700 Subject: [PATCH 1456/2505] Only clean BPA state when also freeing BPA arena This fix is equivalent to a75278f06, but it's cleaner as that happens only if coro_malloc() has been used. --- src/lib/lwan-coro.c | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index f5aa1aa4d..9db6573fb 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -221,13 +221,6 @@ void coro_deferred_run(struct coro *coro, size_t generation) } array->elements = generation; - - /* Even though we might not have executed all the deferred frees - * for all the arenas in this coro, force an allocation next time - * coro_malloc() is called. Otherwise, if the coroutine is reused - * for another request, another handler might try to write to freed - * memory. */ - coro->bump_ptr_alloc.remaining = 0; } ALWAYS_INLINE size_t coro_deferred_get_generation(const struct coro *coro) @@ -481,18 +474,25 @@ static inline void *coro_malloc_bump_ptr(struct coro *coro, size_t aligned_size) coro_malloc_bump_ptr(coro_, aligned_size_) #endif -#if defined(INSTRUMENT_FOR_ASAN) || defined(INSTRUMENT_FOR_VALGRIND) -static void unpoison_and_free(void *ptr) +static void free_bump_ptr(void *arg1, void *arg2) { + struct coro *coro = arg1; + #if defined(INSTRUMENT_FOR_VALGRIND) - VALGRIND_MAKE_MEM_UNDEFINED(ptr, CORO_BUMP_PTR_ALLOC_SIZE); + VALGRIND_MAKE_MEM_UNDEFINED(arg2, CORO_BUMP_PTR_ALLOC_SIZE); #endif #if defined(INSTRUMENT_FOR_ASAN) - __asan_unpoison_memory_region(ptr, CORO_BUMP_PTR_ALLOC_SIZE); + __asan_unpoison_memory_region(arg2, CORO_BUMP_PTR_ALLOC_SIZE); #endif - free(ptr); + + /* Instead of checking if bump_ptr_alloc.ptr is part of the allocation + * with base in arg2, just zero out the arena for this coroutine to + * prevent coro_malloc() from carving up this and any other + * (potentially) freed arenas. */ + coro->bump_ptr_alloc.remaining = 0; + + return free(arg2); } -#endif void *coro_malloc(struct coro *coro, size_t size) { @@ -501,7 +501,7 @@ void *coro_malloc(struct coro *coro, size_t size) * guarantee that the destroy_func is free(), so that if an allocation goes * through the bump pointer allocator, there's nothing that needs to be done * to free the memory (other than freeing the whole bump pointer arena with - * the defer call below). */ + * the defer call below). */ const size_t aligned_size = (size + sizeof(void *) - 1ul) & ~(sizeof(void *) - 1ul); @@ -512,7 +512,7 @@ void *coro_malloc(struct coro *coro, size_t size) /* This will allocate as many "bump pointer arenas" as necessary; the * old ones will be freed automatically as each allocations coro_defers * the free() call. Just don't bother allocating an arena larger than - * CORO_BUMP_PTR_ALLOC.*/ + * CORO_BUMP_PTR_ALLOC. */ if (LIKELY(aligned_size <= CORO_BUMP_PTR_ALLOC_SIZE)) { coro->bump_ptr_alloc.ptr = malloc(CORO_BUMP_PTR_ALLOC_SIZE); if (UNLIKELY(!coro->bump_ptr_alloc.ptr)) @@ -529,13 +529,8 @@ void *coro_malloc(struct coro *coro, size_t size) CORO_BUMP_PTR_ALLOC_SIZE); #endif -#if defined(INSTRUMENT_FOR_ASAN) || defined(INSTRUMENT_FOR_VALGRIND) - coro_defer(coro, unpoison_and_free, coro->bump_ptr_alloc.ptr); -#else - coro_defer(coro, free, coro->bump_ptr_alloc.ptr); -#endif + coro_defer2(coro, free_bump_ptr, coro, coro->bump_ptr_alloc.ptr); - /* Avoid checking if there's still space in the arena again. */ return CORO_MALLOC_BUMP_PTR(coro, aligned_size, size); } From e7dc1a84bdf12474c3a72da1cdd031b0d91d5adf Mon Sep 17 00:00:00 2001 From: Joachim Schipper Date: Mon, 13 Apr 2020 16:28:27 +0200 Subject: [PATCH 1457/2505] CMake: include_directories() for libraries The headers for libraries like zstd and brotli may not be in the standard search path. So add appropriate include_directories(). This issue broke the build when using the OpenBSD system compiler /usr/bin/cc if a package such as zstd or brotli is installed, since the OpenBSD system compiler does not search /usr/local/include by default. --- CMakeLists.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 610354ca4..3ed487484 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,6 +35,9 @@ endif () find_package(ZLIB REQUIRED) find_package(Threads REQUIRED) set(ADDITIONAL_LIBRARIES ${ZLIB_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) +if (NOT ZLIB_INCLUDES STREQUAL "") + include_directories(${ZLIB_INCLUDES}) +endif () foreach (pc_file luajit lua lua51 lua5.1 lua-5.1) if (${pc_file} STREQUAL "luajit") @@ -58,12 +61,18 @@ endif () pkg_check_modules(BROTLI libbrotlienc libbrotlidec libbrotlicommon) if (BROTLI_FOUND) list(APPEND ADDITIONAL_LIBRARIES "${BROTLI_LDFLAGS}") + if (NOT BROTLI_INCLUDE_DIRS STREQUAL "") + include_directories(${BROTLI_INCLUDE_DIRS}) + endif () set(HAVE_BROTLI 1) endif () pkg_check_modules(ZSTD libzstd) if (ZSTD_FOUND) list(APPEND ADDITIONAL_LIBRARIES "${ZSTD_LDFLAGS}") + if (NOT ZSTD_INCLUDE_DIRS STREQUAL "") + include_directories(${ZSTD_INCLUDE_DIRS}) + endif () set(HAVE_ZSTD 1) endif () From b698d334f2039eb2be829de64c741d1afc0b80e2 Mon Sep 17 00:00:00 2001 From: Joachim Schipper Date: Mon, 13 Apr 2020 15:58:20 +0200 Subject: [PATCH 1458/2505] OpenBSD: allocate coroutine stacks with MAP_STACK OpenBSD 6.4 and later checks that the stack pointer points into memory allocated with MAP_STACK, on every page fault and syscall. --- src/lib/lwan-coro.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 9db6573fb..3e138e161 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -27,6 +27,10 @@ #include #include +#ifdef __OpenBSD__ +#include +#endif + #include "lwan-private.h" #include "lwan-array.h" @@ -289,11 +293,22 @@ void coro_reset(struct coro *coro, coro_function_t func, void *data) ALWAYS_INLINE struct coro * coro_new(struct coro_switcher *switcher, coro_function_t function, void *data) { +#ifndef __OpenBSD__ struct coro *coro = lwan_aligned_alloc(sizeof(struct coro) + CORO_STACK_MIN, 64); if (UNLIKELY(!coro)) return NULL; +#else + /* As an exploit mitigation, OpenBSD requires any stacks to be allocated + * via mmap(... MAP_STACK ...). */ + struct coro *coro = + mmap(NULL, sizeof(struct coro) + CORO_STACK_MIN, + PROT_READ | PROT_WRITE, MAP_STACK | MAP_ANON | MAP_PRIVATE, -1, 0); + + if (UNLIKELY(coro == MAP_FAILED)) + return NULL; +#endif if (UNLIKELY(coro_defer_array_init(&coro->defer) < 0)) { free(coro); @@ -374,7 +389,12 @@ void coro_free(struct coro *coro) sizeof(coro->stack_poison)); #endif +#ifndef __OpenBSD__ free(coro); +#else + int result = munmap(coro, sizeof(*coro) + CORO_STACK_MIN); + assert(result == 0); /* only fails if addr, len are invalid */ +#endif } ALWAYS_INLINE void coro_defer(struct coro *coro, defer1_func func, void *data) From f01cf6b71b37621f60604111b9843964609aa270 Mon Sep 17 00:00:00 2001 From: Joachim Schipper Date: Mon, 13 Apr 2020 16:18:21 +0200 Subject: [PATCH 1459/2505] lwan_sendfile() fallback: resolve FIXME As noted in the FIXME, /* FIXME: is this correct? fd being read here is a file, not * a socket, so a WANT_READ may actually lead to a coro never * being woken up */ This problem is indeed triggered when running the test_range_no_to() test on OpenBSD (where lwan uses this fallback implementation); lwan reads until end-of-file, then times out waiting for the file to become ready to read. Resolve this by trying to read the file again, synchronously. The test_range_no_to() testcase is one of the few testcases where the data to be sent is not a multiple of the buffer size (i.e. of 512). --- src/lib/lwan-io-wrappers.c | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index 7214a4585..ca30c634e 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -286,13 +286,13 @@ void lwan_sendfile(struct lwan_request *request, #else static inline size_t min_size(size_t a, size_t b) { return (a > b) ? b : a; } -static off_t try_pread(struct lwan_request *request, - int fd, - void *buffer, - size_t len, - off_t offset) +static size_t try_pread_file(struct lwan_request *request, + int fd, + void *buffer, + size_t len, + off_t offset) { - ssize_t total_read = 0; + size_t total_read = 0; for (int tries = MAX_FAILED_TRIES; tries;) { ssize_t r = pread(fd, buffer, len, offset); @@ -309,16 +309,14 @@ static off_t try_pread(struct lwan_request *request, } } - total_read += r; + total_read += (size_t)r; offset += r; - if ((size_t)total_read == len) - return offset; + if (total_read == len || r == 0) + return total_read; try_again: - /* FIXME: is this correct? fd being read here is a file, not - * a socket, so a WANT_READ may actually lead to a coro never - * being woken up */ - coro_yield(request->conn->coro, CONN_CORO_WANT_READ); + /* fd is a file; just re-read */ + (void)0; } out: @@ -338,11 +336,11 @@ void lwan_sendfile(struct lwan_request *request, lwan_send(request, header, header_len, MSG_MORE); while (count) { - size_t to_read = min_size(count, sizeof(buffer)); - - offset = try_pread(request, in_fd, buffer, to_read, offset); - lwan_send(request, buffer, to_read, 0); - count -= to_read; + size_t bytes_read = try_pread_file(request, in_fd, buffer, + min_size(count, sizeof(buffer)), offset); + lwan_send(request, buffer, bytes_read, 0); + count -= bytes_read; + offset += bytes_read; } } #endif From 485dfe1b790c615041359745824b1be64971acab Mon Sep 17 00:00:00 2001 From: Joachim Schipper Date: Mon, 13 Apr 2020 16:38:12 +0200 Subject: [PATCH 1460/2505] Tests: testrunner output may exceed pipe capacity The tests start run testrunner via subprocess.Popen(..., stdout=subprocess.PIPE, ...), and only read testrunner's output (via communicate()) when tearing down the test. If a test generates more output than can fit in a pipe, testrunner will therefore block when writing its output. This happens when running test_pipelined_requests() on OpenBSD, as reported by fstat(1): python3.7 3 pipe 0x0 state: W Fix this by immediately throwing away testrunner's output via subprocess.DEVNULL, instead of throwing away testrunner's output only after retrieving said output via communicate(). (Note that the capacity of pipes legitimately varies across Unix-like operating systems; OpenBSD appears to use 16KB to Linux' 64KB.) --- src/scripts/testsuite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index a18bef2ac..ead48aa39 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -70,7 +70,7 @@ def setUp(self, env=None, harness='testrunner'): for spawn_try in range(20): self.lwan = subprocess.Popen([self.harness_paths[harness]], env=env, - stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) if self.lwan.poll() is not None: raise Exception('It seems that %s is not starting up' % harness) From 6fab8f0a42351a8a81aef46bdd9399e6d138119c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 13 Apr 2020 17:54:40 -0700 Subject: [PATCH 1461/2505] Remove branches from lwan_array initializations --- src/lib/lwan-array.c | 13 +------------ src/lib/lwan-array.h | 8 ++++---- src/lib/lwan-coro.c | 5 +---- 3 files changed, 6 insertions(+), 20 deletions(-) diff --git a/src/lib/lwan-array.c b/src/lib/lwan-array.c index 8026f197b..7d1fa5358 100644 --- a/src/lib/lwan-array.c +++ b/src/lib/lwan-array.c @@ -27,17 +27,6 @@ #include "lwan.h" #include "lwan-array.h" -int lwan_array_init(struct lwan_array *a) -{ - if (UNLIKELY(!a)) - return -EINVAL; - - a->base = NULL; - a->elements = 0; - - return 0; -} - int lwan_array_reset(struct lwan_array *a, void *inline_storage) { if (UNLIKELY(!a)) @@ -142,7 +131,7 @@ struct lwan_array *coro_lwan_array_new(struct coro *coro, bool inline_first) inline_first ? coro_lwan_array_free_inline : coro_lwan_array_free_heap); if (LIKELY(array)) - lwan_array_init(array); + *array = (struct lwan_array){.base = NULL, .elements = 0}; return array; } diff --git a/src/lib/lwan-array.h b/src/lib/lwan-array.h index 16729da68..3ffeb6edb 100644 --- a/src/lib/lwan-array.h +++ b/src/lib/lwan-array.h @@ -32,7 +32,6 @@ struct lwan_array { size_t elements; }; -int lwan_array_init(struct lwan_array *a); int lwan_array_reset(struct lwan_array *a, void *inline_storage); void *lwan_array_append_heap(struct lwan_array *a, size_t element_size); void *lwan_array_append_inline(struct lwan_array *a, @@ -91,10 +90,11 @@ struct lwan_array *coro_lwan_array_new(struct coro *coro, bool inline_first); DEFINE_ARRAY_TYPE_FUNCS(array_type_, element_type_, &array->storage) #define DEFINE_ARRAY_TYPE_FUNCS(array_type_, element_type_, inline_storage_) \ - __attribute__((unused)) static inline int array_type_##_init( \ - struct array_type_ *array) \ + __attribute__((unused)) \ + __attribute__((nonnull(1))) static inline void array_type_##_init( \ + struct array_type_ *array) \ { \ - return lwan_array_init(&array->base); \ + array->base = (struct lwan_array){.base = NULL, .elements = 0}; \ } \ __attribute__((unused)) static inline int array_type_##_reset( \ struct array_type_ *array) \ diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 3e138e161..47e56c0a1 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -310,10 +310,7 @@ coro_new(struct coro_switcher *switcher, coro_function_t function, void *data) return NULL; #endif - if (UNLIKELY(coro_defer_array_init(&coro->defer) < 0)) { - free(coro); - return NULL; - } + coro_defer_array_init(&coro->defer); coro->switcher = switcher; coro_reset(coro, function, data); From e057ffdaac78b70a72caa06e2d8ba95fa55a269e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 13 Apr 2020 17:55:11 -0700 Subject: [PATCH 1462/2505] No need to allocate a zeroed-out buffer when demoting inline->heap --- src/lib/lwan-array.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-array.c b/src/lib/lwan-array.c index 7d1fa5358..0dd2e1898 100644 --- a/src/lib/lwan-array.c +++ b/src/lib/lwan-array.c @@ -88,7 +88,9 @@ void *lwan_array_append_inline(struct lwan_array *a, assert(a->elements <= LWAN_ARRAY_INCREMENT); if (a->elements == LWAN_ARRAY_INCREMENT) { - void *new_base = calloc(2 * LWAN_ARRAY_INCREMENT, element_size); + void *new_base = + reallocarray(NULL, 2 * LWAN_ARRAY_INCREMENT, element_size); + if (UNLIKELY(!new_base)) return NULL; From 5d570833903ece990db994ab91aeb4161bac8143 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 13 Apr 2020 18:01:10 -0700 Subject: [PATCH 1463/2505] Fix typo in comment --- src/lib/lwan-coro.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 47e56c0a1..ae8c5163e 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -125,7 +125,7 @@ struct coro { * This swapcontext() implementation was obtained from glibc and modified * slightly to not save/restore the floating point registers, unneeded * registers, and signal mask. It is Copyright (C) 2001, 2002, 2003 Free - * Software Foundation, Inc and are distributed under GNU LGPL version 2.1 + * Software Foundation, Inc and is distributed under GNU LGPL version 2.1 * (or later). I'm not sure if I can distribute them inside a GPL program; * they're straightforward so I'm assuming there won't be any problem; if * there is, I'll just roll my own. From fb7c74d2c1059e61b029e3c413e2521258babfd8 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 13 Apr 2020 17:56:23 -0700 Subject: [PATCH 1464/2505] Separate coroutine and its stack in debug builds and OpenBSD Due to an OpenBSD requirement, b698d334 started allocating a coroutine and its stack using mmap() directly. This patch changes this, so that the stack isn't allocated inline with the coroutine struct anymore, but separately; this adds a little bit of robustness as it's harder for stack overflows to corrupt a coroutine struct (and possibly reuse the indirect jump in the context switch as an ROP gadget). Also use this behavior by default in the debug builds, to catch issues that the stack_poison member was supposed to be used for, but without relying on fragile userland instrumentation. --- src/lib/lwan-coro.c | 95 ++++++++++++++++++++++----------------------- 1 file changed, 47 insertions(+), 48 deletions(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index ae8c5163e..87250af68 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -26,10 +26,7 @@ #include #include #include - -#ifdef __OpenBSD__ #include -#endif #include "lwan-private.h" @@ -58,16 +55,31 @@ void __asan_unpoison_memory_region(void const volatile *addr, size_t size); #endif #ifdef HAVE_BROTLI -#define CORO_STACK_MIN (8 * SIGSTKSZ) +#define CORO_STACK_SIZE (8 * SIGSTKSZ) #else -#define CORO_STACK_MIN (4 * SIGSTKSZ) +#define CORO_STACK_SIZE (4 * SIGSTKSZ) #endif #define CORO_BUMP_PTR_ALLOC_SIZE 1024 -static_assert(DEFAULT_BUFFER_SIZE < (CORO_STACK_MIN + SIGSTKSZ), +static_assert(DEFAULT_BUFFER_SIZE < CORO_STACK_SIZE, "Request buffer fits inside coroutine stack"); +#if (!defined(NDEBUG) && defined(MAP_STACK)) || defined(__OpenBSD__) +/* As an exploit mitigation, OpenBSD requires any stacks to be allocated via + * mmap(... MAP_STACK ...). + * + * Also enable this on debug builds to catch stack overflows while testing + * (MAP_STACK exists in Linux, but it's a no-op). */ + +#define ALLOCATE_STACK_WITH_MMAP + +static_assert((CORO_STACK_SIZE % PAGE_SIZE) == 0, + "Coroutine stack size is a multiple of page size"); +static_assert((CORO_STACK_SIZE >= PAGE_SIZE), + "Coroutine stack size is at least a page long"); +#endif + typedef void (*defer1_func)(void *data); typedef void (*defer2_func)(void *data1, void *data2); @@ -106,10 +118,11 @@ struct coro { unsigned int vg_stack_id; #endif -#if defined(INSTRUMENT_FOR_ASAN) || defined(INSTRUMENT_FOR_VALGRIND) - unsigned char stack_poison[64]; +#if defined(ALLOCATE_STACK_WITH_MMAP) + unsigned char *stack; +#else + unsigned char stack[]; #endif - unsigned char stack[] __attribute__((aligned(64))); }; #if defined(__APPLE__) @@ -255,12 +268,12 @@ void coro_reset(struct coro *coro, coro_function_t func, void *data) /* Ensure stack is properly aligned: it should be aligned to a * 16-bytes boundary so SSE will work properly, but should be * aligned on an 8-byte boundary right after calling a function. */ - uintptr_t rsp = (uintptr_t)stack + CORO_STACK_MIN; + uintptr_t rsp = (uintptr_t)stack + CORO_STACK_SIZE; #define STACK_PTR 9 coro->context[STACK_PTR] = (rsp & ~0xful) - 0x8ul; #elif defined(__i386__) - stack = (unsigned char *)(uintptr_t)(stack + CORO_STACK_MIN); + stack = (unsigned char *)(uintptr_t)(stack + CORO_STACK_SIZE); /* Make room for 3 args */ stack -= sizeof(uintptr_t) * 3; @@ -281,7 +294,7 @@ void coro_reset(struct coro *coro, coro_function_t func, void *data) getcontext(&coro->context); coro->context.uc_stack.ss_sp = stack; - coro->context.uc_stack.ss_size = CORO_STACK_MIN; + coro->context.uc_stack.ss_size = CORO_STACK_SIZE; coro->context.uc_stack.ss_flags = 0; coro->context.uc_link = NULL; @@ -293,20 +306,25 @@ void coro_reset(struct coro *coro, coro_function_t func, void *data) ALWAYS_INLINE struct coro * coro_new(struct coro_switcher *switcher, coro_function_t function, void *data) { -#ifndef __OpenBSD__ - struct coro *coro = - lwan_aligned_alloc(sizeof(struct coro) + CORO_STACK_MIN, 64); + struct coro *coro; - if (UNLIKELY(!coro)) +#if defined(ALLOCATE_STACK_WITH_MMAP) + void *stack = mmap(NULL, CORO_STACK_SIZE, PROT_READ | PROT_WRITE, + MAP_STACK | MAP_ANON | MAP_PRIVATE, -1, 0); + if (UNLIKELY(stack == MAP_FAILED)) + return NULL; + + coro = lwan_aligned_alloc(sizeof(*coro), 64); + if (UNLIKELY(!coro)) { + munmap(stack, CORO_STACK_SIZE); return NULL; + } + + coro->stack = stack; #else - /* As an exploit mitigation, OpenBSD requires any stacks to be allocated - * via mmap(... MAP_STACK ...). */ - struct coro *coro = - mmap(NULL, sizeof(struct coro) + CORO_STACK_MIN, - PROT_READ | PROT_WRITE, MAP_STACK | MAP_ANON | MAP_PRIVATE, -1, 0); + coro = lwan_aligned_alloc(sizeof(struct coro) + CORO_STACK_SIZE, 64); - if (UNLIKELY(coro == MAP_FAILED)) + if (UNLIKELY(!coro)) return NULL; #endif @@ -315,21 +333,9 @@ coro_new(struct coro_switcher *switcher, coro_function_t function, void *data) coro->switcher = switcher; coro_reset(coro, function, data); -#if defined(INSTRUMENT_FOR_ASAN) || defined(INSTRUMENT_FOR_VALGRIND) - memset(coro->stack_poison, 'X', sizeof(coro->stack_poison)); -#endif - -#if defined(INSTRUMENT_FOR_ASAN) - __asan_poison_memory_region(coro->stack_poison, - sizeof(coro->stack_poison)); -#endif - #if defined(INSTRUMENT_FOR_VALGRIND) - VALGRIND_MAKE_MEM_NOACCESS(coro->stack_poison, - sizeof(coro->stack_poison)); - - coro->vg_stack_id = - VALGRIND_STACK_REGISTER(coro->stack, (char *)coro->stack + CORO_STACK_MIN); + coro->vg_stack_id = VALGRIND_STACK_REGISTER( + coro->stack, (char *)coro->stack + CORO_STACK_SIZE); #endif return coro; @@ -342,7 +348,7 @@ ALWAYS_INLINE int64_t coro_resume(struct coro *coro) #if defined(STACK_PTR) assert(coro->context[STACK_PTR] >= (uintptr_t)coro->stack && coro->context[STACK_PTR] <= - (uintptr_t)(coro->stack + CORO_STACK_MIN)); + (uintptr_t)(coro->stack + CORO_STACK_SIZE)); #endif coro_swapcontext(&coro->switcher->caller, &coro->context); @@ -375,23 +381,16 @@ void coro_free(struct coro *coro) coro_deferred_run(coro, 0); coro_defer_array_reset(&coro->defer); -#if defined(INSTRUMENT_FOR_ASAN) - __asan_unpoison_memory_region(coro->stack_poison, - sizeof(coro->stack_poison)); -#endif - #if defined(INSTRUMENT_FOR_VALGRIND) VALGRIND_STACK_DEREGISTER(coro->vg_stack_id); - VALGRIND_MAKE_MEM_UNDEFINED(coro->stack_poison, - sizeof(coro->stack_poison)); #endif -#ifndef __OpenBSD__ - free(coro); -#else - int result = munmap(coro, sizeof(*coro) + CORO_STACK_MIN); +#if defined(ALLOCATE_STACK_WITH_MMAP) + int result = munmap(coro->stack, CORO_STACK_SIZE); assert(result == 0); /* only fails if addr, len are invalid */ #endif + + free(coro); } ALWAYS_INLINE void coro_defer(struct coro *coro, defer1_func func, void *data) From e7e4ba86ddcf07c6fe178e9b0f52a159c2fd2d57 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 13 Apr 2020 21:31:12 -0700 Subject: [PATCH 1465/2505] Get rid of calls to rawmemchr() There's no need to use this non-standard function, or to provide a fallback implementation in missing.c. --- CMakeLists.txt | 1 - src/bin/tools/mimegen.c | 2 +- src/lib/lwan-config.c | 2 +- src/lib/lwan-mod-serve-files.c | 2 +- src/lib/lwan-tables.c | 6 +++--- src/lib/lwan-template.c | 2 +- src/lib/missing.c | 4 ---- src/lib/missing/string.h | 4 ---- src/lib/realpathat.c | 2 +- 9 files changed, 8 insertions(+), 17 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ed487484..b49b2c50d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -134,7 +134,6 @@ endif () set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) -check_function_exists(rawmemchr HAVE_RAWMEMCHR) check_function_exists(get_current_dir_name HAVE_GET_CURRENT_DIR_NAME) check_symbol_exists(reallocarray stdlib.h HAVE_REALLOCARRAY) check_function_exists(mempcpy HAVE_MEMPCPY) diff --git a/src/bin/tools/mimegen.c b/src/bin/tools/mimegen.c index 9c99d6871..2ed941e23 100644 --- a/src/bin/tools/mimegen.c +++ b/src/bin/tools/mimegen.c @@ -339,7 +339,7 @@ int main(int argc, char *argv[]) printf("#define MIME_ENTRIES %d\n", hash_get_count(ext_mime)); printf("static const unsigned char mime_entries_compressed[] = {\n"); for (i = 1; compressed_size; compressed_size--, i++) - printf("0x%x,%c", compressed[i - 1] & 0xff, " \n"[i % 13 == 0]); + printf("0x%02x,%c", compressed[i - 1] & 0xff, " \n"[i % 13 == 0]); printf("};\n"); free(compressed); diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 98acad89c..d2702f638 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -116,7 +116,7 @@ unsigned int parse_time_period(const char *str, unsigned int default_value) multiplier); } - str = (const char *)rawmemchr(str, multiplier) + 1; + str = strchr(str, multiplier) + 1; } return total ? total : default_value; diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 9f9958913..6a4259291 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -750,7 +750,7 @@ static const struct cache_funcs *get_funcs(struct serve_files_priv *priv, /* Redirect /path to /path/. This is to help cases where there's * something like , so that actually * /path/../foo.png is served instead of /path../foo.png. */ - const char *key_end = rawmemchr(key, '\0'); + const char *key_end = key + strlen(key); if (*(key_end - 1) != '/') return &redir_funcs; diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index 613d83617..a21ce2fe4 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -80,7 +80,7 @@ void lwan_tables_init(void) unsigned char *ptr = uncompressed_mime_entries + 8 * MIME_ENTRIES; for (size_t i = 0; i < MIME_ENTRIES; i++) { mime_types[i] = (char *)ptr; - ptr = rawmemchr(ptr + 1, '\0') + 1; + ptr += strlen((const char *)ptr) + 1; } mime_entries_initialized = true; @@ -132,7 +132,7 @@ lwan_determine_mime_type_for_file_name(const char *file_name) if (LIKELY(*last_dot)) { char key[9]; - char *extension; + const unsigned char *extension; strncpy(key, last_dot + 1, 8); key[8] = '\0'; @@ -142,7 +142,7 @@ lwan_determine_mime_type_for_file_name(const char *file_name) extension = bsearch(key, uncompressed_mime_entries, MIME_ENTRIES, 8, compare_mime_entry); if (LIKELY(extension)) - return mime_types[(extension - (char*)uncompressed_mime_entries) / 8]; + return mime_types[(extension - uncompressed_mime_entries) / 8]; } fallback: diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index ff8a09ea1..c88f43320 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -529,7 +529,7 @@ static void lex_init(struct lexer *lexer, const char *input) { lexer->state = lex_text; lexer->pos = lexer->start = input; - lexer->end = rawmemchr(input, '\0'); + lexer->end = input + strlen(input); lexeme_ring_buffer_init(&lexer->ring_buffer); } diff --git a/src/lib/missing.c b/src/lib/missing.c index 24c627741..f4c5503f8 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -466,10 +466,6 @@ int mkostemp(char *tmpl, int flags) } #endif -#if !defined(HAVE_RAWMEMCHR) -void *rawmemchr(const void *ptr, char c) { return memchr(ptr, c, SIZE_MAX); } -#endif - #if !defined(HAVE_REALLOCARRAY) /* $OpenBSD: reallocarray.c,v 1.2 2014/12/08 03:45:00 bcook Exp $ */ /* diff --git a/src/lib/missing/string.h b/src/lib/missing/string.h index e44be0fd6..0c8b1508e 100644 --- a/src/lib/missing/string.h +++ b/src/lib/missing/string.h @@ -51,10 +51,6 @@ #endif #endif -#ifndef HAVE_RAWMEMCHR -void *rawmemchr(const void *ptr, char c); -#endif - #ifndef HAVE_MEMPCPY void *mempcpy(void *dest, const void *src, size_t len); #endif diff --git a/src/lib/realpathat.c b/src/lib/realpathat.c index e4e3fc7ff..e7b13aaa1 100644 --- a/src/lib/realpathat.c +++ b/src/lib/realpathat.c @@ -76,7 +76,7 @@ realpathat2(int dirfd, char *dirfdpath, const char *name, char *resolved, rpath_limit = rpath + PATH_MAX; strcpy(rpath, dirfdpath); - dest = rawmemchr(rpath, '\0'); + dest = rpath + strlen(rpath); dirfdlen = dest - rpath; for (start = end = name; *start; start = end) { From d5e4fe998f3c733837a6943f0443a8a8717fe380 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 14 Apr 2020 19:24:58 -0700 Subject: [PATCH 1466/2505] Prevent N_ELEMENTS() from compiling if argument isn't an array --- src/lib/lwan.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 603bf2315..ba223a7d1 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -43,7 +43,16 @@ extern "C" { #define DEFAULT_BUFFER_SIZE 4096 #define DEFAULT_HEADERS_SIZE 512 -#define N_ELEMENTS(array) (sizeof(array) / sizeof(array[0])) +/* This macro expands to 0 if its parameter is an array, and generates a + * compilation error otherwise. This is used by the N_ELEMENTS() macro to catch + * invalid usages of this macro (e.g. when using arrays decayed to pointers in + * function parameters). */ +#define ZERO_IF_IS_ARRAY(array) \ + (!sizeof(char[1 - 2 * __builtin_types_compatible_p(typeof(array), \ + typeof(&(array)[0]))])) + +#define N_ELEMENTS(array) \ + (ZERO_IF_IS_ARRAY(array) | sizeof(array) / sizeof(array[0])) #define LWAN_NO_DISCARD(...) \ do { \ From 7615f9b609ad23c0a9a6f75a2f157b45b9342788 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 15 Apr 2020 23:03:35 -0700 Subject: [PATCH 1467/2505] Cleanup ATOMIC_*() macros --- src/lib/lwan-cache.c | 2 +- src/lib/lwan.h | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-cache.c b/src/lib/lwan-cache.c index a91fb7eca..0e3882f87 100644 --- a/src/lib/lwan-cache.c +++ b/src/lib/lwan-cache.c @@ -324,7 +324,7 @@ static bool cache_pruner_job(void *data) cache->cb.destroy_entry(node, cache->cb.context); } else { /* If not, some other thread had references to this object. */ - ATOMIC_BITWISE(&node->flags, or, FLOATING); + ATOMIC_OP(&node->flags, or, FLOATING); /* If in the time between the ref check above and setting the floating flag the * thread holding the reference drops it, if our reference is 0 after dropping it, * the pruner thread was the last thread holding the reference to this entry, so diff --git a/src/lib/lwan.h b/src/lib/lwan.h index ba223a7d1..22a0caaf8 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -158,10 +158,11 @@ static ALWAYS_INLINE uint64_t string_as_uint64(const char *s) #define UNLIKELY(x) LIKELY_IS((x), 0) #define ATOMIC_READ(V) (*(volatile typeof(V) *)&(V)) -#define ATOMIC_AAF(P, V) (__sync_add_and_fetch((P), (V))) +#define ATOMIC_OP(P, O, V) (__sync_##O##_and_fetch((P), (V))) +#define ATOMIC_AAF(P, V) ATOMIC_OP((P), add, (V)) +#define ATOMIC_SAF(P, V) ATOMIC_OP((P), sub, (V)) #define ATOMIC_INC(V) ATOMIC_AAF(&(V), 1) -#define ATOMIC_DEC(V) ATOMIC_AAF(&(V), -1) -#define ATOMIC_BITWISE(P, O, V) (__sync_##O##_and_fetch((P), (V))) +#define ATOMIC_DEC(V) ATOMIC_SAF(&(V), 1) #if defined(__cplusplus) #define LWAN_ARRAY_PARAM(length) [length] From 49e9a4e4c019479ea5c851f682c9acda28c63653 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 15 Apr 2020 23:03:49 -0700 Subject: [PATCH 1468/2505] Move some constant definitions to lwan-private.h as they're details --- src/lib/lwan-private.h | 3 +++ src/lib/lwan.h | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index d68bcd547..4c561ef0b 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -24,6 +24,9 @@ #include "lwan.h" +#define DEFAULT_BUFFER_SIZE 4096 +#define DEFAULT_HEADERS_SIZE 512 + #define LWAN_CONCAT(a_, b_) a_ ## b_ #define LWAN_TMP_ID_DETAIL(n_) LWAN_CONCAT(lwan_tmp_id, n_) #define LWAN_TMP_ID LWAN_TMP_ID_DETAIL(__COUNTER__) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 22a0caaf8..dcc264e47 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -40,9 +40,6 @@ extern "C" { #include "lwan-strbuf.h" #include "lwan-trie.h" -#define DEFAULT_BUFFER_SIZE 4096 -#define DEFAULT_HEADERS_SIZE 512 - /* This macro expands to 0 if its parameter is an array, and generates a * compilation error otherwise. This is used by the N_ELEMENTS() macro to catch * invalid usages of this macro (e.g. when using arrays decayed to pointers in From b4c9fd7a0158738b46520c07cc19745f470bcb0a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 15 Apr 2020 23:04:11 -0700 Subject: [PATCH 1469/2505] String switch macros are already endianess neutral --- src/lib/lwan.h | 36 ++++++++++++++---------------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index dcc264e47..a6c61a3be 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -94,19 +94,6 @@ extern "C" { #define ALWAYS_INLINE inline __attribute__((always_inline)) -#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ -#define STR4_INT(a, b, c, d) ((uint32_t)((a) | (b) << 8 | (c) << 16 | (d) << 24)) -#define STR2_INT(a, b) ((uint16_t)((a) | (b) << 8)) -#define STR8_INT(a, b, c, d, e, f, g, h) \ - ((uint64_t)STR4_INT(a, b, c, d) | (uint64_t)STR4_INT(e, f, g, h) << 32) -#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ -#define STR4_INT(d, c, b, a) ((uint32_t)((a) | (b) << 8 | (c) << 16 | (d) << 24)) -#define STR2_INT(b, a) ((uint16_t)((a) | (b) << 8)) -#define STR8_INT(a, b, c, d, e, f, g, h) \ - ((uint64_t)STR4_INT(a, b, c, d) << 32 | (uint64_t)STR4_INT(e, f, g, h)) -#elif __BYTE_ORDER__ == __ORDER_PDP_ENDIAN__ -#error A PDP? Seriously? -#endif static ALWAYS_INLINE uint16_t string_as_uint16(const char *s) { @@ -135,20 +122,25 @@ static ALWAYS_INLINE uint64_t string_as_uint64(const char *s) return u; } -#define LOWER2(s) ((s) | (uint16_t)0x2020) -#define LOWER4(s) ((s) | (uint32_t)0x20202020) -#define LOWER8(s) ((s) | (uint64_t)0x2020202020202020) +#define STR4_INT(a, b, c, d) ((uint32_t)((a) | (b) << 8 | (c) << 16 | (d) << 24)) +#define STR2_INT(a, b) ((uint16_t)((a) | (b) << 8)) +#define STR8_INT(a, b, c, d, e, f, g, h) \ + ((uint64_t)STR4_INT(a, b, c, d) | (uint64_t)STR4_INT(e, f, g, h) << 32) + +#define LOWER_CASE2(s) ((s) | (uint16_t)0x2020) +#define LOWER_CASE4(s) ((s) | (uint32_t)0x20202020) +#define LOWER_CASE8(s) ((s) | (uint64_t)0x2020202020202020) -#define STR2_INT_L(a, b) LOWER2(STR2_INT(a, b)) -#define STR4_INT_L(a, b, c, d) LOWER4(STR4_INT(a, b, c, d)) -#define STR8_INT_L(a, b, c, d, e, f, g, h) LOWER8(STR8_INT(a, b, c, d, e, f, g, h)) +#define STR2_INT_L(a, b) LOWER_CASE2(STR2_INT(a, b)) +#define STR4_INT_L(a, b, c, d) LOWER_CASE4(STR4_INT(a, b, c, d)) +#define STR8_INT_L(a, b, c, d, e, f, g, h) LOWER_CASE8(STR8_INT(a, b, c, d, e, f, g, h)) #define STRING_SWITCH_SMALL(s) switch (string_as_uint16(s)) -#define STRING_SWITCH_SMALL_L(s) switch (LOWER2(string_as_uint16(s))) +#define STRING_SWITCH_SMALL_L(s) switch (LOWER_CASE2(string_as_uint16(s))) #define STRING_SWITCH(s) switch (string_as_uint32(s)) -#define STRING_SWITCH_L(s) switch (LOWER4(string_as_uint32(s))) +#define STRING_SWITCH_L(s) switch (LOWER_CASE4(string_as_uint32(s))) #define STRING_SWITCH_LARGE(s) switch (string_as_uint64(s)) -#define STRING_SWITCH_LARGE_L(s) switch (LOWER8(string_as_uint64(s))) +#define STRING_SWITCH_LARGE_L(s) switch (LOWER_CASE8(string_as_uint64(s))) #define LIKELY_IS(x, y) __builtin_expect((x), (y)) #define LIKELY(x) LIKELY_IS(!!(x), 1) From c0717b8e4679bbd3c8106ac515c9064db12a7c53 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 16 Apr 2020 08:41:38 -0700 Subject: [PATCH 1470/2505] Fix build when typeof() isn't available Credit to OSS-Fuzz: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=21723 --- src/lib/lwan.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index a6c61a3be..b6cf77e17 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -45,8 +45,8 @@ extern "C" { * invalid usages of this macro (e.g. when using arrays decayed to pointers in * function parameters). */ #define ZERO_IF_IS_ARRAY(array) \ - (!sizeof(char[1 - 2 * __builtin_types_compatible_p(typeof(array), \ - typeof(&(array)[0]))])) + (!sizeof(char[1 - 2 * __builtin_types_compatible_p( \ + __typeof__(array), __typeof__(&(array)[0]))])) #define N_ELEMENTS(array) \ (ZERO_IF_IS_ARRAY(array) | sizeof(array) / sizeof(array[0])) From 7c39e70fcbcc97ab1ee6a28e66ade0e46834a44b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 18 Apr 2020 21:57:51 -0700 Subject: [PATCH 1471/2505] IF a finalizer returns FINALIZER_TRY_AGAIN, yield with WANT_READ Otherwise, the coroutine might never be resumed again. --- src/lib/lwan-request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 0d5d52c19..cb7befd3d 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -847,10 +847,10 @@ read_from_request_socket(struct lwan_request *request, if (n < 0) { switch (errno) { case EAGAIN: +yield_and_read_again: coro_yield(request->conn->coro, CONN_CORO_WANT_READ); continue; case EINTR: -yield_and_read_again: coro_yield(request->conn->coro, CONN_CORO_YIELD); continue; } From 7b453d62f4123387232ebdb1548dbe4930b88054 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 18 Apr 2020 22:04:06 -0700 Subject: [PATCH 1472/2505] Use LWAN_MIN() instead of defining an ad-hoc min_size() function --- src/lib/lwan-io-wrappers.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index ca30c634e..70e13ec96 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -202,8 +202,6 @@ lwan_recv(struct lwan_request *request, void *buf, size_t count, int flags) } #if defined(__linux__) -static inline size_t min_size(size_t a, size_t b) { return (a > b) ? b : a; } - void lwan_sendfile(struct lwan_request *request, int in_fd, off_t offset, @@ -211,7 +209,7 @@ void lwan_sendfile(struct lwan_request *request, const char *header, size_t header_len) { - size_t chunk_size = min_size(count, 1 << 17); + size_t chunk_size = LWAN_MIN(count, 1ul << 17); size_t to_be_written = count; lwan_send(request, header, header_len, MSG_MORE); @@ -233,7 +231,7 @@ void lwan_sendfile(struct lwan_request *request, if (!to_be_written) break; - chunk_size = min_size(to_be_written, 1 << 19); + chunk_size = LWAN_MIN(to_be_written, 1ul << 19); lwan_readahead_queue(in_fd, offset, chunk_size); try_again: @@ -284,8 +282,6 @@ void lwan_sendfile(struct lwan_request *request, } while (total_written < count); } #else -static inline size_t min_size(size_t a, size_t b) { return (a > b) ? b : a; } - static size_t try_pread_file(struct lwan_request *request, int fd, void *buffer, @@ -336,8 +332,8 @@ void lwan_sendfile(struct lwan_request *request, lwan_send(request, header, header_len, MSG_MORE); while (count) { - size_t bytes_read = try_pread_file(request, in_fd, buffer, - min_size(count, sizeof(buffer)), offset); + size_t bytes_read = try_pread_file( + request, in_fd, buffer, LWAN_MIN(count, sizeof(buffer)), offset); lwan_send(request, buffer, bytes_read, 0); count -= bytes_read; offset += bytes_read; From 2c56b36c50bccda7dd8f97cb2d9829aa478cfd98 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 18 Apr 2020 22:04:23 -0700 Subject: [PATCH 1473/2505] For the sendfile() fallback, yield when pread() EINTRs/EAGAINs This avoids turning the try_pread_file() into an infinite loop and gives some time for other connections in this I/O thread. --- src/lib/lwan-io-wrappers.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index 70e13ec96..82d30cb92 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -299,23 +299,22 @@ static size_t try_pread_file(struct lwan_request *request, switch (errno) { case EAGAIN: case EINTR: - goto try_again; + /* fd is a file, re-read -- but give other coros some time, too */ + coro_yield(request->conn->coro, CONN_CORO_YIELD); + continue; default: - goto out; + break; } } total_read += (size_t)r; - offset += r; - if (total_read == len || r == 0) + + if (r == 0 || total_read == len) return total_read; - try_again: - /* fd is a file; just re-read */ - (void)0; + offset += r; } -out: coro_yield(request->conn->coro, CONN_CORO_ABORT); __builtin_unreachable(); } From 242789391d0ffb84f015364b5ab12b23b0c7e655 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 20 Apr 2020 17:47:39 -0700 Subject: [PATCH 1474/2505] Add OpenBSD build bot information --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c50cef2e6..4828fcae0 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,13 @@ The [project web site](https://lwan.ws/) contains more details. Build status ------------ -| OS | Arch | Release | Debug | Static Analysis | Tests | -|---------|--------|---------|-------|-----------------|------------| -| Linux | x86_64 | ![release](https://shield.lwan.ws/img/gycKbr/release "Release") | ![debug](https://shield.lwan.ws/img/gycKbr/debug "Debug") | ![static-analysis](https://shield.lwan.ws/img/gycKbr/clang-analyze "Static Analysis") ![coverity](https://scan.coverity.com/projects/375/badge.svg) [Report history](https://buildbot.lwan.ws/sa/) | ![tests](https://shield.lwan.ws/img/gycKbr/unit-tests "Test") [![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/lwan.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:lwan) | -| Linux | armv7 | ![release-arm](https://shield.lwan.ws/img/gycKbr/release-arm "Release") | ![debug-arm](https://shield.lwan.ws/img/gycKbr/debug-arm "Debug") | | | -| FreeBSD | x86_64 | ![freebsd-release](https://shield.lwan.ws/img/gycKbr/release-freebsd "Release FreeBSD") | ![freebsd-debug](https://shield.lwan.ws/img/gycKbr/debug-freebsd "Debug FreeBSD") | | | -| macOS | x86_64 | ![osx-release](https://shield.lwan.ws/img/gycKbr/release-sierra "Release macOS") | ![osx-debug](https://shield.lwan.ws/img/gycKbr/debug-sierra "Debug macOS") | | | +| OS | Arch | Release | Debug | Static Analysis | Tests | +|-------------|--------|---------|-------|-----------------|------------| +| Linux | x86_64 | ![release](https://shield.lwan.ws/img/gycKbr/release "Release") | ![debug](https://shield.lwan.ws/img/gycKbr/debug "Debug") | ![static-analysis](https://shield.lwan.ws/img/gycKbr/clang-analyze "Static Analysis") ![coverity](https://scan.coverity.com/projects/375/badge.svg) [Report history](https://buildbot.lwan.ws/sa/) | ![tests](https://shield.lwan.ws/img/gycKbr/unit-tests "Test") [![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/lwan.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:lwan) | +| Linux | armv7 | ![release-arm](https://shield.lwan.ws/img/gycKbr/release-arm "Release") | ![debug-arm](https://shield.lwan.ws/img/gycKbr/debug-arm "Debug") | | | +| FreeBSD | x86_64 | ![freebsd-release](https://shield.lwan.ws/img/gycKbr/release-freebsd "Release FreeBSD") | ![freebsd-debug](https://shield.lwan.ws/img/gycKbr/debug-freebsd "Debug FreeBSD") | | | +| macOS | x86_64 | ![osx-release](https://shield.lwan.ws/img/gycKbr/release-sierra "Release macOS") | ![osx-debug](https://shield.lwan.ws/img/gycKbr/debug-sierra "Debug macOS") | | | +| OpenBSD 6.6 | x86_64 | ![openbsd-release](https://shield.lwan.ws/img/gycKbr/release-openbsd "Release OpenBSD") | ![openbsd-debug](https://shield.lwan.ws/img/gycKbr/debug-openbsd "Debug OpenBSD") | | ![openbsd-tests](https://shield.lwan.ws/img/gycKbr/openbsd-unit-tests "OpenBSD Tests") | Building -------- From cee893e1909ec932975488eb816340e657ed0582 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 20 Apr 2020 17:50:29 -0700 Subject: [PATCH 1475/2505] ZERO_IF_IS_ARRAY() doesn't work in C++, so fuzzers can't be built --- src/lib/lwan.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index b6cf77e17..87c64c1bd 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -40,6 +40,9 @@ extern "C" { #include "lwan-strbuf.h" #include "lwan-trie.h" +#if defined(__cplusplus) +#define ZERO_IF_IS_ARRAY(array) 0 +#else /* This macro expands to 0 if its parameter is an array, and generates a * compilation error otherwise. This is used by the N_ELEMENTS() macro to catch * invalid usages of this macro (e.g. when using arrays decayed to pointers in @@ -47,6 +50,7 @@ extern "C" { #define ZERO_IF_IS_ARRAY(array) \ (!sizeof(char[1 - 2 * __builtin_types_compatible_p( \ __typeof__(array), __typeof__(&(array)[0]))])) +#endif #define N_ELEMENTS(array) \ (ZERO_IF_IS_ARRAY(array) | sizeof(array) / sizeof(array[0])) From fee2b795675b517ccc8d0aa250e39f0ed9bf3b72 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 24 Apr 2020 08:22:35 -0700 Subject: [PATCH 1476/2505] Build with -Wunsequenced if available --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index b49b2c50d..714b51907 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -268,6 +268,7 @@ enable_warning_if_supported(-Wdouble-promotion) enable_warning_if_supported(-Wno-unused-parameter) enable_warning_if_supported(-Wstringop-truncation) enable_warning_if_supported(-Wvla) +enable_warning_if_supported(-Wunsequenced) # While a useful warning, this is giving false positives. enable_warning_if_supported(-Wno-free-nonheap-object) From 2eaad0c8a9726d0f9ec6637c785f40bff5a48910 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 25 Apr 2020 11:00:32 -0700 Subject: [PATCH 1477/2505] Revert "String switch macros are already endianess neutral" While the STRn_INT macros are endianess neutral, the functions used by STRING_SWITCHn macros aren't. This reverts commit b4c9fd7a0158738b46520c07cc19745f470bcb0a. --- src/lib/lwan.h | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 87c64c1bd..5049f51ac 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -98,6 +98,19 @@ extern "C" { #define ALWAYS_INLINE inline __attribute__((always_inline)) +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define STR4_INT(a, b, c, d) ((uint32_t)((a) | (b) << 8 | (c) << 16 | (d) << 24)) +#define STR2_INT(a, b) ((uint16_t)((a) | (b) << 8)) +#define STR8_INT(a, b, c, d, e, f, g, h) \ + ((uint64_t)STR4_INT(a, b, c, d) | (uint64_t)STR4_INT(e, f, g, h) << 32) +#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#define STR4_INT(d, c, b, a) ((uint32_t)((a) | (b) << 8 | (c) << 16 | (d) << 24)) +#define STR2_INT(b, a) ((uint16_t)((a) | (b) << 8)) +#define STR8_INT(a, b, c, d, e, f, g, h) \ + ((uint64_t)STR4_INT(a, b, c, d) << 32 | (uint64_t)STR4_INT(e, f, g, h)) +#elif __BYTE_ORDER__ == __ORDER_PDP_ENDIAN__ +#error A PDP? Seriously? +#endif static ALWAYS_INLINE uint16_t string_as_uint16(const char *s) { @@ -126,25 +139,20 @@ static ALWAYS_INLINE uint64_t string_as_uint64(const char *s) return u; } -#define STR4_INT(a, b, c, d) ((uint32_t)((a) | (b) << 8 | (c) << 16 | (d) << 24)) -#define STR2_INT(a, b) ((uint16_t)((a) | (b) << 8)) -#define STR8_INT(a, b, c, d, e, f, g, h) \ - ((uint64_t)STR4_INT(a, b, c, d) | (uint64_t)STR4_INT(e, f, g, h) << 32) - -#define LOWER_CASE2(s) ((s) | (uint16_t)0x2020) -#define LOWER_CASE4(s) ((s) | (uint32_t)0x20202020) -#define LOWER_CASE8(s) ((s) | (uint64_t)0x2020202020202020) +#define LOWER2(s) ((s) | (uint16_t)0x2020) +#define LOWER4(s) ((s) | (uint32_t)0x20202020) +#define LOWER8(s) ((s) | (uint64_t)0x2020202020202020) -#define STR2_INT_L(a, b) LOWER_CASE2(STR2_INT(a, b)) -#define STR4_INT_L(a, b, c, d) LOWER_CASE4(STR4_INT(a, b, c, d)) -#define STR8_INT_L(a, b, c, d, e, f, g, h) LOWER_CASE8(STR8_INT(a, b, c, d, e, f, g, h)) +#define STR2_INT_L(a, b) LOWER2(STR2_INT(a, b)) +#define STR4_INT_L(a, b, c, d) LOWER4(STR4_INT(a, b, c, d)) +#define STR8_INT_L(a, b, c, d, e, f, g, h) LOWER8(STR8_INT(a, b, c, d, e, f, g, h)) #define STRING_SWITCH_SMALL(s) switch (string_as_uint16(s)) -#define STRING_SWITCH_SMALL_L(s) switch (LOWER_CASE2(string_as_uint16(s))) +#define STRING_SWITCH_SMALL_L(s) switch (LOWER2(string_as_uint16(s))) #define STRING_SWITCH(s) switch (string_as_uint32(s)) -#define STRING_SWITCH_L(s) switch (LOWER_CASE4(string_as_uint32(s))) +#define STRING_SWITCH_L(s) switch (LOWER4(string_as_uint32(s))) #define STRING_SWITCH_LARGE(s) switch (string_as_uint64(s)) -#define STRING_SWITCH_LARGE_L(s) switch (LOWER_CASE8(string_as_uint64(s))) +#define STRING_SWITCH_LARGE_L(s) switch (LOWER8(string_as_uint64(s))) #define LIKELY_IS(x, y) __builtin_expect((x), (y)) #define LIKELY(x) LIKELY_IS(!!(x), 1) From 4b4b59f241c9e651f773541ce1429fdf2f7bb570 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 13 May 2020 23:41:29 -0700 Subject: [PATCH 1478/2505] Use some LIKELY()/UNLIKELY() macros when generating responses --- src/lib/lwan-response.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 85971a5f8..1d96b3125 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -168,7 +168,7 @@ void lwan_response(struct lwan_request *request, enum lwan_http_status status) status = HTTP_INTERNAL_ERROR; } - if (status >= HTTP_CLASS__CLIENT_ERROR) { + if (UNLIKELY(status >= HTTP_CLASS__CLIENT_ERROR)) { request->flags &= ~RESPONSE_STREAM; lwan_default_response(request, status); } @@ -267,22 +267,22 @@ size_t lwan_prepare_response_header_full( p_headers = headers; - if (request->flags & REQUEST_IS_HTTP_1_0) + if (UNLIKELY(request->flags & REQUEST_IS_HTTP_1_0)) APPEND_CONSTANT("HTTP/1.0 "); else APPEND_CONSTANT("HTTP/1.1 "); APPEND_STRING(lwan_http_status_as_string_with_code(status)); - if (request->flags & RESPONSE_CHUNKED_ENCODING) { + if (UNLIKELY(request->flags & RESPONSE_CHUNKED_ENCODING)) { APPEND_CONSTANT("\r\nTransfer-Encoding: chunked"); - } else if (request->flags & RESPONSE_NO_CONTENT_LENGTH) { + } else if (UNLIKELY(request->flags & RESPONSE_NO_CONTENT_LENGTH)) { /* Do nothing. */ } else if (!(request->flags & RESPONSE_STREAM)) { APPEND_CONSTANT("\r\nContent-Length: "); APPEND_UINT(lwan_strbuf_get_length(request->response.buffer)); } - if ((status < HTTP_BAD_REQUEST && additional_headers)) { + if (LIKELY((status < HTTP_BAD_REQUEST && additional_headers))) { const struct lwan_key_value *header; for (header = additional_headers; header->key; header++) { @@ -309,7 +309,7 @@ size_t lwan_prepare_response_header_full( APPEND_CHAR_NOCHECK(' '); APPEND_STRING(header->value); } - } else if (status == HTTP_NOT_AUTHORIZED) { + } else if (UNLIKELY(status == HTTP_NOT_AUTHORIZED)) { const struct lwan_key_value *header; for (header = additional_headers; header->key; header++) { @@ -321,10 +321,10 @@ size_t lwan_prepare_response_header_full( } } - if (request->conn->flags & CONN_IS_UPGRADE) { + if (UNLIKELY(request->conn->flags & CONN_IS_UPGRADE)) { APPEND_CONSTANT("\r\nConnection: Upgrade"); } else { - if (request->conn->flags & CONN_IS_KEEP_ALIVE) { + if (LIKELY(request->conn->flags & CONN_IS_KEEP_ALIVE)) { APPEND_CONSTANT("\r\nConnection: keep-alive"); } else { APPEND_CONSTANT("\r\nConnection: close"); @@ -346,7 +346,7 @@ size_t lwan_prepare_response_header_full( APPEND_STRING_LEN(request->conn->thread->date.expires, 29); } - if (request->flags & REQUEST_ALLOW_CORS) { + if (UNLIKELY(request->flags & REQUEST_ALLOW_CORS)) { APPEND_CONSTANT( "\r\nAccess-Control-Allow-Origin: *" "\r\nAccess-Control-Allow-Methods: GET, POST, OPTIONS" From a013ec5c1ed035c11bc4ee918f12f4ac04ab74d5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 13 May 2020 23:41:50 -0700 Subject: [PATCH 1479/2505] Assert await_fd is within expected bounds --- src/lib/lwan-thread.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index fd7c5c36a..60209b64b 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -276,6 +276,7 @@ static ALWAYS_INLINE void resume_coro(struct timeout_queue *tq, assert(event.events != 0); assert(await_fd >= 0); + assert(await_fd <= tq->lwan->thread.count * tq->lwan->thread.max_fd); if (yield_result == CONN_CORO_ASYNC_RESUME) { op = EPOLL_CTL_DEL; From 46bbe78e43732d90606062948a1fa635d125e3b9 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 21 May 2020 18:05:54 -0700 Subject: [PATCH 1480/2505] Fix comment in lwan_readv() --- src/lib/lwan-io-wrappers.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index 82d30cb92..d44435f84 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -93,7 +93,7 @@ lwan_readv(struct lwan_request *request, struct iovec *iov, int iov_count) ssize_t bytes_read = readv(request->fd, iov + curr_iov, iov_count - curr_iov); if (UNLIKELY(bytes_read < 0)) { - /* FIXME: Consider short writes as another try as well? */ + /* FIXME: Consider short reads as another try as well? */ tries--; switch (errno) { From 6809d3ae24bbb302adb6e808886e48abff016f6c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 18 May 2020 11:22:47 -0700 Subject: [PATCH 1481/2505] Fix compile warning when building a Debug build with GCC 10 --- src/lib/lwan-thread.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 60209b64b..2f489c815 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -276,7 +276,7 @@ static ALWAYS_INLINE void resume_coro(struct timeout_queue *tq, assert(event.events != 0); assert(await_fd >= 0); - assert(await_fd <= tq->lwan->thread.count * tq->lwan->thread.max_fd); + assert(await_fd <= (int)(tq->lwan->thread.count * tq->lwan->thread.max_fd)); if (yield_result == CONN_CORO_ASYNC_RESUME) { op = EPOLL_CTL_DEL; From 60cd81a4b64d8f9cb18b90981a0f4faa23030fcf Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 21 May 2020 17:55:45 -0700 Subject: [PATCH 1482/2505] Reduce number of yields/epoll_ctl() calls by half in async/await --- src/lib/lwan-request.c | 8 ++--- src/lib/lwan-thread.c | 80 ++++++++++++++++++++---------------------- src/lib/lwan.h | 1 - 3 files changed, 40 insertions(+), 49 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index cb7befd3d..eb5e9f842 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1759,13 +1759,9 @@ static inline void async_await_fd(struct coro *coro, enum lwan_connection_coro_yield events) { assert(events >= CONN_CORO_ASYNC_AWAIT_READ && - events <= CONN_CORO_ASYNC_RESUME); + events <= CONN_CORO_ASYNC_AWAIT_READ_WRITE); - coro_yield(coro, make_async_yield_value(fd, events)); - - /* FIXME: it should be possible to remove this second yield */ - return (void)coro_yield(coro, - make_async_yield_value(fd, CONN_CORO_ASYNC_RESUME)); + return (void)coro_yield(coro, make_async_yield_value(fd, events)); } void lwan_request_await_read(struct lwan_request *r, int fd) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 2f489c815..244fdbbe0 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -207,10 +207,6 @@ static void update_epoll_flags(int fd, * suspended. So set both flags here. This works because EPOLLET isn't * used. */ [CONN_CORO_RESUME] = CONN_EVENTS_RESUME, - - [CONN_CORO_ASYNC_AWAIT_READ] = CONN_EVENTS_ASYNC_READ, - [CONN_CORO_ASYNC_AWAIT_WRITE] = CONN_EVENTS_ASYNC_WRITE, - [CONN_CORO_ASYNC_AWAIT_READ_WRITE] = CONN_EVENTS_ASYNC_READ_WRITE, }; static const enum lwan_connection_flags and_mask[CONN_CORO_MAX] = { [CONN_CORO_YIELD] = ~0, @@ -222,10 +218,6 @@ static void update_epoll_flags(int fd, [CONN_CORO_SUSPEND_TIMER] = ~(CONN_EVENTS_READ_WRITE | CONN_SUSPENDED_ASYNC_AWAIT), [CONN_CORO_SUSPEND_ASYNC_AWAIT] = ~(CONN_EVENTS_READ_WRITE | CONN_SUSPENDED_TIMER), [CONN_CORO_RESUME] = ~CONN_SUSPENDED, - - [CONN_CORO_ASYNC_AWAIT_READ] = ~0, - [CONN_CORO_ASYNC_AWAIT_WRITE] = ~0, - [CONN_CORO_ASYNC_AWAIT_READ_WRITE] = ~0, }; enum lwan_connection_flags prev_flags = conn->flags; @@ -244,6 +236,41 @@ static void update_epoll_flags(int fd, lwan_status_perror("epoll_ctl"); } +static enum lwan_connection_coro_yield +resume_async(enum lwan_connection_coro_yield yield_result, + int64_t from_coro, + struct lwan_connection *conn, + int epoll_fd) +{ + assert(yield_result >= CONN_CORO_ASYNC_AWAIT_READ && + yield_result <= CONN_CORO_ASYNC_AWAIT_READ_WRITE); + + static const enum lwan_connection_flags to_connection_flags[] = { + [CONN_CORO_ASYNC_AWAIT_READ] = CONN_EVENTS_ASYNC_READ, + [CONN_CORO_ASYNC_AWAIT_WRITE] = CONN_EVENTS_ASYNC_WRITE, + [CONN_CORO_ASYNC_AWAIT_READ_WRITE] = CONN_EVENTS_ASYNC_READ_WRITE, + }; + struct epoll_event event = { + .events = conn_flags_to_epoll_events(to_connection_flags[yield_result]), + .data.ptr = conn, + }; + int await_fd = (int)((uint64_t)from_coro >> 32); + + assert(event.events != 0); + assert(await_fd >= 0); + + /* FIXME: maybe store the state in the lwan_connection array to avoid trying + * this syscall twice the first time? */ + if (epoll_ctl(epoll_fd, EPOLL_CTL_MOD, await_fd, &event) < 0) { + if (UNLIKELY(errno != ENOENT)) + return CONN_CORO_ABORT; + if (UNLIKELY(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, await_fd, &event) < 0)) + return CONN_CORO_ABORT; + } + + return CONN_CORO_SUSPEND_ASYNC_AWAIT; +} + static ALWAYS_INLINE void resume_coro(struct timeout_queue *tq, struct lwan_connection *conn, int epoll_fd) @@ -253,43 +280,12 @@ static ALWAYS_INLINE void resume_coro(struct timeout_queue *tq, int64_t from_coro = coro_resume(conn->coro); enum lwan_connection_coro_yield yield_result = from_coro & 0xffffffff; + if (UNLIKELY(yield_result >= CONN_CORO_ASYNC)) + yield_result = resume_async(yield_result, from_coro, conn, epoll_fd); + if (UNLIKELY(yield_result == CONN_CORO_ABORT)) return timeout_queue_expire(tq, conn); - if (UNLIKELY(yield_result >= CONN_CORO_ASYNC)) { - assert(yield_result >= CONN_CORO_ASYNC_AWAIT_READ && - yield_result <= CONN_CORO_ASYNC_RESUME); - - static const enum lwan_connection_flags to_connection_flags[] = { - [CONN_CORO_ASYNC_AWAIT_READ] = CONN_EVENTS_ASYNC_READ, - [CONN_CORO_ASYNC_AWAIT_WRITE] = CONN_EVENTS_ASYNC_WRITE, - [CONN_CORO_ASYNC_AWAIT_READ_WRITE] = CONN_EVENTS_ASYNC_READ_WRITE, - [CONN_CORO_ASYNC_RESUME] = 0, - }; - struct epoll_event event = { - .events = - conn_flags_to_epoll_events(to_connection_flags[yield_result]), - .data.ptr = conn, - }; - int await_fd = (int)((uint64_t)from_coro >> 32); - int op; - - assert(event.events != 0); - assert(await_fd >= 0); - assert(await_fd <= (int)(tq->lwan->thread.count * tq->lwan->thread.max_fd)); - - if (yield_result == CONN_CORO_ASYNC_RESUME) { - op = EPOLL_CTL_DEL; - yield_result = CONN_CORO_RESUME; - } else { - op = EPOLL_CTL_ADD; - yield_result = CONN_CORO_SUSPEND_ASYNC_AWAIT; - } - - if (UNLIKELY(epoll_ctl(epoll_fd, op, await_fd, &event) < 0)) - lwan_status_perror("epoll_ctl"); - } - return update_epoll_flags(lwan_connection_get_fd(tq->lwan, conn), conn, epoll_fd, yield_result); } diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 5049f51ac..c34f1daa2 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -314,7 +314,6 @@ enum lwan_connection_coro_yield { CONN_CORO_ASYNC_AWAIT_READ, CONN_CORO_ASYNC_AWAIT_WRITE, CONN_CORO_ASYNC_AWAIT_READ_WRITE, - CONN_CORO_ASYNC_RESUME, CONN_CORO_MAX, From 489eefad2ef827cd169921c64b122d0ea319ab2b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 21 May 2020 18:05:04 -0700 Subject: [PATCH 1483/2505] Use writev() instead of sendmsg() if flags is 0 writev() on Linux only copies the io vector to validate it; sendmsg() requires a memory allocation in order to do so. --- src/lib/lwan-io-wrappers.c | 21 ++++++++++++++------- src/lib/lwan-io-wrappers.h | 2 +- src/lib/lwan-response.c | 2 +- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index d44435f84..63599d5ae 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -32,21 +32,28 @@ static const int MAX_FAILED_TRIES = 5; ssize_t -lwan_writev(struct lwan_request *request, struct iovec *iov, size_t iov_count) +lwan_writev(struct lwan_request *request, struct iovec *iov, int iov_count) { ssize_t total_written = 0; - size_t curr_iov = 0; + int curr_iov = 0; int flags = 0; if (request->conn->flags & CONN_CORK) flags |= MSG_MORE; for (int tries = MAX_FAILED_TRIES; tries;) { - struct msghdr hdr = { - .msg_iov = iov + curr_iov, - .msg_iovlen = iov_count - curr_iov, - }; - ssize_t written = sendmsg(request->fd, &hdr, flags); + ssize_t written; + + if (flags) { + struct msghdr hdr = { + .msg_iov = iov + curr_iov, + .msg_iovlen = (size_t)(iov_count - curr_iov), + }; + written = sendmsg(request->fd, &hdr, flags); + } else { + written = writev(request->fd, iov + curr_iov, iov_count - curr_iov); + } + if (UNLIKELY(written < 0)) { /* FIXME: Consider short writes as another try as well? */ tries--; diff --git a/src/lib/lwan-io-wrappers.h b/src/lib/lwan-io-wrappers.h index 41b9a241d..de60ab025 100644 --- a/src/lib/lwan-io-wrappers.h +++ b/src/lib/lwan-io-wrappers.h @@ -25,7 +25,7 @@ #include "lwan.h" ssize_t lwan_writev(struct lwan_request *request, struct iovec *iov, - size_t iovcnt); + int iovcnt); ssize_t lwan_send(struct lwan_request *request, const void *buf, size_t count, int flags); void lwan_sendfile(struct lwan_request *request, int in_fd, diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 1d96b3125..0d44168c0 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -459,7 +459,7 @@ bool lwan_response_set_event_stream(struct lwan_request *request, void lwan_response_send_event(struct lwan_request *request, const char *event) { struct iovec vec[6]; - size_t last = 0; + int last = 0; if (!(request->flags & RESPONSE_SENT_HEADERS)) { if (UNLIKELY(!lwan_response_set_event_stream(request, HTTP_OK))) From 95754ab4caa8badbac54a3390185e912ec5b06b6 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 26 May 2020 08:34:15 -0700 Subject: [PATCH 1484/2505] The only flag that can be set in lwan_writev() is MSG_MORE No need to bitwise-and with flags, just use a ternary to initialize it. --- src/lib/lwan-io-wrappers.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index 63599d5ae..653cb30cf 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -36,10 +36,7 @@ lwan_writev(struct lwan_request *request, struct iovec *iov, int iov_count) { ssize_t total_written = 0; int curr_iov = 0; - int flags = 0; - - if (request->conn->flags & CONN_CORK) - flags |= MSG_MORE; + int flags = (request->conn->flags & CONN_CORK) ? MSG_MORE : 0; for (int tries = MAX_FAILED_TRIES; tries;) { ssize_t written; From ee7907cb3e74be1a27d4080cf3c3ef1bb002a123 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 26 May 2020 18:01:47 -0700 Subject: [PATCH 1485/2505] Reduce epoll chatter while sending chunked encoding responses No need to yield with CONN_CORO_WANT_WRITE -- lwan_writev() will already do so if necessary. --- src/lib/lwan-response.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 0d44168c0..4168ee0b2 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -431,7 +431,6 @@ void lwan_response_send_chunk(struct lwan_request *request) lwan_writev(request, chunk_vec, N_ELEMENTS(chunk_vec)); lwan_strbuf_reset(request->response.buffer); - coro_yield(request->conn->coro, CONN_CORO_WANT_WRITE); } bool lwan_response_set_event_stream(struct lwan_request *request, From 2a1072525779ae6b0ff625a959a8423b123d970e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 26 May 2020 18:03:18 -0700 Subject: [PATCH 1486/2505] Reduce epoll chatter when resuming from timer Ideally, when suspending a coroutine, the current flags&CONN_EVENTS_MASK would have to be stored and restored -- however, resuming as if the client coroutine is interested in a write event always guarantees that they'll be resumed as they're TCP sockets. There's a good chance that trying to read from a socket after resuming a coroutine will succeed, but if it doesn't because read() returns -EAGAIN, the I/O wrappers will yield with CONN_CORO_WANT_READ anyway. --- src/lib/lwan-thread.c | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 244fdbbe0..45b1b2bc5 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -173,16 +173,6 @@ conn_flags_to_epoll_events(enum lwan_connection_flags flags) return map[flags & CONN_EVENTS_MASK]; } -#if defined(__linux__) -# define CONN_EVENTS_RESUME CONN_EVENTS_READ_WRITE -#else -/* Kqueue doesn't like when you filter on both read and write, so - * wait only on write when resuming a coro suspended by a timer. - * The I/O wrappers should yield if trying to read without anything - * in the buffer, changing the filter to only read, so this is OK. */ -# define CONN_EVENTS_RESUME CONN_EVENTS_WRITE -#endif - static void update_epoll_flags(int fd, struct lwan_connection *conn, int epoll_fd, @@ -202,11 +192,14 @@ static void update_epoll_flags(int fd, [CONN_CORO_SUSPEND_TIMER] = CONN_SUSPENDED_TIMER, [CONN_CORO_SUSPEND_ASYNC_AWAIT] = CONN_SUSPENDED_ASYNC_AWAIT, - /* Either EPOLLIN or EPOLLOUT have to be set here. There's no need to - * know which event, because they were both cleared when the coro was - * suspended. So set both flags here. This works because EPOLLET isn't - * used. */ - [CONN_CORO_RESUME] = CONN_EVENTS_RESUME, + /* Ideally, when suspending a coroutine, the current flags&CONN_EVENTS_MASK + * would have to be stored and restored -- however, resuming as if the + * client coroutine is interested in a write event always guarantees that + * they'll be resumed as they're TCP sockets. There's a good chance that + * trying to read from a socket after resuming a coroutine will succeed, + * but if it doesn't because read() returns -EAGAIN, the I/O wrappers will + * yield with CONN_CORO_WANT_READ anyway. */ + [CONN_CORO_RESUME] = CONN_EVENTS_WRITE, }; static const enum lwan_connection_flags and_mask[CONN_CORO_MAX] = { [CONN_CORO_YIELD] = ~0, From 33d9a9a57f4638690d529a5e3808f01dc96551a8 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 26 May 2020 18:04:13 -0700 Subject: [PATCH 1487/2505] Reduce number of epoll_ctl() calls when doing async/await by 1/fd Instead of issuing a epoll_ctl(EPOLL_CTL_MOD) expecting a -EEXIST error to issue a epoll_ctl(EPOLL_CTL_ADD), utilize the fact that the lwan_connection struct array is indexed by file descriptor and all flags should be cleared up on startup to store a flag stating that a particular file descriptor isn't set up for async/await yet. (The flag is cleared whenever the request coroutine dies through a coro_defer call.) --- src/lib/lwan-thread.c | 32 +++++++++++++++++++++----------- src/lib/lwan.h | 5 +++++ 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 45b1b2bc5..c4bdbdf33 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -229,8 +229,16 @@ static void update_epoll_flags(int fd, lwan_status_perror("epoll_ctl"); } +static void clear_async_await_flag(void *data) +{ + struct lwan_connection *async_fd_conn = data; + + async_fd_conn->flags &= ~CONN_ASYNC_AWAIT; +} + static enum lwan_connection_coro_yield -resume_async(enum lwan_connection_coro_yield yield_result, +resume_async(struct timeout_queue *tq, + enum lwan_connection_coro_yield yield_result, int64_t from_coro, struct lwan_connection *conn, int epoll_fd) @@ -252,16 +260,18 @@ resume_async(enum lwan_connection_coro_yield yield_result, assert(event.events != 0); assert(await_fd >= 0); - /* FIXME: maybe store the state in the lwan_connection array to avoid trying - * this syscall twice the first time? */ - if (epoll_ctl(epoll_fd, EPOLL_CTL_MOD, await_fd, &event) < 0) { - if (UNLIKELY(errno != ENOENT)) - return CONN_CORO_ABORT; - if (UNLIKELY(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, await_fd, &event) < 0)) - return CONN_CORO_ABORT; + struct lwan_connection *await_fd_conn = &tq->lwan->conns[await_fd]; + int ret; + if (await_fd_conn->flags & CONN_ASYNC_AWAIT) { + ret = epoll_ctl(epoll_fd, EPOLL_CTL_MOD, await_fd, &event); + } else { + ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, await_fd, &event); + if (LIKELY(!ret)) { + await_fd_conn->flags |= CONN_ASYNC_AWAIT; + coro_defer(conn->coro, clear_async_await_flag, await_fd_conn); + } } - - return CONN_CORO_SUSPEND_ASYNC_AWAIT; + return LIKELY(!ret) ? CONN_CORO_SUSPEND_ASYNC_AWAIT : CONN_CORO_ABORT; } static ALWAYS_INLINE void resume_coro(struct timeout_queue *tq, @@ -274,7 +284,7 @@ static ALWAYS_INLINE void resume_coro(struct timeout_queue *tq, enum lwan_connection_coro_yield yield_result = from_coro & 0xffffffff; if (UNLIKELY(yield_result >= CONN_CORO_ASYNC)) - yield_result = resume_async(yield_result, from_coro, conn, epoll_fd); + yield_result = resume_async(tq, yield_result, from_coro, conn, epoll_fd); if (UNLIKELY(yield_result == CONN_CORO_ABORT)) return timeout_queue_expire(tq, conn); diff --git a/src/lib/lwan.h b/src/lib/lwan.h index c34f1daa2..0e53067f5 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -294,6 +294,11 @@ enum lwan_connection_flags { CONN_SUSPENDED = CONN_SUSPENDED_TIMER | CONN_SUSPENDED_ASYNC_AWAIT, CONN_CORK = 1 << 9, + + /* Set only on file descriptors being watched by async/await to determine + * which epoll operation to use when suspending/resuming (ADD/MOD). Reset + * whenever associated client connection is closed. */ + CONN_ASYNC_AWAIT = 1 << 10, }; enum lwan_connection_coro_yield { From 659f2176011c6349c892e0c80a088181aa32812c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 27 May 2020 21:18:20 -0700 Subject: [PATCH 1488/2505] Avoid calling stat("/etc/localtime") in clock sample on UTC systems --- src/samples/clock/main.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/samples/clock/main.c b/src/samples/clock/main.c index 717845e34..a98d14360 100644 --- a/src/samples/clock/main.c +++ b/src/samples/clock/main.c @@ -19,6 +19,7 @@ */ #include +#include #include "lwan.h" #include "lwan-template.h" @@ -285,6 +286,23 @@ LWAN_HANDLER(templated_index) return HTTP_INTERNAL_ERROR; } +static void setup_timezone(void) +{ + char tzpath_buf[PATH_MAX], *tzpath; + + tzpath = realpath("/etc/localtime", tzpath_buf); + if (!tzpath) + return; + + char *last_slash = strrchr(tzpath, '/'); + if (last_slash && !strcmp(last_slash, "/UTC")) { + /* If this system is set up to use UTC, there's no need to + * stat(/etc/localtime) every time localtime() is called like + * Glibc likes to do. */ + setenv("TZ", ":/etc/localtime", 1); + } +} + int main(void) { struct index sample_clock = { @@ -338,6 +356,8 @@ int main(void) }; struct lwan l; + setup_timezone(); + lwan_init(&l); lwan_set_url_map(&l, default_map); From 4d34d09e1fec93dd521c90b22cee26d0943efea1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 31 May 2020 08:40:38 -0700 Subject: [PATCH 1489/2505] Avoid re-arming async/await watchers every time I/O is requested By avoiding the use of EPOLLONESHOT, we can avoid calling epoll_ctl() to re-arm the watcher every time a read or a write to a file descriptor is requested. lwan_request_async_write() and lwan_request_async_read() will use, respectively, send() and recv(), passing the MSG_DONTWAIT flag, so that if they return -EWOULDBLOCK, then a request to epoll to wake the coroutine is put in place. This saves not only a roundtrip to the kernel every time one wants to read/write a async fd, but also one coroutine context swap. --- src/lib/lwan-request.c | 20 ++++++++------------ src/lib/lwan-thread.c | 26 +++++++++++++------------- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index eb5e9f842..b883c31f8 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1784,18 +1784,16 @@ ssize_t lwan_request_async_read(struct lwan_request *request, void *buf, size_t len) { -await: - lwan_request_await_read(request, fd); - while (true) { - ssize_t r = read(fd, buf, len); + ssize_t r = recv(fd, buf, len, MSG_DONTWAIT); if (r < 0) { switch (errno) { + case EWOULDBLOCK: + lwan_request_await_read(request, fd); + /* Fallthrough */ case EINTR: continue; - case EWOULDBLOCK: - goto await; } } @@ -1808,18 +1806,16 @@ ssize_t lwan_request_async_write(struct lwan_request *request, const void *buf, size_t len) { -await: - lwan_request_await_write(request, fd); - while (true) { - ssize_t r = write(fd, buf, len); + ssize_t r = send(fd, buf, len, MSG_DONTWAIT); if (r < 0) { switch (errno) { + case EWOULDBLOCK: + lwan_request_await_write(request, fd); + /* Fallthrough */ case EINTR: continue; - case EWOULDBLOCK: - goto await; } } diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index c4bdbdf33..90c840b0c 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -165,9 +165,9 @@ conn_flags_to_epoll_events(enum lwan_connection_flags flags) [CONN_EVENTS_WRITE] = EPOLLOUT | EPOLLRDHUP, [CONN_EVENTS_READ] = EPOLLIN | EPOLLRDHUP, [CONN_EVENTS_READ_WRITE] = EPOLLIN | EPOLLOUT | EPOLLRDHUP, - [CONN_EVENTS_ASYNC_READ] = EPOLLET | EPOLLIN | EPOLLRDHUP | EPOLLONESHOT, - [CONN_EVENTS_ASYNC_WRITE] = EPOLLET | EPOLLOUT | EPOLLRDHUP | EPOLLONESHOT, - [CONN_EVENTS_ASYNC_READ_WRITE] = EPOLLET | EPOLLIN | EPOLLOUT | EPOLLRDHUP | EPOLLONESHOT, + [CONN_EVENTS_ASYNC_READ] = EPOLLET | EPOLLIN | EPOLLRDHUP, + [CONN_EVENTS_ASYNC_WRITE] = EPOLLET | EPOLLOUT | EPOLLRDHUP, + [CONN_EVENTS_ASYNC_READ_WRITE] = EPOLLET | EPOLLIN | EPOLLOUT | EPOLLRDHUP, }; return map[flags & CONN_EVENTS_MASK]; @@ -261,17 +261,17 @@ resume_async(struct timeout_queue *tq, assert(await_fd >= 0); struct lwan_connection *await_fd_conn = &tq->lwan->conns[await_fd]; - int ret; - if (await_fd_conn->flags & CONN_ASYNC_AWAIT) { - ret = epoll_ctl(epoll_fd, EPOLL_CTL_MOD, await_fd, &event); - } else { - ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, await_fd, &event); - if (LIKELY(!ret)) { - await_fd_conn->flags |= CONN_ASYNC_AWAIT; - coro_defer(conn->coro, clear_async_await_flag, await_fd_conn); - } + if (!(await_fd_conn->flags & CONN_ASYNC_AWAIT)) + return CONN_CORO_SUSPEND_ASYNC_AWAIT; + if (!epoll_ctl(epoll_fd, EPOLL_CTL_ADD, await_fd, &event)) { + + await_fd_conn->flags |= CONN_ASYNC_AWAIT; + coro_defer(conn->coro, clear_async_await_flag, await_fd_conn); + + return CONN_CORO_SUSPEND_ASYNC_AWAIT; } - return LIKELY(!ret) ? CONN_CORO_SUSPEND_ASYNC_AWAIT : CONN_CORO_ABORT; + + return CONN_CORO_ABORT; } static ALWAYS_INLINE void resume_coro(struct timeout_queue *tq, From 57b1dde89c9250d7f4645e6e81737abbe96e2291 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 31 May 2020 18:54:20 -0700 Subject: [PATCH 1490/2505] Try harder not to roundtrip to epoll() when using async/await Store the flags in the lwan_connection struct for the file descriptor being watched, and only call epoll_ctl(EPOLL_CTL_MOD) if the requested events actually change from the interest mask. --- src/lib/lwan-thread.c | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 90c840b0c..e0b4afcce 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -243,31 +243,41 @@ resume_async(struct timeout_queue *tq, struct lwan_connection *conn, int epoll_fd) { - assert(yield_result >= CONN_CORO_ASYNC_AWAIT_READ && - yield_result <= CONN_CORO_ASYNC_AWAIT_READ_WRITE); - static const enum lwan_connection_flags to_connection_flags[] = { [CONN_CORO_ASYNC_AWAIT_READ] = CONN_EVENTS_ASYNC_READ, [CONN_CORO_ASYNC_AWAIT_WRITE] = CONN_EVENTS_ASYNC_WRITE, [CONN_CORO_ASYNC_AWAIT_READ_WRITE] = CONN_EVENTS_ASYNC_READ_WRITE, }; - struct epoll_event event = { - .events = conn_flags_to_epoll_events(to_connection_flags[yield_result]), - .data.ptr = conn, - }; int await_fd = (int)((uint64_t)from_coro >> 32); + enum lwan_connection_flags flags; + uint32_t events; + int op; - assert(event.events != 0); assert(await_fd >= 0); + assert(yield_result >= CONN_CORO_ASYNC_AWAIT_READ && + yield_result <= CONN_CORO_ASYNC_AWAIT_READ_WRITE); + + flags = to_connection_flags[yield_result]; + events = conn_flags_to_epoll_events(flags); + + assert(events != 0); struct lwan_connection *await_fd_conn = &tq->lwan->conns[await_fd]; - if (!(await_fd_conn->flags & CONN_ASYNC_AWAIT)) - return CONN_CORO_SUSPEND_ASYNC_AWAIT; - if (!epoll_ctl(epoll_fd, EPOLL_CTL_ADD, await_fd, &event)) { + if (LIKELY(await_fd_conn->flags & CONN_ASYNC_AWAIT)) { + if (LIKELY((await_fd_conn->flags & CONN_EVENTS_MASK) == flags)) + return CONN_CORO_SUSPEND_ASYNC_AWAIT; - await_fd_conn->flags |= CONN_ASYNC_AWAIT; + op = EPOLL_CTL_MOD; + } else { + op = EPOLL_CTL_ADD; + flags |= CONN_ASYNC_AWAIT; coro_defer(conn->coro, clear_async_await_flag, await_fd_conn); + } + struct epoll_event event = {.events = events, .data.ptr = conn}; + if (LIKELY(!epoll_ctl(epoll_fd, op, await_fd, &event))) { + await_fd_conn->flags &= ~CONN_EVENTS_MASK; + await_fd_conn->flags |= flags; return CONN_CORO_SUSPEND_ASYNC_AWAIT; } From f5b4d6c118b8c289491ff3930573a06a99cff1a0 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 3 Jun 2020 21:43:44 -0700 Subject: [PATCH 1491/2505] Use edge-triggered events for async/await fds The asyncawait demo was causing incessant calls to kevent() after changing lwan_request_async_read() was changed to use recv() with the MSG_DONTWAIT flag. By using level-triggered events, like client sockets, async/await fds can use the same flags as client connection coroutines, and as such, the map array in conn_flags_to_epoll_events() can be reduced by half. (Also, only need to look up epoll events from the connection flags if there's a need to roundtrip to epoll_ctl().) --- src/lib/lwan-thread.c | 16 +++++----------- src/lib/lwan.h | 22 +++++++++------------- 2 files changed, 14 insertions(+), 24 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index e0b4afcce..b11bd0d26 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -165,9 +165,6 @@ conn_flags_to_epoll_events(enum lwan_connection_flags flags) [CONN_EVENTS_WRITE] = EPOLLOUT | EPOLLRDHUP, [CONN_EVENTS_READ] = EPOLLIN | EPOLLRDHUP, [CONN_EVENTS_READ_WRITE] = EPOLLIN | EPOLLOUT | EPOLLRDHUP, - [CONN_EVENTS_ASYNC_READ] = EPOLLET | EPOLLIN | EPOLLRDHUP, - [CONN_EVENTS_ASYNC_WRITE] = EPOLLET | EPOLLOUT | EPOLLRDHUP, - [CONN_EVENTS_ASYNC_READ_WRITE] = EPOLLET | EPOLLIN | EPOLLOUT | EPOLLRDHUP, }; return map[flags & CONN_EVENTS_MASK]; @@ -244,13 +241,12 @@ resume_async(struct timeout_queue *tq, int epoll_fd) { static const enum lwan_connection_flags to_connection_flags[] = { - [CONN_CORO_ASYNC_AWAIT_READ] = CONN_EVENTS_ASYNC_READ, - [CONN_CORO_ASYNC_AWAIT_WRITE] = CONN_EVENTS_ASYNC_WRITE, - [CONN_CORO_ASYNC_AWAIT_READ_WRITE] = CONN_EVENTS_ASYNC_READ_WRITE, + [CONN_CORO_ASYNC_AWAIT_READ] = CONN_EVENTS_READ, + [CONN_CORO_ASYNC_AWAIT_WRITE] = CONN_EVENTS_WRITE, + [CONN_CORO_ASYNC_AWAIT_READ_WRITE] = CONN_EVENTS_READ_WRITE, }; int await_fd = (int)((uint64_t)from_coro >> 32); enum lwan_connection_flags flags; - uint32_t events; int op; assert(await_fd >= 0); @@ -258,9 +254,6 @@ resume_async(struct timeout_queue *tq, yield_result <= CONN_CORO_ASYNC_AWAIT_READ_WRITE); flags = to_connection_flags[yield_result]; - events = conn_flags_to_epoll_events(flags); - - assert(events != 0); struct lwan_connection *await_fd_conn = &tq->lwan->conns[await_fd]; if (LIKELY(await_fd_conn->flags & CONN_ASYNC_AWAIT)) { @@ -274,7 +267,8 @@ resume_async(struct timeout_queue *tq, coro_defer(conn->coro, clear_async_await_flag, await_fd_conn); } - struct epoll_event event = {.events = events, .data.ptr = conn}; + struct epoll_event event = {.events = conn_flags_to_epoll_events(flags), + .data.ptr = conn}; if (LIKELY(!epoll_ctl(epoll_fd, op, await_fd, &event))) { await_fd_conn->flags &= ~CONN_EVENTS_MASK; await_fd_conn->flags |= flags; diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 0e53067f5..146770dfb 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -274,31 +274,27 @@ enum lwan_connection_flags { CONN_EVENTS_READ = 1 << 0, CONN_EVENTS_WRITE = 1 << 1, CONN_EVENTS_READ_WRITE = CONN_EVENTS_READ | CONN_EVENTS_WRITE, - CONN_EVENTS_ASYNC = 1 << 2, - CONN_EVENTS_ASYNC_READ = CONN_EVENTS_ASYNC | CONN_EVENTS_READ, - CONN_EVENTS_ASYNC_WRITE = CONN_EVENTS_ASYNC | CONN_EVENTS_WRITE, - CONN_EVENTS_ASYNC_READ_WRITE = CONN_EVENTS_ASYNC | CONN_EVENTS_READ_WRITE, - CONN_EVENTS_MASK = 1 << 0 | 1 << 1 | 1 << 2, + CONN_EVENTS_MASK = 1 << 0 | 1 << 1, - CONN_IS_KEEP_ALIVE = 1 << 3, - CONN_IS_UPGRADE = 1 << 4, - CONN_IS_WEBSOCKET = 1 << 5, + CONN_IS_KEEP_ALIVE = 1 << 2, + CONN_IS_UPGRADE = 1 << 3, + CONN_IS_WEBSOCKET = 1 << 4, /* This is only used to determine if timeout_del() is necessary when * the connection coro ends. */ - CONN_SUSPENDED_TIMER = 1 << 6, - CONN_HAS_REMOVE_SLEEP_DEFER = 1 << 7, + CONN_SUSPENDED_TIMER = 1 << 5, + CONN_HAS_REMOVE_SLEEP_DEFER = 1 << 6, - CONN_SUSPENDED_ASYNC_AWAIT = 1 << 8, + CONN_SUSPENDED_ASYNC_AWAIT = 1 << 7, CONN_SUSPENDED = CONN_SUSPENDED_TIMER | CONN_SUSPENDED_ASYNC_AWAIT, - CONN_CORK = 1 << 9, + CONN_CORK = 1 << 8, /* Set only on file descriptors being watched by async/await to determine * which epoll operation to use when suspending/resuming (ADD/MOD). Reset * whenever associated client connection is closed. */ - CONN_ASYNC_AWAIT = 1 << 10, + CONN_ASYNC_AWAIT = 1 << 9, }; enum lwan_connection_coro_yield { From fb0269ad2d68dfbd16b8c141e47ce703ba9f4bf4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 3 Jun 2020 21:47:30 -0700 Subject: [PATCH 1492/2505] Set EV_CLEAR on kqueue if EPOLLET is set --- src/lib/missing.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/missing.c b/src/lib/missing.c index f4c5503f8..29defdc52 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -166,6 +166,8 @@ int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) if (event->events & EPOLLONESHOT) flags |= EV_ONESHOT; + if (event->events & EPOLLET) + flags |= EV_CLEAR; flags |= EV_ERROR; /* EPOLLERR is always set. */ flags |= EV_EOF; /* EPOLLHUP is always set. */ From a7a409c69b923b46819440eef5408e44ac3be11e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 6 Jun 2020 20:22:19 -0700 Subject: [PATCH 1493/2505] Provide a `_get_array()` method to lwan_array types This returns a typed pointer to the base of the array. --- src/lib/lwan-array.h | 5 +++++ src/lib/lwan-lua.c | 4 ++-- src/lib/lwan-template.c | 3 ++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-array.h b/src/lib/lwan-array.h index 3ffeb6edb..85c85c39d 100644 --- a/src/lib/lwan-array.h +++ b/src/lib/lwan-array.h @@ -90,6 +90,11 @@ struct lwan_array *coro_lwan_array_new(struct coro *coro, bool inline_first); DEFINE_ARRAY_TYPE_FUNCS(array_type_, element_type_, &array->storage) #define DEFINE_ARRAY_TYPE_FUNCS(array_type_, element_type_, inline_storage_) \ + __attribute__((unused)) static inline element_type_ \ + *array_type_##_get_array(struct array_type_ *array) \ + { \ + return (element_type_ *)array->base.base; \ + } \ __attribute__((unused)) \ __attribute__((nonnull(1))) static inline void array_type_##_init( \ struct array_type_ *array) \ diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index 0cf35c4ff..b30d04c83 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -227,7 +227,7 @@ LWAN_LUA_METHOD(set_headers) goto out; kv->key = kv->value = NULL; - request->response.headers = headers->base.base; + request->response.headers = lwan_key_value_array_get_array(headers); lua_pushinteger(L, (lua_Integer)headers->base.elements); return 1; @@ -291,7 +291,7 @@ lua_State *lwan_lua_create_state(const char *script_file, const char *script) luaL_openlibs(L); luaL_newmetatable(L, request_metatable_name); - luaL_register(L, NULL, lua_methods.base.base); + luaL_register(L, NULL, lwan_lua_method_array_get_array(&lua_methods)); lua_setfield(L, -1, "__index"); if (script_file) { diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index c88f43320..65afb7c32 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -1538,7 +1538,8 @@ action_apply_tpl: { struct lwan_tpl *inner_tpl = chunk->data; if (LIKELY(lwan_strbuf_grow_by(buf, inner_tpl->minimum_size))) { - if (!apply(inner_tpl, inner_tpl->chunks.base.base, buf, variables, NULL)) { + if (!apply(inner_tpl, chunk_array_get_array(&inner_tpl->chunks), + buf, variables, NULL)) { lwan_status_warning("Could not apply subtemplate"); return NULL; } From 1568b4691323a215ca919a09aa3da2e89bfd8969 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 6 Jun 2020 22:07:43 -0700 Subject: [PATCH 1494/2505] Correctly produce spaces between words in configuration values --- src/lib/lwan-config.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index d2702f638..88d437799 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -478,6 +478,7 @@ static void *parse_key_value(struct parser *parser) { struct config_line line = {.type = CONFIG_LINE_TYPE_LINE}; const struct lexeme *lexeme; + enum lexeme_type last_lexeme = TOTAL_LEXEMES; size_t key_size; while ((lexeme = lexeme_ring_buffer_get_ptr_or_null(&parser->buffer))) { @@ -541,8 +542,12 @@ static void *parse_key_value(struct parser *parser) break; case LEXEME_STRING: + if (last_lexeme == LEXEME_STRING) + lwan_strbuf_append_char(&parser->strbuf, ' '); + lwan_strbuf_append_str(&parser->strbuf, lexeme->value.value, lexeme->value.len); + break; case LEXEME_CLOSE_BRACKET: @@ -561,6 +566,8 @@ static void *parse_key_value(struct parser *parser) lexeme_type_str[lexeme->type]); return NULL; } + + last_lexeme = lexeme->type; } lwan_status_error("EOF while parsing key-value"); From 775366ca0db0f33d8f6bcb0ad7720ff0549864c9 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 14 Jun 2020 11:31:47 -0700 Subject: [PATCH 1495/2505] Assert that header passed to lwan_request_get_header() does not have a colon --- src/lib/lwan-request.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index b883c31f8..b0277fa9d 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1470,6 +1470,8 @@ const char *lwan_request_get_header(struct lwan_request *request, char name[64]; int r; + assert(strchr(header, ':') == NULL); + r = snprintf(name, sizeof(name), "%s: ", header); if (UNLIKELY(r < 0 || r >= (int)sizeof(name))) return NULL; From 0103c800c18bf9f69581972a8f7e6a995a22a243 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 14 Jun 2020 11:32:14 -0700 Subject: [PATCH 1496/2505] Reorder cases in graceful_close() --- src/lib/lwan-thread.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index b11bd0d26..5c8167d97 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -78,10 +78,10 @@ static void graceful_close(struct lwan *l, if (r < 0) { switch (errno) { - case EINTR: - continue; case EAGAIN: coro_yield(conn->coro, CONN_CORO_WANT_READ); + /* Fallthrough */ + case EINTR: continue; default: return; From 7138791e9cb688ac3dd26d56104ba0fe1972eac4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 14 Jun 2020 11:33:03 -0700 Subject: [PATCH 1497/2505] Simplify how JSON lexer emits tokens --- src/samples/techempower/json.c | 51 +++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/src/samples/techempower/json.c b/src/samples/techempower/json.c index 2ec99a6f9..2d164905a 100644 --- a/src/samples/techempower/json.c +++ b/src/samples/techempower/json.c @@ -80,6 +80,18 @@ static void emit(struct lexer *lexer, enum json_tokens token) lexer->start = lexer->pos; } +static void* emit_cont(struct lexer *lexer, enum json_tokens token) +{ + emit(lexer, token); + return lexer_json; +} + +static void* emit_end(struct lexer *lexer, enum json_tokens token) +{ + emit(lexer, token); + return NULL; +} + static int next(struct lexer *lexer) { if (lexer->pos >= lexer->end) { @@ -112,8 +124,7 @@ static void *lexer_string(struct lexer *lexer) int chr = next(lexer); if (UNLIKELY(chr == '\0')) { - emit(lexer, JSON_TOK_ERROR); - return NULL; + return emit_end(lexer, JSON_TOK_ERROR); } if (chr == '\\') { @@ -162,8 +173,7 @@ static void *lexer_string(struct lexer *lexer) } error: - emit(lexer, JSON_TOK_ERROR); - return NULL; + return emit_end(lexer, JSON_TOK_ERROR); } static int accept_run(struct lexer *lexer, const char *run) @@ -184,31 +194,26 @@ static void *lexer_boolean(struct lexer *lexer) switch (next(lexer)) { case 'r': if (LIKELY(!accept_run(lexer, "ue"))) { - emit(lexer, JSON_TOK_TRUE); - return lexer_json; + return emit_cont(lexer, JSON_TOK_TRUE); } break; case 'a': if (LIKELY(!accept_run(lexer, "lse"))) { - emit(lexer, JSON_TOK_FALSE); - return lexer_json; + return emit_cont(lexer, JSON_TOK_FALSE); } break; } - emit(lexer, JSON_TOK_ERROR); - return NULL; + return emit_end(lexer, JSON_TOK_ERROR); } static void *lexer_null(struct lexer *lexer) { if (UNLIKELY(accept_run(lexer, "ull") < 0)) { - emit(lexer, JSON_TOK_ERROR); - return NULL; + return emit_end(lexer, JSON_TOK_ERROR); } - emit(lexer, JSON_TOK_NULL); - return lexer_json; + return emit_cont(lexer, JSON_TOK_NULL); } static void *lexer_number(struct lexer *lexer) @@ -221,9 +226,7 @@ static void *lexer_number(struct lexer *lexer) } backup(lexer); - emit(lexer, JSON_TOK_NUMBER); - - return lexer_json; + return emit_cont(lexer, JSON_TOK_NUMBER); } } @@ -234,23 +237,26 @@ static void *lexer_json(struct lexer *lexer) switch (chr) { case '\0': - emit(lexer, JSON_TOK_EOF); - return NULL; + return emit_end(lexer, JSON_TOK_EOF); + case '}': case '{': case '[': case ']': case ',': case ':': - emit(lexer, (enum json_tokens)chr); - return lexer_json; + return emit_cont(lexer, (enum json_tokens)chr); + case '"': return lexer_string; + case 'n': return lexer_null; + case 't': case 'f': return lexer_boolean; + case '-': if (LIKELY(isdigit(peek(lexer)))) { return lexer_number; @@ -267,8 +273,7 @@ static void *lexer_json(struct lexer *lexer) return lexer_number; } - emit(lexer, JSON_TOK_ERROR); - return NULL; + return emit_end(lexer, JSON_TOK_ERROR); } } } From 1328357c4614aa8d834b58cd462e727a9e496440 Mon Sep 17 00:00:00 2001 From: halosghost Date: Fri, 29 May 2020 00:01:10 -0500 Subject: [PATCH 1498/2505] (closes #261) Implement low-cost global-headers Includes: - Defaulting global headers to "Server: lwan" - Allowing specification of a key-value set for headers in config struct - Basic configuration-file support - Overrides config-struct global headers member if-specified - Does not allow disabling the Server header - Add basic test for specifying global headers --- src/bin/testrunner/testrunner.conf | 4 ++ src/lib/lwan-response.c | 7 ++- src/lib/lwan-thread.c | 1 + src/lib/lwan.c | 92 ++++++++++++++++++++++++++++++ src/lib/lwan.h | 3 + src/scripts/testsuite.py | 8 ++- 6 files changed, 112 insertions(+), 3 deletions(-) diff --git a/src/bin/testrunner/testrunner.conf b/src/bin/testrunner/testrunner.conf index 63bfc7b1d..e9fd7cb3f 100644 --- a/src/bin/testrunner/testrunner.conf +++ b/src/bin/testrunner/testrunner.conf @@ -125,3 +125,7 @@ listener *:8080 { cache for = ${CACHE_FOR:5} } } +headers { + server = lwan/testrunner + x-global-header = present +} diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 4168ee0b2..a42499100 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -354,9 +354,12 @@ size_t lwan_prepare_response_header_full( "\r\nAccess-Control-Allow-Headers: Origin, Accept, Content-Type"); } - APPEND_CONSTANT("\r\nServer: lwan\r\n\r\n\0"); + assert(request->global_response_headers); + assert(request->global_response_headers->buffer); + assert(request->global_response_headers->used); + APPEND_STRING_LEN(request->global_response_headers->buffer, request->global_response_headers->used); - return (size_t)(p_headers - headers - 1); + return (size_t)(p_headers - headers); } #undef APPEND_CHAR diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 5c8167d97..e80b4dcf5 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -117,6 +117,7 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, while (true) { struct lwan_request request = {.conn = conn, + .global_response_headers = &lwan->headers, .fd = fd, .response = {.buffer = &strbuf}, .flags = flags, diff --git a/src/lib/lwan.c b/src/lib/lwan.c index b3d444cc0..7ef815718 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -132,6 +132,91 @@ static struct lwan_url_map *add_url_map(struct lwan_trie *t, const char *prefix, return copy; } +static void lwan_headers_init(struct lwan *l, struct lwan_key_value *hdrs) +{ + assert(l); + + struct lwan_strbuf * headers = &l->headers; + lwan_strbuf_init(headers); + bool set_server = false; + if (hdrs) { + for (struct lwan_key_value *kv = hdrs; kv->key; ++kv) { + if (!strcasecmp(kv->key, "server")) + set_server = true; + + if (LIKELY(kv->value)) { + lwan_strbuf_append_printf(headers, "\r\n%s: %s", kv->key, kv->value); + } + } + } + + if (LIKELY(!set_server)) { + lwan_strbuf_append_strz(headers, "\r\nServer: lwan"); + } + + lwan_strbuf_append_strz(headers, "\r\n\r\n"); +} + +static void parse_global_headers(struct config *c, + const struct config_line *l, + struct lwan *lwan) +{ + struct lwan_key_value *kv; + struct lwan_key_value_array hdrs; + lwan_key_value_array_init(&hdrs); + + while ((l = config_read_line(c))) { + switch (l->type) { + case CONFIG_LINE_TYPE_SECTION: + config_error( + c, "No sections are supported under the 'headers' section"); + goto cleanup; + + case CONFIG_LINE_TYPE_LINE: + kv = lwan_key_value_array_append(&hdrs); + if (!kv) { + lwan_status_critical_perror( + "Could not allocate memory for custom response header"); + } + + kv->key = strdup(l->key); + if (!kv->key) { + lwan_status_critical_perror( + "Could not allocate memory for custom response header"); + } + + kv->value = strdup(l->value); + if (!kv->value) { + lwan_status_critical_perror( + "Could not allocate memory for custom response header"); + } + break; + + case CONFIG_LINE_TYPE_SECTION_END: + kv = lwan_key_value_array_append(&hdrs); + if (!kv) { + lwan_status_critical_perror( + "Could not allocate memory for custom response header"); + } + + kv->key = NULL; + kv->value = NULL; + + lwan_headers_init(lwan, lwan_key_value_array_get_array(&hdrs)); + goto cleanup; + } + } + + config_error(c, "EOF while looking for end of 'headers' section"); + +cleanup: + LWAN_ARRAY_FOREACH (&hdrs, kv) { + free(kv->key); + free(kv->value); + } + lwan_key_value_array_reset(&hdrs); +} + static void parse_listener_prefix_authorization(struct config *c, const struct config_line *l, struct lwan_url_map *url_map) @@ -416,6 +501,8 @@ static bool setup_from_config(struct lwan *lwan, const char *path) } } else if (streq(line->key, "straitjacket")) { lwan_straitjacket_enforce_from_config(conf); + } else if (streq(line->key, "headers")) { + parse_global_headers(conf, line, lwan); } else { config_error(conf, "Unknown section type: %s", line->key); } @@ -543,6 +630,10 @@ void lwan_init_with_config(struct lwan *l, const struct lwan_config *config) try_setup_from_config(l, config); + if (!lwan_strbuf_get_length(&l->headers)) { + lwan_headers_init(l, config->global_headers); + } + lwan_response_init(l); /* Continue initialization as normal. */ @@ -597,6 +688,7 @@ void lwan_shutdown(struct lwan *l) lwan_status_debug("Shutting down URL handlers"); lwan_trie_destroy(&l->url_map_trie); + lwan_strbuf_free(&l->headers); free(l->conns); lwan_response_shutdown(l); diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 146770dfb..e4133954b 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -377,6 +377,7 @@ struct lwan_request { struct lwan_value url; struct lwan_value original_url; struct lwan_connection *conn; + const struct lwan_strbuf *const global_response_headers; struct lwan_proxy *proxy; struct timeout timeout; @@ -457,6 +458,7 @@ struct lwan_straitjacket { struct lwan_config { /* Field will be overridden during initialization. */ enum lwan_request_flags request_flags; + struct lwan_key_value *global_headers; char *listener; char *error_template; @@ -478,6 +480,7 @@ struct lwan_config { struct lwan { struct lwan_trie url_map_trie; struct lwan_connection *conns; + struct lwan_strbuf headers; struct { pthread_barrier_t barrier; diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index ead48aa39..22406abd5 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -423,7 +423,7 @@ def assertHasImage(name): def test_has_lwan_server_header(self): r = requests.get('http://127.0.0.1:8080/100.html') self.assertTrue('server' in r.headers) - self.assertEqual(r.headers['server'], 'lwan') + self.assertEqual(r.headers['server'], 'lwan/testrunner') def test_directory_without_trailing_slash_redirects(self): @@ -696,6 +696,12 @@ def test_cookies(self): for k, v in list(c.items()): self.assertTrue('Key = "%s"; Value = "%s"\n' % (k, v) in r.text) + def test_global_headers_are_present(self): + r = requests.get('http://127.0.0.1:8080/hello') + + self.assertTrue('x-global-header' in r.headers) + self.assertEqual(r.headers['x-global-header'], 'present') + def test_head_request_hello(self): r = requests.head('http://127.0.0.1:8080/hello', headers={'Accept-Encoding': 'foobar'}) From 66bd0046439fe8c0f6f9d568439f7559af73e07b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 14 Jun 2020 13:33:32 -0700 Subject: [PATCH 1499/2505] Document the new headers section --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 4828fcae0..684ee1be7 100644 --- a/README.md +++ b/README.md @@ -296,6 +296,22 @@ malconfiguration.) | `chroot` | `str` | `NULL` | Path to `chroot()` | | `drop_capabilities` | `bool` | `true` | Drop all capabilities with capset(2) (under Linux), or pledge(2) (under OpenBSD). | +### Headers + +If there's a need to specify custom headers for each response, one can declare +a `headers` section in the global scope; for example: + +``` +headers { + Server = Apache/1.0.0 or nginx/1.0.0 (at your option) + Some-Custom-Header = ${WITH_THIS_ENVIRONMENT_VARIABLE} +} +``` + +Will both override the `Server` header (`Server: lwan` won't be sent), and set +`Some-Custom-Header` with the value obtained from the environment variable +`$WITH_THIS_ENVIRONMENT_VARIABLE`. + ### Listeners In order to specify which interfaces Lwan should listen on, a `listener` section From eaece4fa0add68e8e561d01fc361f9d9d28b9917 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 15 Jun 2020 18:34:09 -0700 Subject: [PATCH 1500/2505] Add LWAN_ARRAY_STATIC_INIT Simplifies allocation of global and/or function-local arrays. --- src/lib/lwan-array.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-array.h b/src/lib/lwan-array.h index 85c85c39d..4a64cdb00 100644 --- a/src/lib/lwan-array.h +++ b/src/lib/lwan-array.h @@ -54,6 +54,10 @@ struct lwan_array *coro_lwan_array_new(struct coro *coro, bool inline_first); (array_)->base.elements - 1); \ iter_ >= (typeof(iter_))(array_)->base.base; iter_--) +#define LWAN_ARRAY_STATIC_INIT \ + { \ + } + #define DEFINE_ARRAY_TYPE(array_type_, element_type_) \ struct array_type_ { \ struct lwan_array base; \ @@ -99,7 +103,7 @@ struct lwan_array *coro_lwan_array_new(struct coro *coro, bool inline_first); __attribute__((nonnull(1))) static inline void array_type_##_init( \ struct array_type_ *array) \ { \ - array->base = (struct lwan_array){.base = NULL, .elements = 0}; \ + array->base = LWAN_ARRAY_STATIC_INIT; \ } \ __attribute__((unused)) static inline int array_type_##_reset( \ struct array_type_ *array) \ From dca0a80b5af6bc6078d9f5f494c55a05722f2b4a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 15 Jun 2020 18:34:49 -0700 Subject: [PATCH 1501/2505] Use strbuf functions to get buffer/length when building response header --- src/lib/lwan-response.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index a42499100..12b95171f 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -265,6 +265,8 @@ size_t lwan_prepare_response_header_full( bool date_overridden = false; bool expires_overridden = false; + assert(request->global_response_headers); + p_headers = headers; if (UNLIKELY(request->flags & REQUEST_IS_HTTP_1_0)) @@ -354,10 +356,8 @@ size_t lwan_prepare_response_header_full( "\r\nAccess-Control-Allow-Headers: Origin, Accept, Content-Type"); } - assert(request->global_response_headers); - assert(request->global_response_headers->buffer); - assert(request->global_response_headers->used); - APPEND_STRING_LEN(request->global_response_headers->buffer, request->global_response_headers->used); + APPEND_STRING_LEN(lwan_strbuf_get_buffer(request->global_response_headers), + lwan_strbuf_get_length(request->global_response_headers)); return (size_t)(p_headers - headers); } From 467da73ff604d68b311b3dbd0ecdb0fb771a1fe2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 15 Jun 2020 18:35:17 -0700 Subject: [PATCH 1502/2505] Disallow overriding headers that might be sent by Lwan --- README.md | 20 +++++++++++- src/lib/lwan-response.c | 3 ++ src/lib/lwan.c | 68 ++++++++++++++++++++++++++++------------- 3 files changed, 68 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 684ee1be7..887429e51 100644 --- a/README.md +++ b/README.md @@ -299,7 +299,10 @@ malconfiguration.) ### Headers If there's a need to specify custom headers for each response, one can declare -a `headers` section in the global scope; for example: +a `headers` section in the global scope. The order which this section appears +isn't important. + +For example, this declaration: ``` headers { @@ -312,6 +315,21 @@ Will both override the `Server` header (`Server: lwan` won't be sent), and set `Some-Custom-Header` with the value obtained from the environment variable `$WITH_THIS_ENVIRONMENT_VARIABLE`. +Some headers can't be overridden, as that would cause issues when sending their +actual values while servicing requests. These include but is not limited to: + + - `Date` + - `Expires` + - `WWW-Authenticate` + - `Connection` + - `Content-Type` + - `Transfer-Encoding` + - All `Access-Control-Allow-` headers + +Header names are also case-insensitive (and case-preserving). Overriding +`SeRVeR` will override the `Server` header, but send it the way it was +written in the configuration file. + ### Listeners In order to specify which interfaces Lwan should listen on, a `listener` section diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 12b95171f..13beb19ce 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -259,6 +259,9 @@ size_t lwan_prepare_response_header_full( size_t headers_buf_size, const struct lwan_key_value *additional_headers) { + /* NOTE: If new response headers are added here, update can_override_header() + * in lwan.c */ + char *p_headers; char *p_headers_end = headers + headers_buf_size; char buffer[INT_TO_STR_BUFFER_SIZE]; diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 7ef815718..fdd919108 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -132,38 +132,63 @@ static struct lwan_url_map *add_url_map(struct lwan_trie *t, const char *prefix, return copy; } -static void lwan_headers_init(struct lwan *l, struct lwan_key_value *hdrs) +static bool can_override_header(const char *name) { - assert(l); + /* NOTE: Update lwan_prepare_response_header_full() in lwan-response.c + * if new headers are added here. */ - struct lwan_strbuf * headers = &l->headers; - lwan_strbuf_init(headers); + if (!strcasecmp(name, "Date")) + return false; + if (!strcasecmp(name, "Expires")) + return false; + if (!strcasecmp(name, "WWW-Authenticate")) + return false; + if (!strcasecmp(name, "Connection")) + return false; + if (!strcasecmp(name, "Content-Type")) + return false; + if (!strcasecmp(name, "Transfer-Encoding")) + return false; + if (!strncasecmp(name, "Access-Control-Allow-", + sizeof("Access-Control-Allow-") - 1)) + return false; + + return true; +} + +static void build_response_headers(struct lwan *l, + const struct lwan_key_value *kv) +{ bool set_server = false; - if (hdrs) { - for (struct lwan_key_value *kv = hdrs; kv->key; ++kv) { - if (!strcasecmp(kv->key, "server")) + + assert(l); + + lwan_strbuf_init(&l->headers); + + for (; kv && kv->key; kv++) { + if (!can_override_header(kv->key)) { + lwan_status_warning("Cannot override header '%s'", kv->key); + } else { + if (!strcasecmp(kv->key, "Server")) set_server = true; - if (LIKELY(kv->value)) { - lwan_strbuf_append_printf(headers, "\r\n%s: %s", kv->key, kv->value); - } + lwan_strbuf_append_printf(&l->headers, "\r\n%s: %s", kv->key, + kv->value); } } - if (LIKELY(!set_server)) { - lwan_strbuf_append_strz(headers, "\r\nServer: lwan"); - } + if (!set_server) + lwan_strbuf_append_strz(&l->headers, "\r\nServer: lwan"); - lwan_strbuf_append_strz(headers, "\r\n\r\n"); + lwan_strbuf_append_strz(&l->headers, "\r\n\r\n"); } static void parse_global_headers(struct config *c, - const struct config_line *l, struct lwan *lwan) { + struct lwan_key_value_array hdrs = LWAN_ARRAY_STATIC_INIT; + const struct config_line *l; struct lwan_key_value *kv; - struct lwan_key_value_array hdrs; - lwan_key_value_array_init(&hdrs); while ((l = config_read_line(c))) { switch (l->type) { @@ -202,7 +227,7 @@ static void parse_global_headers(struct config *c, kv->key = NULL; kv->value = NULL; - lwan_headers_init(lwan, lwan_key_value_array_get_array(&hdrs)); + build_response_headers(lwan, lwan_key_value_array_get_array(&hdrs)); goto cleanup; } } @@ -502,7 +527,7 @@ static bool setup_from_config(struct lwan *lwan, const char *path) } else if (streq(line->key, "straitjacket")) { lwan_straitjacket_enforce_from_config(conf); } else if (streq(line->key, "headers")) { - parse_global_headers(conf, line, lwan); + parse_global_headers(conf, lwan); } else { config_error(conf, "Unknown section type: %s", line->key); } @@ -630,9 +655,8 @@ void lwan_init_with_config(struct lwan *l, const struct lwan_config *config) try_setup_from_config(l, config); - if (!lwan_strbuf_get_length(&l->headers)) { - lwan_headers_init(l, config->global_headers); - } + if (!lwan_strbuf_get_length(&l->headers)) + build_response_headers(l, config->global_headers); lwan_response_init(l); From ad7717c7a8842eb057209de537a63049b8fc701d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 16 Jun 2020 07:19:54 -0700 Subject: [PATCH 1503/2505] Revert "Add LWAN_ARRAY_STATIC_INIT" This reverts commit eaece4fa0add68e8e561d01fc361f9d9d28b9917. --- src/lib/lwan-array.h | 6 +----- src/lib/lwan.c | 4 +++- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan-array.h b/src/lib/lwan-array.h index 4a64cdb00..85c85c39d 100644 --- a/src/lib/lwan-array.h +++ b/src/lib/lwan-array.h @@ -54,10 +54,6 @@ struct lwan_array *coro_lwan_array_new(struct coro *coro, bool inline_first); (array_)->base.elements - 1); \ iter_ >= (typeof(iter_))(array_)->base.base; iter_--) -#define LWAN_ARRAY_STATIC_INIT \ - { \ - } - #define DEFINE_ARRAY_TYPE(array_type_, element_type_) \ struct array_type_ { \ struct lwan_array base; \ @@ -103,7 +99,7 @@ struct lwan_array *coro_lwan_array_new(struct coro *coro, bool inline_first); __attribute__((nonnull(1))) static inline void array_type_##_init( \ struct array_type_ *array) \ { \ - array->base = LWAN_ARRAY_STATIC_INIT; \ + array->base = (struct lwan_array){.base = NULL, .elements = 0}; \ } \ __attribute__((unused)) static inline int array_type_##_reset( \ struct array_type_ *array) \ diff --git a/src/lib/lwan.c b/src/lib/lwan.c index fdd919108..698b5d756 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -186,10 +186,12 @@ static void build_response_headers(struct lwan *l, static void parse_global_headers(struct config *c, struct lwan *lwan) { - struct lwan_key_value_array hdrs = LWAN_ARRAY_STATIC_INIT; + struct lwan_key_value_array hdrs; const struct config_line *l; struct lwan_key_value *kv; + lwan_key_value_array_init(&hdrs); + while ((l = config_read_line(c))) { switch (l->type) { case CONFIG_LINE_TYPE_SECTION: From b8a46db90d449adc293b63b9c2ca8c70ad0cf53b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 16 Jun 2020 08:36:39 -0700 Subject: [PATCH 1504/2505] FreeBSD's sendfile() won't send the headers if count is 0 Fixes #281. --- src/lib/lwan-io-wrappers.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index 653cb30cf..9c042b811 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -254,10 +254,14 @@ void lwan_sendfile(struct lwan_request *request, (struct iovec[]){{.iov_base = (void *)header, .iov_len = header_len}}, .hdr_cnt = 1}; - size_t total_written = 0; off_t sbytes = (off_t)count; - do { + if (!count) { + /* FreeBSD's sendfile() won't send the headers when count is 0. Why? */ + return (void)lwan_writev(request, headers.headers, headers.hdr_cnt); + } + + while (true) { int r; #ifdef __APPLE__ @@ -279,11 +283,13 @@ void lwan_sendfile(struct lwan_request *request, } } - total_written += (size_t)sbytes; + count -= (size_t)sbytes; + if (!count) + break; try_again: coro_yield(request->conn->coro, CONN_CORO_WANT_WRITE); - } while (total_written < count); + } } #else static size_t try_pread_file(struct lwan_request *request, From 5f10ad2c6d0cfe5ae2e47d2955abb80cd362a47d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 16 Jun 2020 08:42:55 -0700 Subject: [PATCH 1505/2505] 'lwan -r' should serve pre-compressed files by default --- src/bin/lwan/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/lwan/main.c b/src/bin/lwan/main.c index 8a450cabc..6f6ba9236 100644 --- a/src/bin/lwan/main.c +++ b/src/bin/lwan/main.c @@ -195,7 +195,7 @@ main(int argc, char *argv[]) lwan_init_with_config(&l, &c); const struct lwan_url_map map[] = { - { .prefix = "/", SERVE_FILES(root) }, + { .prefix = "/", SERVE_FILES_SETTINGS(root, "index.html", true) }, { } }; lwan_set_url_map(&l, map); From d701cea81d61479def7273c20d2e74fdc6e05bd4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 17 Jun 2020 19:16:43 -0700 Subject: [PATCH 1506/2505] For mmap+writev file-serving method, serve precompressed files too Also select which compression method is the best based on the Accept-Encoding header. --- src/lib/lwan-mod-serve-files.c | 182 +++++++++++++++++++++------------ 1 file changed, 118 insertions(+), 64 deletions(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 6a4259291..a600cce0a 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -101,6 +101,7 @@ struct cache_funcs { struct mmap_cache_data { struct lwan_value uncompressed; + struct lwan_value gzip; struct lwan_value deflated; #if defined(HAVE_BROTLI) struct lwan_value brotli; @@ -468,42 +469,14 @@ static void zstd_value(const struct lwan_value *uncompressed, } #endif -static bool mmap_init(struct file_cache_entry *ce, - struct serve_files_priv *priv, - const char *full_path, - struct stat *st) +static void +try_readahead(const struct serve_files_priv *priv, int fd, size_t size) { - struct mmap_cache_data *md = &ce->mmap_cache_data; - const char *path = full_path + priv->root_path_len; - int file_fd; - - path += *path == '/'; - - file_fd = openat(priv->root_fd, path, open_mode); - if (UNLIKELY(file_fd < 0)) - return false; - - md->uncompressed.value = - mmap(NULL, (size_t)st->st_size, PROT_READ, MAP_SHARED, file_fd, 0); - close(file_fd); - if (UNLIKELY(md->uncompressed.value == MAP_FAILED)) - return false; - - lwan_madvise_queue(md->uncompressed.value, (size_t)st->st_size); - - md->uncompressed.len = (size_t)st->st_size; - deflate_value(&md->uncompressed, &md->deflated); -#if defined(HAVE_BROTLI) - brotli_value(&md->uncompressed, &md->brotli, &md->deflated); -#endif -#if defined(HAVE_ZSTD) - zstd_value(&md->uncompressed, &md->zstd, &md->deflated); -#endif - - ce->mime_type = - lwan_determine_mime_type_for_file_name(full_path + priv->root_path_len); + if (size > priv->read_ahead) + size = priv->read_ahead; - return true; + if (LIKELY(size)) + lwan_readahead_queue(fd, 0, size); } static bool is_world_readable(mode_t mode) @@ -513,16 +486,6 @@ static bool is_world_readable(mode_t mode) return (mode & world_readable) == world_readable; } -static void -try_readahead(const struct serve_files_priv *priv, int fd, size_t size) -{ - if (size > priv->read_ahead) - size = priv->read_ahead; - - if (LIKELY(size)) - lwan_readahead_queue(fd, 0, size); -} - static int try_open_compressed(const char *relpath, const struct serve_files_priv *priv, const struct stat *uncompressed, @@ -567,6 +530,69 @@ static int try_open_compressed(const char *relpath, return -ENOENT; } +static bool mmap_fd(const struct serve_files_priv *priv, + int fd, + const size_t size, + struct lwan_value *value) +{ + void *ptr; + + if (UNLIKELY(fd < 0)) + goto fail; + + ptr = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); + close(fd); + if (UNLIKELY(ptr == MAP_FAILED)) + goto fail; + + *value = (struct lwan_value){.value = ptr, .len = size}; + return true; + +fail: + *value = (struct lwan_value){}; + return false; +} + +static bool mmap_init(struct file_cache_entry *ce, + struct serve_files_priv *priv, + const char *full_path, + struct stat *st) +{ + struct mmap_cache_data *md = &ce->mmap_cache_data; + const char *path = full_path + priv->root_path_len; + int file_fd; + + file_fd = openat(priv->root_fd, path + (*path == '/'), open_mode); + if (UNLIKELY(file_fd < 0)) + return false; + if (!mmap_fd(priv, file_fd, (size_t)st->st_size, &md->uncompressed)) + return false; + lwan_madvise_queue(md->uncompressed.value, md->uncompressed.len); + + if (LIKELY(priv->serve_precompressed_files)) { + size_t compressed_size; + + file_fd = try_open_compressed(path, priv, st, &compressed_size); + mmap_fd(priv, file_fd, compressed_size, &md->gzip); + } else { + md->gzip = (struct lwan_value){}; + } + + md->uncompressed.len = (size_t)st->st_size; + deflate_value(&md->uncompressed, &md->deflated); +#if defined(HAVE_BROTLI) + brotli_value(&md->uncompressed, &md->brotli, &md->deflated); +#endif +#if defined(HAVE_ZSTD) + zstd_value(&md->uncompressed, &md->zstd, &md->deflated); +#endif + + ce->mime_type = + lwan_determine_mime_type_for_file_name(full_path + priv->root_path_len); + + return true; +} + static bool sendfile_init(struct file_cache_entry *ce, struct serve_files_priv *priv, const char *full_path, @@ -878,6 +904,8 @@ static void mmap_free(struct file_cache_entry *fce) struct mmap_cache_data *md = &fce->mmap_cache_data; munmap(md->uncompressed.value, md->uncompressed.len); + if (md->gzip.value) + munmap(md->gzip.value, md->gzip.len); free(md->deflated.value); #if defined(HAVE_BROTLI) free(md->brotli.value); @@ -1207,41 +1235,67 @@ serve_value_ok(struct lwan_request *request, return serve_value(request, mime_type, value, headers, HTTP_OK); } -static enum lwan_http_status mmap_serve(struct lwan_request *request, - void *data) +static const struct lwan_value * +mmap_best_data(struct lwan_request *request, + struct mmap_cache_data *md, + const struct lwan_key_value **header) { - struct file_cache_entry *fce = data; - struct mmap_cache_data *md = &fce->mmap_cache_data; + const struct lwan_value *best = &md->uncompressed; + + *header = NULL; #if defined(HAVE_ZSTD) - if (md->zstd.len && accepts_encoding(request, REQUEST_ACCEPT_ZSTD)) { - return serve_value_ok(request, fce->mime_type, &md->zstd, - zstd_compression_hdr); + if (md->zstd.len && md->zstd.len < best->len && + accepts_encoding(request, REQUEST_ACCEPT_ZSTD)) { + best = &md->zstd; + *header = zstd_compression_hdr; } #endif #if defined(HAVE_BROTLI) - if (md->brotli.len && accepts_encoding(request, REQUEST_ACCEPT_BROTLI)) { - return serve_value_ok(request, fce->mime_type, &md->brotli, - br_compression_hdr); + if (md->brotli.len && md->brotli.len < best->len && + accepts_encoding(request, REQUEST_ACCEPT_BROTLI)) { + best = &md->brotli; + *header = br_compression_hdr; } #endif - if (md->deflated.len && accepts_encoding(request, REQUEST_ACCEPT_DEFLATE)) { - return serve_value_ok(request, fce->mime_type, &md->deflated, - deflate_compression_hdr); + if (md->gzip.len && md->gzip.len < best->len && + accepts_encoding(request, REQUEST_ACCEPT_GZIP)) { + best = &md->gzip; + *header = gzip_compression_hdr; } + if (md->deflated.len && md->deflated.len < best->len && + accepts_encoding(request, REQUEST_ACCEPT_DEFLATE)) { + best = &md->deflated; + *header = deflate_compression_hdr; + } + + return best; +} + +static enum lwan_http_status mmap_serve(struct lwan_request *request, + void *data) +{ + struct file_cache_entry *fce = data; + struct mmap_cache_data *md = &fce->mmap_cache_data; + const struct lwan_key_value *compression_hdr; + const struct lwan_value *to_serve = + mmap_best_data(request, md, &compression_hdr); + + if (compression_hdr) + return serve_value_ok(request, fce->mime_type, to_serve, + compression_hdr); + off_t from, to; enum lwan_http_status status = - compute_range(request, &from, &to, (off_t)md->uncompressed.len); - if (status == HTTP_OK || status == HTTP_PARTIAL_CONTENT) { - return serve_buffer(request, fce->mime_type, - (char *)md->uncompressed.value + from, - (size_t)(to - from), NULL, status); - } + compute_range(request, &from, &to, (off_t)to_serve->len); + if (status != HTTP_OK && status != HTTP_PARTIAL_CONTENT) + return status; - return status; + return serve_buffer(request, fce->mime_type, (char *)to_serve->value + from, + (size_t)(to - from), NULL, status); } static enum lwan_http_status dirlist_serve(struct lwan_request *request, From 13e93670a9d47313e5ef4a389ce3aa7056cb9ae2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 18 Jun 2020 21:20:58 -0700 Subject: [PATCH 1507/2505] Use relative paths in directory listing This should fix the case where Lwan has an active chroot, or is using the rewrite module, and gets lost when trying to produce links to files in the auto_index feature of mod-serve-files. --- src/lib/lwan-mod-serve-files.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index a600cce0a..96d808afc 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -280,8 +280,7 @@ static const char *directory_list_tpl_str = " \n" " \n" - " {{{file_list.name}}}\n" + " {{{file_list.name}}}\n" " {{file_list.type}}\n" " {{file_list.size}}{{file_list.unit}}\n" " \n" From 78a7e7b649a4142eec3c623880385f136376b8fd Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 21 Jun 2020 10:56:59 -0700 Subject: [PATCH 1508/2505] Improve number of retired instructions in JSON string escaping Use a SWAR technique to figure out how to escape a character in JSON. --- src/samples/techempower/json.c | 35 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/samples/techempower/json.c b/src/samples/techempower/json.c index 2d164905a..125f1281d 100644 --- a/src/samples/techempower/json.c +++ b/src/samples/techempower/json.c @@ -614,26 +614,25 @@ int json_obj_parse(char *payload, return obj_parse(&obj, descr, descr_len, val); } -static char escape_as(char chr) +/* + * Routines has_zero() and has_value() are from + * https://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord + */ +static ALWAYS_INLINE uint64_t has_zero(uint64_t v) { - switch (chr) { - case '"': - return '"'; - case '\\': - return '\\'; - case '\b': - return 'b'; - case '\f': - return 'f'; - case '\n': - return 'n'; - case '\r': - return 'r'; - case '\t': - return 't'; - } + return (v - 0x0101010101010101UL) & ~v & 0x8080808080808080UL; +} - return 0; +static ALWAYS_INLINE uint64_t has_value(uint64_t x, char n) +{ + return has_zero(x ^ (~0UL / 255 * (uint64_t)n)); +} + +static char escape_as(char chr) +{ + static const char escaped[] = {'"', '\\', 'b', 'f', 'n', 'r', 't', 't'}; + uint64_t mask = has_value(0x225c080c0a0d0909UL, chr); + return mask == 0 ? 0 : escaped[__builtin_clzl(mask) / 8]; } static int json_escape_internal(const char *str, From b8bf6b0b048d171a1af7b03b0d763c0f8ffd0122 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 24 Jun 2020 22:53:49 -0700 Subject: [PATCH 1509/2505] Implement TWFB "cached queries" benchmark Use the cache infrastructure already present in Lwan to cache the responses from the database. --- src/samples/techempower/techempower.c | 88 +++++++++++++++++++++++- src/samples/techempower/techempower.conf | 1 + 2 files changed, 87 insertions(+), 2 deletions(-) diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index 51f4666cc..8d3023ce5 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -23,9 +23,11 @@ #include #include "lwan-private.h" +#include "lwan-cache.h" #include "lwan-config.h" #include "lwan-template.h" #include "lwan-mod-lua.h" +#include "int-to-str.h" #include "database.h" #include "json.h" @@ -188,9 +190,9 @@ LWAN_HANDLER(json) N_ELEMENTS(hello_world_json_desc), &j); } -static bool db_query(struct db_stmt *stmt, struct db_json *out) +static bool db_query_key(struct db_stmt *stmt, struct db_json *out, int key) { - struct db_row row = {.kind = 'i', .u.i = (rand() % 10000) + 1}; + struct db_row row = {.kind = 'i', .u.i = key + 1}; if (UNLIKELY(!db_stmt_bind(stmt, &row, 1))) return false; @@ -204,6 +206,11 @@ static bool db_query(struct db_stmt *stmt, struct db_json *out) return true; } +static inline bool db_query(struct db_stmt *stmt, struct db_json *out) +{ + return db_query_key(stmt, out, rand() % 10000); +} + LWAN_HANDLER(db) { struct db_stmt *stmt = db_prepare_stmt(get_db(), random_number_query, @@ -260,6 +267,75 @@ LWAN_HANDLER(queries) return ret; } +static struct cache *cached_queries_cache; +struct db_json_cached { + struct cache_entry base; + struct db_json db_json; +}; + +static struct cache_entry *cached_queries_new(const char *key, void *context) +{ + struct db_json_cached *entry; + struct db_stmt *stmt; + + entry = malloc(sizeof(*entry)); + if (UNLIKELY(!entry)) + return NULL; + + stmt = db_prepare_stmt(get_db(), random_number_query, + sizeof(random_number_query) - 1); + if (UNLIKELY(!stmt)) { + free(entry); + return NULL; + } + + if (!db_query_key(stmt, &entry->db_json, atoi(key))) { + free(entry); + entry = NULL; + } + + db_stmt_finalize(stmt); + + return (struct cache_entry *)entry; +} + +static void cached_queries_free(struct cache_entry *entry, void *context) +{ + free(entry); +} + +LWAN_HANDLER(cached_queries) +{ + const char *queries_str = lwan_request_get_query_param(request, "queries"); + long queries; + + queries = LIKELY(queries_str) + ? LWAN_MIN(500, LWAN_MAX(1, parse_long(queries_str, -1))) + : 1; + + struct queries_json qj = {.queries_len = (size_t)queries}; + for (long i = 0; i < queries; i++) { + char key_buf[INT_TO_STR_BUFFER_SIZE]; + struct db_json_cached *jc; + size_t discard; + + jc = (struct db_json_cached *)cache_coro_get_and_ref_entry( + cached_queries_cache, request->conn->coro, + int_to_string(rand() % 10000, key_buf, &discard)); + if (UNLIKELY(!jc)) + return HTTP_INTERNAL_ERROR; + + qj.queries[i] = jc->db_json; + } + + /* Avoid reallocations/copies while building response. Each response + * has ~32bytes. 500 queries (max) should be less than 16384 bytes, + * so this is a good approximation. */ + lwan_strbuf_grow_to(response->buffer, (size_t)(32l * queries)); + + return json_response_arr(response, &queries_array_desc, &qj); +} + LWAN_HANDLER(plaintext) { lwan_strbuf_set_static(response->buffer, hello_world, @@ -399,8 +475,16 @@ int main(void) if (!fortune_tpl) lwan_status_critical("Could not compile fortune templates"); + cached_queries_cache = cache_create(cached_queries_new, + cached_queries_free, + NULL, + 3600 /* 1 hour */); + if (!cached_queries_cache) + lwan_status_critical("Could not create cached queries cache"); + lwan_main_loop(&l); + cache_destroy(cached_queries_cache); lwan_tpl_free(fortune_tpl); lwan_shutdown(&l); diff --git a/src/samples/techempower/techempower.conf b/src/samples/techempower/techempower.conf index 1e343d7cc..28f3177c2 100644 --- a/src/samples/techempower/techempower.conf +++ b/src/samples/techempower/techempower.conf @@ -4,6 +4,7 @@ listener *:8080 { &json /json &db /db &queries /queries + &cached_queries /cached-queries &fortunes /fortunes # For Lua version of TWFB benchmarks From 82a389f3204d140adbde962ff5bf169e5934a69b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 24 Jun 2020 23:14:17 -0700 Subject: [PATCH 1510/2505] Use names as specified in the specs for Cached Worlds benchmark https://github.com/TechEmpower/FrameworkBenchmarks/wiki/Project-Information-Framework-Tests-Overview#caching-work-in-progress --- src/samples/techempower/techempower.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index 8d3023ce5..49e3eaed6 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -53,6 +53,8 @@ static struct db_connection_params { static const char hello_world[] = "Hello, World!"; static const char random_number_query[] = "SELECT randomNumber FROM world WHERE id=?"; +static const char cached_random_number_query[] = + "SELECT randomNumber FROM cachedworld WHERE id=?"; struct Fortune { struct { @@ -282,8 +284,8 @@ static struct cache_entry *cached_queries_new(const char *key, void *context) if (UNLIKELY(!entry)) return NULL; - stmt = db_prepare_stmt(get_db(), random_number_query, - sizeof(random_number_query) - 1); + stmt = db_prepare_stmt(get_db(), cached_random_number_query, + sizeof(cached_random_number_query) - 1); if (UNLIKELY(!stmt)) { free(entry); return NULL; @@ -304,9 +306,9 @@ static void cached_queries_free(struct cache_entry *entry, void *context) free(entry); } -LWAN_HANDLER(cached_queries) +LWAN_HANDLER(cached_world) { - const char *queries_str = lwan_request_get_query_param(request, "queries"); + const char *queries_str = lwan_request_get_query_param(request, "count"); long queries; queries = LIKELY(queries_str) From 774369aea4f705e011a4d9f454b797a018582468 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 28 Jun 2020 20:52:19 -0700 Subject: [PATCH 1511/2505] No need to yield when concluding websockets handshake The coroutine that called lwan_request_websocket_upgrade() will still be active, and by using the functions to either read or write to the websocket should trigger yields as usual from the I/O wrapper functions that are used. --- src/lib/lwan-request.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index b0277fa9d..cfa2e7258 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1305,7 +1305,6 @@ lwan_request_websocket_upgrade(struct lwan_request *request) request->conn->flags |= CONN_IS_WEBSOCKET; lwan_send(request, header_buf, header_buf_len, 0); - coro_yield(request->conn->coro, CONN_CORO_WANT_READ_WRITE); return HTTP_SWITCHING_PROTOCOLS; } From 4ac41b563ab4359b0e2b12904e37ad9895d60fdd Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 28 Jun 2020 20:57:34 -0700 Subject: [PATCH 1512/2505] Don't send the Expires header when concluding a WebSockets handhake --- src/lib/lwan-response.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 13beb19ce..5404d2f66 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -328,6 +328,10 @@ size_t lwan_prepare_response_header_full( if (UNLIKELY(request->conn->flags & CONN_IS_UPGRADE)) { APPEND_CONSTANT("\r\nConnection: Upgrade"); + + /* Lie that the Expires header has ben overriden just so that we + * don't send them when performing a websockets handhsake. */ + expires_overridden = true; } else { if (LIKELY(request->conn->flags & CONN_IS_KEEP_ALIVE)) { APPEND_CONSTANT("\r\nConnection: keep-alive"); From a7e282fb20e3e1f816ff374f9248557c608c7e4d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 28 Jun 2020 21:27:08 -0700 Subject: [PATCH 1513/2505] Cached Queries is named "cached_world" per the TWFB docs --- src/samples/techempower/techempower.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/samples/techempower/techempower.conf b/src/samples/techempower/techempower.conf index 28f3177c2..1089e1758 100644 --- a/src/samples/techempower/techempower.conf +++ b/src/samples/techempower/techempower.conf @@ -4,7 +4,7 @@ listener *:8080 { &json /json &db /db &queries /queries - &cached_queries /cached-queries + &cached_world /cached-world &fortunes /fortunes # For Lua version of TWFB benchmarks From eda6cc3bf1c2769781676e61db9e0482365a5f52 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 29 Jun 2020 19:10:47 -0700 Subject: [PATCH 1514/2505] Make one less call to read() when loading auto-index README files --- src/lib/lwan-mod-serve-files.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 96d808afc..85c5fba9c 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -685,11 +685,12 @@ static const char *dirlist_find_readme(struct lwan_strbuf *readme, continue; goto error; } - if (!n) - break; if (!lwan_strbuf_append_str(readme, buffer, (size_t)n)) goto error; + + if ((size_t)n < sizeof(buffer)) + break; } close(fd); From b0c0009be639cb57c0b40d98036cc49124c5e589 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 29 Jun 2020 19:11:07 -0700 Subject: [PATCH 1515/2505] Actually parse ``read_ahead'' parameter from configuration file Embarassing error here: parse_long() was being called to parse the string "read_ahead", instead of passing "read_ahead" to hash_find(). --- src/lib/lwan-mod-serve-files.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 85c5fba9c..521a19960 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -1035,8 +1035,8 @@ static void *serve_files_create_from_hash(const char *prefix, parse_bool(hash_find(hash, "serve_precompressed_files"), true), .auto_index = parse_bool(hash_find(hash, "auto_index"), true), .directory_list_template = hash_find(hash, "directory_list_template"), - .read_ahead = - (size_t)parse_long("read_ahead", SERVE_FILES_READ_AHEAD_BYTES), + .read_ahead = (size_t)parse_long(hash_find(hash, "read_ahead"), + SERVE_FILES_READ_AHEAD_BYTES), .auto_index_readme = parse_bool(hash_find(hash, "auto_index_readme"), true), .cache_for = (time_t)parse_time_period(hash_find(hash, "cache_for"), From f1c8a786480e30a8b6ff5881863e51a31a3b9224 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 29 Jun 2020 19:12:26 -0700 Subject: [PATCH 1516/2505] Avoid using coro_defer() in the fast path for the cached_world benchmark Instead of using cache_coro_get_and_ref_entry(), define a local my_cache_coro_get_and_ref_entry() function that does not schedule a unref when the coroutine is done. This avoids demoting the coro_defer array to the heap and, for large number of queries, avoids a large number of calls to realloc(). This change is fine because between reading the cached entry and copying it to the queries_json array the coroutine can't be destroyed from the outside.r --- src/samples/techempower/techempower.c | 33 ++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index 49e3eaed6..7a3bc22e7 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -306,6 +306,35 @@ static void cached_queries_free(struct cache_entry *entry, void *context) free(entry); } +static struct cache_entry *my_cache_coro_get_and_ref_entry(struct cache *cache, + struct coro *coro, + const char *key) +{ + /* Using this function instead of cache_coro_get_and_ref_entry() will avoid + * calling coro_defer(), which, in cases where the number of cached queries is + * too high, will trigger reallocations of the coro_defer array (and the "demotion" + * from the storage inlined in the coro struct to somewhere in the heap). + * + * For large number of cached elements, too, this will reduce the number of + * indirect calls that are performed every time a request is serviced. + */ + + for (int tries = 16; tries; tries--) { + int error; + struct cache_entry *ce = cache_get_and_ref_entry(cache, key, &error); + + if (LIKELY(ce)) + return ce; + + if (error != EWOULDBLOCK) + break; + + coro_yield(coro, CONN_CORO_YIELD); + } + + return NULL; +} + LWAN_HANDLER(cached_world) { const char *queries_str = lwan_request_get_query_param(request, "count"); @@ -321,13 +350,15 @@ LWAN_HANDLER(cached_world) struct db_json_cached *jc; size_t discard; - jc = (struct db_json_cached *)cache_coro_get_and_ref_entry( + jc = (struct db_json_cached *)my_cache_coro_get_and_ref_entry( cached_queries_cache, request->conn->coro, int_to_string(rand() % 10000, key_buf, &discard)); if (UNLIKELY(!jc)) return HTTP_INTERNAL_ERROR; qj.queries[i] = jc->db_json; + + cache_entry_unref(cached_queries_cache, (struct cache_entry *)jc); } /* Avoid reallocations/copies while building response. Each response From 4e864d78b1b4e5e750c53cb65dffc6a0c76477c4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 29 Jun 2020 19:15:42 -0700 Subject: [PATCH 1517/2505] Use ``world'' database for the cached queries benchmark Easier than setting up a new database just for this benchmark, given that both have the same schemas. --- src/samples/techempower/techempower.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index 7a3bc22e7..abf4597ee 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -54,7 +54,7 @@ static const char hello_world[] = "Hello, World!"; static const char random_number_query[] = "SELECT randomNumber FROM world WHERE id=?"; static const char cached_random_number_query[] = - "SELECT randomNumber FROM cachedworld WHERE id=?"; + "SELECT randomNumber FROM world WHERE id=?"; struct Fortune { struct { From a719af9aceb92e55ff5c7e1daf4d8367e9df3fac Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 29 Jun 2020 19:56:41 -0700 Subject: [PATCH 1518/2505] Fix file serving tests after 13e93670 --- src/scripts/testsuite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index 22406abd5..eec2553c7 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -401,7 +401,7 @@ def test_directory_listing(self): self.assertTrue('

Index of /icons

' in r.text) def assertHasImage(name): - imgtag = "%s.gif" % (name, name) + imgtag = "%s.gif" % (name, name) self.assertTrue(imgtag in r.text) assertHasImage('back') From 1133f80f0aeea6e521b6798af2ee02063f2b7452 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 30 Jun 2020 17:46:06 -0700 Subject: [PATCH 1519/2505] lwan_recv(..., MSG_DONTWAIT) should return early when errno = EAGAIN --- src/lib/lwan-io-wrappers.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index 9c042b811..eb39618fb 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -183,6 +183,9 @@ lwan_recv(struct lwan_request *request, void *buf, size_t count, int flags) switch (errno) { case EAGAIN: + if (flags & MSG_DONTWAIT) + return total_recv; + /* Fallthrough */ case EINTR: goto try_again; default: From 1b348f0c13c29d3e04f7c478056ff3e32160b015 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 30 Jun 2020 17:47:42 -0700 Subject: [PATCH 1520/2505] Allow peeking at a WebSocket connection to write an event pump If a handler now tries to read from a WebSocket connection, it'll return values different than true/false: - ENOTCONN: read() called before connection was upgraded - ECONNRESET: Client has closed the connection - EAGAIN: Nothing is available, try again - 0: There's something in the request buffer, go read it! This way, it's possible to write a message pump by looking at the return value of this function. At the time, there's no possibilty of performing a read with a given timeout, so if a handler sleeps for a while, that sleep won't be canceled. This will eventually be fixed, but it should now be possible to write event pumps with this and write chat pub/sub applications. The example has been modified as well to showcase the possibilities, by implementing an echo server that counts the time since the last message that was received. It's still all barebones, and I'm not happy with the API still, but it's coming along. This change also fixes a lot of stuff when reading WebSocket frames and is more standards compliant. --- src/lib/lwan-websocket.c | 156 +++++++++++++++++++---------------- src/lib/lwan.h | 4 +- src/samples/websocket/main.c | 110 ++++++++++++++++++++++-- 3 files changed, 190 insertions(+), 80 deletions(-) diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index ab51c0cd0..095d9bfe2 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -18,6 +18,7 @@ * USA. */ +#include #include #include "lwan-private.h" @@ -30,6 +31,18 @@ enum ws_opcode { WS_OPCODE_CLOSE = 8, WS_OPCODE_PING = 9, WS_OPCODE_PONG = 10, + + WS_OPCODE_RSVD_1 = 3, + WS_OPCODE_RSVD_2 = 4, + WS_OPCODE_RSVD_3 = 5, + WS_OPCODE_RSVD_4 = 6, + WS_OPCODE_RSVD_5 = 7, + + WS_OPCODE_RSVD_CONTROL_1 = 11, + WS_OPCODE_RSVD_CONTROL_2 = 12, + WS_OPCODE_RSVD_CONTROL_3 = 13, + WS_OPCODE_RSVD_CONTROL_4 = 14, + WS_OPCODE_RSVD_CONTROL_5 = 15, }; static void write_websocket_frame(struct lwan_request *request, @@ -80,136 +93,139 @@ void lwan_response_websocket_write(struct lwan_request *request) static void send_websocket_pong(struct lwan_request *request, size_t len) { - size_t generation; - char *temp; + char temp[128]; if (UNLIKELY(len > 125)) { lwan_status_debug("Received PING opcode with length %zu." "Max is 125. Aborting connection.", len); - goto abort; + coro_yield(request->conn->coro, CONN_CORO_ABORT); + __builtin_unreachable(); } - generation = coro_deferred_get_generation(request->conn->coro); - - temp = coro_malloc(request->conn->coro, len); - if (UNLIKELY(!temp)) - goto abort; - lwan_recv(request, temp, len, 0); write_websocket_frame(request, WS_OPCODE_PONG, temp, len); +} - coro_deferred_run(request->conn->coro, generation); +static uint64_t get_frame_length(struct lwan_request *request, uint16_t header) +{ + uint64_t len; - return; + switch (header & 0x7f) { + case 0x7e: + lwan_recv(request, &len, 2, 0); + return (uint64_t)ntohs((uint16_t)len); + case 0x7f: + lwan_recv(request, &len, 8, 0); + return (uint64_t)be64toh(len); + default: + return (uint64_t)(header & 0x7f); + } +} -abort: - coro_yield(request->conn->coro, CONN_CORO_ABORT); - __builtin_unreachable(); +static void discard_frame(struct lwan_request *request, uint16_t header) +{ + uint64_t len = get_frame_length(request, header); + + for (char buffer[128]; len;) + len -= (size_t)lwan_recv(request, buffer, sizeof(buffer), 0); } -bool lwan_response_websocket_read(struct lwan_request *request) +int lwan_response_websocket_read(struct lwan_request *request) { uint16_t header; - uint64_t len_frame; + uint64_t frame_len; char *msg; bool continuation = false; - bool fin; if (!(request->conn->flags & CONN_IS_WEBSOCKET)) - return false; + return ENOTCONN; lwan_strbuf_reset(request->response.buffer); next_frame: - lwan_recv(request, &header, sizeof(header), 0); + if (!lwan_recv(request, &header, sizeof(header), continuation ? 0 : MSG_DONTWAIT)) + return EAGAIN; + header = htons(header); - fin = (header & 0x8000); + if (UNLIKELY(header & 0x7000)) { + lwan_status_debug("RSV1...RSV3 has non-zero value %d, aborting", header & 0x7000); + /* No extensions are supported yet, so fail connection per RFC6455. */ + coro_yield(request->conn->coro, CONN_CORO_ABORT); + __builtin_unreachable(); + } - switch ((enum ws_opcode)((header & 0xf00) >> 8)) { + switch ((enum ws_opcode)((header & 0x0f00) >> 8)) { case WS_OPCODE_CONTINUATION: continuation = true; break; + case WS_OPCODE_TEXT: case WS_OPCODE_BINARY: break; + case WS_OPCODE_CLOSE: request->conn->flags &= ~CONN_IS_WEBSOCKET; break; + case WS_OPCODE_PING: - /* FIXME: handling PING packets here doesn't seem ideal; they won't be - * handled, for instance, if the user never receives data from the - * websocket. */ send_websocket_pong(request, header & 0x7f); goto next_frame; - default: - lwan_status_debug( - "Received unexpected WebSockets opcode: 0x%x, ignoring", - (header & 0xf00) >> 8); + + case WS_OPCODE_PONG: + lwan_status_debug("Received unsolicited PONG frame, discarding frame"); + discard_frame(request, header); goto next_frame; - } - switch (header & 0x7f) { - default: - len_frame = (uint64_t)(header & 0x7f); - break; - case 0x7e: - lwan_recv(request, &len_frame, 2, 0); - len_frame = (uint64_t)ntohs((uint16_t)len_frame); - break; - case 0x7f: - lwan_recv(request, &len_frame, 8, 0); - len_frame = be64toh(len_frame); - break; + case WS_OPCODE_RSVD_1 ... WS_OPCODE_RSVD_5: + lwan_status_debug("Received reserved non-control frame opcode: 0x%x, aborting", + (header & 0x0f00) >> 8); + /* RFC6455: ...the receiving endpoint MUST _Fail the WebSocket Connection_ */ + coro_yield(request->conn->coro, CONN_CORO_ABORT); + __builtin_unreachable(); + + case WS_OPCODE_RSVD_CONTROL_1 ... WS_OPCODE_RSVD_CONTROL_5: + lwan_status_debug("Received reserved control frame opcode: 0x%x, aborting", + (header & 0x0f00) >> 8); + /* RFC6455: ...the receiving endpoint MUST _Fail the WebSocket Connection_ */ + coro_yield(request->conn->coro, CONN_CORO_ABORT); + __builtin_unreachable(); } - size_t cur_len = lwan_strbuf_get_length(request->response.buffer); + size_t cur_buf_len = lwan_strbuf_get_length(request->response.buffer); - if (UNLIKELY(!lwan_strbuf_grow_by(request->response.buffer, len_frame))) { + frame_len = get_frame_length(request, header); + if (UNLIKELY(!lwan_strbuf_grow_by(request->response.buffer, frame_len))) { coro_yield(request->conn->coro, CONN_CORO_ABORT); __builtin_unreachable(); } + /* FIXME: API to update used size, too, not just capacity! Also, this can't + * overflow if adding frame_len, as this is checked by grow_by() already. */ + request->response.buffer->used += frame_len; - msg = lwan_strbuf_get_buffer(request->response.buffer) + cur_len; - + msg = lwan_strbuf_get_buffer(request->response.buffer) + cur_buf_len; if (LIKELY(header & 0x80)) { /* Payload is masked; should always be true on Client->Server comms but * don't assume this is always the case. */ - uint32_t mask; + char mask[4]; struct iovec vec[] = { - {.iov_base = &mask, .iov_len = sizeof(mask)}, - {.iov_base = msg, .iov_len = len_frame}, + {.iov_base = mask, .iov_len = sizeof(mask)}, + {.iov_base = msg, .iov_len = frame_len}, }; lwan_readv(request, vec, N_ELEMENTS(vec)); - if (mask) { - uint64_t i; - - for (i = 0; len_frame - i >= sizeof(mask); i += sizeof(mask)) { - uint32_t v; - - memcpy(&v, &msg[i], sizeof(v)); - v ^= mask; - memcpy(&msg[i], &v, sizeof(v)); - } - - switch (i & 3) { - case 3: msg[i + 2] ^= (char)((mask >> 16) & 0xff); /* fallthrough */ - case 2: msg[i + 1] ^= (char)((mask >> 8) & 0xff); /* fallthrough */ - case 1: msg[i + 0] ^= (char)(mask & 0xff); - } - } + /* FIXME: try to do more bytes at a time! */ + for (uint64_t i = 0; i < frame_len; i++) + msg[i] ^= mask[i & 3]; } else { - lwan_recv(request, msg, len_frame, 0); + lwan_recv(request, msg, frame_len, 0); } - if (continuation && !fin) { - coro_yield(request->conn->coro, CONN_CORO_WANT_READ); + if (continuation && !(header & 0x8000)) { continuation = false; - goto next_frame; } - return request->conn->flags & CONN_IS_WEBSOCKET; + return (request->conn->flags & CONN_IS_WEBSOCKET) ? 0 : ECONNRESET; } diff --git a/src/lib/lwan.h b/src/lib/lwan.h index e4133954b..bffb98cb9 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -532,8 +532,6 @@ bool lwan_response_set_event_stream(struct lwan_request *request, enum lwan_http_status status); void lwan_response_send_event(struct lwan_request *request, const char *event); -void lwan_response_websocket_write(struct lwan_request *request); -bool lwan_response_websocket_read(struct lwan_request *request); const char *lwan_http_status_as_string(enum lwan_http_status status) __attribute__((const)) __attribute__((warn_unused_result)); @@ -590,6 +588,8 @@ lwan_request_get_accept_encoding(struct lwan_request *request); enum lwan_http_status lwan_request_websocket_upgrade(struct lwan_request *request); +void lwan_response_websocket_write(struct lwan_request *request); +int lwan_response_websocket_read(struct lwan_request *request); void lwan_request_await_read(struct lwan_request *r, int fd); void lwan_request_await_write(struct lwan_request *r, int fd); diff --git a/src/samples/websocket/main.c b/src/samples/websocket/main.c index 920862c12..e02245ad8 100644 --- a/src/samples/websocket/main.c +++ b/src/samples/websocket/main.c @@ -18,11 +18,14 @@ * USA. */ +#include #include #include "lwan.h" -LWAN_HANDLER(ws) +/* This is a write-only sample of the API: it just sends random integers + * over a WebSockets connection. */ +LWAN_HANDLER(ws_write) { enum lwan_http_status status = lwan_request_websocket_upgrade(request); @@ -35,22 +38,112 @@ LWAN_HANDLER(ws) lwan_request_sleep(request, 1000); } - return HTTP_OK; + __builtin_unreachable(); +} + +static void free_strbuf(void *data) +{ + lwan_strbuf_free((struct lwan_strbuf *)data); +} + +/* This is a slightly more featured echo server that tells how many seconds + * passed since the last message has been received, and keeps sending it back + * again and again. */ +LWAN_HANDLER(ws_read) +{ + enum lwan_http_status status = lwan_request_websocket_upgrade(request); + struct lwan_strbuf *last_msg_recv; + int seconds_since_last_msg = 0; + + if (status != HTTP_SWITCHING_PROTOCOLS) + return status; + + last_msg_recv = lwan_strbuf_new(); + if (!last_msg_recv) + return HTTP_INTERNAL_ERROR; + coro_defer(request->conn->coro, free_strbuf, last_msg_recv); + + while (true) { + switch (lwan_response_websocket_read(request)) { + case ENOTCONN: /* read() called before connection is websocket */ + case ECONNRESET: /* Client closed the connection */ + goto out; + + case EAGAIN: /* Nothing is available */ + lwan_strbuf_printf(response->buffer, + "Last message was received %d seconds ago: %.*s", + seconds_since_last_msg, + (int)lwan_strbuf_get_length(last_msg_recv), + lwan_strbuf_get_buffer(last_msg_recv)); + lwan_response_websocket_write(request); + + lwan_request_sleep(request, 1000); + seconds_since_last_msg++; + break; + + case 0: /* We got something! Copy it to echo it back */ + lwan_strbuf_set(last_msg_recv, + lwan_strbuf_get_buffer(response->buffer), + lwan_strbuf_get_length(response->buffer)); + + seconds_since_last_msg = 0; + + break; + } + } + +out: + /* We abort the coroutine here because there's not much we can do at this + * point as this isn't a HTTP connection anymore. */ + coro_yield(request->conn->coro, CORO_ABORT); + __builtin_unreachable(); } LWAN_HANDLER(index) { - static const char message[] = "\n" + static const char message[] = + "\n" " \n" " \n" " \n" " \n" - "
\n" + "

Lwan WebSocket demo!

\n" + "

Send-only sample: server is writing this continuously:

\n" + "

Disconnected

\n" + "

Echo server sample:

\n" + "

\n" + "

Server said this:

Disconnected

\n" " \n" ""; @@ -63,7 +156,8 @@ LWAN_HANDLER(index) int main(void) { const struct lwan_url_map default_map[] = { - {.prefix = "/ws", .handler = LWAN_HANDLER_REF(ws)}, + {.prefix = "/ws-write", .handler = LWAN_HANDLER_REF(ws_write)}, + {.prefix = "/ws-read", .handler = LWAN_HANDLER_REF(ws_read)}, {.prefix = "/", .handler = LWAN_HANDLER_REF(index)}, {}, }; From fed962ed21ef6ef65bfb140bad78c5b74d256175 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 30 Jun 2020 17:54:49 -0700 Subject: [PATCH 1521/2505] Adapt Lua ws_read() to conform to the current version of the API --- README.md | 2 +- src/lib/lwan-lua.c | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 887429e51..c243fe574 100644 --- a/README.md +++ b/README.md @@ -422,7 +422,7 @@ information from the request, or to set the response, as seen below: - `req:sleep(ms)` pauses the current handler for the specified amount of milliseconds - `req:ws_upgrade()` returns `1` if the connection could be upgraded to a WebSocket; `0` otherwise - `req:ws_write(str)` sends `str` through the WebSocket-upgraded connection - - `req:ws_read()` returns a string obtained from the WebSocket, or `nil` on error + - `req:ws_read()` returns a string with the contents of the last WebSocket frame, or a number indicating an status (ENOTCONN/107 on Linux if it has been disconnected; EAGAIN/11 on Linux if nothing was available; ENOMSG/42 on Linux otherwise). The return value here might change in the future for something more Lua-like. Handler functions may return either `nil` (in which case, a `200 OK` response is generated), or a number matching an HTTP status code. Attempting to return diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index b30d04c83..da129bd3f 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -19,6 +19,7 @@ */ #define _GNU_SOURCE +#include #include #include #include @@ -137,12 +138,23 @@ LWAN_LUA_METHOD(ws_write) LWAN_LUA_METHOD(ws_read) { struct lwan_request *request = lwan_lua_get_request_from_userdata(L); + int r; - if (lwan_response_websocket_read(request)) { + /* FIXME: maybe return a table {status=r, content=buf}? */ + + r = lwan_response_websocket_read(request); + switch (r) { + case 0: lua_pushlstring(L, lwan_strbuf_get_buffer(request->response.buffer), lwan_strbuf_get_length(request->response.buffer)); - } else { - lua_pushnil(L); + break; + case ENOTCONN: + case EAGAIN: + lua_pushinteger(L, r); + break; + default: + lua_pushinteger(L, ENOMSG); + break; } return 1; From a3fd005e849aefbfde43d673b54a9b266c9f2409 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 30 Jun 2020 17:59:19 -0700 Subject: [PATCH 1522/2505] Update the prototype SignalR application to conform to the new WS API --- src/samples/chatr/main.c | 89 +++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 47 deletions(-) diff --git a/src/samples/chatr/main.c b/src/samples/chatr/main.c index 1e271c017..dd5e425ef 100644 --- a/src/samples/chatr/main.c +++ b/src/samples/chatr/main.c @@ -180,17 +180,6 @@ static int sync_map_add(struct sync_map *sync_map, const char *key, const void * return ret; } -static const void *sync_map_find(struct sync_map *sync_map, const char *key) -{ - void *ret; - - pthread_mutex_lock(&sync_map->mutex); - ret = hash_find(sync_map->table, key); - pthread_mutex_unlock(&sync_map->mutex); - - return ret; -} - static void sync_map_range(struct sync_map *sync_map, bool (*iter_func)(const char *key, void *value, void *data), @@ -305,7 +294,7 @@ static int send_json(struct lwan_response *request, static bool send_error(struct lwan_request *request, const char *error) { lwan_strbuf_set_printf(request->response->buffer, - "{\"error\":\"%s\"}\u001e", error); + "{\"error\":\"%s\"}\x1e", error); lwan_response_websocket_send(request); return false; } @@ -331,7 +320,7 @@ static bool process_handshake(struct lwan_request *request, if (!streq(handshake.protocol, "json")) return send_error(request, "Only `json' protocol supported"); - lwan_strbuf_set_static(response->buffer, "{}\u001e", 3); + lwan_strbuf_set_static(response->buffer, "{}\x1e", 3); lwan_response_websocket_send(request); return true; @@ -339,7 +328,7 @@ static bool process_handshake(struct lwan_request *request, static void handle_ping(struct lwan_request *request) { - lwan_strbuf_set_staticz("{\"type\":6}\u001e"); + lwan_strbuf_set_staticz("{\"type\":6}\x1e"); lwan_response_websocket_send(request); } @@ -347,10 +336,8 @@ static bool broadcast_msg(const void *key, void *value, void *data) { struct msg_ring *messages = value; - if (message->numArguments == 1) { - msg_ring_append(messages, message->arguments[0]); - return true; - } + if (message->numArguments == 1) + return msg_ring_try_put(messages, message->arguments[0]); return false; } @@ -426,6 +413,25 @@ static bool hub_msg_ring_send_message(char *msg, void *data) ARRAY_SIZE(invocation_message_descr), &invocation) == 0; } +static void parse_hub_msg(struct lwan_request *request, + struct sync_map *clients) +{ + struct message message; + int ret = parse_json(response, message_descr, + ARRAY_SIZE(message_descr), &message); + if (ret < 0) + return; + if (!(ret & 1 << 0)) /* `type` not present, ignore */ + return; + + switch (message.type) { + case 1: + return handle_invocation(request, clients); + case 6: + return handle_ping(request); + } +} + static enum lwan_http_status hub_connection_handler(struct lwan_request *request, struct lwan_response *response, @@ -444,37 +450,26 @@ hub_connection_handler(struct lwan_request *request, coro_defer(request->conn->coro, msg_ring_free, messages); while (true) { - /* FIXME: there's no way to specify that we want to either read from - * the websocket *or* get a message in the message ring buffer. */ - if (!lwan_response_websocket_read(request)) { - send_error(request, "Could not read from WebSocket"); + switch (lwan_response_websocket_read(request)) { + case ENOTCONN: + /* Shouldn't happen because if we get here, this connection + * has been upgraded to a websocket. */ return HTTP_INTERNAL_ERROR; - } - /* Big FIXMES: - * - * Ideally, messages would be refcounted to avoid duplication of - * messages lingering around. But this is fine for a sample app. - * - * Also, this is in the wrong "layer"; shouldn't be in the hub main - * loop. But the WebSockets API in Lwan isn't that great and lacks - * the facilities to perform this correctly. */ - msg_ring_consume(&msg_ring, hub_msg_ring_send_message, request); - - struct message message; - int ret = parse_json(response, message_descr, ARRAY_SIZE(message_descr), - &message); - if (ret < 0) - continue; - if (!(ret & 1 << 0)) /* `type` not present, ignore */ - continue; - - switch (message.type) { - case 1: - handle_invocation(request, clients); + case ECONNRESET: + /* Connection has been closed by the peer. */ + return HTTP_UNAVAILABLE; + + case EAGAIN: + msg_ring_consume(&msg_ring, hub_msg_ring_send_message, request); + + /* FIXME: ideally, websocket_read() should have a timeout; this is + * just a workaround */ + lwan_request_sleep(1000); break; - case 6: - handle_ping(request); + + case 0: + parse_hub_msg(request, clients); break; } } @@ -482,7 +477,7 @@ hub_connection_handler(struct lwan_request *request, LWAN_HANDLER(chat) { - static const char handshake_response[] = "{}\u001e"; + static const char handshake_response[] = "{}\x1e"; const char *connection_id; if (lwan_request_websocket_upgrade(request) != HTTP_SWITCHING_PROTOCOLS) From 6464cd96539778a1e20ea8baacb54a703d61f9ad Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 30 Jun 2020 18:07:10 -0700 Subject: [PATCH 1523/2505] Fix build after 1b348f0c1 --- src/samples/websocket/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/samples/websocket/main.c b/src/samples/websocket/main.c index e02245ad8..d7c285855 100644 --- a/src/samples/websocket/main.c +++ b/src/samples/websocket/main.c @@ -95,7 +95,7 @@ LWAN_HANDLER(ws_read) out: /* We abort the coroutine here because there's not much we can do at this * point as this isn't a HTTP connection anymore. */ - coro_yield(request->conn->coro, CORO_ABORT); + coro_yield(request->conn->coro, CONN_CORO_ABORT); __builtin_unreachable(); } From afff44f8089013959e881f190aad008a8c88f2c6 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 30 Jun 2020 23:03:37 -0700 Subject: [PATCH 1524/2505] Add simple pub/sub API --- src/lib/CMakeLists.txt | 1 + src/lib/lwan-pubsub.c | 218 +++++++++++++++++++++++++++++++++++++++++ src/lib/lwan-pubsub.h | 43 ++++++++ 3 files changed, 262 insertions(+) create mode 100644 src/lib/lwan-pubsub.c create mode 100644 src/lib/lwan-pubsub.h diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index 024ce713b..6c5b438ba 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -34,6 +34,7 @@ set(SOURCES lwan-tq.c lwan-trie.c lwan-websocket.c + lwan-pubsub.c missing.c missing-pthread.c murmur3.c diff --git a/src/lib/lwan-pubsub.c b/src/lib/lwan-pubsub.c new file mode 100644 index 000000000..4dbca1c8c --- /dev/null +++ b/src/lib/lwan-pubsub.c @@ -0,0 +1,218 @@ +/* + * lwan - simple web server + * Copyright (c) 2020 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include + +#include "list.h" +#include "lwan-private.h" + +struct lwan_pubsub_topic { + struct list_head subscribers; + pthread_mutex_t lock; +}; + +struct lwan_pubsub_msg { + struct lwan_value value; + int refcount; +}; + +struct lwan_pubsub_sub_msg { + struct list_node message; + struct lwan_pubsub_msg *msg; +}; + +struct lwan_pubsub_subscriber { + struct list_node subscriber; + + struct list_head messages; + pthread_mutex_t lock; +}; + +static void lwan_pubsub_unsubscribe_internal(struct lwan_pubsub_topic *topic, + struct lwan_pubsub_subscriber *sub, + bool take_topic_lock); + +struct lwan_pubsub_topic *lwan_pubsub_new_topic(void) +{ + struct lwan_pubsub_topic *topic = calloc(1, sizeof(*topic)); + + if (!topic) + return NULL; + + list_head_init(&topic->subscribers); + pthread_mutex_init(&topic->lock, NULL); + + return topic; +} + +void lwan_pubsub_free_topic(struct lwan_pubsub_topic *topic) +{ + struct lwan_pubsub_subscriber *iter, *next; + + pthread_mutex_lock(&topic->lock); + list_for_each_safe (&topic->subscribers, iter, next, subscriber) + lwan_pubsub_unsubscribe_internal(topic, iter, false); + pthread_mutex_unlock(&topic->lock); + + pthread_mutex_destroy(&topic->lock); + + free(topic); +} + +static void *my_memdup(const void *src, size_t len) +{ + void *dup = malloc(len); + + return dup ? memcpy(dup, src, len) : NULL; +} + +bool lwan_pubsub_publish(struct lwan_pubsub_topic *topic, + const void *contents, + size_t len) +{ + struct lwan_pubsub_msg *msg = calloc(1, sizeof(*msg)); + struct lwan_pubsub_subscriber *sub; + bool published = false; + + if (!msg) + return false; + + msg->value = (struct lwan_value){ + .value = my_memdup(contents, len), + .len = len, + }; + if (!msg->value.value) { + free(msg); + return false; + } + + pthread_mutex_lock(&topic->lock); + list_for_each (&topic->subscribers, sub, subscriber) { + struct lwan_pubsub_sub_msg *sub_msg = malloc(sizeof(*sub_msg)); + + if (!sub_msg) { + lwan_status_warning("Dropping message: couldn't allocate memory"); + continue; + } + + published = true; + sub_msg->msg = msg; + + pthread_mutex_lock(&sub->lock); + msg->refcount++; + list_add_tail(&sub->messages, &sub_msg->message); + pthread_mutex_unlock(&sub->lock); + } + pthread_mutex_unlock(&topic->lock); + + if (!published) + free(msg); + + return true; +} + +struct lwan_pubsub_subscriber * +lwan_pubsub_subscribe(struct lwan_pubsub_topic *topic) +{ + struct lwan_pubsub_subscriber *sub = calloc(1, sizeof(*sub)); + + if (!sub) + return NULL; + + pthread_mutex_init(&sub->lock, NULL); + list_head_init(&sub->messages); + + pthread_mutex_lock(&topic->lock); + list_add(&topic->subscribers, &sub->subscriber); + pthread_mutex_unlock(&topic->lock); + + return sub; +} + +struct lwan_pubsub_msg *lwan_pubsub_consume(struct lwan_pubsub_subscriber *sub) +{ + struct lwan_pubsub_sub_msg *sub_msg; + struct lwan_pubsub_msg *msg; + + pthread_mutex_lock(&sub->lock); + sub_msg = list_pop(&sub->messages, struct lwan_pubsub_sub_msg, message); + pthread_mutex_unlock(&sub->lock); + + if (sub_msg) { + msg = sub_msg->msg; + free(sub_msg); + return msg; + } + + return NULL; +} + +void lwan_pubsub_msg_done(struct lwan_pubsub_msg *msg) +{ + if (!ATOMIC_DEC(msg->refcount)) { + free(msg->value.value); + free(msg); + } +} + +static void lwan_pubsub_unsubscribe_internal(struct lwan_pubsub_topic *topic, + struct lwan_pubsub_subscriber *sub, + bool take_topic_lock) +{ + struct lwan_pubsub_sub_msg *iter, *next; + struct list_head to_free; + + if (take_topic_lock) + pthread_mutex_lock(&topic->lock); + list_del(&sub->subscriber); + if (take_topic_lock) + pthread_mutex_unlock(&topic->lock); + + list_head_init(&to_free); + + pthread_mutex_lock(&sub->lock); + list_for_each_safe (&sub->messages, iter, next, message) { + list_del(&iter->message); + + if (!ATOMIC_DEC(iter->msg->refcount)) + list_add(&to_free, &iter->message); + } + pthread_mutex_unlock(&sub->lock); + pthread_mutex_destroy(&sub->lock); + + list_for_each_safe (&to_free, iter, next, message) { + free(iter->msg->value.value); + free(iter->msg); + free(iter); + } + + free(sub); +} + +void lwan_pubsub_unsubscribe(struct lwan_pubsub_topic *topic, + struct lwan_pubsub_subscriber *sub) +{ + return (void)lwan_pubsub_unsubscribe_internal(topic, sub, true); +} + +const struct lwan_value *lwan_pubsub_msg_value(const struct lwan_pubsub_msg *msg) +{ + return &msg->value; +} diff --git a/src/lib/lwan-pubsub.h b/src/lib/lwan-pubsub.h new file mode 100644 index 000000000..e9d4dbdad --- /dev/null +++ b/src/lib/lwan-pubsub.h @@ -0,0 +1,43 @@ +/* + * lwan - simple web server + * Copyright (c) 2020 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#pragma once + +#include "lwan.h" + +struct lwan_pubsub_topic; +struct lwan_pubsub_msg; +struct lwan_pubsub_subscriber; + +struct lwan_pubsub_topic *lwan_pubsub_new_topic(void); +void lwan_pubsub_free_topic(struct lwan_pubsub_topic *topic); + +bool lwan_pubsub_publish(struct lwan_pubsub_topic *topic, + const void *contents, + size_t len); + +struct lwan_pubsub_subscriber * +lwan_pubsub_subscribe(struct lwan_pubsub_topic *topic); +void lwan_pubsub_unsubscribe(struct lwan_pubsub_topic *topic, + struct lwan_pubsub_subscriber *sub); + +struct lwan_pubsub_msg *lwan_pubsub_consume(struct lwan_pubsub_subscriber *sub); +const struct lwan_value *lwan_pubsub_msg_value(const struct lwan_pubsub_msg *msg); +void lwan_pubsub_msg_done(struct lwan_pubsub_msg *msg); From c5a55d7485a21723d3c8ea59cab031e44f33ec8f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 30 Jun 2020 23:04:42 -0700 Subject: [PATCH 1525/2505] Use the pub/sub API to implement a chat sample with websockets --- src/samples/websocket/main.c | 113 +++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/src/samples/websocket/main.c b/src/samples/websocket/main.c index d7c285855..83ef21bb1 100644 --- a/src/samples/websocket/main.c +++ b/src/samples/websocket/main.c @@ -22,6 +22,9 @@ #include #include "lwan.h" +#include "lwan-pubsub.h" + +static struct lwan_pubsub_topic *chat; /* This is a write-only sample of the API: it just sends random integers * over a WebSockets connection. */ @@ -99,6 +102,89 @@ LWAN_HANDLER(ws_read) __builtin_unreachable(); } +static void unsub_chat(void *data1, void *data2) +{ + lwan_pubsub_unsubscribe((struct lwan_pubsub_topic *)data1, + (struct lwan_pubsub_subscriber *)data2); +} + +static void pub_depart_message(void *data1, void *data2) +{ + char buffer[128]; + int r; + + r = snprintf(buffer, sizeof(buffer), "*** User%d has departed the chat!\n", + (int)(intptr_t)data2); + if (r < 0 || (size_t)r >= sizeof(buffer)) + return; + + lwan_pubsub_publish((struct lwan_pubsub_topic *)data1, buffer, (size_t)r); +} + +LWAN_HANDLER(ws_chat) +{ + struct lwan_pubsub_subscriber *sub; + struct lwan_pubsub_msg *msg; + struct lwan_strbuf tmp_msg = LWAN_STRBUF_STATIC_INIT; + enum lwan_http_status status; + static int total_user_count; + int user_id; + + sub = lwan_pubsub_subscribe(chat); + if (!sub) + return HTTP_INTERNAL_ERROR; + coro_defer2(request->conn->coro, unsub_chat, chat, sub); + + status = lwan_request_websocket_upgrade(request); + if (status != HTTP_SWITCHING_PROTOCOLS) + return status; + + user_id = ATOMIC_INC(total_user_count); + + lwan_strbuf_printf(response->buffer, "*** Welcome to the chat, User%d!\n", user_id); + lwan_response_websocket_write(request); + + coro_defer2(request->conn->coro, pub_depart_message, chat, (void *)(intptr_t)user_id); + lwan_strbuf_printf(&tmp_msg, "*** User%d has joined the chat!\n", user_id); + lwan_pubsub_publish(chat, lwan_strbuf_get_buffer(&tmp_msg), + lwan_strbuf_get_length(&tmp_msg)); + + while (true) { + switch (lwan_response_websocket_read(request)) { + case ENOTCONN: /* read() called before connection is websocket */ + case ECONNRESET: /* Client closed the connection */ + goto out; + + case EAGAIN: /* Nothing is available from other clients */ + while ((msg = lwan_pubsub_consume(sub))) { + const struct lwan_value *value = lwan_pubsub_msg_value(msg); + + lwan_strbuf_set(response->buffer, value->value, value->len); + lwan_response_websocket_write(request); + + lwan_pubsub_msg_done(msg); + } + + lwan_request_sleep(request, 1000); + break; + + case 0: /* We got something! Copy it to echo it back */ + lwan_strbuf_printf(&tmp_msg, "User%d: %.*s\n", + user_id, + (int)lwan_strbuf_get_length(response->buffer), + lwan_strbuf_get_buffer(response->buffer)); + lwan_pubsub_publish(chat, lwan_strbuf_get_buffer(&tmp_msg), + lwan_strbuf_get_length(&tmp_msg)); + break; + } + } + +out: + /* We abort the coroutine here because there's not much we can do at this + * point as this isn't a HTTP connection anymore. */ + coro_yield(request->conn->coro, CONN_CORO_ABORT); + __builtin_unreachable(); +} LWAN_HANDLER(index) { static const char message[] = @@ -135,6 +221,26 @@ LWAN_HANDLER(index) " send_to_read_sock = function() {\n" " read_sock.send(document.getElementById(\"read-input\").value);\n" " }\n" + " chat_sock = new WebSocket(\"ws://localhost:8080/ws-chat\")\n" + " chat_sock.onopen = function(event) {\n" + " document.getElementById(\"chat-button\").disabled = false;\n" + " document.getElementById(\"chat-input\").disabled = false;\n" + " document.getElementById(\"chat-textarea\").style.background = \"blue\";\n" + " document.getElementById(\"chat-input\").innerText = \"\";\n" + " }\n" + " chat_sock.onerror = function(event) {\n" + " document.getElementById(\"chat-button\").disabled = true;\n" + " document.getElementById(\"chat-input\").disabled = true;\n" + " document.getElementById(\"chat-input\").innerText = \"Disconnected\";\n" + " document.getElementById(\"chat-textarea\").style.background = \"red\";\n" + " }\n" + " chat_sock.onmessage = function (event) {\n" + " document.getElementById(\"chat-textarea\").value += event.data;\n" + " }\n" + " send_chat_msg = function() {\n" + " chat_sock.send(document.getElementById(\"chat-input\").value);\n" + " document.getElementById(\"chat-input\").value = \"\";\n" + " }\n" " \n" " \n" " \n" @@ -144,6 +250,9 @@ LWAN_HANDLER(index) "

Echo server sample:

\n" "

\n" "

Server said this:

Disconnected

\n" + "

Chat sample:

\n" + " Send message:

\n" + " \n" " \n" ""; @@ -158,6 +267,7 @@ int main(void) const struct lwan_url_map default_map[] = { {.prefix = "/ws-write", .handler = LWAN_HANDLER_REF(ws_write)}, {.prefix = "/ws-read", .handler = LWAN_HANDLER_REF(ws_read)}, + {.prefix = "/ws-chat", .handler = LWAN_HANDLER_REF(ws_chat)}, {.prefix = "/", .handler = LWAN_HANDLER_REF(index)}, {}, }; @@ -165,10 +275,13 @@ int main(void) lwan_init(&l); + chat = lwan_pubsub_new_topic(); + lwan_set_url_map(&l, default_map); lwan_main_loop(&l); lwan_shutdown(&l); + lwan_pubsub_free_topic(chat); return 0; } From 3a5ff88614af7d2ccddfba7600b29e5a53c11818 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 1 Jul 2020 00:12:51 -0700 Subject: [PATCH 1526/2505] Mark message as done before the coroutine can be killed --- src/samples/websocket/main.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/samples/websocket/main.c b/src/samples/websocket/main.c index 83ef21bb1..935238da2 100644 --- a/src/samples/websocket/main.c +++ b/src/samples/websocket/main.c @@ -160,9 +160,13 @@ LWAN_HANDLER(ws_chat) const struct lwan_value *value = lwan_pubsub_msg_value(msg); lwan_strbuf_set(response->buffer, value->value, value->len); - lwan_response_websocket_write(request); + /* Mark as done before writing: websocket_write() can abort the + * coroutine and we want to drop the reference before this + * happens. */ lwan_pubsub_msg_done(msg); + + lwan_response_websocket_write(request); } lwan_request_sleep(request, 1000); @@ -185,6 +189,7 @@ LWAN_HANDLER(ws_chat) coro_yield(request->conn->coro, CONN_CORO_ABORT); __builtin_unreachable(); } + LWAN_HANDLER(index) { static const char message[] = From 839049f79ed23cb0f27a9a0b43a9b1ec17e9852b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 1 Jul 2020 00:18:52 -0700 Subject: [PATCH 1527/2505] Simplify pub/sub unsubscribe function --- src/lib/lwan-pubsub.c | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/lib/lwan-pubsub.c b/src/lib/lwan-pubsub.c index 4dbca1c8c..651201a9f 100644 --- a/src/lib/lwan-pubsub.c +++ b/src/lib/lwan-pubsub.c @@ -177,7 +177,6 @@ static void lwan_pubsub_unsubscribe_internal(struct lwan_pubsub_topic *topic, bool take_topic_lock) { struct lwan_pubsub_sub_msg *iter, *next; - struct list_head to_free; if (take_topic_lock) pthread_mutex_lock(&topic->lock); @@ -185,24 +184,15 @@ static void lwan_pubsub_unsubscribe_internal(struct lwan_pubsub_topic *topic, if (take_topic_lock) pthread_mutex_unlock(&topic->lock); - list_head_init(&to_free); - pthread_mutex_lock(&sub->lock); list_for_each_safe (&sub->messages, iter, next, message) { list_del(&iter->message); - - if (!ATOMIC_DEC(iter->msg->refcount)) - list_add(&to_free, &iter->message); - } - pthread_mutex_unlock(&sub->lock); - pthread_mutex_destroy(&sub->lock); - - list_for_each_safe (&to_free, iter, next, message) { - free(iter->msg->value.value); - free(iter->msg); + lwan_pubsub_msg_done(iter->msg); free(iter); } + pthread_mutex_unlock(&sub->lock); + pthread_mutex_destroy(&sub->lock); free(sub); } From fa20a9f094694314ccd4376ef670dd820e796c32 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 1 Jul 2020 20:24:20 -0700 Subject: [PATCH 1528/2505] Account for the fact that parse_long() maybe called with a NULL pointer This can happen, for instance, when passing the return value from hash_find() for a key that's not present in the hash table, common in module setup scenarios. --- src/lib/lwan-config.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 88d437799..75dfedfef 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -127,6 +127,9 @@ long parse_long(const char *value, long default_value) char *endptr; long parsed; + if (!value) + return default_value; + errno = 0; parsed = strtol(value, &endptr, 0); From a3306e3b26837f3f50ddea60a7a0b5336370c315 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 2 Jul 2020 18:06:12 -0700 Subject: [PATCH 1529/2505] Fix race condition in pubsub while publishing Incrementing the refcount of a shared message has to be done in a atomic way; taking sub->lock isn't protecting this shared resource, and another subscriber for this message might be consuming it as that message is being published to other subscribers. By initializaing the refcount to 1 when the message is allocated, atomically increasing the number, and dropping the message when we atomically decrease the reference count after the message has been published guarantees that the refcount won't be corrupted. Also, this fixes a memory leak when dropping the published message when there were no subscribers: the message itself was not being freed. --- src/lib/lwan-pubsub.c | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/lib/lwan-pubsub.c b/src/lib/lwan-pubsub.c index 651201a9f..2fc124e2f 100644 --- a/src/lib/lwan-pubsub.c +++ b/src/lib/lwan-pubsub.c @@ -83,17 +83,29 @@ static void *my_memdup(const void *src, size_t len) return dup ? memcpy(dup, src, len) : NULL; } +void lwan_pubsub_msg_done(struct lwan_pubsub_msg *msg) +{ + if (!ATOMIC_DEC(msg->refcount)) { + free(msg->value.value); + free(msg); + } +} + bool lwan_pubsub_publish(struct lwan_pubsub_topic *topic, const void *contents, size_t len) { struct lwan_pubsub_msg *msg = calloc(1, sizeof(*msg)); struct lwan_pubsub_subscriber *sub; - bool published = false; if (!msg) return false; + /* Initialize refcount to 1, so we can drop one ref after publishing to + * all subscribers. If it drops to 0, it means we didn't publish the + * message and we can free it. */ + msg->refcount = 1; + msg->value = (struct lwan_value){ .value = my_memdup(contents, len), .len = len, @@ -112,18 +124,16 @@ bool lwan_pubsub_publish(struct lwan_pubsub_topic *topic, continue; } - published = true; sub_msg->msg = msg; + ATOMIC_INC(msg->refcount); pthread_mutex_lock(&sub->lock); - msg->refcount++; list_add_tail(&sub->messages, &sub_msg->message); pthread_mutex_unlock(&sub->lock); } pthread_mutex_unlock(&topic->lock); - if (!published) - free(msg); + lwan_pubsub_msg_done(msg); return true; } @@ -164,14 +174,6 @@ struct lwan_pubsub_msg *lwan_pubsub_consume(struct lwan_pubsub_subscriber *sub) return NULL; } -void lwan_pubsub_msg_done(struct lwan_pubsub_msg *msg) -{ - if (!ATOMIC_DEC(msg->refcount)) { - free(msg->value.value); - free(msg); - } -} - static void lwan_pubsub_unsubscribe_internal(struct lwan_pubsub_topic *topic, struct lwan_pubsub_subscriber *sub, bool take_topic_lock) From 314f8743bd22036c392881fa63689c6b882ee8e0 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 2 Jul 2020 18:09:18 -0700 Subject: [PATCH 1530/2505] Small cleanup in pubsub consume function --- src/lib/lwan-pubsub.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-pubsub.c b/src/lib/lwan-pubsub.c index 2fc124e2f..5964af5ff 100644 --- a/src/lib/lwan-pubsub.c +++ b/src/lib/lwan-pubsub.c @@ -159,15 +159,16 @@ lwan_pubsub_subscribe(struct lwan_pubsub_topic *topic) struct lwan_pubsub_msg *lwan_pubsub_consume(struct lwan_pubsub_subscriber *sub) { struct lwan_pubsub_sub_msg *sub_msg; - struct lwan_pubsub_msg *msg; pthread_mutex_lock(&sub->lock); sub_msg = list_pop(&sub->messages, struct lwan_pubsub_sub_msg, message); pthread_mutex_unlock(&sub->lock); if (sub_msg) { - msg = sub_msg->msg; + struct lwan_pubsub_msg *msg = sub_msg->msg; + free(sub_msg); + return msg; } From a4fa7a211939c446d3d71980189129ce3792374b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 2 Jul 2020 18:09:40 -0700 Subject: [PATCH 1531/2505] Fix build in OpenBSD --- src/lib/lwan-websocket.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index 095d9bfe2..801f876f6 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -18,11 +18,12 @@ * USA. */ -#include #include +#include +#include -#include "lwan-private.h" #include "lwan-io-wrappers.h" +#include "lwan-private.h" enum ws_opcode { WS_OPCODE_CONTINUATION = 0, From 36accf7cececa32ddda34fbfb23244e710efb545 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 3 Jul 2020 08:22:32 -0700 Subject: [PATCH 1532/2505] Reduce memory allocation churn in pubsub Use a doubly-linked list of ring buffers to store pointers to struct lwan_pubsub_msg, instead of allocating a linked list node per message in the queue. All subscription queues begin with a single node in the linked list. If that overflows, a new one is created (and added to the list tail) and the message is put there. Messages are consumed from each linked list node in succession; if one of these nodes contain an empty ring buffer, the entire node is removed from the linked list, and messages are then consumed from the ring buffer in the next node. --- src/lib/lwan-pubsub.c | 107 ++++++++++++++++++++++++++++++------------ 1 file changed, 76 insertions(+), 31 deletions(-) diff --git a/src/lib/lwan-pubsub.c b/src/lib/lwan-pubsub.c index 5964af5ff..b8eb482ab 100644 --- a/src/lib/lwan-pubsub.c +++ b/src/lib/lwan-pubsub.c @@ -21,6 +21,7 @@ #include #include "list.h" +#include "ringbuffer.h" #include "lwan-private.h" struct lwan_pubsub_topic { @@ -33,18 +34,78 @@ struct lwan_pubsub_msg { int refcount; }; -struct lwan_pubsub_sub_msg { - struct list_node message; - struct lwan_pubsub_msg *msg; +DEFINE_RING_BUFFER_TYPE(lwan_pubsub_msg_ref, struct lwan_pubsub_msg *, 16) + +struct lwan_pubsub_sub_queue_rb { + struct list_node rb; + struct lwan_pubsub_msg_ref ref; +}; + +struct lwan_pubsub_sub_queue { + struct list_head rbs; }; struct lwan_pubsub_subscriber { struct list_node subscriber; - struct list_head messages; pthread_mutex_t lock; + struct lwan_pubsub_sub_queue queue; }; +static bool lwan_pubsub_queue_init(struct lwan_pubsub_sub_queue *queue) +{ + struct lwan_pubsub_sub_queue_rb *rb; + + rb = malloc(sizeof(*rb)); + if (!rb) + return false; + + lwan_pubsub_msg_ref_init(&rb->ref); + list_head_init(&queue->rbs); + list_add(&queue->rbs, &rb->rb); + + return true; +} + +static bool lwan_pubsub_queue_put(struct lwan_pubsub_sub_queue *queue, + const struct lwan_pubsub_msg *msg) +{ + struct lwan_pubsub_sub_queue_rb *rb; + + list_for_each (&queue->rbs, rb, rb) { + if (lwan_pubsub_msg_ref_try_put(&rb->ref, &msg)) + return true; + } + + rb = malloc(sizeof(*rb)); + if (!rb) + return false; + + lwan_pubsub_msg_ref_init(&rb->ref); + lwan_pubsub_msg_ref_put(&rb->ref, &msg); + list_add_tail(&queue->rbs, &rb->rb); + + return true; +} + +static struct lwan_pubsub_msg * +lwan_pubsub_queue_get(struct lwan_pubsub_sub_queue *queue) +{ + struct lwan_pubsub_sub_queue_rb *rb, *next; + + list_for_each_safe (&queue->rbs, rb, next, rb) { + if (lwan_pubsub_msg_ref_empty(&rb->ref)) { + list_del(&rb->rb); + free(rb); + continue; + } + + return lwan_pubsub_msg_ref_get(&rb->ref); + } + + return NULL; +} + static void lwan_pubsub_unsubscribe_internal(struct lwan_pubsub_topic *topic, struct lwan_pubsub_subscriber *sub, bool take_topic_lock); @@ -117,18 +178,13 @@ bool lwan_pubsub_publish(struct lwan_pubsub_topic *topic, pthread_mutex_lock(&topic->lock); list_for_each (&topic->subscribers, sub, subscriber) { - struct lwan_pubsub_sub_msg *sub_msg = malloc(sizeof(*sub_msg)); - - if (!sub_msg) { - lwan_status_warning("Dropping message: couldn't allocate memory"); - continue; - } - - sub_msg->msg = msg; ATOMIC_INC(msg->refcount); pthread_mutex_lock(&sub->lock); - list_add_tail(&sub->messages, &sub_msg->message); + if (!lwan_pubsub_queue_put(&sub->queue, msg)) { + lwan_status_warning("Couldn't enqueue message, dropping"); + ATOMIC_DEC(msg->refcount); + } pthread_mutex_unlock(&sub->lock); } pthread_mutex_unlock(&topic->lock); @@ -147,7 +203,7 @@ lwan_pubsub_subscribe(struct lwan_pubsub_topic *topic) return NULL; pthread_mutex_init(&sub->lock, NULL); - list_head_init(&sub->messages); + lwan_pubsub_queue_init(&sub->queue); pthread_mutex_lock(&topic->lock); list_add(&topic->subscribers, &sub->subscriber); @@ -158,28 +214,20 @@ lwan_pubsub_subscribe(struct lwan_pubsub_topic *topic) struct lwan_pubsub_msg *lwan_pubsub_consume(struct lwan_pubsub_subscriber *sub) { - struct lwan_pubsub_sub_msg *sub_msg; + struct lwan_pubsub_msg *msg; pthread_mutex_lock(&sub->lock); - sub_msg = list_pop(&sub->messages, struct lwan_pubsub_sub_msg, message); + msg = lwan_pubsub_queue_get(&sub->queue); pthread_mutex_unlock(&sub->lock); - if (sub_msg) { - struct lwan_pubsub_msg *msg = sub_msg->msg; - - free(sub_msg); - - return msg; - } - - return NULL; + return msg; } static void lwan_pubsub_unsubscribe_internal(struct lwan_pubsub_topic *topic, struct lwan_pubsub_subscriber *sub, bool take_topic_lock) { - struct lwan_pubsub_sub_msg *iter, *next; + struct lwan_pubsub_msg *iter; if (take_topic_lock) pthread_mutex_lock(&topic->lock); @@ -188,11 +236,8 @@ static void lwan_pubsub_unsubscribe_internal(struct lwan_pubsub_topic *topic, pthread_mutex_unlock(&topic->lock); pthread_mutex_lock(&sub->lock); - list_for_each_safe (&sub->messages, iter, next, message) { - list_del(&iter->message); - lwan_pubsub_msg_done(iter->msg); - free(iter); - } + while ((iter = lwan_pubsub_queue_get(&sub->queue))) + lwan_pubsub_msg_done(iter); pthread_mutex_unlock(&sub->lock); pthread_mutex_destroy(&sub->lock); From 5984e56c8df86d9c99b33e3b36ebd3259d166485 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 3 Jul 2020 09:01:38 -0700 Subject: [PATCH 1533/2505] Always try putting a message in the last ringbuffer allocated for a queue Try putting the message in the last ringbuffer in this queue: if it's full, will need to allocate a new ring buffer, even if others might have space in them: the FIFO order must be preserved, and short of compacting the queue at this point -- which will eventually happen as it is consumed -- this is the only option. --- src/lib/lwan-pubsub.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-pubsub.c b/src/lib/lwan-pubsub.c index b8eb482ab..0d3ef83d3 100644 --- a/src/lib/lwan-pubsub.c +++ b/src/lib/lwan-pubsub.c @@ -72,9 +72,15 @@ static bool lwan_pubsub_queue_put(struct lwan_pubsub_sub_queue *queue, { struct lwan_pubsub_sub_queue_rb *rb; - list_for_each (&queue->rbs, rb, rb) { + /* Try putting the message in the last ringbuffer in this queue: if it's + * full, will need to allocate a new ring buffer, even if others might + * have space in them: the FIFO order must be preserved, and short of + * compacting the queue at this point -- which will eventually happen + * as it is consumed -- this is the only option. */ + list_for_each_rev (&queue->rbs, rb, rb) { if (lwan_pubsub_msg_ref_try_put(&rb->ref, &msg)) return true; + break; } rb = malloc(sizeof(*rb)); From 790e70678e0a14c0867138f0ba75f1f730091c5b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 3 Jul 2020 09:11:37 -0700 Subject: [PATCH 1534/2505] Avoid allocating ring buffer segment unless a message is published --- src/lib/lwan-pubsub.c | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/src/lib/lwan-pubsub.c b/src/lib/lwan-pubsub.c index 0d3ef83d3..de130b210 100644 --- a/src/lib/lwan-pubsub.c +++ b/src/lib/lwan-pubsub.c @@ -52,19 +52,9 @@ struct lwan_pubsub_subscriber { struct lwan_pubsub_sub_queue queue; }; -static bool lwan_pubsub_queue_init(struct lwan_pubsub_sub_queue *queue) +static void lwan_pubsub_queue_init(struct lwan_pubsub_sub_queue *queue) { - struct lwan_pubsub_sub_queue_rb *rb; - - rb = malloc(sizeof(*rb)); - if (!rb) - return false; - - lwan_pubsub_msg_ref_init(&rb->ref); list_head_init(&queue->rbs); - list_add(&queue->rbs, &rb->rb); - - return true; } static bool lwan_pubsub_queue_put(struct lwan_pubsub_sub_queue *queue, @@ -72,15 +62,15 @@ static bool lwan_pubsub_queue_put(struct lwan_pubsub_sub_queue *queue, { struct lwan_pubsub_sub_queue_rb *rb; - /* Try putting the message in the last ringbuffer in this queue: if it's - * full, will need to allocate a new ring buffer, even if others might - * have space in them: the FIFO order must be preserved, and short of - * compacting the queue at this point -- which will eventually happen - * as it is consumed -- this is the only option. */ - list_for_each_rev (&queue->rbs, rb, rb) { + if (!list_empty(&queue->rbs)) { + /* Try putting the message in the last ringbuffer in this queue: if it's + * full, will need to allocate a new ring buffer, even if others might + * have space in them: the FIFO order must be preserved, and short of + * compacting the queue at this point -- which will eventually happen + * as it is consumed -- this is the only option. */ + rb = list_tail(&queue->rbs, struct lwan_pubsub_sub_queue_rb, rb); if (lwan_pubsub_msg_ref_try_put(&rb->ref, &msg)) return true; - break; } rb = malloc(sizeof(*rb)); From 15bb908dfaf37e8446e077dc2186d179fadd9640 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 3 Jul 2020 10:30:15 -0700 Subject: [PATCH 1535/2505] Try iteratively compacting the linked list of ring buffers --- src/lib/lwan-pubsub.c | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-pubsub.c b/src/lib/lwan-pubsub.c index de130b210..a288b7f3e 100644 --- a/src/lib/lwan-pubsub.c +++ b/src/lib/lwan-pubsub.c @@ -90,13 +90,44 @@ lwan_pubsub_queue_get(struct lwan_pubsub_sub_queue *queue) struct lwan_pubsub_sub_queue_rb *rb, *next; list_for_each_safe (&queue->rbs, rb, next, rb) { + struct lwan_pubsub_msg *msg; + if (lwan_pubsub_msg_ref_empty(&rb->ref)) { list_del(&rb->rb); free(rb); continue; } - return lwan_pubsub_msg_ref_get(&rb->ref); + msg = lwan_pubsub_msg_ref_get(&rb->ref); + + if (rb->rb.next != rb->rb.prev) { + /* If this segment isn't the last one, try pulling in just one + * element from the next segment, as there's space in the + * current segment now. + * + * This might lead to an empty ring buffer segment in the middle + * of the linked list. This is by design, to introduce some + * hysteresis and avoid the pathological case where malloc churn + * will happen when subscribers consume at the same rate as + * publishers are able to publish. + * + * The condition above will take care of these empty segments + * once they're dealt with, eventually compacting the queue + * completely (and ultimately reducing it to an empty list + * without any ring buffers). + */ + struct lwan_pubsub_sub_queue_rb *next_rb; + + next_rb = container_of(rb->rb.next, struct lwan_pubsub_sub_queue_rb, rb); + if (!lwan_pubsub_msg_ref_empty(&next_rb->ref)) { + const struct lwan_pubsub_msg *next_msg; + + next_msg = lwan_pubsub_msg_ref_get(&next_rb->ref); + lwan_pubsub_msg_ref_put(&rb->ref, &next_msg); + } + } + + return msg; } return NULL; From 50733aad0b576be10ae454ad8627a0ec34b6c27a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 4 Jul 2020 12:13:07 -0700 Subject: [PATCH 1536/2505] Add port of @conejoninja's pongclock to the clock sample --- src/samples/clock/CMakeLists.txt | 2 + src/samples/clock/main.c | 57 ++++++- src/samples/clock/pong.c | 285 +++++++++++++++++++++++++++++++ src/samples/clock/pong.h | 53 ++++++ 4 files changed, 391 insertions(+), 6 deletions(-) create mode 100644 src/samples/clock/pong.c create mode 100644 src/samples/clock/pong.h diff --git a/src/samples/clock/CMakeLists.txt b/src/samples/clock/CMakeLists.txt index 69d335258..16407fe5a 100644 --- a/src/samples/clock/CMakeLists.txt +++ b/src/samples/clock/CMakeLists.txt @@ -4,9 +4,11 @@ add_executable(clock xdaliclock.c numbers.c blocks.c + pong.c ) target_link_libraries(clock ${LWAN_COMMON_LIBS} ${ADDITIONAL_LIBRARIES} + m ) diff --git a/src/samples/clock/main.c b/src/samples/clock/main.c index a98d14360..052cc5898 100644 --- a/src/samples/clock/main.c +++ b/src/samples/clock/main.c @@ -27,9 +27,10 @@ #include "gifenc.h" #include "xdaliclock.h" #include "blocks.h" +#include "pong.h" /* Font stolen from https://github.com/def-/time.gif */ -static const uint8_t font[10][5] = { +const uint8_t digital_clock_font[10][5] = { [0] = {7, 5, 5, 5, 7}, [1] = {2, 2, 2, 2, 2}, [2] = {7, 1, 7, 4, 7}, [3] = {7, 1, 3, 1, 7}, [4] = {5, 5, 7, 1, 1}, [5] = {7, 4, 7, 1, 7}, [6] = {7, 4, 7, 5, 7}, [7] = {7, 1, 1, 1, 1}, [8] = {7, 5, 7, 5, 7}, @@ -85,10 +86,9 @@ LWAN_HANDLER(clock) uint8_t off = base_offsets[digit]; for (line = 0, base = digit * 4; line < 5; line++, base += width) { - gif->frame[base + 0 + off] = !!(font[dig][line] & 1<<2); - gif->frame[base + 1 + off] = !!(font[dig][line] & 1<<1); - gif->frame[base + 2 + off] = !!(font[dig][line] & 1<<0); - + gif->frame[base + 0 + off] = !!(digital_clock_font[dig][line] & 1<<2); + gif->frame[base + 1 + off] = !!(digital_clock_font[dig][line] & 1<<1); + gif->frame[base + 2 + off] = !!(digital_clock_font[dig][line] & 1<<0); } } @@ -192,6 +192,36 @@ LWAN_HANDLER(blocks) return HTTP_OK; } +LWAN_HANDLER(pong) +{ + ge_GIF *gif = ge_new_gif(response->buffer, 64, 32, NULL, 4, -1); + struct pong pong; + uint64_t total_waited = 0; + + if (!gif) + return HTTP_INTERNAL_ERROR; + + coro_defer(request->conn->coro, destroy_gif, gif); + + pong_init(&pong, gif); + + response->mime_type = "image/gif"; + response->headers = seriously_do_not_cache; + + while (total_waited <= 3600000) { + uint64_t timeout; + + timeout = pong_draw(&pong); + total_waited += timeout; + + ge_add_frame(gif, 0); + lwan_response_send_chunk(request); + lwan_request_sleep(request, timeout); + } + + return HTTP_OK; +} + struct index { const char *title; const char *variant; @@ -235,7 +265,7 @@ __attribute__((constructor)) static void initialize_template(void) " position: absolute;\n" " padding: 16px;\n" " left: calc(50% - 100px - 16px);\n" - " width: 250px;\n" + " width: 300px;\n" "}\n" "#styles a, #styles a:visited, #lwan a, #lwan a:visited { color: #666; }\n" "#lwan {\n" @@ -265,6 +295,7 @@ __attribute__((constructor)) static void initialize_template(void) " Styles: " "Digital · " "Dali · " + "Pong · " "Blocks\n" " \n" "\n" @@ -320,6 +351,11 @@ int main(void) .variant = "blocks", .width = 320, }; + struct index pong_clock = { + .title = "Lwan Pong Clock", + .variant = "pong", + .width = 320, + }; const struct lwan_url_map default_map[] = { { .prefix = "/clock.gif", @@ -333,6 +369,10 @@ int main(void) .prefix = "/blocks.gif", .handler = LWAN_HANDLER_REF(blocks), }, + { + .prefix = "/pong.gif", + .handler = LWAN_HANDLER_REF(pong), + }, { .prefix = "/clock", .handler = LWAN_HANDLER_REF(templated_index), @@ -348,6 +388,11 @@ int main(void) .handler = LWAN_HANDLER_REF(templated_index), .data = &blocks_clock, }, + { + .prefix = "/pong", + .handler = LWAN_HANDLER_REF(templated_index), + .data = &pong_clock, + }, { .prefix = "/", REDIRECT("/clock"), diff --git a/src/samples/clock/pong.c b/src/samples/clock/pong.c new file mode 100644 index 000000000..84540c7fd --- /dev/null +++ b/src/samples/clock/pong.c @@ -0,0 +1,285 @@ +/* + * C port of Daniel Esteban's Pong Clock for Lwan + * Copyright (C) 2019 Daniel Esteban + * Copyright (C) 2020 Leandro A. F. Pereira + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include +#include +#include + +#include "pong.h" + +extern const uint8_t digital_clock_font[10][5]; + +static float rand_float(float scale) +{ + return ((float)rand() / (float)(RAND_MAX)) * scale; +} + +static void pong_time_update(struct pong_time *pong_time) +{ + time_t cur_time = time(NULL); + + if (cur_time != pong_time->last_time) { + char digits[5]; + + strftime(digits, sizeof(digits), "%H%M", localtime(&cur_time)); + + for (int i = 0; i < 4; i++) + pong_time->time[i] = digits[i] - '0'; + + pong_time->hour = (digits[0] - '0') * 10 + digits[1] - '0'; + pong_time->minute = (digits[2] - '0') * 10 + digits[3] - '0'; + + pong_time->last_time = cur_time; + } +} + +void pong_init(struct pong *pong, ge_GIF *gif) +{ + float ball_y = rand_float(16.0f) + 8.0f; + + *pong = (struct pong){ + .gif = gif, + .ball_x = {.pos = 31.0f, .vel = 1.0f}, + .ball_y = {.pos = ball_y, .vel = rand() % 2 ? -0.5f : 0.5f}, + .player_left = {.y = 8, .target_y = ball_y}, + .player_right = {.y = 18, .target_y = ball_y}, + .player_loss = 0, + .game_stopped = 0, + }; + + pong_time_update(&pong->time); +} + +static void draw_pixel(unsigned char *frame, int x, int y, unsigned char color) +{ + if (x < 64 && y < 32) + frame[y * 64 + x] = color; +} + +static void pong_draw_net(const struct pong *pong) +{ + for (int i = 1; i < 32; i += 2) + draw_pixel(pong->gif->frame, 31, i, 6); +} + +static void pong_draw_player(const struct pong *pong, int x, int y) +{ + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 8; j++) + draw_pixel(pong->gif->frame, x + i, y + j, 3); + } +} + +static void pong_draw_ball(const struct pong *pong) +{ + int x = (int)pong->ball_x.pos; + int y = (int)pong->ball_y.pos; + + draw_pixel(pong->gif->frame, x, y, 1); + draw_pixel(pong->gif->frame, x + 1, y, 1); + draw_pixel(pong->gif->frame, x, y + 1, 1); + draw_pixel(pong->gif->frame, x + 1, y + 1, 1); +} + +static void pong_draw_time(const struct pong *pong) +{ + static const uint8_t base_offsets[] = {23, 23, 25, 25}; + static const uint8_t colors[] = {0, 6}; + unsigned char *frame = pong->gif->frame; + + for (int digit = 0; digit < 4; digit++) { + int dig = pong->time.time[digit]; + uint8_t off = base_offsets[digit]; + + for (int line = 0, base = digit * 4; line < 5; line++, base += 64) { + frame[base + 0 + off] = colors[!!(digital_clock_font[dig][line] & 1<<2)]; + frame[base + 1 + off] = colors[!!(digital_clock_font[dig][line] & 1<<1)]; + frame[base + 2 + off] = colors[!!(digital_clock_font[dig][line] & 1<<0)]; + } + } +} + +static float pong_calculate_end_point(const struct pong *pong, bool hit) +{ + float x = pong->ball_x.pos; + float y = pong->ball_y.pos; + float vel_x = pong->ball_x.vel; + float vel_y = pong->ball_y.vel; + + for (;;) { + x += vel_x; + y += vel_y; + if (hit) { + if (x >= 60.0f || x <= 2.0f) + return y; + } else { + if (x >= 62.0f || x <= 0.0f) + return y; + } + if (y >= 30.0f || y <= 0.0f) + vel_y = -vel_y; + } +} + +uint64_t pong_draw(struct pong *pong) +{ + if (pong->game_stopped < 20) { + pong->game_stopped++; + } else { + pong->ball_x.pos += pong->ball_x.vel; + pong->ball_y.pos += pong->ball_y.vel; + + if ((pong->ball_x.pos >= 60.0f && pong->player_loss != 1) || + (pong->ball_x.pos <= 2.0f && pong->player_loss != -1)) { + pong->ball_x.vel = -pong->ball_x.vel; + if (rand() % 4 > 0) { + if (rand() % 2 == 0) { + if (pong->ball_y.vel > 0.0f && pong->ball_y.vel < 2.5f) + pong->ball_y.vel += 0.2f; + else if (pong->ball_y.vel < 0.0f && + pong->ball_y.vel > -2.5f) + pong->ball_y.vel -= 0.2f; + + if (pong->ball_x.pos >= 60.0f) + pong->player_right.target_y += 1.0f + rand_float(3); + else + pong->player_left.target_y += 1.0f + rand_float(3); + } else { + if (pong->ball_y.vel > 0.5f) + pong->ball_y.vel -= 0.2f; + else if (pong->ball_y.vel < -0.5f) + pong->ball_y.vel += 0.2f; + + if (pong->ball_x.pos >= 60.0f) + pong->player_right.target_y -= 1.0f + rand_float(3); + else + pong->player_left.target_y -= 1.0f + rand_float(3); + } + + if (pong->player_left.target_y < 0.0f) + pong->player_left.target_y = 0.0f; + else if (pong->player_left.target_y > 24.0f) + pong->player_left.target_y = 24.0f; + + if (pong->player_right.target_y < 0.0f) + pong->player_right.target_y = 0.0f; + else if (pong->player_right.target_y > 24.0f) + pong->player_right.target_y = 24.0f; + } + } else if ((pong->ball_x.pos > 62.0f && pong->player_loss == 1) || + (pong->ball_x.pos < 0.0f && pong->player_loss == -1)) { + pong_init(pong, pong->gif); + } + + if (pong->ball_y.pos >= 30.0f || pong->ball_y.pos <= 0.0f) + pong->ball_y.vel = -pong->ball_y.vel; + + if (roundf(pong->ball_x.pos) == 40.0f + rand_float(13)) { + pong->player_left.target_y = pong->ball_y.pos - 3.0f; + + if (pong->player_left.target_y < 0.0f) + pong->player_left.target_y = 0.0f; + else if (pong->player_left.target_y > 24.0f) + pong->player_left.target_y = 24.0f; + } + if (roundf(pong->ball_x.pos) == 8 + rand_float(13)) { + pong->player_right.target_y = pong->ball_y.pos - 3; + + if (pong->player_right.target_y < 0) + pong->player_right.target_y = 0; + else if (pong->player_right.target_y > 24) + pong->player_right.target_y = 24; + } + + if (pong->player_left.target_y > pong->player_left.y) + pong->player_left.y++; + else if (pong->player_left.target_y < pong->player_left.y) + pong->player_left.y--; + + if (pong->player_right.target_y > pong->player_right.y) + pong->player_right.y++; + else if (pong->player_right.target_y < pong->player_right.y) + pong->player_right.y--; + + /* If the ball is in the middle, check if we need to lose and calculate + * the endpoint to avoid/hit the ball */ + if (roundf(pong->ball_x.pos) == 32.0f) { + struct pong_time cur_time; + + pong_time_update(&cur_time); + + if (cur_time.minute != pong->time.minute && pong->player_loss == 0) { + /* Need to change one or the other */ + if (cur_time.minute == 0) /* Need to change the hour */ + pong->player_loss = 1; + else /* Need to change the minute */ + pong->player_loss = -1; + } + + if (pong->ball_x.vel < 0) { /* Moving to the left */ + pong->player_left.target_y = + pong_calculate_end_point(pong, pong->player_loss != -1) - 3; + if (pong->player_loss == -1) { /* We need to lose */ + if (pong->player_left.target_y < 16) + pong->player_left.target_y = 19 + rand_float(5); + else + pong->player_left.target_y = 5 + rand_float(2); + } + + if (pong->player_left.target_y < 0) + pong->player_left.target_y = 0; + else if (pong->player_left.target_y > 24) + pong->player_left.target_y = 24; + } else if (pong->ball_x.vel > 0) { /* Moving to the right */ + pong->player_right.target_y = + pong_calculate_end_point(pong, pong->player_loss != 1) - 3; + if (pong->player_loss == -1) { /* We need to lose */ + if (pong->player_right.target_y < 16) + pong->player_right.target_y = 19 + rand_float(5); + else + pong->player_right.target_y = 5 + rand_float(2); + } + + if (pong->player_right.target_y < 0) + pong->player_right.target_y = 0; + else if (pong->player_right.target_y > 24) + pong->player_right.target_y = 24; + } + + if (pong->ball_y.pos < 0) + pong->ball_y.pos = 0; + else if (pong->ball_y.pos > 30) + pong->ball_y.pos = 30; + } + } + + memset(pong->gif->frame, 0, 64 * 32); + pong_draw_net(pong); + pong_draw_time(pong); + pong_draw_player(pong, 0, pong->player_left.y); + pong_draw_player(pong, 62, pong->player_right.y); + pong_draw_ball(pong); + + return 100; +} diff --git a/src/samples/clock/pong.h b/src/samples/clock/pong.h new file mode 100644 index 000000000..ee8ce7f2a --- /dev/null +++ b/src/samples/clock/pong.h @@ -0,0 +1,53 @@ +/* + * C port of Daniel Esteban's Pong Clock for Lwan + * Copyright (C) 2019 Daniel Esteban + * Copyright (C) 2020 Leandro A. F. Pereira + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#pragma once + +#include +#include +#include "gifenc.h" + +struct pong_time { + time_t last_time; + char time[4]; + int hour, minute; +}; + +struct pong { + ge_GIF *gif; + struct { + float pos; + float vel; + } ball_x, ball_y; + struct { + int y; + float target_y; + } player_left, player_right; + int player_loss; + int game_stopped; + struct pong_time time; +}; + +void pong_init(struct pong *pong, ge_GIF *gif); +uint64_t pong_draw(struct pong *pong); From 8ec12e6f838cc0765868fb41414bbb46a7827d98 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 4 Jul 2020 13:10:42 -0700 Subject: [PATCH 1537/2505] Better timing in Pong clock --- src/samples/clock/main.c | 2 +- src/samples/clock/pong.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/samples/clock/main.c b/src/samples/clock/main.c index 052cc5898..3d91bf5d3 100644 --- a/src/samples/clock/main.c +++ b/src/samples/clock/main.c @@ -214,7 +214,7 @@ LWAN_HANDLER(pong) timeout = pong_draw(&pong); total_waited += timeout; - ge_add_frame(gif, 0); + ge_add_frame(gif, (uint16_t)timeout); lwan_response_send_chunk(request); lwan_request_sleep(request, timeout); } diff --git a/src/samples/clock/pong.c b/src/samples/clock/pong.c index 84540c7fd..62e090449 100644 --- a/src/samples/clock/pong.c +++ b/src/samples/clock/pong.c @@ -281,5 +281,5 @@ uint64_t pong_draw(struct pong *pong) pong_draw_player(pong, 62, pong->player_right.y); pong_draw_ball(pong); - return 100; + return 8; } From 447bd420d8c2a06427f5a00660b845d57b6df072 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 4 Jul 2020 14:07:55 -0700 Subject: [PATCH 1538/2505] Cleanup pong_time_update(): no need to subtract '0' from each digit --- src/samples/clock/pong.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/samples/clock/pong.c b/src/samples/clock/pong.c index 62e090449..5681977e3 100644 --- a/src/samples/clock/pong.c +++ b/src/samples/clock/pong.c @@ -23,8 +23,8 @@ */ #include -#include #include +#include #include "pong.h" @@ -47,8 +47,8 @@ static void pong_time_update(struct pong_time *pong_time) for (int i = 0; i < 4; i++) pong_time->time[i] = digits[i] - '0'; - pong_time->hour = (digits[0] - '0') * 10 + digits[1] - '0'; - pong_time->minute = (digits[2] - '0') * 10 + digits[3] - '0'; + pong_time->hour = pong_time->time[0] * 10 + pong_time->time[1]; + pong_time->minute = pong_time->time[2] * 10 + pong_time->time[3]; pong_time->last_time = cur_time; } From 2ed800674b7dc6e4749e2ffae0a1de7d2209e904 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 4 Jul 2020 14:08:29 -0700 Subject: [PATCH 1539/2505] Factor out function to clamp player position --- src/samples/clock/pong.c | 223 +++++++++++++++++++-------------------- 1 file changed, 109 insertions(+), 114 deletions(-) diff --git a/src/samples/clock/pong.c b/src/samples/clock/pong.c index 5681977e3..b6fea6f1d 100644 --- a/src/samples/clock/pong.c +++ b/src/samples/clock/pong.c @@ -142,138 +142,133 @@ static float pong_calculate_end_point(const struct pong *pong, bool hit) } } +static void pong_clamp_player_pos(struct pong *pong, bool left, bool right) +{ + if (left) { + if (pong->player_left.target_y < 0.0f) + pong->player_left.target_y = 0.0f; + else if (pong->player_left.target_y > 24.0f) + pong->player_left.target_y = 24.0f; + } + + if (right) { + if (pong->player_right.target_y < 0.0f) + pong->player_right.target_y = 0.0f; + else if (pong->player_right.target_y > 24.0f) + pong->player_right.target_y = 24.0f; + } +} + uint64_t pong_draw(struct pong *pong) { if (pong->game_stopped < 20) { pong->game_stopped++; - } else { - pong->ball_x.pos += pong->ball_x.vel; - pong->ball_y.pos += pong->ball_y.vel; - - if ((pong->ball_x.pos >= 60.0f && pong->player_loss != 1) || - (pong->ball_x.pos <= 2.0f && pong->player_loss != -1)) { - pong->ball_x.vel = -pong->ball_x.vel; - if (rand() % 4 > 0) { - if (rand() % 2 == 0) { - if (pong->ball_y.vel > 0.0f && pong->ball_y.vel < 2.5f) - pong->ball_y.vel += 0.2f; - else if (pong->ball_y.vel < 0.0f && - pong->ball_y.vel > -2.5f) - pong->ball_y.vel -= 0.2f; - - if (pong->ball_x.pos >= 60.0f) - pong->player_right.target_y += 1.0f + rand_float(3); - else - pong->player_left.target_y += 1.0f + rand_float(3); - } else { - if (pong->ball_y.vel > 0.5f) - pong->ball_y.vel -= 0.2f; - else if (pong->ball_y.vel < -0.5f) - pong->ball_y.vel += 0.2f; - - if (pong->ball_x.pos >= 60.0f) - pong->player_right.target_y -= 1.0f + rand_float(3); - else - pong->player_left.target_y -= 1.0f + rand_float(3); - } - - if (pong->player_left.target_y < 0.0f) - pong->player_left.target_y = 0.0f; - else if (pong->player_left.target_y > 24.0f) - pong->player_left.target_y = 24.0f; - - if (pong->player_right.target_y < 0.0f) - pong->player_right.target_y = 0.0f; - else if (pong->player_right.target_y > 24.0f) - pong->player_right.target_y = 24.0f; + goto draw; + } + + pong->ball_x.pos += pong->ball_x.vel; + pong->ball_y.pos += pong->ball_y.vel; + + if ((pong->ball_x.pos >= 60.0f && pong->player_loss != 1) || + (pong->ball_x.pos <= 2.0f && pong->player_loss != -1)) { + pong->ball_x.vel = -pong->ball_x.vel; + if (rand() % 4 > 0) { + if (rand() % 2 == 0) { + if (pong->ball_y.vel > 0.0f && pong->ball_y.vel < 2.5f) + pong->ball_y.vel += 0.2f; + else if (pong->ball_y.vel < 0.0f && pong->ball_y.vel > -2.5f) + pong->ball_y.vel -= 0.2f; + + if (pong->ball_x.pos >= 60.0f) + pong->player_right.target_y += 1.0f + rand_float(3); + else + pong->player_left.target_y += 1.0f + rand_float(3); + } else { + if (pong->ball_y.vel > 0.5f) + pong->ball_y.vel -= 0.2f; + else if (pong->ball_y.vel < -0.5f) + pong->ball_y.vel += 0.2f; + + if (pong->ball_x.pos >= 60.0f) + pong->player_right.target_y -= 1.0f + rand_float(3); + else + pong->player_left.target_y -= 1.0f + rand_float(3); } - } else if ((pong->ball_x.pos > 62.0f && pong->player_loss == 1) || - (pong->ball_x.pos < 0.0f && pong->player_loss == -1)) { - pong_init(pong, pong->gif); - } - if (pong->ball_y.pos >= 30.0f || pong->ball_y.pos <= 0.0f) - pong->ball_y.vel = -pong->ball_y.vel; + pong_clamp_player_pos(pong, true, true); + } + } else if ((pong->ball_x.pos > 62.0f && pong->player_loss == 1) || + (pong->ball_x.pos < 0.0f && pong->player_loss == -1)) { + pong_init(pong, pong->gif); + } - if (roundf(pong->ball_x.pos) == 40.0f + rand_float(13)) { - pong->player_left.target_y = pong->ball_y.pos - 3.0f; + if (pong->ball_y.pos >= 30.0f || pong->ball_y.pos <= 0.0f) + pong->ball_y.vel = -pong->ball_y.vel; - if (pong->player_left.target_y < 0.0f) - pong->player_left.target_y = 0.0f; - else if (pong->player_left.target_y > 24.0f) - pong->player_left.target_y = 24.0f; - } - if (roundf(pong->ball_x.pos) == 8 + rand_float(13)) { - pong->player_right.target_y = pong->ball_y.pos - 3; + if (roundf(pong->ball_x.pos) == 40.0f + rand_float(13)) { + pong->player_left.target_y = pong->ball_y.pos - 3.0f; + pong_clamp_player_pos(pong, true, false); + } else if (roundf(pong->ball_x.pos) == 8 + rand_float(13)) { + pong->player_right.target_y = pong->ball_y.pos - 3.0f; + pong_clamp_player_pos(pong, false, true); + } - if (pong->player_right.target_y < 0) - pong->player_right.target_y = 0; - else if (pong->player_right.target_y > 24) - pong->player_right.target_y = 24; + if (pong->player_left.target_y > pong->player_left.y) + pong->player_left.y++; + else if (pong->player_left.target_y < pong->player_left.y) + pong->player_left.y--; + + if (pong->player_right.target_y > pong->player_right.y) + pong->player_right.y++; + else if (pong->player_right.target_y < pong->player_right.y) + pong->player_right.y--; + + /* If the ball is in the middle, check if we need to lose and calculate + * the endpoint to avoid/hit the ball */ + if (roundf(pong->ball_x.pos) == 32.0f) { + struct pong_time cur_time; + + pong_time_update(&cur_time); + + if (cur_time.minute != pong->time.minute && pong->player_loss == 0) { + /* Need to change one or the other */ + if (cur_time.minute == 0) /* Need to change the hour */ + pong->player_loss = 1; + else /* Need to change the minute */ + pong->player_loss = -1; } - if (pong->player_left.target_y > pong->player_left.y) - pong->player_left.y++; - else if (pong->player_left.target_y < pong->player_left.y) - pong->player_left.y--; - - if (pong->player_right.target_y > pong->player_right.y) - pong->player_right.y++; - else if (pong->player_right.target_y < pong->player_right.y) - pong->player_right.y--; - - /* If the ball is in the middle, check if we need to lose and calculate - * the endpoint to avoid/hit the ball */ - if (roundf(pong->ball_x.pos) == 32.0f) { - struct pong_time cur_time; - - pong_time_update(&cur_time); - - if (cur_time.minute != pong->time.minute && pong->player_loss == 0) { - /* Need to change one or the other */ - if (cur_time.minute == 0) /* Need to change the hour */ - pong->player_loss = 1; - else /* Need to change the minute */ - pong->player_loss = -1; + if (pong->ball_x.vel < 0) { /* Moving to the left */ + pong->player_left.target_y = + pong_calculate_end_point(pong, pong->player_loss != -1) - 3; + if (pong->player_loss == -1) { /* We need to lose */ + if (pong->player_left.target_y < 16) + pong->player_left.target_y = 19 + rand_float(5); + else + pong->player_left.target_y = 5 + rand_float(2); } - if (pong->ball_x.vel < 0) { /* Moving to the left */ - pong->player_left.target_y = - pong_calculate_end_point(pong, pong->player_loss != -1) - 3; - if (pong->player_loss == -1) { /* We need to lose */ - if (pong->player_left.target_y < 16) - pong->player_left.target_y = 19 + rand_float(5); - else - pong->player_left.target_y = 5 + rand_float(2); - } - - if (pong->player_left.target_y < 0) - pong->player_left.target_y = 0; - else if (pong->player_left.target_y > 24) - pong->player_left.target_y = 24; - } else if (pong->ball_x.vel > 0) { /* Moving to the right */ - pong->player_right.target_y = - pong_calculate_end_point(pong, pong->player_loss != 1) - 3; - if (pong->player_loss == -1) { /* We need to lose */ - if (pong->player_right.target_y < 16) - pong->player_right.target_y = 19 + rand_float(5); - else - pong->player_right.target_y = 5 + rand_float(2); - } - - if (pong->player_right.target_y < 0) - pong->player_right.target_y = 0; - else if (pong->player_right.target_y > 24) - pong->player_right.target_y = 24; + pong_clamp_player_pos(pong, true, false); + } else if (pong->ball_x.vel > 0) { /* Moving to the right */ + pong->player_right.target_y = + pong_calculate_end_point(pong, pong->player_loss != 1) - 3; + if (pong->player_loss == -1) { /* We need to lose */ + if (pong->player_right.target_y < 16) + pong->player_right.target_y = 19 + rand_float(5); + else + pong->player_right.target_y = 5 + rand_float(2); } - if (pong->ball_y.pos < 0) - pong->ball_y.pos = 0; - else if (pong->ball_y.pos > 30) - pong->ball_y.pos = 30; + pong_clamp_player_pos(pong, false, true); } + + if (pong->ball_y.pos < 0) + pong->ball_y.pos = 0; + else if (pong->ball_y.pos > 30) + pong->ball_y.pos = 30; } +draw: memset(pong->gif->frame, 0, 64 * 32); pong_draw_net(pong); pong_draw_time(pong); From f5653a262be40e74b0062528eab3c5440c0aa51d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 4 Jul 2020 18:51:17 -0700 Subject: [PATCH 1540/2505] Fix bug where the left side would lose even though the right side should --- src/samples/clock/pong.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/samples/clock/pong.c b/src/samples/clock/pong.c index b6fea6f1d..f03495a84 100644 --- a/src/samples/clock/pong.c +++ b/src/samples/clock/pong.c @@ -252,7 +252,7 @@ uint64_t pong_draw(struct pong *pong) } else if (pong->ball_x.vel > 0) { /* Moving to the right */ pong->player_right.target_y = pong_calculate_end_point(pong, pong->player_loss != 1) - 3; - if (pong->player_loss == -1) { /* We need to lose */ + if (pong->player_loss == 1) { /* We need to lose */ if (pong->player_right.target_y < 16) pong->player_right.target_y = 19 + rand_float(5); else From a4293bd075322d0ced9111457584e9434141d71b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 4 Jul 2020 18:51:39 -0700 Subject: [PATCH 1541/2505] Simplify how values are clamped in pong clock --- src/samples/clock/pong.c | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/samples/clock/pong.c b/src/samples/clock/pong.c index f03495a84..443cfec6d 100644 --- a/src/samples/clock/pong.c +++ b/src/samples/clock/pong.c @@ -142,21 +142,21 @@ static float pong_calculate_end_point(const struct pong *pong, bool hit) } } -static void pong_clamp_player_pos(struct pong *pong, bool left, bool right) +static void clamp(float *val, float min, float max) { - if (left) { - if (pong->player_left.target_y < 0.0f) - pong->player_left.target_y = 0.0f; - else if (pong->player_left.target_y > 24.0f) - pong->player_left.target_y = 24.0f; + if (*val < min) { + *val = min; + } else if (*val > max) { + *val = max; } +} - if (right) { - if (pong->player_right.target_y < 0.0f) - pong->player_right.target_y = 0.0f; - else if (pong->player_right.target_y > 24.0f) - pong->player_right.target_y = 24.0f; - } +static void pong_clamp_player_pos(struct pong *pong, bool left, bool right) +{ + if (left) + clamp(&pong->player_left.target_y, 0.0f, 24.0f); + if (right) + clamp(&pong->player_right.target_y, 0.0f, 24.0f); } uint64_t pong_draw(struct pong *pong) @@ -262,10 +262,7 @@ uint64_t pong_draw(struct pong *pong) pong_clamp_player_pos(pong, false, true); } - if (pong->ball_y.pos < 0) - pong->ball_y.pos = 0; - else if (pong->ball_y.pos > 30) - pong->ball_y.pos = 30; + clamp(&pong->ball_y.pos, 0.0f, 30.0f); } draw: From f855ca79dcc71101ebbc4a74556f0e1e6f7061c1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 4 Jul 2020 18:56:26 -0700 Subject: [PATCH 1542/2505] Fix bug where left/right pong players would be moving up/down by 1px --- src/samples/clock/pong.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/samples/clock/pong.c b/src/samples/clock/pong.c index 443cfec6d..e6759f1e0 100644 --- a/src/samples/clock/pong.c +++ b/src/samples/clock/pong.c @@ -215,12 +215,12 @@ uint64_t pong_draw(struct pong *pong) if (pong->player_left.target_y > pong->player_left.y) pong->player_left.y++; - else if (pong->player_left.target_y < pong->player_left.y) + if (pong->player_left.target_y < pong->player_left.y) pong->player_left.y--; if (pong->player_right.target_y > pong->player_right.y) pong->player_right.y++; - else if (pong->player_right.target_y < pong->player_right.y) + if (pong->player_right.target_y < pong->player_right.y) pong->player_right.y--; /* If the ball is in the middle, check if we need to lose and calculate From c6c8a453ece325341dc0a5627b0390df50f05811 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 4 Jul 2020 18:58:38 -0700 Subject: [PATCH 1543/2505] Ensure temporary cur_time struct is initialized --- src/samples/clock/pong.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/samples/clock/pong.c b/src/samples/clock/pong.c index e6759f1e0..d8a76a028 100644 --- a/src/samples/clock/pong.c +++ b/src/samples/clock/pong.c @@ -226,7 +226,7 @@ uint64_t pong_draw(struct pong *pong) /* If the ball is in the middle, check if we need to lose and calculate * the endpoint to avoid/hit the ball */ if (roundf(pong->ball_x.pos) == 32.0f) { - struct pong_time cur_time; + struct pong_time cur_time = {}; pong_time_update(&cur_time); From d2dde26cbde2a6b2006e0a3d2e372ec6fdf7ea67 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 5 Jul 2020 12:16:33 -0700 Subject: [PATCH 1544/2505] Get rid of pong_clamp_player_pos() and use clamp() directly --- src/samples/clock/pong.c | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/samples/clock/pong.c b/src/samples/clock/pong.c index d8a76a028..96b7e5983 100644 --- a/src/samples/clock/pong.c +++ b/src/samples/clock/pong.c @@ -151,14 +151,6 @@ static void clamp(float *val, float min, float max) } } -static void pong_clamp_player_pos(struct pong *pong, bool left, bool right) -{ - if (left) - clamp(&pong->player_left.target_y, 0.0f, 24.0f); - if (right) - clamp(&pong->player_right.target_y, 0.0f, 24.0f); -} - uint64_t pong_draw(struct pong *pong) { if (pong->game_stopped < 20) { @@ -195,7 +187,8 @@ uint64_t pong_draw(struct pong *pong) pong->player_left.target_y -= 1.0f + rand_float(3); } - pong_clamp_player_pos(pong, true, true); + clamp(&pong->player_left.target_y, 0.0f, 24.0f); + clamp(&pong->player_right.target_y, 0.0f, 24.0f); } } else if ((pong->ball_x.pos > 62.0f && pong->player_loss == 1) || (pong->ball_x.pos < 0.0f && pong->player_loss == -1)) { @@ -207,10 +200,10 @@ uint64_t pong_draw(struct pong *pong) if (roundf(pong->ball_x.pos) == 40.0f + rand_float(13)) { pong->player_left.target_y = pong->ball_y.pos - 3.0f; - pong_clamp_player_pos(pong, true, false); + clamp(&pong->player_left.target_y, 0.0f, 24.0f); } else if (roundf(pong->ball_x.pos) == 8 + rand_float(13)) { pong->player_right.target_y = pong->ball_y.pos - 3.0f; - pong_clamp_player_pos(pong, false, true); + clamp(&pong->player_right.target_y, 0.0f, 24.0f); } if (pong->player_left.target_y > pong->player_left.y) @@ -248,7 +241,7 @@ uint64_t pong_draw(struct pong *pong) pong->player_left.target_y = 5 + rand_float(2); } - pong_clamp_player_pos(pong, true, false); + clamp(&pong->player_left.target_y, 0.0f, 24.0f); } else if (pong->ball_x.vel > 0) { /* Moving to the right */ pong->player_right.target_y = pong_calculate_end_point(pong, pong->player_loss != 1) - 3; @@ -259,7 +252,7 @@ uint64_t pong_draw(struct pong *pong) pong->player_right.target_y = 5 + rand_float(2); } - pong_clamp_player_pos(pong, false, true); + clamp(&pong->player_right.target_y, 0.0f, 24.0f); } clamp(&pong->ball_y.pos, 0.0f, 30.0f); From 94282bbfb0d44285e0b0a5d6732d9ee791f8d76f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 5 Jul 2020 12:17:08 -0700 Subject: [PATCH 1545/2505] Get better parity with original code when calculating target_y position --- src/samples/clock/pong.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/samples/clock/pong.c b/src/samples/clock/pong.c index 96b7e5983..4259218bf 100644 --- a/src/samples/clock/pong.c +++ b/src/samples/clock/pong.c @@ -238,7 +238,7 @@ uint64_t pong_draw(struct pong *pong) if (pong->player_left.target_y < 16) pong->player_left.target_y = 19 + rand_float(5); else - pong->player_left.target_y = 5 + rand_float(2); + pong->player_left.target_y = rand_float(5); } clamp(&pong->player_left.target_y, 0.0f, 24.0f); @@ -249,7 +249,7 @@ uint64_t pong_draw(struct pong *pong) if (pong->player_right.target_y < 16) pong->player_right.target_y = 19 + rand_float(5); else - pong->player_right.target_y = 5 + rand_float(2); + pong->player_right.target_y = rand_float(5); } clamp(&pong->player_right.target_y, 0.0f, 24.0f); From 4551555b104600f9de13191165e0ed18f7e325ae Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 5 Jul 2020 12:17:58 -0700 Subject: [PATCH 1546/2505] Better parity with the original code when reinitializing playfield --- src/samples/clock/pong.c | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/samples/clock/pong.c b/src/samples/clock/pong.c index 4259218bf..ca28493ef 100644 --- a/src/samples/clock/pong.c +++ b/src/samples/clock/pong.c @@ -54,14 +54,25 @@ static void pong_time_update(struct pong_time *pong_time) } } -void pong_init(struct pong *pong, ge_GIF *gif) +static void pong_init_internal(struct pong *pong, ge_GIF *gif, bool reset) { float ball_y = rand_float(16.0f) + 8.0f; + struct { + float y, target_y; + } left, right; + + if (reset) { + memcpy(&left, &pong->player_left, sizeof(left)); + memcpy(&right, &pong->player_right, sizeof(right)); + } else { + left.y = right.y = 8; + left.target_y = right.target_y = ball_y; + } *pong = (struct pong){ .gif = gif, .ball_x = {.pos = 31.0f, .vel = 1.0f}, - .ball_y = {.pos = ball_y, .vel = rand() % 2 ? -0.5f : 0.5f}, + .ball_y = {.pos = ball_y, .vel = rand() % 2 ? 0.5f : -0.5f}, .player_left = {.y = 8, .target_y = ball_y}, .player_right = {.y = 18, .target_y = ball_y}, .player_loss = 0, @@ -71,6 +82,11 @@ void pong_init(struct pong *pong, ge_GIF *gif) pong_time_update(&pong->time); } +void pong_init(struct pong *pong, ge_GIF *gif) +{ + return pong_init_internal(pong, gif, false); +} + static void draw_pixel(unsigned char *frame, int x, int y, unsigned char color) { if (x < 64 && y < 32) @@ -191,8 +207,8 @@ uint64_t pong_draw(struct pong *pong) clamp(&pong->player_right.target_y, 0.0f, 24.0f); } } else if ((pong->ball_x.pos > 62.0f && pong->player_loss == 1) || - (pong->ball_x.pos < 0.0f && pong->player_loss == -1)) { - pong_init(pong, pong->gif); + (pong->ball_x.pos < 1.0f && pong->player_loss == -1)) { + pong_init_internal(pong, pong->gif, true); } if (pong->ball_y.pos >= 30.0f || pong->ball_y.pos <= 0.0f) From 07451396ea7a9e9909f06dd4116752433a3916bb Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 6 Jul 2020 18:30:15 -0700 Subject: [PATCH 1547/2505] Unmask WebSockets frames in bulk Try to do it with SSE2 (only on x86-64, which is always available), reverting back to SWAR on 64 and 32-bit integers, falling back to byte-per-byte to finish unmasking the last few (up to 3) bytes. Also, fail the connection if the client sends an unmasked frame. --- src/lib/lwan-websocket.c | 82 ++++++++++++++++++++++++++++++---------- 1 file changed, 63 insertions(+), 19 deletions(-) diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index 801f876f6..9f7521fb9 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -18,10 +18,16 @@ * USA. */ +#define _GNU_SOURCE #include #include +#include #include +#if defined(__x86_64__) +#include +#endif + #include "lwan-io-wrappers.h" #include "lwan-private.h" @@ -132,11 +138,54 @@ static void discard_frame(struct lwan_request *request, uint16_t header) len -= (size_t)lwan_recv(request, buffer, sizeof(buffer), 0); } +static void unmask(char *msg, uint64_t msg_len, char mask[static 4]) +{ + const uint32_t mask32 = string_as_uint32(mask); + char *msg_end = msg + msg_len; + + if (sizeof(void *) == 8) { + const uint64_t mask64 = (uint64_t)mask32 << 32 | mask32; + +#if defined(__x86_64__) + if (msg_end - msg >= 16) { + const __m128i mask128 = + _mm_setr_epi64((__m64)mask64, (__m64)mask64); + + do { + __m128i v = _mm_loadu_si128((__m128i *)msg); + _mm_storeu_si128((__m128i *)msg, _mm_xor_si128(v, mask128)); + msg += 16; + } while (msg_end - msg >= 16); + } +#endif + + if (msg_end - msg >= 8) { + uint64_t v = string_as_uint64(msg); + v ^= mask64; + msg = mempcpy(msg, &v, sizeof(v)); + } + } + + while (msg_end - msg >= 4) { + uint32_t v = string_as_uint32(msg); + v ^= mask32; + msg = mempcpy(msg, &v, sizeof(v)); + } + + switch (msg_end - msg) { + case 3: + msg[2] ^= mask[2]; /* fallthrough */ + case 2: + msg[1] ^= mask[1]; /* fallthrough */ + case 1: + msg[0] ^= mask[0]; + } +} + int lwan_response_websocket_read(struct lwan_request *request) { uint16_t header; uint64_t frame_len; - char *msg; bool continuation = false; if (!(request->conn->flags & CONN_IS_WEBSOCKET)) @@ -155,6 +204,11 @@ int lwan_response_websocket_read(struct lwan_request *request) coro_yield(request->conn->coro, CONN_CORO_ABORT); __builtin_unreachable(); } + if (UNLIKELY(!(header & 0x80))) { + lwan_status_debug("Client sent an unmasked WebSockets frame, aborting"); + coro_yield(request->conn->coro, CONN_CORO_ABORT); + __builtin_unreachable(); + } switch ((enum ws_opcode)((header & 0x0f00) >> 8)) { case WS_OPCODE_CONTINUATION: @@ -204,24 +258,14 @@ int lwan_response_websocket_read(struct lwan_request *request) * overflow if adding frame_len, as this is checked by grow_by() already. */ request->response.buffer->used += frame_len; - msg = lwan_strbuf_get_buffer(request->response.buffer) + cur_buf_len; - if (LIKELY(header & 0x80)) { - /* Payload is masked; should always be true on Client->Server comms but - * don't assume this is always the case. */ - char mask[4]; - struct iovec vec[] = { - {.iov_base = mask, .iov_len = sizeof(mask)}, - {.iov_base = msg, .iov_len = frame_len}, - }; - - lwan_readv(request, vec, N_ELEMENTS(vec)); - - /* FIXME: try to do more bytes at a time! */ - for (uint64_t i = 0; i < frame_len; i++) - msg[i] ^= mask[i & 3]; - } else { - lwan_recv(request, msg, frame_len, 0); - } + char *msg = lwan_strbuf_get_buffer(request->response.buffer) + cur_buf_len; + char mask[4]; + struct iovec vec[] = { + {.iov_base = mask, .iov_len = sizeof(mask)}, + {.iov_base = msg, .iov_len = frame_len}, + }; + lwan_readv(request, vec, N_ELEMENTS(vec)); + unmask(msg, frame_len, mask); if (continuation && !(header & 0x8000)) { continuation = false; From 6a39d0346388c877c2e7e2e97680f906a96ff3a9 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 6 Jul 2020 18:35:42 -0700 Subject: [PATCH 1548/2505] WebSockets continuation frames can only continue opcodes [0..2] Abort the conneciton otherwise. --- src/lib/lwan-websocket.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index 9f7521fb9..6f64b9429 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -50,6 +50,8 @@ enum ws_opcode { WS_OPCODE_RSVD_CONTROL_3 = 13, WS_OPCODE_RSVD_CONTROL_4 = 14, WS_OPCODE_RSVD_CONTROL_5 = 15, + + WS_OPCODE_INVALID = 16, }; static void write_websocket_frame(struct lwan_request *request, @@ -184,6 +186,8 @@ static void unmask(char *msg, uint64_t msg_len, char mask[static 4]) int lwan_response_websocket_read(struct lwan_request *request) { + enum ws_opcode opcode = WS_OPCODE_INVALID; + enum ws_opcode last_opcode; uint16_t header; uint64_t frame_len; bool continuation = false; @@ -194,6 +198,8 @@ int lwan_response_websocket_read(struct lwan_request *request) lwan_strbuf_reset(request->response.buffer); next_frame: + last_opcode = opcode; + if (!lwan_recv(request, &header, sizeof(header), continuation ? 0 : MSG_DONTWAIT)) return EAGAIN; header = htons(header); @@ -210,8 +216,15 @@ int lwan_response_websocket_read(struct lwan_request *request) __builtin_unreachable(); } - switch ((enum ws_opcode)((header & 0x0f00) >> 8)) { + opcode = (header & 0x0f00) >> 8; + switch (opcode) { case WS_OPCODE_CONTINUATION: + if (UNLIKELY(last_opcode > WS_OPCODE_BINARY)) { + /* Continuation frames are only available for opcodes [0..2] */ + coro_yield(request->conn->coro, CONN_CORO_ABORT); + __builtin_unreachable(); + } + continuation = true; break; @@ -245,6 +258,9 @@ int lwan_response_websocket_read(struct lwan_request *request) /* RFC6455: ...the receiving endpoint MUST _Fail the WebSocket Connection_ */ coro_yield(request->conn->coro, CONN_CORO_ABORT); __builtin_unreachable(); + + case WS_OPCODE_INVALID: + __builtin_unreachable(); } size_t cur_buf_len = lwan_strbuf_get_length(request->response.buffer); From 3a7ae31607eda1fba279109ca2eeb2c6b928de6b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 6 Jul 2020 18:57:33 -0700 Subject: [PATCH 1549/2505] Actually loop through the 64-bit SWAR version of WebSockets unmask() --- src/lib/lwan-websocket.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index 6f64b9429..c905dff15 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -161,7 +161,7 @@ static void unmask(char *msg, uint64_t msg_len, char mask[static 4]) } #endif - if (msg_end - msg >= 8) { + while (msg_end - msg >= 8) { uint64_t v = string_as_uint64(msg); v ^= mask64; msg = mempcpy(msg, &v, sizeof(v)); From 946dbbd881033f9ead702f47324d490ee4b214e7 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 6 Jul 2020 21:41:47 -0700 Subject: [PATCH 1550/2505] Improve WebSockets unmasking by ~2x --- src/lib/lwan-websocket.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index c905dff15..5c9640538 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -149,26 +149,27 @@ static void unmask(char *msg, uint64_t msg_len, char mask[static 4]) const uint64_t mask64 = (uint64_t)mask32 << 32 | mask32; #if defined(__x86_64__) - if (msg_end - msg >= 16) { - const __m128i mask128 = - _mm_setr_epi64((__m64)mask64, (__m64)mask64); - - do { + const size_t len128 = msg_len / 16; + if (len128) { + const __m128i mask128 = _mm_setr_epi64((__m64)mask64, (__m64)mask64); + for (size_t i = 0; i < len128; i++) { __m128i v = _mm_loadu_si128((__m128i *)msg); _mm_storeu_si128((__m128i *)msg, _mm_xor_si128(v, mask128)); msg += 16; - } while (msg_end - msg >= 16); + } } #endif - while (msg_end - msg >= 8) { + const size_t len64 = (size_t)((msg_end - msg) / 8); + for (size_t i = 0; i < len64; i++) { uint64_t v = string_as_uint64(msg); v ^= mask64; msg = mempcpy(msg, &v, sizeof(v)); } } - while (msg_end - msg >= 4) { + const size_t len32 = (size_t)((msg_end - msg) / 4); + for (size_t i = 0; i < len32; i++) { uint32_t v = string_as_uint32(msg); v ^= mask32; msg = mempcpy(msg, &v, sizeof(v)); From 277e145b69aeb3efaedf45c83bb7c6d3773147ea Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 7 Jul 2020 08:15:58 -0700 Subject: [PATCH 1551/2505] Implement private function to extend a strbuf This is inherently unsafe because it changes the "used" field in the strbuf as well; it's supposed to be used when the string buffer is being used to do I/O and the allocated buffer is going to be read() into. It's not being exposed in lwan-strbuf.h, only in lwan-private.h. It also has a "_unsafe" prefix in the function name, to be explicit about how dangerous this function can be. --- src/lib/lwan-private.h | 2 ++ src/lib/lwan-strbuf.c | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 4c561ef0b..6e7861870 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -73,6 +73,8 @@ void lwan_readahead_shutdown(void); void lwan_readahead_queue(int fd, off_t off, size_t size); void lwan_madvise_queue(void *addr, size_t size); +char *lwan_strbuf_extend_unsafe(struct lwan_strbuf *s, size_t by); + char *lwan_process_request(struct lwan *l, struct lwan_request *request, struct lwan_value *buffer, char *next_request); size_t lwan_prepare_response_header_full(struct lwan_request *request, diff --git a/src/lib/lwan-strbuf.c b/src/lib/lwan-strbuf.c index 4bd5bda80..184959ce5 100644 --- a/src/lib/lwan-strbuf.c +++ b/src/lib/lwan-strbuf.c @@ -263,3 +263,15 @@ void lwan_strbuf_reset(struct lwan_strbuf *s) s->used = 0; } + +/* This function is quite dangerous, so the prototype is only in lwan-private.h */ +char *lwan_strbuf_extend_unsafe(struct lwan_strbuf *s, size_t by) +{ + if (!lwan_strbuf_grow_by(s, by)) + return NULL; + + size_t prev_used = s->used; + s->used += by; + + return s->buffer + prev_used; +} From 62a25b122c4af32a3d60287a0a65b3c25f55a24f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 7 Jul 2020 08:18:33 -0700 Subject: [PATCH 1552/2505] Use lwan_strbuf_extend_unsafe() to read WebSocket frame into --- src/lib/lwan-websocket.c | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index 5c9640538..a8f0d711b 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -19,6 +19,7 @@ */ #define _GNU_SOURCE +#include #include #include #include @@ -116,19 +117,21 @@ static void send_websocket_pong(struct lwan_request *request, size_t len) write_websocket_frame(request, WS_OPCODE_PONG, temp, len); } -static uint64_t get_frame_length(struct lwan_request *request, uint16_t header) +static size_t get_frame_length(struct lwan_request *request, uint16_t header) { - uint64_t len; + size_t len; + + static_assert(sizeof(size_t) == sizeof(uint64_t), "size_t has a sane size"); switch (header & 0x7f) { case 0x7e: lwan_recv(request, &len, 2, 0); - return (uint64_t)ntohs((uint16_t)len); + return (size_t)ntohs((uint16_t)len); case 0x7f: lwan_recv(request, &len, 8, 0); - return (uint64_t)be64toh(len); + return (size_t)be64toh(len); default: - return (uint64_t)(header & 0x7f); + return (size_t)(header & 0x7f); } } @@ -190,7 +193,6 @@ int lwan_response_websocket_read(struct lwan_request *request) enum ws_opcode opcode = WS_OPCODE_INVALID; enum ws_opcode last_opcode; uint16_t header; - uint64_t frame_len; bool continuation = false; if (!(request->conn->flags & CONN_IS_WEBSOCKET)) @@ -264,18 +266,13 @@ int lwan_response_websocket_read(struct lwan_request *request) __builtin_unreachable(); } - size_t cur_buf_len = lwan_strbuf_get_length(request->response.buffer); - - frame_len = get_frame_length(request, header); - if (UNLIKELY(!lwan_strbuf_grow_by(request->response.buffer, frame_len))) { + size_t frame_len = get_frame_length(request, header); + char *msg = lwan_strbuf_extend_unsafe(request->response.buffer, frame_len); + if (UNLIKELY(!msg)) { coro_yield(request->conn->coro, CONN_CORO_ABORT); __builtin_unreachable(); } - /* FIXME: API to update used size, too, not just capacity! Also, this can't - * overflow if adding frame_len, as this is checked by grow_by() already. */ - request->response.buffer->used += frame_len; - char *msg = lwan_strbuf_get_buffer(request->response.buffer) + cur_buf_len; char mask[4]; struct iovec vec[] = { {.iov_base = mask, .iov_len = sizeof(mask)}, From b15f2f7e356db1ed6651b45493ce189e14f0dbce Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 7 Jul 2020 08:19:03 -0700 Subject: [PATCH 1553/2505] Clean up WebSocket opcode switch statement Bundle all the reserved opcodes together in a single case. Those cases are rare and should not happen, so reducing the code slightly is a better idea. --- src/lib/lwan-websocket.c | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index a8f0d711b..a484b1f5b 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -249,21 +249,11 @@ int lwan_response_websocket_read(struct lwan_request *request) goto next_frame; case WS_OPCODE_RSVD_1 ... WS_OPCODE_RSVD_5: - lwan_status_debug("Received reserved non-control frame opcode: 0x%x, aborting", - (header & 0x0f00) >> 8); - /* RFC6455: ...the receiving endpoint MUST _Fail the WebSocket Connection_ */ - coro_yield(request->conn->coro, CONN_CORO_ABORT); - __builtin_unreachable(); - case WS_OPCODE_RSVD_CONTROL_1 ... WS_OPCODE_RSVD_CONTROL_5: - lwan_status_debug("Received reserved control frame opcode: 0x%x, aborting", - (header & 0x0f00) >> 8); + case WS_OPCODE_INVALID: /* RFC6455: ...the receiving endpoint MUST _Fail the WebSocket Connection_ */ coro_yield(request->conn->coro, CONN_CORO_ABORT); __builtin_unreachable(); - - case WS_OPCODE_INVALID: - __builtin_unreachable(); } size_t frame_len = get_frame_length(request, header); From ae818029fdf8de48729045f37925ab8e7cd59c65 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 8 Jul 2020 21:09:48 -0700 Subject: [PATCH 1554/2505] Cleanup how the root directory is cached/used in file serving module --- src/lib/lwan-mod-serve-files.c | 41 +++++++++++++++++++++++++--------- src/lib/realpathat.c | 2 +- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 521a19960..389412a59 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -495,7 +495,7 @@ static int try_open_compressed(const char *relpath, int ret, fd; /* Try to serve a compressed file using sendfile() if $FILENAME.gz exists */ - ret = snprintf(gzpath, PATH_MAX, "%s.gz", relpath + 1); + ret = snprintf(gzpath, PATH_MAX, "%s.gz", relpath); if (UNLIKELY(ret < 0 || ret >= PATH_MAX)) goto out; @@ -561,7 +561,7 @@ static bool mmap_init(struct file_cache_entry *ce, const char *path = full_path + priv->root_path_len; int file_fd; - file_fd = openat(priv->root_fd, path + (*path == '/'), open_mode); + file_fd = openat(priv->root_fd, path, open_mode); if (UNLIKELY(file_fd < 0)) return false; if (!mmap_fd(priv, file_fd, (size_t)st->st_size, &md->uncompressed)) @@ -602,7 +602,7 @@ static bool sendfile_init(struct file_cache_entry *ce, ce->mime_type = lwan_determine_mime_type_for_file_name(relpath); - sd->uncompressed.fd = openat(priv->root_fd, relpath + 1, open_mode); + sd->uncompressed.fd = openat(priv->root_fd, relpath, open_mode); if (UNLIKELY(sd->uncompressed.fd < 0)) { switch (errno) { case ENFILE: @@ -805,16 +805,14 @@ static const struct cache_funcs *get_funcs(struct serve_files_priv *priv, return NULL; /* If it does, we want its full path. */ - /* FIXME: Use strlcpy() here instead of calling strlen()? */ - if (UNLIKELY(priv->root_path_len + 1 /* slash */ + + if (UNLIKELY(priv->root_path_len + strlen(index_html_path) + 1 >= PATH_MAX)) return NULL; - full_path[priv->root_path_len] = '/'; - strncpy(full_path + priv->root_path_len + 1, index_html_path, - PATH_MAX - priv->root_path_len - 1); + strncpy(full_path + priv->root_path_len, index_html_path, + PATH_MAX - priv->root_path_len); } /* Only serve regular files. */ @@ -943,19 +941,42 @@ static void redir_free(struct file_cache_entry *fce) free(rd->redir_to); } +static char *get_real_root_path(const char *root_path) +{ + char path_buf[PATH_MAX]; + char *path; + + path = realpath(root_path, path_buf); + if (!path) + return NULL; + + char *last_slash = strrchr(path, '/'); + if (!last_slash) + return NULL; + + if (*(last_slash + 1) == '\0') + return strdup(path); + + char *ret; + if (asprintf(&ret, "%s/", path)) + return ret; + + return NULL; +} + static void *serve_files_create(const char *prefix, void *args) { struct lwan_serve_files_settings *settings = args; + struct serve_files_priv *priv; char *canonical_root; int root_fd; - struct serve_files_priv *priv; if (!settings->root_path) { lwan_status_error("root_path not specified"); return NULL; } - canonical_root = realpath(settings->root_path, NULL); + canonical_root = get_real_root_path(settings->root_path); if (!canonical_root) { lwan_status_perror("Could not obtain real path of \"%s\"", settings->root_path); diff --git a/src/lib/realpathat.c b/src/lib/realpathat.c index e7b13aaa1..8736f1ea9 100644 --- a/src/lib/realpathat.c +++ b/src/lib/realpathat.c @@ -137,7 +137,7 @@ realpathat2(int dirfd, char *dirfdpath, const char *name, char *resolved, strncmp(rpath, dirfdpath, (size_t)dirfdlen)) { pathat = rpath; } else { - pathat = rpath + dirfdlen + 1; + pathat = rpath + dirfdlen; } if (UNLIKELY(*pathat == '\0')) pathat = rpath; From 9b9754d1439d1414a6f311e41635ca5423734807 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 8 Jul 2020 21:22:35 -0700 Subject: [PATCH 1555/2505] Abort initialization if a module can't be initialized --- src/lib/lwan.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 698b5d756..42ba9119e 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -291,6 +291,18 @@ static void parse_listener_prefix_authorization(struct config *c, free(url_map->authorization.password_file); } +const char *get_module_name(const struct lwan_module *module) +{ + const struct lwan_module_info *iter; + + LWAN_SECTION_FOREACH(lwan_module, iter) { + if (iter->module == module) + return iter->name; + } + + return ""; +} + static void parse_listener_prefix(struct config *c, const struct config_line *l, struct lwan *lwan, @@ -342,6 +354,9 @@ static void parse_listener_prefix(struct config *c, hash = NULL; } else if (module->create_from_hash && module->handle_request) { + lwan_status_debug("Initializing module %s from config", + get_module_name(module)); + url_map.data = module->create_from_hash(prefix, hash); if (!url_map.data) { config_error(c, "Could not create module instance"); @@ -384,7 +399,15 @@ void lwan_set_url_map(struct lwan *l, const struct lwan_url_map *map) struct lwan_url_map *copy = add_url_map(&l->url_map_trie, NULL, map); if (copy->module && copy->module->create) { - copy->data = copy->module->create (map->prefix, copy->args); + lwan_status_debug("Initializing module %s from struct", + get_module_name(copy->module)); + + copy->data = copy->module->create(map->prefix, copy->args); + if (!copy->data) { + lwan_status_critical("Could not initialize module %s", + get_module_name(copy->module)); + } + copy->flags = copy->module->flags; copy->handler = copy->module->handle_request; } else { From ed3977899ef1012597f53161cbb1b919066506c2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 8 Jul 2020 22:22:21 -0700 Subject: [PATCH 1556/2505] get_module_name() should be static --- src/lib/lwan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 42ba9119e..f984769ad 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -291,7 +291,7 @@ static void parse_listener_prefix_authorization(struct config *c, free(url_map->authorization.password_file); } -const char *get_module_name(const struct lwan_module *module) +static const char *get_module_name(const struct lwan_module *module) { const struct lwan_module_info *iter; From b71c011c33e30ab10482eb294d8093883ff12bef Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 9 Jul 2020 07:48:07 -0700 Subject: [PATCH 1557/2505] Ensure get_module_name() won't trigger ASan errors w/Clang --- src/lib/lwan.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index f984769ad..8e398e61e 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -291,6 +291,7 @@ static void parse_listener_prefix_authorization(struct config *c, free(url_map->authorization.password_file); } +__attribute__((no_sanitize_address)) static const char *get_module_name(const struct lwan_module *module) { const struct lwan_module_info *iter; From a48fc48aa36fb9bd9519a902b592d1babdf16764 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 9 Jul 2020 20:36:22 -0700 Subject: [PATCH 1558/2505] Get rid of struct lwan_pubsub_sub_queue It contained only one element, and can be easily embedded in struct lwan_pubsub_subscriber itself. --- src/lib/lwan-pubsub.c | 74 ++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 39 deletions(-) diff --git a/src/lib/lwan-pubsub.c b/src/lib/lwan-pubsub.c index a288b7f3e..926ffedb8 100644 --- a/src/lib/lwan-pubsub.c +++ b/src/lib/lwan-pubsub.c @@ -31,76 +31,72 @@ struct lwan_pubsub_topic { struct lwan_pubsub_msg { struct lwan_value value; - int refcount; + unsigned int refcount; }; -DEFINE_RING_BUFFER_TYPE(lwan_pubsub_msg_ref, struct lwan_pubsub_msg *, 16) +DEFINE_RING_BUFFER_TYPE(lwan_pubsub_msg_ref_ring, struct lwan_pubsub_msg *, 16) -struct lwan_pubsub_sub_queue_rb { - struct list_node rb; - struct lwan_pubsub_msg_ref ref; -}; - -struct lwan_pubsub_sub_queue { - struct list_head rbs; +struct lwan_pubsub_msg_ref { + struct list_node ref; + struct lwan_pubsub_msg_ref_ring ring; }; struct lwan_pubsub_subscriber { struct list_node subscriber; pthread_mutex_t lock; - struct lwan_pubsub_sub_queue queue; + struct list_head msg_refs; }; -static void lwan_pubsub_queue_init(struct lwan_pubsub_sub_queue *queue) +static void lwan_pubsub_queue_init(struct lwan_pubsub_subscriber *sub) { - list_head_init(&queue->rbs); + list_head_init(&sub->msg_refs); } -static bool lwan_pubsub_queue_put(struct lwan_pubsub_sub_queue *queue, +static bool lwan_pubsub_queue_put(struct lwan_pubsub_subscriber *sub, const struct lwan_pubsub_msg *msg) { - struct lwan_pubsub_sub_queue_rb *rb; + struct lwan_pubsub_msg_ref *ref; - if (!list_empty(&queue->rbs)) { + if (!list_empty(&sub->msg_refs)) { /* Try putting the message in the last ringbuffer in this queue: if it's * full, will need to allocate a new ring buffer, even if others might * have space in them: the FIFO order must be preserved, and short of * compacting the queue at this point -- which will eventually happen * as it is consumed -- this is the only option. */ - rb = list_tail(&queue->rbs, struct lwan_pubsub_sub_queue_rb, rb); - if (lwan_pubsub_msg_ref_try_put(&rb->ref, &msg)) + ref = list_tail(&sub->msg_refs, struct lwan_pubsub_msg_ref, ref); + if (lwan_pubsub_msg_ref_ring_try_put(&ref->ring, &msg)) return true; } - rb = malloc(sizeof(*rb)); - if (!rb) + ref = malloc(sizeof(*ref)); + if (!ref) return false; - lwan_pubsub_msg_ref_init(&rb->ref); - lwan_pubsub_msg_ref_put(&rb->ref, &msg); - list_add_tail(&queue->rbs, &rb->rb); + lwan_pubsub_msg_ref_ring_init(&ref->ring); + lwan_pubsub_msg_ref_ring_put(&ref->ring, &msg); + list_add_tail(&sub->msg_refs, &ref->ref); return true; } static struct lwan_pubsub_msg * -lwan_pubsub_queue_get(struct lwan_pubsub_sub_queue *queue) +lwan_pubsub_queue_get(struct lwan_pubsub_subscriber *sub) { - struct lwan_pubsub_sub_queue_rb *rb, *next; + struct lwan_pubsub_msg_ref *ref, *next; - list_for_each_safe (&queue->rbs, rb, next, rb) { + list_for_each_safe (&sub->msg_refs, ref, next, ref) { struct lwan_pubsub_msg *msg; - if (lwan_pubsub_msg_ref_empty(&rb->ref)) { - list_del(&rb->rb); - free(rb); + if (lwan_pubsub_msg_ref_ring_empty(&ref->ring)) { + list_del(&ref->ref); + free(ref); continue; } - msg = lwan_pubsub_msg_ref_get(&rb->ref); + msg = lwan_pubsub_msg_ref_ring_get(&ref->ring); - if (rb->rb.next != rb->rb.prev) { + if (ref->ref.next != ref->ref.prev) { /* If this segment isn't the last one, try pulling in just one * element from the next segment, as there's space in the * current segment now. @@ -116,14 +112,14 @@ lwan_pubsub_queue_get(struct lwan_pubsub_sub_queue *queue) * completely (and ultimately reducing it to an empty list * without any ring buffers). */ - struct lwan_pubsub_sub_queue_rb *next_rb; + struct lwan_pubsub_msg_ref *next_ring; - next_rb = container_of(rb->rb.next, struct lwan_pubsub_sub_queue_rb, rb); - if (!lwan_pubsub_msg_ref_empty(&next_rb->ref)) { + next_ring = container_of(ref->ref.next, struct lwan_pubsub_msg_ref, ref); + if (!lwan_pubsub_msg_ref_ring_empty(&next_ring->ring)) { const struct lwan_pubsub_msg *next_msg; - next_msg = lwan_pubsub_msg_ref_get(&next_rb->ref); - lwan_pubsub_msg_ref_put(&rb->ref, &next_msg); + next_msg = lwan_pubsub_msg_ref_ring_get(&next_ring->ring); + lwan_pubsub_msg_ref_ring_put(&ref->ring, &next_msg); } } @@ -208,7 +204,7 @@ bool lwan_pubsub_publish(struct lwan_pubsub_topic *topic, ATOMIC_INC(msg->refcount); pthread_mutex_lock(&sub->lock); - if (!lwan_pubsub_queue_put(&sub->queue, msg)) { + if (!lwan_pubsub_queue_put(sub, msg)) { lwan_status_warning("Couldn't enqueue message, dropping"); ATOMIC_DEC(msg->refcount); } @@ -230,7 +226,7 @@ lwan_pubsub_subscribe(struct lwan_pubsub_topic *topic) return NULL; pthread_mutex_init(&sub->lock, NULL); - lwan_pubsub_queue_init(&sub->queue); + lwan_pubsub_queue_init(sub); pthread_mutex_lock(&topic->lock); list_add(&topic->subscribers, &sub->subscriber); @@ -244,7 +240,7 @@ struct lwan_pubsub_msg *lwan_pubsub_consume(struct lwan_pubsub_subscriber *sub) struct lwan_pubsub_msg *msg; pthread_mutex_lock(&sub->lock); - msg = lwan_pubsub_queue_get(&sub->queue); + msg = lwan_pubsub_queue_get(sub); pthread_mutex_unlock(&sub->lock); return msg; @@ -263,7 +259,7 @@ static void lwan_pubsub_unsubscribe_internal(struct lwan_pubsub_topic *topic, pthread_mutex_unlock(&topic->lock); pthread_mutex_lock(&sub->lock); - while ((iter = lwan_pubsub_queue_get(&sub->queue))) + while ((iter = lwan_pubsub_queue_get(sub))) lwan_pubsub_msg_done(iter); pthread_mutex_unlock(&sub->lock); From 0a6bbc563f1e8e05677049208a0e0a58316ff343 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 9 Jul 2020 20:37:25 -0700 Subject: [PATCH 1559/2505] Use platform-agnostic thread IDs when printing status --- src/lib/lwan-status.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/lib/lwan-status.c b/src/lib/lwan-status.c index 4b49ce9df..4db344883 100644 --- a/src/lib/lwan-status.c +++ b/src/lib/lwan-status.c @@ -122,12 +122,13 @@ static long gettid_cached(void) * https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=15216 */ return gettid(); #else - static __thread long tid; + static long thread_count; + static __thread long current_thread_id; - if (!tid) - tid = gettid(); + if (!current_thread_id) + current_thread_id = ATOMIC_INC(thread_count); - return tid; + return current_thread_id; #endif } #endif @@ -153,11 +154,10 @@ static void status_out( #ifndef NDEBUG char *base_name = basename(strdupa(file)); if (LIKELY(use_colors)) { - printf(FORMAT_WITH_COLOR("%ld ", "32;1"), gettid_cached()); printf(FORMAT_WITH_COLOR("%s:%d ", "3"), base_name, line); - printf(FORMAT_WITH_COLOR("%s() ", "33"), func); + printf(FORMAT_WITH_COLOR("%s(%ld) ", "33"), func, gettid_cached()); } else { - printf("%ld %s:%d %s() ", gettid_cached(), base_name, line, func); + printf("%s:%d %s(%ld) ", base_name, line, func, gettid_cached()); } #endif From 6b466f3d71b1a5b00788079bfcc5eb21c895a33e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 11 Jul 2020 10:56:52 -0700 Subject: [PATCH 1560/2505] Fix memory leaks when requesting query_param, cookies, or post_data This was tricky to find. Since the storage for the helper struct lived in the frame for lwan_process_coro(), once it returned, all the coro_defer() calls were trying to free up the arrays whose storage was in the dead call frame. I have no clue how this all worked until now! Moved the helper struct to the coroutine, where the lifetime of that struct is guaranteed to exist while the coroutine is cleaning up its things. This should also fix a performance regression found with the TWFB preparations for the round 20, where Lwan didn't fare well in the cached queries benchmark, where it's one of the places it should really shine. Fixes #283. --- src/lib/lwan-private.h | 47 ++++++++++++++++++++++++-- src/lib/lwan-request.c | 77 ++++++------------------------------------ src/lib/lwan-thread.c | 40 ++++++++++++---------- 3 files changed, 78 insertions(+), 86 deletions(-) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 6e7861870..f9f5b5f1e 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -24,9 +24,45 @@ #include "lwan.h" +struct lwan_request_parser_helper { + struct lwan_value *buffer; /* The whole request buffer */ + char *next_request; /* For pipelined requests */ + + char **header_start; /* Headers: n: start, n+1: end */ + size_t n_header_start; /* len(header_start) */ + + struct lwan_value accept_encoding; /* Accept-Encoding: */ + + struct lwan_value query_string; /* Stuff after ? and before # */ + + struct lwan_value post_data; /* Request body for POST */ + struct lwan_value content_type; /* Content-Type: for POST */ + struct lwan_value content_length; /* Content-Length: */ + + struct lwan_value connection; /* Connection: */ + + struct lwan_key_value_array cookies, query_params, post_params; + + struct { /* If-Modified-Since: */ + struct lwan_value raw; + time_t parsed; + } if_modified_since; + + struct { /* Range: */ + struct lwan_value raw; + off_t from, to; + } range; + + time_t error_when_time; /* Time to abort request read */ + int error_when_n_packets; /* Max. number of packets */ + int urls_rewritten; /* Times URLs have been rewritten */ +}; + #define DEFAULT_BUFFER_SIZE 4096 #define DEFAULT_HEADERS_SIZE 512 +#define N_HEADER_START 64 + #define LWAN_CONCAT(a_, b_) a_ ## b_ #define LWAN_TMP_ID_DETAIL(n_) LWAN_CONCAT(lwan_tmp_id, n_) #define LWAN_TMP_ID LWAN_TMP_ID_DETAIL(__COUNTER__) @@ -75,8 +111,8 @@ void lwan_madvise_queue(void *addr, size_t size); char *lwan_strbuf_extend_unsafe(struct lwan_strbuf *s, size_t by); -char *lwan_process_request(struct lwan *l, struct lwan_request *request, - struct lwan_value *buffer, char *next_request); +void lwan_process_request(struct lwan *l, struct lwan_request *request, + char *next_request); size_t lwan_prepare_response_header_full(struct lwan_request *request, enum lwan_http_status status, char headers[], size_t headers_buf_size, const struct lwan_key_value *additional_headers); @@ -163,3 +199,10 @@ lwan_aligned_alloc(size_t n, size_t alignment) return ret; } + +static ALWAYS_INLINE int lwan_calculate_n_packets(size_t total) +{ + /* 740 = 1480 (a common MTU) / 2, so that Lwan'll optimistically error out + * after ~2x number of expected packets to fully read the request body.*/ + return LWAN_MAX(5, (int)(total / 740)); +} diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index cfa2e7258..9bd9642c4 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -46,7 +46,6 @@ #define HEADER_TERMINATOR_LEN (sizeof("\r\n") - 1) #define MIN_REQUEST_SIZE (sizeof("GET / HTTP/1.1\r\n\r\n") - 1) -#define N_HEADER_START 64 enum lwan_read_finalizer { FINALIZER_DONE, @@ -54,40 +53,6 @@ enum lwan_read_finalizer { FINALIZER_ERROR_TIMEOUT }; -struct lwan_request_parser_helper { - struct lwan_value *buffer; /* The whole request buffer */ - char *next_request; /* For pipelined requests */ - - char **header_start; /* Headers: n: start, n+1: end */ - size_t n_header_start; /* len(header_start) */ - - struct lwan_value accept_encoding; /* Accept-Encoding: */ - - struct lwan_value query_string; /* Stuff after ? and before # */ - - struct lwan_value post_data; /* Request body for POST */ - struct lwan_value content_type; /* Content-Type: for POST */ - struct lwan_value content_length; /* Content-Length: */ - - struct lwan_value connection; /* Connection: */ - - struct lwan_key_value_array cookies, query_params, post_params; - - struct { /* If-Modified-Since: */ - struct lwan_value raw; - time_t parsed; - } if_modified_since; - - struct { /* Range: */ - struct lwan_value raw; - off_t from, to; - } range; - - time_t error_when_time; /* Time to abort request read */ - int error_when_n_packets; /* Max. number of packets */ - int urls_rewritten; /* Times URLs have been rewritten */ -}; - struct proxy_header_v2 { uint8_t sig[12]; uint8_t cmd_ver; @@ -906,7 +871,7 @@ read_request_finalizer(size_t total_read, * hogging the server. (Clients can't only connect and do nothing, they * need to send data, otherwise the timeout queue timer will kick in and * close the connection. Limit the number of packets to avoid them sending - * just a byte at a time.) See calculate_n_packets() to see how this is + * just a byte at a time.) See lwan_calculate_n_packets() to see how this is * calculated. */ if (UNLIKELY(n_packets > helper->error_when_n_packets)) return FINALIZER_ERROR_TIMEOUT; @@ -972,13 +937,6 @@ post_data_finalizer(size_t total_read, return FINALIZER_TRY_AGAIN; } -static ALWAYS_INLINE int calculate_n_packets(size_t total) -{ - /* 740 = 1480 (a common MTU) / 2, so that Lwan'll optimistically error out - * after ~2x number of expected packets to fully read the request body.*/ - return LWAN_MAX(5, (int)(total / 740)); -} - static const char *is_dir(const char *v) { struct stat st; @@ -1180,7 +1138,7 @@ static enum lwan_http_status read_post_data(struct lwan_request *request) helper->next_request = NULL; helper->error_when_time = time(NULL) + config->keep_alive_timeout; - helper->error_when_n_packets = calculate_n_packets(post_data_size); + helper->error_when_n_packets = lwan_calculate_n_packets(post_data_size); struct lwan_value buffer = {.value = new_buffer, .len = post_data_size}; @@ -1362,29 +1320,19 @@ static bool handle_rewrite(struct lwan_request *request) return true; } -char *lwan_process_request(struct lwan *l, - struct lwan_request *request, - struct lwan_value *buffer, - char *next_request) +void lwan_process_request(struct lwan *l, + struct lwan_request *request, + char *next_request) { - char *header_start[N_HEADER_START]; - struct lwan_request_parser_helper helper = { - .buffer = buffer, - .next_request = next_request, - .error_when_n_packets = calculate_n_packets(DEFAULT_BUFFER_SIZE), - .header_start = header_start, - }; enum lwan_http_status status; struct lwan_url_map *url_map; - request->helper = &helper; - status = read_request(request); if (UNLIKELY(status != HTTP_OK)) { /* This request was bad, but maybe there's a good one in the * pipeline. */ - if (status == HTTP_BAD_REQUEST && helper.next_request) - goto out; + if (status == HTTP_BAD_REQUEST && request->helper->next_request) + return; /* Response here can be: HTTP_TOO_LARGE, HTTP_BAD_REQUEST (without * next request), or HTTP_TIMEOUT. Nothing to do, just abort the @@ -1397,20 +1345,20 @@ char *lwan_process_request(struct lwan *l, status = parse_http_request(request); if (UNLIKELY(status != HTTP_OK)) { lwan_default_response(request, status); - goto out; + return; } lookup_again: url_map = lwan_trie_lookup_prefix(&l->url_map_trie, request->url.value); if (UNLIKELY(!url_map)) { lwan_default_response(request, HTTP_NOT_FOUND); - goto out; + return; } status = prepare_for_response(url_map, request); if (UNLIKELY(status != HTTP_OK)) { lwan_default_response(request, status); - goto out; + return; } status = url_map->handler(request, &request->response, url_map->data); @@ -1418,14 +1366,11 @@ char *lwan_process_request(struct lwan *l, if (request->flags & RESPONSE_URL_REWRITTEN) { if (LIKELY(handle_rewrite(request))) goto lookup_again; - goto out; + return; } } lwan_response(request, status); - -out: - return helper.next_request; } static inline void * diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index e80b4dcf5..47b2c72c1 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -108,7 +108,9 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, char request_buffer[DEFAULT_BUFFER_SIZE]; struct lwan_value buffer = {.value = request_buffer, .len = 0}; char *next_request = NULL; + char *header_start[N_HEADER_START]; struct lwan_proxy proxy; + const int error_when_n_packets = lwan_calculate_n_packets(DEFAULT_BUFFER_SIZE); coro_defer(coro, lwan_strbuf_free_defer, &strbuf); @@ -116,22 +118,24 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, assert(init_gen == coro_deferred_get_generation(coro)); while (true) { - struct lwan_request request = {.conn = conn, - .global_response_headers = &lwan->headers, - .fd = fd, - .response = {.buffer = &strbuf}, - .flags = flags, - .proxy = &proxy}; - - next_request = - lwan_process_request(lwan, &request, &buffer, next_request); - - if (coro_deferred_get_generation(coro) > ((2 * LWAN_ARRAY_INCREMENT) / 3)) { - /* Batch execution of coro_defers() up to 2/3 LWAN_ARRAY_INCREMENT times, - * to avoid moving deferred array to heap in most cases. (This is to give - * some slack to the next request being processed by this coro.) */ - coro_deferred_run(coro, init_gen); - } + struct lwan_request_parser_helper helper = { + .buffer = &buffer, + .next_request = next_request, + .error_when_n_packets = error_when_n_packets, + .header_start = header_start, + }; + struct lwan_request request = { + .conn = conn, + .global_response_headers = &lwan->headers, + .fd = fd, + .response = {.buffer = &strbuf}, + .flags = flags, + .proxy = &proxy, + .helper = &helper, + }; + + lwan_process_request(lwan, &request, next_request); + if (LIKELY(conn->flags & CONN_IS_KEEP_ALIVE)) { if (next_request && *next_request) { @@ -147,17 +151,17 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, } lwan_strbuf_reset(&strbuf); + coro_deferred_run(coro, init_gen); /* Only allow flags from config. */ flags = request.flags & (REQUEST_PROXIED | REQUEST_ALLOW_CORS); + next_request = helper.next_request; } coro_yield(coro, CONN_CORO_ABORT); __builtin_unreachable(); } -#undef REQUEST_FLAG - static ALWAYS_INLINE uint32_t conn_flags_to_epoll_events(enum lwan_connection_flags flags) { From 2cd17c76848a4e20629c13a8fb965d6e829db2df Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 11 Jul 2020 11:10:26 -0700 Subject: [PATCH 1561/2505] `next_request' parameter don't need to be passed to lwan_process_request() --- src/lib/lwan-private.h | 3 +-- src/lib/lwan-request.c | 3 +-- src/lib/lwan-thread.c | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index f9f5b5f1e..af394232e 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -111,8 +111,7 @@ void lwan_madvise_queue(void *addr, size_t size); char *lwan_strbuf_extend_unsafe(struct lwan_strbuf *s, size_t by); -void lwan_process_request(struct lwan *l, struct lwan_request *request, - char *next_request); +void lwan_process_request(struct lwan *l, struct lwan_request *request); size_t lwan_prepare_response_header_full(struct lwan_request *request, enum lwan_http_status status, char headers[], size_t headers_buf_size, const struct lwan_key_value *additional_headers); diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 9bd9642c4..35b49f7fe 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1321,8 +1321,7 @@ static bool handle_rewrite(struct lwan_request *request) } void lwan_process_request(struct lwan *l, - struct lwan_request *request, - char *next_request) + struct lwan_request *request) { enum lwan_http_status status; struct lwan_url_map *url_map; diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 47b2c72c1..755a1021e 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -134,8 +134,7 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, .helper = &helper, }; - lwan_process_request(lwan, &request, next_request); - + lwan_process_request(lwan, &request); if (LIKELY(conn->flags & CONN_IS_KEEP_ALIVE)) { if (next_request && *next_request) { From 3e37ad128eec6a3ea43941316c3d6d8754db4a45 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 11 Jul 2020 11:18:19 -0700 Subject: [PATCH 1562/2505] Revert "Use platform-agnostic thread IDs when printing status" This reverts commit 0a6bbc563f1e8e05677049208a0e0a58316ff343. I don't really like how this turned out. --- src/lib/lwan-status.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/lib/lwan-status.c b/src/lib/lwan-status.c index 4db344883..4b49ce9df 100644 --- a/src/lib/lwan-status.c +++ b/src/lib/lwan-status.c @@ -122,13 +122,12 @@ static long gettid_cached(void) * https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=15216 */ return gettid(); #else - static long thread_count; - static __thread long current_thread_id; + static __thread long tid; - if (!current_thread_id) - current_thread_id = ATOMIC_INC(thread_count); + if (!tid) + tid = gettid(); - return current_thread_id; + return tid; #endif } #endif @@ -154,10 +153,11 @@ static void status_out( #ifndef NDEBUG char *base_name = basename(strdupa(file)); if (LIKELY(use_colors)) { + printf(FORMAT_WITH_COLOR("%ld ", "32;1"), gettid_cached()); printf(FORMAT_WITH_COLOR("%s:%d ", "3"), base_name, line); - printf(FORMAT_WITH_COLOR("%s(%ld) ", "33"), func, gettid_cached()); + printf(FORMAT_WITH_COLOR("%s() ", "33"), func); } else { - printf("%s:%d %s(%ld) ", base_name, line, func, gettid_cached()); + printf("%ld %s:%d %s() ", gettid_cached(), base_name, line, func); } #endif From 30d95cd48b51daca333c1c9365c66ea9926fdae1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 11 Jul 2020 11:39:28 -0700 Subject: [PATCH 1563/2505] Yield "want-write" when obtaining items from cache within a coro Otherwise, it might always block while trying to read from a socket that never has anything to be read from, and that worker thread won't respond anymore to any other request. --- src/lib/lwan-cache.c | 15 +++++++-------- src/samples/techempower/techempower.c | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/lib/lwan-cache.c b/src/lib/lwan-cache.c index 0e3882f87..b14e4a4ad 100644 --- a/src/lib/lwan-cache.c +++ b/src/lib/lwan-cache.c @@ -381,15 +381,14 @@ struct cache_entry *cache_coro_get_and_ref_entry(struct cache *cache, return ce; } - /* - * If the cache would block while reading its hash table, yield and - * try again. On any other error, just return NULL. - */ - if (error == EWOULDBLOCK) { - coro_yield(coro, CONN_CORO_YIELD); - } else { + if (error != EWOULDBLOCK) break; - } + + /* If the cache would block while reading its hash table, yield and + * try again. (This yields "want-write" because otherwise this + * worker thread might never be resumed again; it's not always that + * a socket can be read from, but you can always write to it.) */ + coro_yield(coro, CONN_CORO_WANT_WRITE); } return NULL; diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index abf4597ee..df90e7192 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -329,7 +329,7 @@ static struct cache_entry *my_cache_coro_get_and_ref_entry(struct cache *cache, if (error != EWOULDBLOCK) break; - coro_yield(coro, CONN_CORO_YIELD); + coro_yield(coro, CONN_CORO_WANT_WRITE); } return NULL; From 4068da5ce808c279fe1921daa52bfd728229a434 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 11 Jul 2020 11:47:04 -0700 Subject: [PATCH 1564/2505] Wait a bit before trying obtaining write lock to the cache (Only in the TWFB cached queries benchmark at the moment.) --- src/samples/techempower/techempower.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index df90e7192..258c99e20 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -307,7 +307,7 @@ static void cached_queries_free(struct cache_entry *entry, void *context) } static struct cache_entry *my_cache_coro_get_and_ref_entry(struct cache *cache, - struct coro *coro, + struct lwan_request *request, const char *key) { /* Using this function instead of cache_coro_get_and_ref_entry() will avoid @@ -319,7 +319,7 @@ static struct cache_entry *my_cache_coro_get_and_ref_entry(struct cache *cache, * indirect calls that are performed every time a request is serviced. */ - for (int tries = 16; tries; tries--) { + for (int tries = 64; tries; tries--) { int error; struct cache_entry *ce = cache_get_and_ref_entry(cache, key, &error); @@ -329,7 +329,10 @@ static struct cache_entry *my_cache_coro_get_and_ref_entry(struct cache *cache, if (error != EWOULDBLOCK) break; - coro_yield(coro, CONN_CORO_WANT_WRITE); + coro_yield(request->conn->coro, CONN_CORO_WANT_WRITE); + + if (tries > 16) + lwan_request_sleep(request, (unsigned int)(tries / 8)); } return NULL; @@ -351,7 +354,7 @@ LWAN_HANDLER(cached_world) size_t discard; jc = (struct db_json_cached *)my_cache_coro_get_and_ref_entry( - cached_queries_cache, request->conn->coro, + cached_queries_cache, request, int_to_string(rand() % 10000, key_buf, &discard)); if (UNLIKELY(!jc)) return HTTP_INTERNAL_ERROR; From 11d793bbda7507f396ae08d144efa51f467a98ef Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 11 Jul 2020 12:50:08 -0700 Subject: [PATCH 1565/2505] s/cached_world/cached_queries/ --- src/samples/techempower/techempower.c | 2 +- src/samples/techempower/techempower.conf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index 258c99e20..6cd38b692 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -338,7 +338,7 @@ static struct cache_entry *my_cache_coro_get_and_ref_entry(struct cache *cache, return NULL; } -LWAN_HANDLER(cached_world) +LWAN_HANDLER(cached_queries) { const char *queries_str = lwan_request_get_query_param(request, "count"); long queries; diff --git a/src/samples/techempower/techempower.conf b/src/samples/techempower/techempower.conf index 1089e1758..28f3177c2 100644 --- a/src/samples/techempower/techempower.conf +++ b/src/samples/techempower/techempower.conf @@ -4,7 +4,7 @@ listener *:8080 { &json /json &db /db &queries /queries - &cached_world /cached-world + &cached_queries /cached-queries &fortunes /fortunes # For Lua version of TWFB benchmarks From 283298b4251d09af7be54abd8d9e2a19208241a5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 11 Jul 2020 13:04:32 -0700 Subject: [PATCH 1566/2505] Fix test after changes in file serving module --- src/scripts/testsuite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index eec2553c7..6d8fe7dd7 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -431,7 +431,7 @@ def test_directory_without_trailing_slash_redirects(self): self.assertResponsePlain(r, 301) self.assertTrue('location' in r.headers) - self.assertEqual(r.headers['location'], '/icons/') + self.assertEqual(r.headers['location'], 'icons/') class TestRedirect(LwanTest): def test_redirect_default(self): From 77ff33bb46717505f76965d2165cd5dc6a85a8e1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 11 Jul 2020 15:34:58 -0700 Subject: [PATCH 1567/2505] Ensure storage for `helper' exists when connections are gracefully closed --- src/lib/lwan-thread.c | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 755a1021e..b1ee42daa 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -124,33 +124,35 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, .error_when_n_packets = error_when_n_packets, .header_start = header_start, }; - struct lwan_request request = { - .conn = conn, - .global_response_headers = &lwan->headers, - .fd = fd, - .response = {.buffer = &strbuf}, - .flags = flags, - .proxy = &proxy, - .helper = &helper, - }; + struct lwan_request request = {.conn = conn, + .global_response_headers = &lwan->headers, + .fd = fd, + .response = {.buffer = &strbuf}, + .flags = flags, + .proxy = &proxy, + .helper = &helper}; lwan_process_request(lwan, &request); - if (LIKELY(conn->flags & CONN_IS_KEEP_ALIVE)) { - if (next_request && *next_request) { - conn->flags |= CONN_CORK; - coro_yield(coro, CONN_CORO_WANT_WRITE); - } else { - conn->flags &= ~CONN_CORK; - coro_yield(coro, CONN_CORO_WANT_READ); - } - } else { + /* Run the deferred instructions now (except those used to initialize + * the coroutine), so that if the connection is gracefully closed, + * the storage for ``helper'' is still there. */ + coro_deferred_run(coro, init_gen); + + if (UNLIKELY(!(conn->flags & CONN_IS_KEEP_ALIVE))) { graceful_close(lwan, conn, request_buffer); break; } + if (next_request && *next_request) { + conn->flags |= CONN_CORK; + coro_yield(coro, CONN_CORO_WANT_WRITE); + } else { + conn->flags &= ~CONN_CORK; + coro_yield(coro, CONN_CORO_WANT_READ); + } + lwan_strbuf_reset(&strbuf); - coro_deferred_run(coro, init_gen); /* Only allow flags from config. */ flags = request.flags & (REQUEST_PROXIED | REQUEST_ALLOW_CORS); From daf0f89ceac04a2e939727667088fb113443f1e1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 13 Jul 2020 18:30:36 -0700 Subject: [PATCH 1568/2505] Lwan: Also pull in `id` from `world` tables to be compliant --- src/samples/techempower/techempower.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index 6cd38b692..a8ef46359 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -52,9 +52,9 @@ static struct db_connection_params { static const char hello_world[] = "Hello, World!"; static const char random_number_query[] = - "SELECT randomNumber FROM world WHERE id=?"; + "SELECT randomNumber, id FROM world WHERE id=?"; static const char cached_random_number_query[] = - "SELECT randomNumber FROM world WHERE id=?"; + "SELECT randomNumber, id FROM world WHERE id=?"; struct Fortune { struct { @@ -199,10 +199,11 @@ static bool db_query_key(struct db_stmt *stmt, struct db_json *out, int key) return false; long random_number; - if (UNLIKELY(!db_stmt_step(stmt, "i", &random_number))) + long id; + if (UNLIKELY(!db_stmt_step(stmt, "ii", &random_number, &id))) return false; - out->id = row.u.i; + out->id = (int)id; out->randomNumber = (int)random_number; return true; From 9edaa094493d4c35ffc270c9970b156fa6fb97f6 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 15 Jul 2020 19:42:41 -0700 Subject: [PATCH 1569/2505] Improve how the read-request-finalizer finds the request terminator On non-pipelined requests, it's sufficient to look at the last four bytes in the received buffer to determine if they contain the request terminator, "\r\n\r\n". This makes the memmem() call go away from profiles (it usually takes ~1-2% of the total time.) --- src/lib/lwan-request.c | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 35b49f7fe..8d73773e6 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -867,17 +867,19 @@ read_request_finalizer(size_t total_read, MIN_REQUEST_SIZE + sizeof(struct proxy_header_v2); struct lwan_request_parser_helper *helper = request->helper; - /* Yield a timeout error to avoid clients being intentionally slow and - * hogging the server. (Clients can't only connect and do nothing, they - * need to send data, otherwise the timeout queue timer will kick in and - * close the connection. Limit the number of packets to avoid them sending - * just a byte at a time.) See lwan_calculate_n_packets() to see how this is - * calculated. */ - if (UNLIKELY(n_packets > helper->error_when_n_packets)) - return FINALIZER_ERROR_TIMEOUT; + if (!(request->conn->flags & CONN_CORK)) { + /* CONN_CORK is set on pipelined requests. For non-pipelined requests, + * try looking at the last four bytes to see if we have a complete + * request in the buffer as a fast path. (The memmem() below appears + * in profiles with measurable impact.) */ + if (LIKELY(total_read >= MIN_REQUEST_SIZE)) { + if (!memcmp(helper->buffer->value + total_read - 4, "\r\n\r\n", 4)) + return FINALIZER_DONE; + } + } char *crlfcrlf = - memmem(helper->buffer->value, helper->buffer->len, "\r\n\r\n", 4); + memmem(helper->buffer->value, total_read, "\r\n\r\n", 4); if (LIKELY(crlfcrlf)) { const size_t crlfcrlf_to_base = (size_t)(crlfcrlf - helper->buffer->value); @@ -901,6 +903,15 @@ read_request_finalizer(size_t total_read, } } + /* Yield a timeout error to avoid clients being intentionally slow and + * hogging the server. (Clients can't only connect and do nothing, they + * need to send data, otherwise the timeout queue timer will kick in and + * close the connection. Limit the number of packets to avoid them sending + * just a byte at a time.) See lwan_calculate_n_packets() to see how this is + * calculated. */ + if (UNLIKELY(n_packets > helper->error_when_n_packets)) + return FINALIZER_ERROR_TIMEOUT; + return FINALIZER_TRY_AGAIN; } From eeaae6484fb9c73ccdf566431b0ac304d89ef4b9 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 15 Jul 2020 19:46:34 -0700 Subject: [PATCH 1570/2505] Try making the call to lwan_response() be a tail call --- src/lib/lwan-request.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 8d73773e6..ec3b82c77 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1331,8 +1331,7 @@ static bool handle_rewrite(struct lwan_request *request) return true; } -void lwan_process_request(struct lwan *l, - struct lwan_request *request) +void lwan_process_request(struct lwan *l, struct lwan_request *request) { enum lwan_http_status status; struct lwan_url_map *url_map; @@ -1380,7 +1379,7 @@ void lwan_process_request(struct lwan *l, } } - lwan_response(request, status); + return (void)lwan_response(request, status); } static inline void * From 2c1410503104e4809b21c73ad1d0dfc56cf36bbe Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 15 Jul 2020 19:47:17 -0700 Subject: [PATCH 1571/2505] Fix file listing test --- src/scripts/testsuite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index 6d8fe7dd7..8ee37b3ca 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -398,7 +398,7 @@ def test_directory_listing(self): self.assertResponseHtml(r) - self.assertTrue('

Index of /icons

' in r.text) + self.assertTrue('

Index of icons

' in r.text) def assertHasImage(name): imgtag = "%s.gif" % (name, name) From fc7cd946ab71ecd61fbc3e6d76068e28257aaa22 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 16 Jul 2020 19:19:08 -0700 Subject: [PATCH 1572/2505] Cleanup read_from_request_socket() and finalizer function prototype This renames read_from_request_socket() to client_read(), and gets rid of the "total_read" parameter (which can be inferred from buffer). It also has some other minor changes, including always yielding with CONN_CORO_WANT_READ (for both EINTR and EAGAIN), renaming of FINALIZER_ERROR_TIMEOUT to FINALIZER_TIMEOUT, and the usage of a string switch to find the header terminator in the fast path. --- src/lib/lwan-request.c | 83 ++++++++++++++++++------------------------ 1 file changed, 36 insertions(+), 47 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index ec3b82c77..e0ddf1af7 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -50,7 +50,7 @@ enum lwan_read_finalizer { FINALIZER_DONE, FINALIZER_TRY_AGAIN, - FINALIZER_ERROR_TIMEOUT + FINALIZER_TIMEOUT, }; struct proxy_header_v2 { @@ -771,17 +771,14 @@ static void save_to_corpus_for_fuzzing(struct lwan_value buffer) #endif static enum lwan_http_status -read_from_request_socket(struct lwan_request *request, - struct lwan_value *buffer, - const size_t buffer_size, - enum lwan_read_finalizer (*finalizer)( - size_t total_read, - size_t buffer_size, - struct lwan_request *request, - int n_packets)) +client_read(struct lwan_request *request, + struct lwan_value *buffer, + const size_t buffer_size, + enum lwan_read_finalizer (*finalizer)(size_t buffer_size, + struct lwan_request *request, + int n_packets)) { struct lwan_request_parser_helper *helper = request->helper; - size_t total_read = 0; int n_packets = 0; if (helper->next_request) { @@ -795,33 +792,31 @@ read_from_request_socket(struct lwan_request *request, * stucture (maybe a ringbuffer, reading with readv(), and each * pointer is coro_strdup() if they wrap around?) were used for * the request buffer. */ - total_read = buffer->len = new_len; + buffer->len = new_len; memmove(buffer->value, helper->next_request, new_len); goto try_to_finalize; } } - for (;; n_packets++) { - size_t to_read = (size_t)(buffer_size - total_read); + for (buffer->len = 0;; n_packets++) { + size_t to_read = (size_t)(buffer_size - buffer->len); if (UNLIKELY(to_read == 0)) return HTTP_TOO_LARGE; - ssize_t n = read(request->fd, buffer->value + total_read, to_read); + ssize_t n = read(request->fd, buffer->value + buffer->len, to_read); if (UNLIKELY(n <= 0)) { if (n < 0) { switch (errno) { + case EINTR: case EAGAIN: yield_and_read_again: coro_yield(request->conn->coro, CONN_CORO_WANT_READ); continue; - case EINTR: - coro_yield(request->conn->coro, CONN_CORO_YIELD); - continue; } /* Unexpected error before reading anything */ - if (UNLIKELY(!total_read)) + if (UNLIKELY(!buffer->len)) return HTTP_BAD_REQUEST; } @@ -830,24 +825,21 @@ read_from_request_socket(struct lwan_request *request, break; } - total_read += (size_t)n; - buffer->len = (size_t)total_read; + buffer->len += (size_t)n; try_to_finalize: - switch (finalizer(total_read, buffer_size, request, n_packets)) { + switch (finalizer(buffer_size, request, n_packets)) { case FINALIZER_DONE: buffer->value[buffer->len] = '\0'; - #if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) save_to_corpus_for_fuzzing(*buffer); #endif - return HTTP_OK; case FINALIZER_TRY_AGAIN: goto yield_and_read_again; - case FINALIZER_ERROR_TIMEOUT: + case FINALIZER_TIMEOUT: return HTTP_TIMEOUT; } } @@ -858,31 +850,31 @@ read_from_request_socket(struct lwan_request *request, } static enum lwan_read_finalizer -read_request_finalizer(size_t total_read, - size_t buffer_size __attribute__((unused)), +read_request_finalizer(size_t buffer_size __attribute__((unused)), struct lwan_request *request, int n_packets) { static const size_t min_proxied_request_size = MIN_REQUEST_SIZE + sizeof(struct proxy_header_v2); struct lwan_request_parser_helper *helper = request->helper; + const struct lwan_value *buffer = helper->buffer; if (!(request->conn->flags & CONN_CORK)) { /* CONN_CORK is set on pipelined requests. For non-pipelined requests, * try looking at the last four bytes to see if we have a complete * request in the buffer as a fast path. (The memmem() below appears * in profiles with measurable impact.) */ - if (LIKELY(total_read >= MIN_REQUEST_SIZE)) { - if (!memcmp(helper->buffer->value + total_read - 4, "\r\n\r\n", 4)) + if (LIKELY(buffer->len >= MIN_REQUEST_SIZE)) { + STRING_SWITCH (buffer->value + buffer->len - 4) { + case STR4_INT('\r', '\n', '\r', '\n'): return FINALIZER_DONE; + } } } - char *crlfcrlf = - memmem(helper->buffer->value, total_read, "\r\n\r\n", 4); + char *crlfcrlf = memmem(buffer->value, buffer->len, "\r\n\r\n", 4); if (LIKELY(crlfcrlf)) { - const size_t crlfcrlf_to_base = - (size_t)(crlfcrlf - helper->buffer->value); + const size_t crlfcrlf_to_base = (size_t)(crlfcrlf - buffer->value); if (LIKELY(helper->next_request)) { helper->next_request = NULL; @@ -892,7 +884,7 @@ read_request_finalizer(size_t total_read, if (crlfcrlf_to_base >= MIN_REQUEST_SIZE - 4) return FINALIZER_DONE; - if (total_read > min_proxied_request_size && + if (buffer->len > min_proxied_request_size && request->flags & REQUEST_ALLOW_PROXY_REQS) { /* FIXME: Checking for PROXYv2 protocol header here is a layering * violation. */ @@ -910,7 +902,7 @@ read_request_finalizer(size_t total_read, * just a byte at a time.) See lwan_calculate_n_packets() to see how this is * calculated. */ if (UNLIKELY(n_packets > helper->error_when_n_packets)) - return FINALIZER_ERROR_TIMEOUT; + return FINALIZER_TIMEOUT; return FINALIZER_TRY_AGAIN; } @@ -918,32 +910,31 @@ read_request_finalizer(size_t total_read, static ALWAYS_INLINE enum lwan_http_status read_request(struct lwan_request *request) { - return read_from_request_socket(request, request->helper->buffer, - DEFAULT_BUFFER_SIZE - 1 /* -1 for NUL byte */, - read_request_finalizer); + return client_read(request, request->helper->buffer, + DEFAULT_BUFFER_SIZE - 1 /* -1 for NUL byte */, + read_request_finalizer); } static enum lwan_read_finalizer -post_data_finalizer(size_t total_read, - size_t buffer_size, +post_data_finalizer(size_t buffer_size, struct lwan_request *request, int n_packets) { struct lwan_request_parser_helper *helper = request->helper; - if (buffer_size == total_read) + if (buffer_size == helper->buffer->len) return FINALIZER_DONE; /* For POST requests, the body can be larger, and due to small MTUs on * most ethernet connections, responding with a timeout solely based on * number of packets doesn't work. Use keepalive timeout instead. */ if (UNLIKELY(time(NULL) > helper->error_when_time)) - return FINALIZER_ERROR_TIMEOUT; + return FINALIZER_TIMEOUT; /* In addition to time, also estimate the number of packets based on an * usual MTU value and the request body size. */ if (UNLIKELY(n_packets > helper->error_when_n_packets)) - return FINALIZER_ERROR_TIMEOUT; + return FINALIZER_TIMEOUT; return FINALIZER_TRY_AGAIN; } @@ -1151,10 +1142,8 @@ static enum lwan_http_status read_post_data(struct lwan_request *request) helper->error_when_time = time(NULL) + config->keep_alive_timeout; helper->error_when_n_packets = lwan_calculate_n_packets(post_data_size); - struct lwan_value buffer = {.value = new_buffer, - .len = post_data_size}; - return read_from_request_socket(request, &buffer, buffer.len, - post_data_finalizer); + struct lwan_value buffer = {.value = new_buffer}; + return client_read(request, &buffer, post_data_size, post_data_finalizer); } static char * @@ -1657,7 +1646,7 @@ __attribute__((used)) int fuzz_parse_http_request(const uint8_t *data, FINALIZER_DONE) return 0; - /* read_from_request_socket() NUL-terminates the string */ + /* client_read() NUL-terminates the string */ data_copy[length - 1] = '\0'; if (parse_http_request(&request) == HTTP_OK) { From cd55b2b49cf7c50b8932a924a6afa2525b7f430e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 17 Jul 2020 08:38:00 -0700 Subject: [PATCH 1573/2505] Fix reading of POST request bodies when needing second read() When issuing a second read() call to finish reading the request body, a buffer is created in the stack and passed to client_read(). The finalizer function called by client_read() would assume that the helper->buffer struct would be the same as the one created in the frame that called client_read(). Since the number of bytes read would never coincide with the amount of bytes we wanted to read, the whole operation would time out. Re-introduce the buffer parameter to the finalizer function, so that we can be sure that the buffer it operates on is the one we're interested in. This is fine as this a internal-only API and changing won't break anybody. (Hopefully.) These tests were not passing locally but were failing in the CI system. --- src/lib/lwan-request.c | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index e0ddf1af7..2b559c76b 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -773,9 +773,10 @@ static void save_to_corpus_for_fuzzing(struct lwan_value buffer) static enum lwan_http_status client_read(struct lwan_request *request, struct lwan_value *buffer, - const size_t buffer_size, - enum lwan_read_finalizer (*finalizer)(size_t buffer_size, - struct lwan_request *request, + const size_t want_to_read, + enum lwan_read_finalizer (*finalizer)(const struct lwan_value *buffer, + size_t want_to_read, + const struct lwan_request *request, int n_packets)) { struct lwan_request_parser_helper *helper = request->helper; @@ -799,7 +800,7 @@ client_read(struct lwan_request *request, } for (buffer->len = 0;; n_packets++) { - size_t to_read = (size_t)(buffer_size - buffer->len); + size_t to_read = (size_t)(want_to_read - buffer->len); if (UNLIKELY(to_read == 0)) return HTTP_TOO_LARGE; @@ -828,7 +829,7 @@ client_read(struct lwan_request *request, buffer->len += (size_t)n; try_to_finalize: - switch (finalizer(buffer_size, request, n_packets)) { + switch (finalizer(buffer, want_to_read, request, n_packets)) { case FINALIZER_DONE: buffer->value[buffer->len] = '\0'; #if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) @@ -850,14 +851,14 @@ client_read(struct lwan_request *request, } static enum lwan_read_finalizer -read_request_finalizer(size_t buffer_size __attribute__((unused)), - struct lwan_request *request, +read_request_finalizer(const struct lwan_value *buffer, + size_t want_to_read __attribute__((unused)), + const struct lwan_request *request, int n_packets) { static const size_t min_proxied_request_size = MIN_REQUEST_SIZE + sizeof(struct proxy_header_v2); struct lwan_request_parser_helper *helper = request->helper; - const struct lwan_value *buffer = helper->buffer; if (!(request->conn->flags & CONN_CORK)) { /* CONN_CORK is set on pipelined requests. For non-pipelined requests, @@ -916,13 +917,14 @@ read_request(struct lwan_request *request) } static enum lwan_read_finalizer -post_data_finalizer(size_t buffer_size, - struct lwan_request *request, +post_data_finalizer(const struct lwan_value *buffer, + size_t want_to_read, + const struct lwan_request *request, int n_packets) { - struct lwan_request_parser_helper *helper = request->helper; + const struct lwan_request_parser_helper *helper = request->helper; - if (buffer_size == helper->buffer->len) + if (want_to_read == buffer->len) return FINALIZER_DONE; /* For POST requests, the body can be larger, and due to small MTUs on From d353bed5f47f552de173b5da0e9462efa866a262 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 18 Jul 2020 09:28:55 -0700 Subject: [PATCH 1574/2505] Slight cleanup in prepare_for_response() when calling auth functions --- src/lib/lwan-http-authorize.h | 8 ++++++++ src/lib/lwan-request.c | 8 ++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-http-authorize.h b/src/lib/lwan-http-authorize.h index d571aac2e..b3a5f815c 100644 --- a/src/lib/lwan-http-authorize.h +++ b/src/lib/lwan-http-authorize.h @@ -28,3 +28,11 @@ void lwan_http_authorize_shutdown(void); bool lwan_http_authorize(struct lwan_request *request, const char *realm, const char *password_file); + +static inline bool +lwan_http_authorize_urlmap(struct lwan_request *request, + const struct lwan_url_map *url_map) +{ + return lwan_http_authorize(request, url_map->authorization.realm, + url_map->authorization.password_file); +} diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 2b559c76b..0fe51298c 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1275,10 +1275,10 @@ static enum lwan_http_status prepare_for_response(struct lwan_url_map *url_map, request->url.value += url_map->prefix_len; request->url.len -= url_map->prefix_len; - if (UNLIKELY(url_map->flags & HANDLER_MUST_AUTHORIZE && - !lwan_http_authorize(request, url_map->authorization.realm, - url_map->authorization.password_file))) - return HTTP_NOT_AUTHORIZED; + if (UNLIKELY(url_map->flags & HANDLER_MUST_AUTHORIZE)) { + if (!lwan_http_authorize_urlmap(request, url_map)) + return HTTP_NOT_AUTHORIZED; + } while (*request->url.value == '/' && request->url.len > 0) { request->url.value++; From 4d0086258802a3fb05bfbd3807ec0794d8e26075 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 18 Jul 2020 09:29:36 -0700 Subject: [PATCH 1575/2505] Make it easier to publish printf-formatted strings in pubsub --- src/lib/lwan-pubsub.c | 37 ++++++++++++++++++++++++++++++++---- src/lib/lwan-pubsub.h | 3 +++ src/samples/websocket/main.c | 20 ++++++++----------- 3 files changed, 44 insertions(+), 16 deletions(-) diff --git a/src/lib/lwan-pubsub.c b/src/lib/lwan-pubsub.c index 926ffedb8..833a276c4 100644 --- a/src/lib/lwan-pubsub.c +++ b/src/lib/lwan-pubsub.c @@ -18,6 +18,9 @@ * USA. */ +#define _GNU_SOURCE +#include +#include #include #include "list.h" @@ -175,9 +178,10 @@ void lwan_pubsub_msg_done(struct lwan_pubsub_msg *msg) } } -bool lwan_pubsub_publish(struct lwan_pubsub_topic *topic, - const void *contents, - size_t len) +static bool lwan_pubsub_publish_full(struct lwan_pubsub_topic *topic, + const void *contents, + size_t len, + bool want_memdup) { struct lwan_pubsub_msg *msg = calloc(1, sizeof(*msg)); struct lwan_pubsub_subscriber *sub; @@ -191,7 +195,7 @@ bool lwan_pubsub_publish(struct lwan_pubsub_topic *topic, msg->refcount = 1; msg->value = (struct lwan_value){ - .value = my_memdup(contents, len), + .value = want_memdup ? my_memdup(contents, len) : (void*)contents, .len = len, }; if (!msg->value.value) { @@ -217,6 +221,31 @@ bool lwan_pubsub_publish(struct lwan_pubsub_topic *topic, return true; } +bool lwan_pubsub_publish(struct lwan_pubsub_topic *topic, + const void *contents, + size_t len) +{ + return lwan_pubsub_publish_full(topic, contents, len, true); +} + +bool lwan_pubsub_publishf(struct lwan_pubsub_topic *topic, + const char *format, + ...) +{ + char *msg; + int len; + va_list ap; + + va_start(ap, format); + len = vasprintf(&msg, format, ap); + va_end(ap); + + if (len < 0) + return false; + + return lwan_pubsub_publish_full(topic, msg, (size_t)len, false); +} + struct lwan_pubsub_subscriber * lwan_pubsub_subscribe(struct lwan_pubsub_topic *topic) { diff --git a/src/lib/lwan-pubsub.h b/src/lib/lwan-pubsub.h index e9d4dbdad..5f8c1e658 100644 --- a/src/lib/lwan-pubsub.h +++ b/src/lib/lwan-pubsub.h @@ -32,6 +32,9 @@ void lwan_pubsub_free_topic(struct lwan_pubsub_topic *topic); bool lwan_pubsub_publish(struct lwan_pubsub_topic *topic, const void *contents, size_t len); +bool lwan_pubsub_publishf(struct lwan_pubsub_topic *topic, + const char *format, + ...) __attribute__((format(printf, 2, 3))); struct lwan_pubsub_subscriber * lwan_pubsub_subscribe(struct lwan_pubsub_topic *topic); diff --git a/src/samples/websocket/main.c b/src/samples/websocket/main.c index 935238da2..e276a04d3 100644 --- a/src/samples/websocket/main.c +++ b/src/samples/websocket/main.c @@ -125,7 +125,6 @@ LWAN_HANDLER(ws_chat) { struct lwan_pubsub_subscriber *sub; struct lwan_pubsub_msg *msg; - struct lwan_strbuf tmp_msg = LWAN_STRBUF_STATIC_INIT; enum lwan_http_status status; static int total_user_count; int user_id; @@ -141,13 +140,13 @@ LWAN_HANDLER(ws_chat) user_id = ATOMIC_INC(total_user_count); - lwan_strbuf_printf(response->buffer, "*** Welcome to the chat, User%d!\n", user_id); + lwan_strbuf_printf(response->buffer, "*** Welcome to the chat, User%d!\n", + user_id); lwan_response_websocket_write(request); - coro_defer2(request->conn->coro, pub_depart_message, chat, (void *)(intptr_t)user_id); - lwan_strbuf_printf(&tmp_msg, "*** User%d has joined the chat!\n", user_id); - lwan_pubsub_publish(chat, lwan_strbuf_get_buffer(&tmp_msg), - lwan_strbuf_get_length(&tmp_msg)); + coro_defer2(request->conn->coro, pub_depart_message, chat, + (void *)(intptr_t)user_id); + lwan_pubsub_publishf(chat, "*** User%d has joined the chat!\n", user_id); while (true) { switch (lwan_response_websocket_read(request)) { @@ -173,12 +172,9 @@ LWAN_HANDLER(ws_chat) break; case 0: /* We got something! Copy it to echo it back */ - lwan_strbuf_printf(&tmp_msg, "User%d: %.*s\n", - user_id, - (int)lwan_strbuf_get_length(response->buffer), - lwan_strbuf_get_buffer(response->buffer)); - lwan_pubsub_publish(chat, lwan_strbuf_get_buffer(&tmp_msg), - lwan_strbuf_get_length(&tmp_msg)); + lwan_pubsub_publishf(chat, "User%d: %.*s\n", user_id, + (int)lwan_strbuf_get_length(response->buffer), + lwan_strbuf_get_buffer(response->buffer)); break; } } From 1db0694ee77e224ae9720ffa82de439d123ffc1c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 18 Jul 2020 17:13:19 -0700 Subject: [PATCH 1576/2505] Discard request body if handler is not expecting it --- src/lib/lwan-request.c | 109 +++++++++++++++++++++++++++-------------- 1 file changed, 72 insertions(+), 37 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 0fe51298c..77c46cefb 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1095,57 +1095,101 @@ alloc_post_buffer(struct coro *coro, size_t size, bool allow_file) return ptr; } -static enum lwan_http_status read_post_data(struct lwan_request *request) +static enum lwan_http_status +get_remaining_post_data_length(struct lwan_request *request, + const size_t max_size, + size_t *total, + size_t *have) { struct lwan_request_parser_helper *helper = request->helper; - /* Holy indirection, Batman! */ - struct lwan_config *config = &request->conn->thread->lwan->config; - const size_t max_post_data_size = config->max_post_data_size; - char *new_buffer; long parsed_size; if (UNLIKELY(!helper->content_length.value)) return HTTP_BAD_REQUEST; + parsed_size = parse_long(helper->content_length.value, -1); if (UNLIKELY(parsed_size < 0)) return HTTP_BAD_REQUEST; - if (UNLIKELY(parsed_size >= (long)max_post_data_size)) + if (UNLIKELY(parsed_size >= (long)max_size)) return HTTP_TOO_LARGE; - size_t post_data_size = (size_t)parsed_size; - size_t have; - if (!helper->next_request) { - have = 0; - } else { - char *buffer_end = helper->buffer->value + helper->buffer->len; - have = (size_t)(ptrdiff_t)(buffer_end - helper->next_request); + *total = (size_t)parsed_size; - if (have >= post_data_size) { - helper->post_data.value = helper->next_request; - helper->post_data.len = post_data_size; - helper->next_request += post_data_size; - return HTTP_OK; - } + if (!helper->next_request) { + *have = 0; + return HTTP_PARTIAL_CONTENT; } - new_buffer = alloc_post_buffer(request->conn->coro, post_data_size + 1, + char *buffer_end = helper->buffer->value + helper->buffer->len; + + *have = (size_t)(ptrdiff_t)(buffer_end - helper->next_request); + + if (*have < *total) + return HTTP_PARTIAL_CONTENT; + + helper->post_data.value = helper->next_request; + helper->post_data.len = *total; + helper->next_request += *total; + return HTTP_OK; +} + +static enum lwan_http_status read_post_data(struct lwan_request *request) +{ + /* Holy indirection, Batman! */ + const struct lwan_config *config = &request->conn->thread->lwan->config; + struct lwan_request_parser_helper *helper = request->helper; + enum lwan_http_status status; + size_t total, have; + char *new_buffer; + + status = get_remaining_post_data_length(request, config->max_post_data_size, + &total, &have); + if (status != HTTP_PARTIAL_CONTENT) + return status; + + new_buffer = alloc_post_buffer(request->conn->coro, total + 1, config->allow_post_temp_file); if (UNLIKELY(!new_buffer)) return HTTP_INTERNAL_ERROR; helper->post_data.value = new_buffer; - helper->post_data.len = post_data_size; + helper->post_data.len = total; if (have) { new_buffer = mempcpy(new_buffer, helper->next_request, have); - post_data_size -= have; + total -= have; } helper->next_request = NULL; helper->error_when_time = time(NULL) + config->keep_alive_timeout; - helper->error_when_n_packets = lwan_calculate_n_packets(post_data_size); + helper->error_when_n_packets = lwan_calculate_n_packets(total); struct lwan_value buffer = {.value = new_buffer}; - return client_read(request, &buffer, post_data_size, post_data_finalizer); + return client_read(request, &buffer, total, post_data_finalizer); +} + +static enum lwan_http_status discard_post_data(struct lwan_request *request) +{ + /* Holy indirection, Batman! */ + const struct lwan_config *config = &request->conn->thread->lwan->config; + enum lwan_http_status status; + size_t total, have; + + status = get_remaining_post_data_length(request, config->max_post_data_size, + &total, &have); + if (status != HTTP_PARTIAL_CONTENT) + return status; + + /* FIXME: set/use error_when_*? */ + total -= have; + while (total) { + char buffer[DEFAULT_BUFFER_SIZE]; + ssize_t r; + + r = lwan_recv(request, buffer, LWAN_MIN(sizeof(buffer), total), 0); + total -= (size_t)r; + } + + return HTTP_OK; } static char * @@ -1286,20 +1330,11 @@ static enum lwan_http_status prepare_for_response(struct lwan_url_map *url_map, } if (lwan_request_get_method(request) == REQUEST_METHOD_POST) { - enum lwan_http_status status; - - if (!(url_map->flags & HANDLER_HAS_POST_DATA)) { - /* FIXME: Discard POST data here? If a POST request is sent - * to a handler that is not supposed to handle a POST request, - * the next request in the pipeline will fail because the - * body of the previous request will be used as the next - * request itself. */ - return HTTP_NOT_ALLOWED; - } + if (url_map->flags & HANDLER_HAS_POST_DATA) + return read_post_data(request); - status = read_post_data(request); - if (UNLIKELY(status != HTTP_OK)) - return status; + enum lwan_http_status status = discard_post_data(request); + return (status == HTTP_OK) ? HTTP_NOT_ALLOWED : status; } return HTTP_OK; From de365d23f262c2406fab20c0eede740ae2cb4161 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 18 Jul 2020 17:13:36 -0700 Subject: [PATCH 1577/2505] Abort early if Content-Length is zero when determining body size --- src/lib/lwan-request.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 77c46cefb..d09e9df84 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1112,6 +1112,8 @@ get_remaining_post_data_length(struct lwan_request *request, return HTTP_BAD_REQUEST; if (UNLIKELY(parsed_size >= (long)max_size)) return HTTP_TOO_LARGE; + if (UNLIKELY(!parsed_size)) + return HTTP_OK; *total = (size_t)parsed_size; From 3b6561da94920a8010120a11ea4d2610c4f2073d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 18 Jul 2020 17:14:15 -0700 Subject: [PATCH 1578/2505] Little tweak to ensure lwan.h can be included from C++ programs --- src/lib/lwan.c | 6 ++++++ src/lib/lwan.h | 6 ------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 8e398e61e..e98b71e3e 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -43,6 +43,12 @@ #include "lwan-lua.h" #endif +/* Ideally, this would check if all items in enum lwan_request_flags, + * when bitwise-or'd together, would not have have any bit set that + * is also set in REQUEST_METHOD_MASK. */ +static_assert(REQUEST_ACCEPT_DEFLATE > REQUEST_METHOD_MASK, + "enough bits to store request methods"); + /* See detect_fastest_monotonic_clock() */ clockid_t monotonic_clock_id = CLOCK_MONOTONIC; diff --git a/src/lib/lwan.h b/src/lib/lwan.h index bffb98cb9..bdbb3034d 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -260,12 +260,6 @@ enum lwan_request_flags { #undef SELECT_MASK #undef GENERATE_ENUM_ITEM -/* Ideally, this would check if all items in enum lwan_request_flags, - * when bitwise-or'd together, would not have have any bit set that - * is also set in REQUEST_METHOD_MASK. */ -static_assert(REQUEST_ACCEPT_DEFLATE > REQUEST_METHOD_MASK, - "enough bits to store request methods"); - enum lwan_connection_flags { CONN_MASK = -1, From 391674a56b6a48006cac17c24c8ae5cbcdff40e3 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 19 Jul 2020 08:26:29 -0700 Subject: [PATCH 1579/2505] Try fixing fuzzing build More details: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=24258 --- src/lib/lwan-request.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index d09e9df84..59a985c24 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1678,10 +1678,14 @@ __attribute__((used)) int fuzz_parse_http_request(const uint8_t *data, .flags = REQUEST_ALLOW_PROXY_REQS, .proxy = &proxy, }; + struct lwan_value buffer = { + .value = data_copy, + .len = length, + }; /* If the finalizer isn't happy with a request, there's no point in * going any further with parsing it. */ - if (read_request_finalizer(length, sizeof(data_copy), &request, 1) != + if (read_request_finalizer(&buffer, sizeof(data_copy), &request, 1) != FINALIZER_DONE) return 0; From 03e51492605d97ffb8dffd70c600f8c540a9ef19 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 28 Jul 2020 07:41:48 -0700 Subject: [PATCH 1580/2505] Slight simplification in lwan_strbuf_init_with_size() --- src/lib/lwan-strbuf.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-strbuf.c b/src/lib/lwan-strbuf.c index 184959ce5..2c3f76654 100644 --- a/src/lib/lwan-strbuf.c +++ b/src/lib/lwan-strbuf.c @@ -81,11 +81,9 @@ bool lwan_strbuf_init_with_size(struct lwan_strbuf *s, size_t size) if (UNLIKELY(!s)) return false; - if (!size) { - *s = LWAN_STRBUF_STATIC_INIT; - } else { - memset(s, 0, sizeof(*s)); + *s = LWAN_STRBUF_STATIC_INIT; + if (size) { if (UNLIKELY(!grow_buffer_if_needed(s, size))) return false; From a00a00dc40a79fe2f307acd2b4fa07b3e1f3a8f1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 1 Aug 2020 08:44:29 -0700 Subject: [PATCH 1581/2505] Fix crash when handler returns 401 without setting additional headers Closes #286. --- src/lib/lwan-response.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 5404d2f66..9dddbdad8 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -314,7 +314,7 @@ size_t lwan_prepare_response_header_full( APPEND_CHAR_NOCHECK(' '); APPEND_STRING(header->value); } - } else if (UNLIKELY(status == HTTP_NOT_AUTHORIZED)) { + } else if (UNLIKELY(status == HTTP_NOT_AUTHORIZED) && additional_headers) { const struct lwan_key_value *header; for (header = additional_headers; header->key; header++) { From 717f6df4e921de784ca5cc358b6011b9d770472a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 4 Aug 2020 08:44:12 -0700 Subject: [PATCH 1582/2505] Try using libucontext for context switching if found --- CMakeLists.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 714b51907..5b2410c18 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,6 +76,20 @@ if (ZSTD_FOUND) set(HAVE_ZSTD 1) endif () +if (CMAKE_SYSTEM_PROCESSOR MATCHES "^arm") + message(STATUS "${CMAKE_SYSTEM_PROCESSOR} processor detected, looking for libucontext") + + find_library(LIBUCONTEXT_LIBRARY NAMES libucontext.a) + if (LIBUCONTEXT_LIBRARY) + message(STATUS "libucontext found, using it for context switching routines") + list(APPEND ADDITIONAL_LIBRARIES "${LIBUCONTEXT_LIBRARY}") + else () + message(STATUS "Trying to use libc context switching; this might not work!") + endif () +else () + message(STATUS "Using built-in context switching routines for ${CMAKE_SYSTEM_PROCESSOR} processors") +endif () + option(USE_ALTERNATIVE_MALLOC "Use alternative malloc implementations" "OFF") if (USE_ALTERNATIVE_MALLOC) unset(ALTMALLOC_LIBS CACHE) From a07f949141f38e46c09561b30dfb0cb31b70be35 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 5 Aug 2020 18:43:38 -0700 Subject: [PATCH 1583/2505] Fix reading of request bodies when a second client_read() is needed Tentative fix for #287. --- src/lib/lwan-request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 59a985c24..2823e4b06 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1165,7 +1165,7 @@ static enum lwan_http_status read_post_data(struct lwan_request *request) helper->error_when_time = time(NULL) + config->keep_alive_timeout; helper->error_when_n_packets = lwan_calculate_n_packets(total); - struct lwan_value buffer = {.value = new_buffer}; + struct lwan_value buffer = {.value = new_buffer, .len = total}; return client_read(request, &buffer, total, post_data_finalizer); } From c6840b980874205aceae2ba6f9d8ff01f8e1a3bb Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 6 Sep 2020 08:59:50 -0700 Subject: [PATCH 1584/2505] Avoid calling list_empty() twice in timeouts_get() --- src/lib/timeout.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/lib/timeout.c b/src/lib/timeout.c index 7012b4829..5f82d0e03 100644 --- a/src/lib/timeout.c +++ b/src/lib/timeout.c @@ -402,14 +402,13 @@ timeout_t timeouts_timeout(struct timeouts *T) struct timeout *timeouts_get(struct timeouts *T) { - if (!list_empty(&T->expired)) { - struct timeout *to = list_top(&T->expired, struct timeout, tqe); + struct timeout *to = list_top(&T->expired, struct timeout, tqe); - list_del_from(&T->expired, &to->tqe); - to->pending = NULL; - - return to; - } else { + if (!to) return NULL; - } + + list_del_from(&T->expired, &to->tqe); + to->pending = NULL; + + return to; } From e3d6daeb0367adb01eca62e344f636bab36cbce7 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 6 Sep 2020 09:03:57 -0700 Subject: [PATCH 1585/2505] Bail if the temporary hash table can't be allocated --- src/lib/lwan.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index e98b71e3e..6227ba90d 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -321,6 +321,9 @@ static void parse_listener_prefix(struct config *c, char *prefix = strdupa(l->value); struct config *isolated; + if (!hash) + lwan_status_critical("Could not allocate hash table"); + isolated = config_isolate_section(c, l); if (!isolated) { config_error(c, "Could not isolate configuration file"); From c0df9eb3d78f8a4355b374abf9691ad58ace8587 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 8 Sep 2020 20:54:36 -0700 Subject: [PATCH 1586/2505] Tentative fix for a crash where number of online CPUs < physical CPUs --- src/lib/lwan-thread.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index b1ee42daa..0e844767a 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -617,10 +617,27 @@ static bool read_cpu_topology(struct lwan *l, uint32_t siblings[]) __builtin_unreachable(); } - fclose(sib); } + /* Some systems may lie about the number of online CPUs (obtainable with + * sysconf()), but don't filter out the CPU topology information from + * sysfs, which might reference CPU numbers higher than the amount + * obtained with sysconf(). */ + for (unsigned int i = 0; i < l->n_cpus; i++) { + if (siblings[i] == 0xbebacafe) { + lwan_status_warning("Could not determine sibling for CPU %d", i); + return false; + } + + if (siblings[i] > l->n_cpus) { + lwan_status_warning("CPU topology information says CPU %d exists, " + "but max CPUs is %d. Is Lwan running in a " + "container?", siblings[i], l->n_cpus); + return false; + } + } + return true; } @@ -651,6 +668,9 @@ topology_to_schedtbl(struct lwan *l, uint32_t schedtbl[], uint32_t n_threads) { uint32_t *siblings = alloca(l->n_cpus * sizeof(uint32_t)); + for (uint32_t i = 0; i < l->n_cpus; i++) + siblings[i] = 0xbebacafe; + if (!read_cpu_topology(l, siblings)) { for (uint32_t i = 0; i < n_threads; i++) schedtbl[i] = (i / 2) % l->thread.count; From 7e2119e85d8a807e307124f74a35942549256078 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 14 Sep 2020 20:56:58 -0700 Subject: [PATCH 1587/2505] Don't assume that online CPUs start at 0 --- src/lib/lwan-thread.c | 71 +++++++++++++++++++++++++------------------ src/lib/lwan.c | 32 ++++++++++++------- src/lib/lwan.h | 3 +- 3 files changed, 65 insertions(+), 41 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 0e844767a..afec69a9f 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -584,7 +584,10 @@ static bool read_cpu_topology(struct lwan *l, uint32_t siblings[]) { char path[PATH_MAX]; - for (unsigned int i = 0; i < l->n_cpus; i++) { + for (uint32_t i = 0; i < l->available_cpus; i++) + siblings[i] = 0xbebacafe; + + for (unsigned int i = 0; i < l->available_cpus; i++) { FILE *sib; uint32_t id, sibling; char separator; @@ -620,20 +623,23 @@ static bool read_cpu_topology(struct lwan *l, uint32_t siblings[]) fclose(sib); } - /* Some systems may lie about the number of online CPUs (obtainable with - * sysconf()), but don't filter out the CPU topology information from - * sysfs, which might reference CPU numbers higher than the amount - * obtained with sysconf(). */ - for (unsigned int i = 0; i < l->n_cpus; i++) { + /* Perform a sanity check here, as some systems seem to filter out the + * result of sysconf() to obtain the number of configured and online + * CPUs but don't bother changing what's available through sysfs as far + * as the CPU topology information goes. It's better to fall back to a + * possibly non-optimal setup than just crash during startup while + * trying to perform an out-of-bounds array access. */ + for (unsigned int i = 0; i < l->available_cpus; i++) { if (siblings[i] == 0xbebacafe) { lwan_status_warning("Could not determine sibling for CPU %d", i); return false; } - if (siblings[i] > l->n_cpus) { - lwan_status_warning("CPU topology information says CPU %d exists, " - "but max CPUs is %d. Is Lwan running in a " - "container?", siblings[i], l->n_cpus); + if (siblings[i] >= l->available_cpus) { + lwan_status_warning("CPU information topology says CPU %d exists, " + "but max available CPUs is %d (online CPUs: %d). " + "Is Lwan running in a (broken) container?", + siblings[i], l->available_cpus, l->online_cpus); return false; } } @@ -644,13 +650,13 @@ static bool read_cpu_topology(struct lwan *l, uint32_t siblings[]) static void siblings_to_schedtbl(struct lwan *l, uint32_t siblings[], uint32_t schedtbl[]) { - int *seen = alloca(l->n_cpus * sizeof(int)); - int n_schedtbl = 0; + int *seen = alloca(l->available_cpus * sizeof(int)); + unsigned int n_schedtbl = 0; - for (uint32_t i = 0; i < l->n_cpus; i++) + for (uint32_t i = 0; i < l->available_cpus; i++) seen[i] = -1; - for (uint32_t i = 0; i < l->n_cpus; i++) { + for (uint32_t i = 0; i < l->available_cpus; i++) { if (seen[siblings[i]] < 0) { seen[siblings[i]] = (int)i; } else { @@ -659,29 +665,28 @@ siblings_to_schedtbl(struct lwan *l, uint32_t siblings[], uint32_t schedtbl[]) } } - if (!n_schedtbl) - memcpy(schedtbl, seen, l->n_cpus * sizeof(int)); + if (n_schedtbl != l->available_cpus) + memcpy(schedtbl, seen, l->available_cpus * sizeof(int)); } -static void +static bool topology_to_schedtbl(struct lwan *l, uint32_t schedtbl[], uint32_t n_threads) { - uint32_t *siblings = alloca(l->n_cpus * sizeof(uint32_t)); - - for (uint32_t i = 0; i < l->n_cpus; i++) - siblings[i] = 0xbebacafe; + uint32_t *siblings = alloca(l->available_cpus * sizeof(uint32_t)); - if (!read_cpu_topology(l, siblings)) { - for (uint32_t i = 0; i < n_threads; i++) - schedtbl[i] = (i / 2) % l->thread.count; - } else { - uint32_t *affinity = alloca(l->n_cpus * sizeof(uint32_t)); + if (read_cpu_topology(l, siblings)) { + uint32_t *affinity = alloca(l->available_cpus * sizeof(uint32_t)); siblings_to_schedtbl(l, siblings, affinity); for (uint32_t i = 0; i < n_threads; i++) - schedtbl[i] = affinity[i % l->n_cpus]; + schedtbl[i] = affinity[i % l->available_cpus]; + return true; } + + for (uint32_t i = 0; i < n_threads; i++) + schedtbl[i] = (i / 2) % l->thread.count; + return false; } static void @@ -735,6 +740,11 @@ void lwan_thread_init(struct lwan *l) #ifdef __x86_64__ static_assert(sizeof(struct lwan_connection) == 32, "Two connections per cache line"); + + lwan_status_debug("%d CPUs of %d are online. " + "Reading topology to pre-schedule clients", + l->online_cpus, l->available_cpus); + /* * Pre-schedule each file descriptor, to reduce some operations in the * fast path. @@ -747,10 +757,13 @@ void lwan_thread_init(struct lwan *l) uint32_t n_threads = (uint32_t)lwan_nextpow2((size_t)((l->thread.count - 1) * 2)); uint32_t *schedtbl = alloca(n_threads * sizeof(uint32_t)); - topology_to_schedtbl(l, schedtbl, n_threads); + bool adj_affinity = topology_to_schedtbl(l, schedtbl, n_threads); n_threads--; /* Transform count into mask for AND below */ - adjust_threads_affinity(l, schedtbl, n_threads); + + if (adj_affinity) + adjust_threads_affinity(l, schedtbl, n_threads); + for (unsigned int i = 0; i < total_conns; i++) l->conns[i].thread = &l->thread.threads[schedtbl[i & n_threads]]; #else diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 6227ba90d..d08014d84 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -645,17 +645,26 @@ static void allocate_connections(struct lwan *l, size_t max_open_files) memset(l->conns, 0, sz); } -static unsigned int get_number_of_cpus(void) +static void get_number_of_cpus(struct lwan *l) { long n_online_cpus = sysconf(_SC_NPROCESSORS_ONLN); + long n_available_cpus = sysconf(_SC_NPROCESSORS_CONF); - if (UNLIKELY(n_online_cpus < 0)) { + if (n_online_cpus < 0) { lwan_status_warning( "Could not get number of online CPUs, assuming 1 CPU"); - return 1; + n_online_cpus = 1; } - return (unsigned int)n_online_cpus; + if (n_available_cpus < 0) { + lwan_status_warning( + "Could not get number of available CPUs, assuming %ld CPUs", + n_online_cpus); + n_available_cpus = 1; + } + + l->online_cpus = (unsigned int)n_online_cpus; + l->available_cpus = (unsigned int)n_available_cpus; } void lwan_init(struct lwan *l) { lwan_init_with_config(l, &default_config); } @@ -698,17 +707,18 @@ void lwan_init_with_config(struct lwan *l, const struct lwan_config *config) /* Continue initialization as normal. */ lwan_status_debug("Initializing lwan web server"); - l->n_cpus = get_number_of_cpus(); + get_number_of_cpus(l); if (!l->config.n_threads) { - l->thread.count = l->n_cpus; + l->thread.count = l->online_cpus; if (l->thread.count == 1) l->thread.count = 2; - } else if (l->config.n_threads > 3 * l->n_cpus) { - l->thread.count = l->n_cpus * 3; + } else if (l->config.n_threads > 3 * l->online_cpus) { + l->thread.count = l->online_cpus * 3; - lwan_status_warning("%d threads requested, but only %d online CPUs; " - "capping to %d threads", - l->config.n_threads, l->n_cpus, 3 * l->n_cpus); + lwan_status_warning("%d threads requested, but only %d online CPUs " + "(out of %d configured CPUs); capping to %d threads", + l->config.n_threads, l->online_cpus, l->available_cpus, + 3 * l->online_cpus); } else if (l->config.n_threads > 255) { l->thread.count = 256; diff --git a/src/lib/lwan.h b/src/lib/lwan.h index bdbb3034d..234c0806d 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -488,7 +488,8 @@ struct lwan { int main_socket; - unsigned int n_cpus; + unsigned int online_cpus; + unsigned int available_cpus; }; void lwan_set_url_map(struct lwan *l, const struct lwan_url_map *map); From 4a2b39276082378996ccf156a0e791aefc4a4b29 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 14 Sep 2020 21:14:41 -0700 Subject: [PATCH 1588/2505] Fix build on non-Linux x86-64 systems --- src/lib/lwan-thread.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index afec69a9f..dff615064 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -704,11 +704,12 @@ adjust_threads_affinity(struct lwan *l, uint32_t *schedtbl, uint32_t mask) } } #elif defined(__x86_64__) -static void +static bool topology_to_schedtbl(struct lwan *l, uint32_t schedtbl[], uint32_t n_threads) { for (uint32_t i = 0; i < n_threads; i++) schedtbl[i] = (i / 2) % l->thread.count; + return false; } static void From c43d8d2707f3e45f1774a86b7eb52d737c95fd3f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 27 Sep 2020 12:43:09 -0700 Subject: [PATCH 1589/2505] If getting number of available CPUs fail, use #online_cpus --- src/lib/lwan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index d08014d84..7606970e6 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -660,7 +660,7 @@ static void get_number_of_cpus(struct lwan *l) lwan_status_warning( "Could not get number of available CPUs, assuming %ld CPUs", n_online_cpus); - n_available_cpus = 1; + n_available_cpus = n_online_cpus; } l->online_cpus = (unsigned int)n_online_cpus; From ddb8ff40fe3763d12244ffc0b2428bc7fa43dd7c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 27 Sep 2020 13:52:31 -0700 Subject: [PATCH 1590/2505] Avoid yielding for every request when servicing pipelined requests Improves throughput by ~30%. --- src/lib/lwan-thread.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index dff615064..aa63c3d30 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -146,7 +146,9 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, if (next_request && *next_request) { conn->flags |= CONN_CORK; - coro_yield(coro, CONN_CORO_WANT_WRITE); + + if (!(conn->flags & CONN_EVENTS_WRITE)) + coro_yield(coro, CONN_CORO_WANT_WRITE); } else { conn->flags &= ~CONN_CORK; coro_yield(coro, CONN_CORO_WANT_READ); From eee6de8a059ade13e2ec78c09723cf1a5a14fb53 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 27 Sep 2020 14:22:11 -0700 Subject: [PATCH 1591/2505] Remove AutoFDO profiles These get stale, and without proper tooling to update them regularly, they might hurt more than help. --- CMakeLists.txt | 8 -------- src/gcda/lwan.gcov | Bin 23828 -> 0 bytes src/gcda/techempower.gcov | Bin 21676 -> 0 bytes 3 files changed, 8 deletions(-) delete mode 100644 src/gcda/lwan.gcov delete mode 100644 src/gcda/techempower.gcov diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b2410c18..b13869a5b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -210,14 +210,6 @@ else () message(STATUS "Valgrind headers not found -- disabling valgrind support") endif() -if (EXISTS ${CMAKE_SOURCE_DIR}/src/gcda/lwan.gcov) - enable_c_flag_if_avail(-fauto-profile=${CMAKE_SOURCE_DIR}/src/gcda/lwan.gcov - C_FLAGS_REL HAVE_AUTO_PROFILE) - if (HAVE_AUTO_PROFILE) - message(STATUS "Using AutoFDO profile") - endif () -endif () - enable_c_flag_if_avail(-mtune=native C_FLAGS_REL HAVE_MTUNE_NATIVE) enable_c_flag_if_avail(-march=native C_FLAGS_REL HAVE_MARCH_NATIVE) diff --git a/src/gcda/lwan.gcov b/src/gcda/lwan.gcov deleted file mode 100644 index 58526d83198931ff4deca0c39337b624ccf49ed5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23828 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 W{p&&>&GaXP)?A?1UkUBC)c*qLPBei4 diff --git a/src/gcda/techempower.gcov b/src/gcda/techempower.gcov deleted file mode 100644 index 1c6e27f153e12de22388873bac73a7f89b0a4704..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21676 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 Date: Thu, 1 Oct 2020 10:36:54 -0700 Subject: [PATCH 1592/2505] Enable code scanning workflow https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/enabling-code-scanning-for-a-repository --- .github/workflows/codeql-analysis.yml | 71 +++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 .github/workflows/codeql-analysis.yml diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 000000000..0b7a974d0 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,71 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +name: "CodeQL" + +on: + push: + branches: [master] + pull_request: + # The branches below must be a subset of the branches above + branches: [master] + schedule: + - cron: '0 13 * * 3' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + # Override automatic language detection by changing the below list + # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] + language: ['cpp', 'python'] + # Learn more... + # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + # We must fetch at least the immediate parents so that if this is + # a pull request then we can checkout the head. + fetch-depth: 2 + + # If this run was triggered by a pull request event, then checkout + # the head of the pull request instead of the merge commit. + - run: git checkout HEAD^2 + if: ${{ github.event_name == 'pull_request' }} + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 From b57e2c3eaa6166041248cd69486d9835bb2296e9 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 3 Oct 2020 09:34:10 -0700 Subject: [PATCH 1593/2505] Fix build on 32-bit systems Also provider better validation of WebSockets frame lengths, according to the RFC. Closes #293. --- src/lib/lwan-websocket.c | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index a484b1f5b..7dd6f225b 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -119,17 +119,39 @@ static void send_websocket_pong(struct lwan_request *request, size_t len) static size_t get_frame_length(struct lwan_request *request, uint16_t header) { - size_t len; - - static_assert(sizeof(size_t) == sizeof(uint64_t), "size_t has a sane size"); + uint64_t len; switch (header & 0x7f) { case 0x7e: lwan_recv(request, &len, 2, 0); - return (size_t)ntohs((uint16_t)len); + len = (uint64_t)ntohs((uint16_t)len); + + if (len < 0x7e) { + lwan_status_warning("Can't use 16-bit encoding for frame length of %zu", + len); + coro_yield(request->conn->coro, CONN_CORO_ABORT); + __builtin_unreachable(); + } + + return (size_t)len; case 0x7f: lwan_recv(request, &len, 8, 0); - return (size_t)be64toh(len); + len = be64toh(len); + + if (UNLIKELY(len > SSIZE_MAX)) { + lwan_status_warning("Frame length of %zu won't fit a ssize_t", + len); + coro_yield(request->conn->coro, CONN_CORO_ABORT); + __builtin_unreachable(); + } + if (UNLIKELY(len < 0x7f)) { + lwan_status_warning("Can't use 64-bit encoding for frame length of %zu", + len); + coro_yield(request->conn->coro, CONN_CORO_ABORT); + __builtin_unreachable(); + } + + return (size_t)len; default: return (size_t)(header & 0x7f); } @@ -137,13 +159,13 @@ static size_t get_frame_length(struct lwan_request *request, uint16_t header) static void discard_frame(struct lwan_request *request, uint16_t header) { - uint64_t len = get_frame_length(request, header); + size_t len = get_frame_length(request, header); for (char buffer[128]; len;) len -= (size_t)lwan_recv(request, buffer, sizeof(buffer), 0); } -static void unmask(char *msg, uint64_t msg_len, char mask[static 4]) +static void unmask(char *msg, size_t msg_len, char mask[static 4]) { const uint32_t mask32 = string_as_uint32(mask); char *msg_end = msg + msg_len; From f7f41068d58e784f2f62a3e98e117b1b5d73c8ef Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 3 Oct 2020 09:36:03 -0700 Subject: [PATCH 1594/2505] Reset continuation flag after calling lwan_recv() Otherwise, the ternary operator to choose between no flags or MSG_DONTWAIT won't ever result in no flags being used at all. --- src/lib/lwan-websocket.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index 7dd6f225b..5c3423be5 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -228,6 +228,7 @@ int lwan_response_websocket_read(struct lwan_request *request) if (!lwan_recv(request, &header, sizeof(header), continuation ? 0 : MSG_DONTWAIT)) return EAGAIN; header = htons(header); + continuation = false; if (UNLIKELY(header & 0x7000)) { lwan_status_debug("RSV1...RSV3 has non-zero value %d, aborting", header & 0x7000); @@ -293,10 +294,8 @@ int lwan_response_websocket_read(struct lwan_request *request) lwan_readv(request, vec, N_ELEMENTS(vec)); unmask(msg, frame_len, mask); - if (continuation && !(header & 0x8000)) { - continuation = false; + if (continuation && !(header & 0x8000)) goto next_frame; - } return (request->conn->flags & CONN_IS_WEBSOCKET) ? 0 : ECONNRESET; } From 324400c7d8b70b7a9246353be416169e55fc1913 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 4 Oct 2020 09:39:10 -0700 Subject: [PATCH 1595/2505] Use the reentrant localtime() variant for all clock samples --- src/samples/clock/main.c | 12 +++++++++--- src/samples/clock/pong.c | 4 +++- src/samples/clock/xdaliclock.c | 4 +++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/samples/clock/main.c b/src/samples/clock/main.c index 3d91bf5d3..314b31cda 100644 --- a/src/samples/clock/main.c +++ b/src/samples/clock/main.c @@ -57,6 +57,12 @@ static void destroy_gif(void *data) ge_close_gif(gif); } +struct tm* my_localtime(const time_t *t) +{ + static __thread struct tm result; + return localtime_r(t, &result); +} + LWAN_HANDLER(clock) { static const uint8_t base_offsets[] = {0, 0, 2, 2, 4, 4}; @@ -79,7 +85,7 @@ LWAN_HANDLER(clock) int digit, line, base; curtime = time(NULL); - strftime(digits, sizeof(digits), "%H%M%S", localtime(&curtime)); + strftime(digits, sizeof(digits), "%H%M%S", my_localtime(&curtime)); for (digit = 0; digit < 6; digit++) { int dig = digits[digit] - '0'; @@ -173,7 +179,7 @@ LWAN_HANDLER(blocks) if (curtime != last) { char digits[5]; - strftime(digits, sizeof(digits), "%H%M", localtime(&curtime)); + strftime(digits, sizeof(digits), "%H%M", my_localtime(&curtime)); last = curtime; odd_second = last & 1; @@ -328,7 +334,7 @@ static void setup_timezone(void) char *last_slash = strrchr(tzpath, '/'); if (last_slash && !strcmp(last_slash, "/UTC")) { /* If this system is set up to use UTC, there's no need to - * stat(/etc/localtime) every time localtime() is called like + * stat(/etc/localtime) every time my_localtime() is called like * Glibc likes to do. */ setenv("TZ", ":/etc/localtime", 1); } diff --git a/src/samples/clock/pong.c b/src/samples/clock/pong.c index ca28493ef..b44ebab69 100644 --- a/src/samples/clock/pong.c +++ b/src/samples/clock/pong.c @@ -30,6 +30,8 @@ extern const uint8_t digital_clock_font[10][5]; +struct tm* my_localtime(const time_t *t); + static float rand_float(float scale) { return ((float)rand() / (float)(RAND_MAX)) * scale; @@ -42,7 +44,7 @@ static void pong_time_update(struct pong_time *pong_time) if (cur_time != pong_time->last_time) { char digits[5]; - strftime(digits, sizeof(digits), "%H%M", localtime(&cur_time)); + strftime(digits, sizeof(digits), "%H%M", my_localtime(&cur_time)); for (int i = 0; i < 4; i++) pong_time->time[i] = digits[i] - '0'; diff --git a/src/samples/clock/xdaliclock.c b/src/samples/clock/xdaliclock.c index 51a641081..d0a976e1a 100644 --- a/src/samples/clock/xdaliclock.c +++ b/src/samples/clock/xdaliclock.c @@ -64,6 +64,8 @@ static POS char_height, char_width, colon_width; static int digit_widths[8]; static unsigned int easing[FRAMES_PER_SECOND]; +struct tm* my_localtime(const time_t *t); + static struct frame *frame_mk(int width, int height) { struct frame *fr = malloc(sizeof(struct frame) + @@ -293,7 +295,7 @@ void xdaliclock_update(struct xdaliclock *xdc) { if (xdc->frame >= FRAMES_PER_SECOND) { const time_t now = time(NULL); - const struct tm *tm = localtime(&now); + const struct tm *tm = my_localtime(&now); memcpy(xdc->current_digits, xdc->target_digits, sizeof(xdc->current_digits)); From d6ace9068ae168a7fe60bb3f4476d230c3f62e7a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 4 Oct 2020 09:41:50 -0700 Subject: [PATCH 1596/2505] Don't let `user` and `chroot` settings to be redefined These are copied to the stack using `strdupa()`, so avoid copying them again if they were set before. --- src/lib/lwan-straitjacket.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/lib/lwan-straitjacket.c b/src/lib/lwan-straitjacket.c index 61fe287ae..957116558 100644 --- a/src/lib/lwan-straitjacket.c +++ b/src/lib/lwan-straitjacket.c @@ -237,8 +237,16 @@ void lwan_straitjacket_enforce_from_config(struct config *c) case CONFIG_LINE_TYPE_LINE: /* TODO: limit_syscalls */ if (streq(l->key, "user")) { + if (user_name) { + config_error(c, "`user' already specified"); + return; + } user_name = strdupa(l->value); } else if (streq(l->key, "chroot")) { + if (chroot_path) { + config_error(c, "`chroot' already specified"); + return; + } chroot_path = strdupa(l->value); } else if (streq(l->key, "drop_capabilities")) { drop_capabilities = parse_bool(l->value, true); From 263a09c65a4db1e0028c48d5e88035207b5efba2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 4 Oct 2020 13:59:49 -0700 Subject: [PATCH 1597/2505] WebSockets frame validation for long len should consider short max --- src/lib/lwan-websocket.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index 5c3423be5..8998b09da 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -144,7 +144,7 @@ static size_t get_frame_length(struct lwan_request *request, uint16_t header) coro_yield(request->conn->coro, CONN_CORO_ABORT); __builtin_unreachable(); } - if (UNLIKELY(len < 0x7f)) { + if (UNLIKELY(len <= 0xffff)) { lwan_status_warning("Can't use 64-bit encoding for frame length of %zu", len); coro_yield(request->conn->coro, CONN_CORO_ABORT); From 29583a5ee6150c9550f089c8ae86d721a5fea4b8 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 24 Oct 2020 10:28:00 -0700 Subject: [PATCH 1598/2505] config_fuzzer: Also fuzz bool/long/int/time parsing functions --- src/bin/fuzz/config_fuzzer.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/bin/fuzz/config_fuzzer.cc b/src/bin/fuzz/config_fuzzer.cc index 1753f37d2..4d1a0d494 100644 --- a/src/bin/fuzz/config_fuzzer.cc +++ b/src/bin/fuzz/config_fuzzer.cc @@ -15,6 +15,10 @@ dump(struct config *config, int indent_level) while ((line = config_read_line(config))) { switch (line->type) { case CONFIG_LINE_TYPE_LINE: + LWAN_NO_DISCARD(parse_bool(line->value, false)); + LWAN_NO_DISCARD(parse_long(line->value, 0)); + LWAN_NO_DISCARD(parse_int(line->value, 0)); + LWAN_NO_DISCARD(parse_time_period(line->value, 0)); break; case CONFIG_LINE_TYPE_SECTION_END: From 702c0fb90d91df7518de3a07074cff2f5f754500 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 24 Oct 2020 10:29:04 -0700 Subject: [PATCH 1599/2505] Comment the need for LWAN_NO_DISCARD() --- src/lib/lwan.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 234c0806d..fe19f511f 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -52,9 +52,13 @@ extern "C" { __typeof__(array), __typeof__(&(array)[0]))])) #endif + #define N_ELEMENTS(array) \ (ZERO_IF_IS_ARRAY(array) | sizeof(array) / sizeof(array[0])) +/* This macro is used as an attempt to convince the compiler that it should + * never elide an expression -- for instance, when writing fuzz-test or + * micro-benchmarks. */ #define LWAN_NO_DISCARD(...) \ do { \ __typeof__(__VA_ARGS__) no_discard_ = __VA_ARGS__; \ From 1cb6bce9188fa3368d692a8458450fd94947bf94 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 24 Oct 2020 10:29:35 -0700 Subject: [PATCH 1600/2505] Simplify slightly how messages are published in pub/sub --- src/lib/lwan-pubsub.c | 42 +++++++++++++++++++----------------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/src/lib/lwan-pubsub.c b/src/lib/lwan-pubsub.c index 833a276c4..4cd6716a9 100644 --- a/src/lib/lwan-pubsub.c +++ b/src/lib/lwan-pubsub.c @@ -163,13 +163,6 @@ void lwan_pubsub_free_topic(struct lwan_pubsub_topic *topic) free(topic); } -static void *my_memdup(const void *src, size_t len) -{ - void *dup = malloc(len); - - return dup ? memcpy(dup, src, len) : NULL; -} - void lwan_pubsub_msg_done(struct lwan_pubsub_msg *msg) { if (!ATOMIC_DEC(msg->refcount)) { @@ -178,12 +171,10 @@ void lwan_pubsub_msg_done(struct lwan_pubsub_msg *msg) } } -static bool lwan_pubsub_publish_full(struct lwan_pubsub_topic *topic, - const void *contents, - size_t len, - bool want_memdup) +static bool lwan_pubsub_publish_value(struct lwan_pubsub_topic *topic, + const struct lwan_value value) { - struct lwan_pubsub_msg *msg = calloc(1, sizeof(*msg)); + struct lwan_pubsub_msg *msg = malloc(sizeof(*msg)); struct lwan_pubsub_subscriber *sub; if (!msg) @@ -193,15 +184,7 @@ static bool lwan_pubsub_publish_full(struct lwan_pubsub_topic *topic, * all subscribers. If it drops to 0, it means we didn't publish the * message and we can free it. */ msg->refcount = 1; - - msg->value = (struct lwan_value){ - .value = want_memdup ? my_memdup(contents, len) : (void*)contents, - .len = len, - }; - if (!msg->value.value) { - free(msg); - return false; - } + msg->value = value; pthread_mutex_lock(&topic->lock); list_for_each (&topic->subscribers, sub, subscriber) { @@ -221,11 +204,23 @@ static bool lwan_pubsub_publish_full(struct lwan_pubsub_topic *topic, return true; } +static void *my_memdup(const void *src, size_t len) +{ + void *dup = malloc(len); + + return dup ? memcpy(dup, src, len) : NULL; +} + bool lwan_pubsub_publish(struct lwan_pubsub_topic *topic, const void *contents, size_t len) { - return lwan_pubsub_publish_full(topic, contents, len, true); + const struct lwan_value value = { .value = my_memdup(contents, len), .len = len }; + + if (!value.value) + return false; + + return lwan_pubsub_publish_value(topic, value); } bool lwan_pubsub_publishf(struct lwan_pubsub_topic *topic, @@ -243,7 +238,8 @@ bool lwan_pubsub_publishf(struct lwan_pubsub_topic *topic, if (len < 0) return false; - return lwan_pubsub_publish_full(topic, msg, (size_t)len, false); + const struct lwan_value value = { .value = msg, .len = (size_t)len }; + return lwan_pubsub_publish_value(topic, value); } struct lwan_pubsub_subscriber * From 6b69ccc567eed523e2377974dfe7a8c8d8c56b36 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 24 Oct 2020 10:30:11 -0700 Subject: [PATCH 1601/2505] Make graceful_close() only yield the WANT_READ hint This ensures that all yield points will notify the scheduler that that particular coroutine will be performing read operations, regardless of what it previously did. --- src/lib/lwan-thread.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index aa63c3d30..53ba7dba6 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -79,8 +79,7 @@ static void graceful_close(struct lwan *l, if (r < 0) { switch (errno) { case EAGAIN: - coro_yield(conn->coro, CONN_CORO_WANT_READ); - /* Fallthrough */ + break; case EINTR: continue; default: @@ -88,7 +87,7 @@ static void graceful_close(struct lwan *l, } } - coro_yield(conn->coro, CONN_CORO_YIELD); + coro_yield(conn->coro, CONN_CORO_WANT_READ); } /* close(2) will be called when the coroutine yields with CONN_CORO_ABORT */ From e9fe1271c169ecbdadf02b190981f0324bc9d4b2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 24 Oct 2020 10:33:55 -0700 Subject: [PATCH 1602/2505] It's UB if a function has signature `bool is[a-z]+(...)` --- src/lib/lwan-config.c | 14 +++++++------- src/lib/lwan-template.c | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 75dfedfef..dbf621fde 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -258,7 +258,7 @@ static size_t remaining(struct lexer *lexer) static void *lex_config(struct lexer *lexer); static void *lex_variable(struct lexer *lexer); -static bool isstring(int chr) +static bool is_string(int chr) { return chr && !isspace(chr) && chr != '=' && chr != '#'; } @@ -278,7 +278,7 @@ static void *lex_string(struct lexer *lexer) return lex_variable; } - } while (isstring(chr)); + } while (is_string(chr)); backup(lexer); emit(lexer, LEXEME_STRING); @@ -327,7 +327,7 @@ static void *lex_multiline_string(struct lexer *lexer) return lex_error(lexer, "EOF while scanning multiline string"); } -static bool isvariable(int chr) +static bool is_variable(int chr) { return isalpha(chr) || chr == '_'; } @@ -375,19 +375,19 @@ static void *lex_variable(struct lexer *lexer) return lex_config; } - } while (isvariable(chr)); + } while (is_variable(chr)); return lex_error(lexer, "EOF while scanning for end of variable"); } -static bool iscomment(int chr) +static bool is_comment(int chr) { return chr != '\0' && chr != '\n'; } static void *lex_comment(struct lexer *lexer) { - while (iscomment(next(lexer))) + while (is_comment(next(lexer))) ; backup(lexer); return lex_config; @@ -437,7 +437,7 @@ static void *lex_config(struct lexer *lexer) if (chr == '$' && peek(lexer) == '{') return lex_variable; - if (isstring(chr)) + if (is_string(chr)) return lex_string; return lex_error(lexer, "Invalid character"); diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index 65afb7c32..df9d6c7b7 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -347,14 +347,14 @@ static void *lex_error(struct lexer *lexer, const char *msg, ...) return NULL; } -static bool isident(int ch) +static bool is_ident(int ch) { return isalnum(ch) || ch == '_' || ch == '.' || ch == '/'; } static void *lex_identifier(struct lexer *lexer) { - while (isident(next(lexer))) + while (is_ident(next(lexer))) ; backup(lexer); emit(lexer, LEXEME_IDENTIFIER); @@ -374,7 +374,7 @@ static void *lex_partial(struct lexer *lexer) ignore(lexer); continue; } - if (isident(r)) { + if (is_ident(r)) { backup(lexer); return lex_identifier; } @@ -452,7 +452,7 @@ static void *lex_inside_action(struct lexer *lexer) ignore(lexer); continue; } - if (isident(r)) { + if (is_ident(r)) { backup(lexer); return lex_identifier; } From 20f1ff5b32f3d42274030d4a8c49ee53e1e14fad Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 24 Oct 2020 10:34:45 -0700 Subject: [PATCH 1603/2505] Always define CLOCK_MONOTONIC and CLOCK_MONOTONIC_COARSE Even on systems that don't support this, like old macOS systems, so that the fallback implementation can use time() instead -- even if not monotonic, it's the best I can do without using platform-specific APIs. --- src/lib/missing/time.h | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/lib/missing/time.h b/src/lib/missing/time.h index a6118cbd9..06721a15e 100644 --- a/src/lib/missing/time.h +++ b/src/lib/missing/time.h @@ -29,15 +29,19 @@ int clock_gettime(clockid_t clk_id, struct timespec *ts); #endif #if !defined(CLOCK_MONOTONIC_COARSE) && defined(CLOCK_MONOTONIC_FAST) -#define CLOCK_MONOTONIC_COARSE CLOCK_MONOTONIC_FAST /* FreeBSD */ +# define CLOCK_MONOTONIC_COARSE CLOCK_MONOTONIC_FAST /* FreeBSD */ #elif !defined(CLOCK_MONOTONIC_COARSE) && defined(CLOCK_MONOTONIC_RAW_APPROX) -#define CLOCK_MONOTONIC_COARSE CLOCK_MONOTONIC_RAW_APPROX /* macOS */ -#elif !defined(CLOCK_MONOTONIC_COARSE) && defined(CLOCK_MONOTONIC) -#define CLOCK_MONOTONIC_COARSE CLOCK_MONOTONIC +# define CLOCK_MONOTONIC_COARSE CLOCK_MONOTONIC_RAW_APPROX /* New-ish macOS */ +#elif !defined(CLOCK_MONOTONIC_COARSE) +# if defined(CLOCK_MONOTONIC) +# define CLOCK_MONOTONIC_COARSE CLOCK_MONOTONIC +# else +# define CLOCK_MONOTONIC_COARSE 0xbebac0ca /* Old macOS, usually */ +# endif #endif #if !defined(CLOCK_MONOTONIC) -#define CLOCK_MONOTONIC 0xBebaCafe +#define CLOCK_MONOTONIC 0xbebacafe #endif #endif /* MISSING_TIME_H */ From b2cc253f4caa81c1afbd20b70726f85da8c7c132 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 28 Oct 2020 21:40:01 -0700 Subject: [PATCH 1604/2505] Fix build of config_fuzzer in oss-fuzz Should fix https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=26653 --- src/bin/fuzz/config_fuzzer.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/bin/fuzz/config_fuzzer.cc b/src/bin/fuzz/config_fuzzer.cc index 4d1a0d494..b2c426382 100644 --- a/src/bin/fuzz/config_fuzzer.cc +++ b/src/bin/fuzz/config_fuzzer.cc @@ -2,10 +2,12 @@ #include #include +extern "C" { #include "lwan-config.h" +#include "lwan-private.h" +} -static bool -dump(struct config *config, int indent_level) +static bool dump(struct config *config, int indent_level) { const struct config_line *line; From bd1d6eaf0c73eb34414f0f53d79af8065f6b5318 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 1 Nov 2020 08:50:16 -0800 Subject: [PATCH 1605/2505] README cleanup: regexp section for the rewrite module --- README.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c243fe574..5a3ec16f0 100644 --- a/README.md +++ b/README.md @@ -441,10 +441,17 @@ in a `500 Internal Server Error` response being thrown. The `rewrite` module will match [patterns](https://man.openbsd.org/patterns.7) in URLs and give the option to either redirect to another URL, or rewrite the request in a way that Lwan -will handle the request as if it were made in that way originally. The -patterns are a special kind of regular expressions, forked from Lua 5.3.1, -that do not contain backreferences and other features that could create -denial-of-service issues in Lwan. The new URL can be specified using a +will handle the request as if it were made in that way originally. + +Forked from Lua 5.3.1, the regular expresion engine may not be as +feature-packed as most general-purpose engines, but has been chosen +specifically because it is a [deterministic finite +automaton](https://en.wikipedia.org/wiki/Deterministic_finite_automaton) in +an attempt to make some kinds of [denial of service +attacks](https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS) +not possible. + +The new URL can be specified using a simple text substitution syntax, or use Lua scripts; Lua scripts will contain the same metamethods available in the `req` metatable provided by the Lua module, so it can be quite powerful. From 1c585a69aac78f62c8ecccc68b65fb979e072021 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 1 Nov 2020 08:51:05 -0800 Subject: [PATCH 1606/2505] Set *error if creating cache entry fails --- src/lib/lwan-cache.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/lwan-cache.c b/src/lib/lwan-cache.c index b14e4a4ad..e0d6f6cfd 100644 --- a/src/lib/lwan-cache.c +++ b/src/lib/lwan-cache.c @@ -185,6 +185,7 @@ struct cache_entry *cache_get_and_ref_entry(struct cache *cache, entry = cache->cb.create_entry(key, cache->cb.context); if (UNLIKELY(!entry)) { + *error = ECANCELED; free(key_copy); return NULL; } From a28a3411fb58cec24c58fd5350b37c433fd399e2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 1 Nov 2020 08:51:36 -0800 Subject: [PATCH 1607/2505] s/time_to_expire/now/ in cache_get_and_ref_entry() It's clearer this way --- src/lib/lwan-cache.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-cache.c b/src/lib/lwan-cache.c index e0d6f6cfd..dfbf922c9 100644 --- a/src/lib/lwan-cache.c +++ b/src/lib/lwan-cache.c @@ -203,12 +203,12 @@ struct cache_entry *cache_get_and_ref_entry(struct cache *cache, } if (!hash_add_unique(cache->hash.table, entry->key, entry)) { - struct timespec time_to_expire; + struct timespec now; - if (UNLIKELY(clock_gettime(monotonic_clock_id, &time_to_expire) < 0)) + if (UNLIKELY(clock_gettime(monotonic_clock_id, &now) < 0)) lwan_status_critical("clock_gettime"); - entry->time_to_expire = time_to_expire.tv_sec + cache->settings.time_to_live; + entry->time_to_expire = now.tv_sec + cache->settings.time_to_live; if (LIKELY(!pthread_rwlock_wrlock(&cache->queue.lock))) { list_add_tail(&cache->queue.list, &entry->entries); From 460e2111f1886acf0b61a2fc848e8112ed862c14 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 1 Nov 2020 08:52:10 -0800 Subject: [PATCH 1608/2505] Use coro_memdup() to build redirect headers --- src/lib/lwan-mod-rewrite.c | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index 31c0de883..b9aca70e9 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -68,22 +68,18 @@ struct str_builder { static enum lwan_http_status module_redirect_to(struct lwan_request *request, const char *url) { - struct lwan_key_value *headers = - coro_malloc(request->conn->coro, sizeof(*headers) * 2); + const struct lwan_key_value headers[] = { + {"Location", coro_strdup(request->conn->coro, url)}, + {}, + }; - if (UNLIKELY(!headers)) - return HTTP_INTERNAL_ERROR; - - headers[0].key = "Location"; - headers[0].value = coro_strdup(request->conn->coro, url); - if (UNLIKELY(!headers[0].value)) - return HTTP_INTERNAL_ERROR; + request->response.headers = + coro_memdup(request->conn->coro, headers, sizeof(headers)); - headers[1].key = NULL; - headers[1].value = NULL; - request->response.headers = headers; + if (LIKELY(headers[0].value && request->response.headers)) + return HTTP_MOVED_PERMANENTLY; - return HTTP_MOVED_PERMANENTLY; + return HTTP_INTERNAL_ERROR; } static enum lwan_http_status module_rewrite_as(struct lwan_request *request, From f5a8557491d9f9b185d1d238c9d3e588b8578b77 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 5 Nov 2020 07:43:49 -0800 Subject: [PATCH 1609/2505] Avoid calling list_empty() when publishing to a pubsub queue list_tail() will call it and return NULL if the list is empty. --- src/lib/lwan-pubsub.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-pubsub.c b/src/lib/lwan-pubsub.c index 4cd6716a9..0fe1e6a97 100644 --- a/src/lib/lwan-pubsub.c +++ b/src/lib/lwan-pubsub.c @@ -61,13 +61,13 @@ static bool lwan_pubsub_queue_put(struct lwan_pubsub_subscriber *sub, { struct lwan_pubsub_msg_ref *ref; - if (!list_empty(&sub->msg_refs)) { + ref = list_tail(&sub->msg_refs, struct lwan_pubsub_msg_ref, ref); + if (ref) { /* Try putting the message in the last ringbuffer in this queue: if it's * full, will need to allocate a new ring buffer, even if others might * have space in them: the FIFO order must be preserved, and short of * compacting the queue at this point -- which will eventually happen * as it is consumed -- this is the only option. */ - ref = list_tail(&sub->msg_refs, struct lwan_pubsub_msg_ref, ref); if (lwan_pubsub_msg_ref_ring_try_put(&ref->ring, &msg)) return true; } From f0a63ca4c8dc55de2023039456fe3ccf2722fe9c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 7 Nov 2020 08:25:38 -0800 Subject: [PATCH 1610/2505] Make memory sanitizer happy Even though `value` here is unitialized, it'll be filled by the syscall before it's actually used. --- src/lib/hash.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/hash.c b/src/lib/hash.c index 5f0d04880..f77ae415e 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -82,7 +82,7 @@ static unsigned (*hash_int)(const void *key) = hash_int_shift_mult; static unsigned int get_random_unsigned(void) { - unsigned int value; + unsigned int value = 0; #if defined(SYS_getrandom) long int ret = syscall(SYS_getrandom, &value, sizeof(value), 0); From 6dfbe7a3f39ecf4d376eb98f68a209a7049c25fe Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 7 Nov 2020 11:38:22 -0800 Subject: [PATCH 1611/2505] Fix UB when parsing Range headers Found with ubsan with the following warning: ../src/lib/lwan-request.c:634:13: runtime error: left shift of 1 by 63 places cannot be represented in type 'long int' Assume that off_t is 64-bit if size_t is 64-bit. --- fuzz/crash-9df31650ba7cf7fa3aae24d59b3e0a335ab6c23a | 8 ++++++++ src/lib/missing/limits.h | 6 +++++- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 fuzz/crash-9df31650ba7cf7fa3aae24d59b3e0a335ab6c23a diff --git a/fuzz/crash-9df31650ba7cf7fa3aae24d59b3e0a335ab6c23a b/fuzz/crash-9df31650ba7cf7fa3aae24d59b3e0a335ab6c23a new file mode 100644 index 000000000..a43e840ea --- /dev/null +++ b/fuzz/crash-9df31650ba7cf7fa3aae24d59b3e0a335ab6c23a @@ -0,0 +1,8 @@ +GET /zero HTTP/1.1 +Host: 127.0.0.1:8080 +User-Agent: python-requests/2.21.0 +Accept-Encoding: gzip, deflate +Accept: */* +Connection: keep-alive +Range: bytes=50- + diff --git a/src/lib/missing/limits.h b/src/lib/missing/limits.h index 750f0416a..b86683f0c 100644 --- a/src/lib/missing/limits.h +++ b/src/lib/missing/limits.h @@ -38,7 +38,11 @@ #ifndef OFF_MAX # include -# define OFF_MAX ~((off_t)1 << (sizeof(off_t) * CHAR_BIT - 1)) +#if SIZE_MAX == ULONG_MAX +# define OFF_MAX LLONG_MAX +#else +# define OFF_MAX LONG_MAX +#endif #endif #ifndef PAGE_SIZE From 0a9c8cd042ee05962881bd4a7960acd5baf9a7c7 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 7 Nov 2020 12:07:29 -0800 Subject: [PATCH 1612/2505] Fix declaration of OFF_MAX I'm clearly not in the mood to write code. --- src/lib/missing/limits.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/missing/limits.h b/src/lib/missing/limits.h index b86683f0c..3024ffa9e 100644 --- a/src/lib/missing/limits.h +++ b/src/lib/missing/limits.h @@ -38,7 +38,7 @@ #ifndef OFF_MAX # include -#if SIZE_MAX == ULONG_MAX +#if SIZE_MAX == ULLONG_MAX # define OFF_MAX LLONG_MAX #else # define OFF_MAX LONG_MAX From 4299589ea354bd8ed58a72d47eb3153d96e72451 Mon Sep 17 00:00:00 2001 From: Tim Gates Date: Sun, 15 Nov 2020 10:01:37 +1100 Subject: [PATCH 1613/2505] docs: fix simple typo, wich -> which There is a small typo in src/lib/list.h. Should read `which` rather than `wich`. --- src/lib/list.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/list.h b/src/lib/list.h index 1b5caeceb..59739cd0b 100644 --- a/src/lib/list.h +++ b/src/lib/list.h @@ -608,7 +608,7 @@ static inline void list_prepend_list(struct list_head *to, /** * list_for_each_off - iterate through a list of memory regions. * @h: the list_head - * @i: the pointer to a memory region wich contains list node data. + * @i: the pointer to a memory region which contains list node data. * @off: offset(relative to @i) at which list node data resides. * * This is a low-level wrapper to iterate @i over the entire list, used to @@ -644,7 +644,7 @@ static inline void list_prepend_list(struct list_head *to, * list_for_each_safe_off - iterate through a list of memory regions, maybe * during deletion * @h: the list_head - * @i: the pointer to a memory region wich contains list node data. + * @i: the pointer to a memory region which contains list node data. * @nxt: the structure containing the list_node * @off: offset(relative to @i) at which list node data resides. * From 966ad48cfada3c5261b732bd12a9c50d8afab520 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 2 Dec 2020 08:43:49 -0800 Subject: [PATCH 1614/2505] Disable readahead thread if pipe couldn't be set up Fixes #298. --- src/lib/lwan-readahead.c | 48 ++++++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/src/lib/lwan-readahead.c b/src/lib/lwan-readahead.c index fda61696c..a9414c43a 100644 --- a/src/lib/lwan-readahead.c +++ b/src/lib/lwan-readahead.c @@ -176,22 +176,52 @@ void lwan_readahead_init(void) lwan_status_debug("Initializing low priority readahead thread"); - if (pipe2(readahead_pipe_fd, O_CLOEXEC | PIPE_DIRECT_FLAG) < 0) - lwan_status_critical_perror("pipe2"); + if (pipe2(readahead_pipe_fd, O_CLOEXEC | PIPE_DIRECT_FLAG) < 0) { + lwan_status_warning("Could not create pipe for readahead queue"); + goto disable_readahead; + } /* Only write side should be non-blocking. */ flags = fcntl(readahead_pipe_fd[1], F_GETFL); - if (flags < 0) - lwan_status_critical_perror("fcntl"); - if (fcntl(readahead_pipe_fd[1], F_SETFL, flags | O_NONBLOCK) < 0) - lwan_status_critical_perror("fcntl"); + if (flags < 0) { + lwan_status_warning( + "Could not get flags for readahead pipe write side"); + goto disable_readahead_close_pipe; + } + if (fcntl(readahead_pipe_fd[1], F_SETFL, flags | O_NONBLOCK) < 0) { + lwan_status_warning( + "Could not set readahead write side to be no-blocking"); + goto disable_readahead_close_pipe; + } - if (pthread_create(&readahead_self, NULL, lwan_readahead_loop, NULL)) - lwan_status_critical_perror("pthread_create"); + if (pthread_create(&readahead_self, NULL, lwan_readahead_loop, NULL)) { + lwan_status_warning("Could not create low-priority readahead thread"); + goto disable_readahead_close_pipe; + } #ifdef SCHED_IDLE struct sched_param sched_param = {.sched_priority = 0}; if (pthread_setschedparam(readahead_self, SCHED_IDLE, &sched_param) < 0) - lwan_status_perror("pthread_setschedparam"); + lwan_status_perror( + "Could not set scheduling policy of readahead thread to idle"); #endif /* SCHED_IDLE */ + + return; + +disable_readahead_close_pipe: + close(readahead_pipe_fd[0]); + close(readahead_pipe_fd[1]); + +disable_readahead: + /* Set these to -1 just to ensure that even if the page_size check inside + * the enqueuing functions fail, we don't write stuff to a file descriptor + * that's not the readahead queue. */ + readahead_pipe_fd[0] = readahead_pipe_fd[1] = -1; + + /* If page_size is 0, then the enqueuing functions won't write to the pipe. + * This way, we don't need to introduce new checks there for + * this corner case of not being able to create/set up the pipe. */ + page_size = 0; + + lwan_status_warning("Readahead thread has been disabled"); } From 85a327c4dacedfb37b0ac08c8b0850c227417f94 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 2 Dec 2020 08:45:04 -0800 Subject: [PATCH 1615/2505] Bump minimum required CMake version to 3.0 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b13869a5b..1e4fc3d89 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ project(lwan C) -cmake_minimum_required(VERSION 2.8.10) +cmake_minimum_required(VERSION 3.0) set(PROJECT_DESCRIPTION "Scalable, high performance, experimental web server") message(STATUS "Running CMake for ${PROJECT_NAME} (${PROJECT_DESCRIPTION})") From cccf76510622e3212a7422b3af4b085b1bf991cc Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 12 Dec 2020 12:33:31 -0800 Subject: [PATCH 1616/2505] Ensure temporary dir isn't tmpfs --- CMakeLists.txt | 11 ++++++++ src/cmake/lwan-build-config.h.cmake | 1 + src/lib/lwan-request.c | 28 +++++++++++++++---- src/lib/missing.c | 12 ++++++++ src/lib/missing/sys/vfs.h | 43 +++++++++++++++++++++++++++++ 5 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 src/lib/missing/sys/vfs.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e4fc3d89..2872afdd6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -166,6 +166,17 @@ check_function_exists(fwrite_unlocked HAVE_FWRITE_UNLOCKED) check_function_exists(gettid HAVE_GETTID) check_function_exists(secure_getenv HAVE_SECURE_GETENV) +check_include_files("sys/vfs.h" HAVE_SYS_VFS_H) +if (HAVE_SYS_VFS_H) + check_symbol_exists(statfs sys/vfs.h HAVE_STATFS) +endif () +if (NOT HAVE_STATFS) + check_include_files("sys/mount.h;sys/param.h" HAVE_SYS_MOUNT_AND_SYS_PARAM_H) + if (HAVE_SYS_MOUNT_AND_SYS_PARAM_H) + check_symbol_exists(statfs "sys/mount.h;sys/param.h" HAVE_STATFS) + endif () +endif() + # This is available on -ldl in glibc, but some systems (such as OpenBSD) # will bundle these in the C library. This isn't required for glibc anyway, # as there's getauxval(), with a fallback to reading the link diff --git a/src/cmake/lwan-build-config.h.cmake b/src/cmake/lwan-build-config.h.cmake index d2669a6f9..ecb6b3286 100644 --- a/src/cmake/lwan-build-config.h.cmake +++ b/src/cmake/lwan-build-config.h.cmake @@ -44,6 +44,7 @@ #cmakedefine HAVE_FWRITE_UNLOCKED #cmakedefine HAVE_GETTID #cmakedefine HAVE_SECURE_GETENV +#cmakedefine HAVE_STATFS /* Compiler builtins for specific CPU instruction support */ #cmakedefine HAVE_BUILTIN_CLZLL diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 2823e4b06..68a06c2da 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include "lwan-private.h" @@ -967,6 +968,23 @@ static const char *is_dir(const char *v) return v; } +static const char *is_dir_good_for_tmp(const char *v) +{ + struct statfs sb; + + v = is_dir(v); + if (!v) + return NULL; + + if (!statfs(v, &sb) && sb.f_type == TMPFS_MAGIC) { + lwan_status_warning("%s is a tmpfs filesystem, " + "not considering it", v); + return NULL; + } + + return v; +} + static const char *temp_dir; static const char * @@ -974,23 +992,23 @@ get_temp_dir(void) { const char *tmpdir; - tmpdir = is_dir(secure_getenv("TMPDIR")); + tmpdir = is_dir_good_for_tmp(secure_getenv("TMPDIR")); if (tmpdir) return tmpdir; - tmpdir = is_dir(secure_getenv("TMP")); + tmpdir = is_dir_good_for_tmp(secure_getenv("TMP")); if (tmpdir) return tmpdir; - tmpdir = is_dir(secure_getenv("TEMP")); + tmpdir = is_dir_good_for_tmp(secure_getenv("TEMP")); if (tmpdir) return tmpdir; - tmpdir = is_dir("/var/tmp"); + tmpdir = is_dir_good_for_tmp("/var/tmp"); if (tmpdir) return tmpdir; - tmpdir = is_dir(P_tmpdir); + tmpdir = is_dir_good_for_tmp(P_tmpdir); if (tmpdir) return tmpdir; diff --git a/src/lib/missing.c b/src/lib/missing.c index 29defdc52..8f459ca00 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include "lwan.h" @@ -600,3 +601,14 @@ size_t fwrite_unlocked(const void *ptr, size_t size, size_t n, FILE *stream) return (total_to_write - to_write) / size; } #endif + +#if !defined(HAVE_STATFS) +int statfs(const char *path, struct statfs *buf) +{ + (void)path; + (void)buf; + + *errno = ENOSYS; + return -1; +} +#endif diff --git a/src/lib/missing/sys/vfs.h b/src/lib/missing/sys/vfs.h new file mode 100644 index 000000000..2539fe8cf --- /dev/null +++ b/src/lib/missing/sys/vfs.h @@ -0,0 +1,43 @@ +/* + * lwan - simple web server + * Copyright (c) 2020 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__APPLE__) +#include +#include +#elif defined(__linux__) +#include_next +#endif + +#ifndef _MISSING_VFS_H_ +#define _MISSING_VFS_H_ + +#if !defined(HAVE_STATFS) +struct statfs { + int f_type; +}; + +int statfs(const char *path, struct statfs *buf); +#endif + +#ifndef TMPFS_MAGIC +#define TMPFS_MAGIC 0xbebacafe +#endif + +#endif /* _MISSING_VFS_H_ */ From 73c008fa06ae6803a643c6a745806580de02ba03 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 12 Dec 2020 12:34:56 -0800 Subject: [PATCH 1617/2505] Add more tests for MIME-type tables --- src/lib/lwan-tables.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index a21ce2fe4..8dabb2f12 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -91,6 +91,10 @@ void lwan_tables_init(void) "application/xml")); assert(streq(lwan_determine_mime_type_for_file_name(".nosuchext"), "application/octet-stream")); + assert(streq(lwan_determine_mime_type_for_file_name("nodotinfilename"), + "application/octet-stream")); + assert(streq(lwan_determine_mime_type_for_file_name(""), + "application/octet-stream")); assert(streq(lwan_determine_mime_type_for_file_name(".gif"), "image/gif")); assert(streq(lwan_determine_mime_type_for_file_name(".JS"), From b69d94e88150c7e44caef237a5373836118f7683 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 22 Dec 2020 15:44:09 -0800 Subject: [PATCH 1618/2505] Check only for sys/mount.h on BSD systems when looking for statfs() --- CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2872afdd6..1c0faae02 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -171,9 +171,9 @@ if (HAVE_SYS_VFS_H) check_symbol_exists(statfs sys/vfs.h HAVE_STATFS) endif () if (NOT HAVE_STATFS) - check_include_files("sys/mount.h;sys/param.h" HAVE_SYS_MOUNT_AND_SYS_PARAM_H) - if (HAVE_SYS_MOUNT_AND_SYS_PARAM_H) - check_symbol_exists(statfs "sys/mount.h;sys/param.h" HAVE_STATFS) + check_include_files("sys/mount.h" HAVE_SYS_MOUNT_H) + if (HAVE_SYS_MOUNT_H) + check_symbol_exists(statfs "sys/mount.h" HAVE_STATFS) endif () endif() From b99fd3ac25df2501de19b2a9e66f6f995386ce96 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 22 Dec 2020 15:46:05 -0800 Subject: [PATCH 1619/2505] Only check for presence of statfs() function --- CMakeLists.txt | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c0faae02..3f58ee12e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -165,17 +165,7 @@ check_function_exists(getentropy HAVE_GETENTROPY) check_function_exists(fwrite_unlocked HAVE_FWRITE_UNLOCKED) check_function_exists(gettid HAVE_GETTID) check_function_exists(secure_getenv HAVE_SECURE_GETENV) - -check_include_files("sys/vfs.h" HAVE_SYS_VFS_H) -if (HAVE_SYS_VFS_H) - check_symbol_exists(statfs sys/vfs.h HAVE_STATFS) -endif () -if (NOT HAVE_STATFS) - check_include_files("sys/mount.h" HAVE_SYS_MOUNT_H) - if (HAVE_SYS_MOUNT_H) - check_symbol_exists(statfs "sys/mount.h" HAVE_STATFS) - endif () -endif() +check_function_exists(statfs HAVE_STATFS) # This is available on -ldl in glibc, but some systems (such as OpenBSD) # will bundle these in the C library. This isn't required for glibc anyway, From 595b5d165f1e64d5f9009b08a31bad9ce4de020d Mon Sep 17 00:00:00 2001 From: David Ross Date: Sun, 3 Jan 2021 16:39:22 -0800 Subject: [PATCH 1620/2505] Add application/wasm Web Assembly mime type This adds the application/wasm mime type, which is used to represent compiled Web Assembly files, and registers it with the associated file extension, .wasm. While this isn't a registered standard, it's a widely recognized mimetype, and is the one officially set out by the WASM committee and current JavaScript standards. It's concretely useful to use this mimetype, as it enables use of the "WebAssembly.instantiateStreaming" browser API. References: https://webassembly.github.io/spec/core/binary/conventions.html - the webassembly document recommending application/wasm https://github.com/WebAssembly/spec/issues/573 - progress on getting this registered as an official mimetype https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiateStreaming#Instantiating_streaming - JS API which requires the application/wasm mimetype to function --- src/bin/tools/mime.types | 1 + 1 file changed, 1 insertion(+) diff --git a/src/bin/tools/mime.types b/src/bin/tools/mime.types index b90b16587..7722199a8 100644 --- a/src/bin/tools/mime.types +++ b/src/bin/tools/mime.types @@ -1000,6 +1000,7 @@ application/vnd.zul zir zirz application/vnd.zzazz.deck+xml zaz application/voicexml+xml vxml # application/vq-rtcpxr +application/wasm wasm # application/watcherinfo+xml # application/whoispp-query # application/whoispp-response From ce0ac4677ea3da75717409296f833cf8ff1eec17 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 10 Jan 2021 12:29:33 -0800 Subject: [PATCH 1621/2505] Fix off-by-one comparison when looking for next pipelined request --- src/lib/lwan-request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 68a06c2da..474258a97 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -519,7 +519,7 @@ static bool parse_headers(struct lwan_request_parser_helper *helper, return false; if (next_chr == next_header) { - if (buffer_end - next_chr > (ptrdiff_t)HEADER_TERMINATOR_LEN) { + if (buffer_end - next_chr >= (ptrdiff_t)HEADER_TERMINATOR_LEN) { STRING_SWITCH_SMALL (next_header) { case STR2_INT('\r', '\n'): helper->next_request = next_header + HEADER_TERMINATOR_LEN; From bc46932ecbd9bb992f29d753525c5684f96fe80a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 10 Jan 2021 12:37:25 -0800 Subject: [PATCH 1622/2505] Remove unneeded #include from lwan-response.c --- src/lib/lwan-response.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 9dddbdad8..dd3a6a89b 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -126,7 +126,6 @@ static void log_request(struct lwan_request *request, #else #define log_request(...) #endif -#include static inline bool has_response_body(enum lwan_request_flags method, enum lwan_http_status status) From ecea1eea270a569dccbc2af05a6df7e47ce6ddbb Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 10 Jan 2021 12:43:11 -0800 Subject: [PATCH 1623/2505] Log requests in lwan-request.c, not lwan-response.c This is the more logical place to log them: when processing a request, and not a response. The only problem here is, however, that until url_map->handler() returns, we don't know if it succeeded or not if it's something like an infinite loop, say, in a websockets server handler. --- src/lib/lwan-request.c | 51 +++++++++++++++++++++++++++++++++-------- src/lib/lwan-response.c | 35 ---------------------------- 2 files changed, 41 insertions(+), 45 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 474258a97..459348c0b 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1377,6 +1377,38 @@ static bool handle_rewrite(struct lwan_request *request) return true; } +#ifndef NDEBUG +static const char *get_request_method(struct lwan_request *request) +{ +#define GENERATE_CASE_STMT(upper, lower, mask, constant) \ + case REQUEST_METHOD_##upper: \ + return #upper; + + switch (lwan_request_get_method(request)) { + FOR_EACH_REQUEST_METHOD(GENERATE_CASE_STMT) + default: + return "UNKNOWN"; + } + +#undef GENERATE_CASE_STMT +} + +static void log_request(struct lwan_request *request, + enum lwan_http_status status) +{ + char ip_buffer[INET6_ADDRSTRLEN]; + + lwan_status_debug("%s [%s] \"%s %s HTTP/%s\" %d %s", + lwan_request_get_remote_address(request, ip_buffer), + request->conn->thread->date.date, + get_request_method(request), request->original_url.value, + request->flags & REQUEST_IS_HTTP_1_0 ? "1.0" : "1.1", + status, request->response.mime_type); +} +#else +#define log_request(...) +#endif + void lwan_process_request(struct lwan *l, struct lwan_request *request) { enum lwan_http_status status; @@ -1398,23 +1430,19 @@ void lwan_process_request(struct lwan *l, struct lwan_request *request) } status = parse_http_request(request); - if (UNLIKELY(status != HTTP_OK)) { - lwan_default_response(request, status); - return; - } + if (UNLIKELY(status != HTTP_OK)) + goto log_and_return; lookup_again: url_map = lwan_trie_lookup_prefix(&l->url_map_trie, request->url.value); if (UNLIKELY(!url_map)) { - lwan_default_response(request, HTTP_NOT_FOUND); - return; + status = HTTP_NOT_FOUND; + goto log_and_return; } status = prepare_for_response(url_map, request); - if (UNLIKELY(status != HTTP_OK)) { - lwan_default_response(request, status); - return; - } + if (UNLIKELY(status != HTTP_OK)) + goto log_and_return; status = url_map->handler(request, &request->response, url_map->data); if (UNLIKELY(url_map->flags & HANDLER_CAN_REWRITE_URL)) { @@ -1425,6 +1453,9 @@ void lwan_process_request(struct lwan *l, struct lwan_request *request) } } +log_and_return: + log_request(request, status); + return (void)lwan_response(request, status); } diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index dd3a6a89b..4b939d1be 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -95,38 +95,6 @@ void lwan_response_shutdown(struct lwan *l __attribute__((unused))) lwan_tpl_free(error_template); } -#ifndef NDEBUG -static const char *get_request_method(struct lwan_request *request) -{ -#define GENERATE_CASE_STMT(upper, lower, mask, constant) \ - case REQUEST_METHOD_##upper: \ - return #upper; - - switch (lwan_request_get_method(request)) { - FOR_EACH_REQUEST_METHOD(GENERATE_CASE_STMT) - default: - return "UNKNOWN"; - } - -#undef GENERATE_CASE_STMT -} - -static void log_request(struct lwan_request *request, - enum lwan_http_status status) -{ - char ip_buffer[INET6_ADDRSTRLEN]; - - lwan_status_debug("%s [%s] \"%s %s HTTP/%s\" %d %s", - lwan_request_get_remote_address(request, ip_buffer), - request->conn->thread->date.date, - get_request_method(request), request->original_url.value, - request->flags & REQUEST_IS_HTTP_1_0 ? "1.0" : "1.1", - status, request->response.mime_type); -} -#else -#define log_request(...) -#endif - static inline bool has_response_body(enum lwan_request_flags method, enum lwan_http_status status) { @@ -143,7 +111,6 @@ void lwan_response(struct lwan_request *request, enum lwan_http_status status) /* Send last, 0-sized chunk */ lwan_strbuf_reset(response->buffer); lwan_response_send_chunk(request); - log_request(request, status); return; } @@ -158,8 +125,6 @@ void lwan_response(struct lwan_request *request, enum lwan_http_status status) return lwan_default_response(request, status); } - log_request(request, status); - if (request->flags & RESPONSE_STREAM) { if (LIKELY(response->stream.callback)) { status = response->stream.callback(request, response->stream.data); From d05e8bd29fd358493b306e578cf386a9d33b152d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 10 Jan 2021 18:37:16 -0800 Subject: [PATCH 1624/2505] Make lwan_response() and lwan_default_response() private functions --- src/lib/liblwan.sym | 2 -- src/lib/lwan-private.h | 4 ++++ src/lib/lwan.h | 3 --- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/lib/liblwan.sym b/src/lib/liblwan.sym index 76f91a77b..ba820b083 100644 --- a/src/lib/liblwan.sym +++ b/src/lib/liblwan.sym @@ -23,12 +23,10 @@ global: lwan_request_get_*; lwan_request_sleep; - lwan_response; lwan_response_send_chunk; lwan_response_send_event; lwan_response_set_chunked; lwan_response_set_event_stream; - lwan_default_response; lwan_prepare_response_header; lwan_prepare_response_header_full; diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index af394232e..fa7e0a347 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -116,6 +116,10 @@ size_t lwan_prepare_response_header_full(struct lwan_request *request, enum lwan_http_status status, char headers[], size_t headers_buf_size, const struct lwan_key_value *additional_headers); +void lwan_response(struct lwan_request *request, enum lwan_http_status status); +void lwan_default_response(struct lwan_request *request, + enum lwan_http_status status); + void lwan_straitjacket_enforce_from_config(struct config *c); const char *lwan_get_config_path(char *path_buf, size_t path_buf_len); diff --git a/src/lib/lwan.h b/src/lib/lwan.h index fe19f511f..1fe6c6a50 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -499,9 +499,6 @@ struct lwan { void lwan_set_url_map(struct lwan *l, const struct lwan_url_map *map); void lwan_main_loop(struct lwan *l); -void lwan_response(struct lwan_request *request, enum lwan_http_status status); -void lwan_default_response(struct lwan_request *request, - enum lwan_http_status status); size_t lwan_prepare_response_header(struct lwan_request *request, enum lwan_http_status status, char header_buffer[], From 3bb25e235e76028d7687ae4636a059474f42b19c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 18 Jan 2021 12:21:02 -0800 Subject: [PATCH 1625/2505] Warn that POST will fail if tmpdir couldn't be determined --- src/lib/lwan-request.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 459348c0b..107529dbb 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -986,6 +986,7 @@ static const char *is_dir_good_for_tmp(const char *v) } static const char *temp_dir; +static const size_t post_buffer_temp_file_thresh = 1<<20; static const char * get_temp_dir(void) @@ -1012,6 +1013,9 @@ get_temp_dir(void) if (tmpdir) return tmpdir; + lwan_status_warning("Temporary directory could not be determined. POST " + "requests over %zu bytes will fail.", + post_buffer_temp_file_thresh); return NULL; } @@ -1073,7 +1077,7 @@ alloc_post_buffer(struct coro *coro, size_t size, bool allow_file) void *ptr = (void *)MAP_FAILED; int fd; - if (LIKELY(size < 1<<20)) { + if (LIKELY(size < post_buffer_temp_file_thresh)) { ptr = coro_malloc(coro, size); if (LIKELY(ptr)) From da8a31e1a2b9f3d51d7db001ad6d964f8161c3f5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 18 Jan 2021 17:56:55 -0800 Subject: [PATCH 1626/2505] Split hash_entry struct into 3 arrays This makes the lookup more cache-friendly. Still need to test this further. --- src/lib/hash.c | 242 ++++++++++++++++++++++++++++--------------------- 1 file changed, 140 insertions(+), 102 deletions(-) diff --git a/src/lib/hash.c b/src/lib/hash.c index f77ae415e..5a5ad7d7c 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -36,15 +36,10 @@ #include "hash.h" #include "murmur3.h" -struct hash_entry { - const char *key; - const void *value; - - unsigned int hashval; -}; - struct hash_bucket { - struct hash_entry *entries; + void **keys; + void **values; + unsigned int *hashvals; unsigned int used; unsigned int total; @@ -62,6 +57,16 @@ struct hash { struct hash_bucket *buckets; }; +struct hash_entry { + void **key; + void **value; + unsigned int *hashval; + + /* Only set when adding a new entry if it was already in the + * hash table -- always 0/false otherwise. */ + bool existing; +}; + #define MIN_BUCKETS 64 /* Due to rehashing heuristics, most hash tables won't have more than 4 @@ -80,6 +85,32 @@ static unsigned int odd_constant = DEFAULT_ODD_CONSTANT; static unsigned (*hash_str)(const void *key) = murmur3_simple; static unsigned (*hash_int)(const void *key) = hash_int_shift_mult; +static bool resize_bucket(struct hash_bucket *bucket, unsigned int new_size) +{ + void **new_keys; + void **new_values; + unsigned int *new_hashvals; + + new_keys = reallocarray(bucket->keys, new_size, STEPS * sizeof(void *)); + new_values = reallocarray(bucket->values, new_size, STEPS * sizeof(void *)); + new_hashvals = + reallocarray(bucket->hashvals, new_size, STEPS * sizeof(unsigned int)); + + if (new_keys) + bucket->keys = new_keys; + if (new_values) + bucket->values = new_values; + if (new_hashvals) + bucket->hashvals = new_hashvals; + + if (new_keys && new_values && new_hashvals) { + bucket->total = new_size * STEPS; + return true; + } + + return false; +} + static unsigned int get_random_unsigned(void) { unsigned int value = 0; @@ -252,66 +283,68 @@ void hash_free(struct hash *hash) bucket = hash->buckets; bucket_end = hash->buckets + hash_n_buckets(hash); for (; bucket < bucket_end; bucket++) { - struct hash_entry *entry, *entry_end; - entry = bucket->entries; - entry_end = entry + bucket->used; - for (; entry < entry_end; entry++) { - hash->free_value((void *)entry->value); - hash->free_key((void *)entry->key); + for (unsigned int entry = 0; entry < bucket->used; entry++) { + hash->free_value(bucket->values[entry]); + hash->free_key(bucket->keys[entry]); } - free(bucket->entries); + free(bucket->keys); + free(bucket->values); + free(bucket->hashvals); } free(hash->buckets); free(hash); } -static struct hash_entry *hash_add_entry_hashed(struct hash *hash, const void *key, - unsigned int hashval) +static struct hash_entry hash_add_entry_hashed(struct hash *hash, + const void *key, + unsigned int hashval) { unsigned int pos = hashval & hash->n_buckets_mask; struct hash_bucket *bucket = hash->buckets + pos; - struct hash_entry *entry, *entry_end; + unsigned int entry; + bool existing = false; if (bucket->used + 1 >= bucket->total) { - unsigned int new_total; - struct hash_entry *tmp; - - if (__builtin_add_overflow(bucket->total, STEPS, &new_total)) { - errno = EOVERFLOW; - return NULL; - } + unsigned int new_bucket_total; - tmp = reallocarray(bucket->entries, new_total, sizeof(*tmp)); - if (tmp == NULL) - return NULL; + if (__builtin_add_overflow(bucket->total, 1, &new_bucket_total)) + return (struct hash_entry) {}; - bucket->entries = tmp; - bucket->total = new_total; + if (!resize_bucket(bucket, new_bucket_total)) + return (struct hash_entry) {}; } - entry = bucket->entries; - entry_end = entry + bucket->used; - for (; entry < entry_end; entry++) { - if (hashval != entry->hashval) + for (entry = 0; entry < bucket->used; entry++) { + if (hashval != bucket->hashvals[entry]) continue; - if (!hash->key_compare(key, entry->key)) - return entry; + if (!hash->key_compare(key, bucket->keys[entry])) { + existing = true; + goto done; + } } + entry = bucket->used; + + bucket->keys[entry] = (void *)key; + bucket->values[entry] = NULL; + bucket->hashvals[entry] = hashval; + bucket->used++; hash->count++; - entry->hashval = hashval; - entry->key = entry->value = NULL; - - return entry; +done: + return (struct hash_entry){ + .key = &bucket->keys[entry], + .value = &bucket->values[entry], + .hashval = &bucket->hashvals[entry], + .existing = existing, + }; } static void rehash(struct hash *hash, unsigned int new_bucket_size) { struct hash_bucket *buckets = calloc(new_bucket_size, sizeof(*buckets)); - const struct hash_bucket *bucket_end = hash->buckets + hash_n_buckets(hash); - const struct hash_bucket *bucket; + const unsigned int n_buckets = hash_n_buckets(hash); struct hash hash_copy = *hash; assert((new_bucket_size & (new_bucket_size - 1)) == 0); @@ -324,27 +357,30 @@ static void rehash(struct hash *hash, unsigned int new_bucket_size) hash_copy.n_buckets_mask = new_bucket_size - 1; hash_copy.buckets = buckets; + struct hash_bucket *bucket; + struct hash_bucket *bucket_end = hash->buckets + n_buckets; for (bucket = hash->buckets; bucket < bucket_end; bucket++) { - const struct hash_entry *old = bucket->entries; - const struct hash_entry *old_end = old + bucket->used; - - for (; old < old_end; old++) { - struct hash_entry *new; - - new = hash_add_entry_hashed(&hash_copy, old->key, old->hashval); - if (UNLIKELY(!new)) + for (unsigned int old = 0; old < bucket->used; old++) { + struct hash_entry new = + hash_add_entry_hashed(&hash_copy, + bucket->keys[old], + bucket->hashvals[old]); + if (UNLIKELY(!new.key)) goto fail; - new->key = old->key; - new->value = old->value; + *new.key = bucket->keys[old]; + *new.value = bucket->values[old]; } } /* Original table must remain untouched in the event resizing fails: * previous loop may return early on allocation failure, so can't free * bucket entry arrays there. */ - for (bucket = hash->buckets; bucket < bucket_end; bucket++) - free(bucket->entries); + for (bucket = hash->buckets; bucket < bucket_end; bucket++) { + free(bucket->keys); + free(bucket->values); + free(bucket->hashvals); + } free(hash->buckets); hash->buckets = buckets; @@ -356,13 +392,16 @@ static void rehash(struct hash *hash, unsigned int new_bucket_size) fail: for (bucket_end = bucket, bucket = hash->buckets; bucket < bucket_end; - bucket++) - free(bucket->entries); + bucket++) { + free(bucket->keys); + free(bucket->values); + free(bucket->hashvals); + } free(buckets); } -static struct hash_entry *hash_add_entry(struct hash *hash, const void *key) +static struct hash_entry hash_add_entry(struct hash *hash, const void *key) { unsigned int hashval = hash->hash_value(key); @@ -377,16 +416,16 @@ static struct hash_entry *hash_add_entry(struct hash *hash, const void *key) */ int hash_add(struct hash *hash, const void *key, const void *value) { - struct hash_entry *entry = hash_add_entry(hash, key); + struct hash_entry entry = hash_add_entry(hash, key); - if (!entry) + if (!entry.key) return -errno; - hash->free_value((void *)entry->value); - hash->free_key((void *)entry->key); + hash->free_value(*entry.value); + hash->free_key(*entry.key); - entry->key = key; - entry->value = value; + *entry.key = (void *)key; + *entry.value = (void *)value; if (hash->count > hash->n_buckets_mask) rehash(hash, hash_n_buckets(hash) * 2); @@ -397,16 +436,15 @@ int hash_add(struct hash *hash, const void *key, const void *value) /* similar to hash_add(), but fails if key already exists */ int hash_add_unique(struct hash *hash, const void *key, const void *value) { - struct hash_entry *entry = hash_add_entry(hash, key); + struct hash_entry entry = hash_add_entry(hash, key); - if (!entry) + if (!entry.key) return -errno; - - if (entry->key || entry->value) + if (entry.existing) return -EEXIST; - entry->key = key; - entry->value = value; + *entry.key = (void *)key; + *entry.value = (void *)value; if (hash->count > hash->n_buckets_mask) rehash(hash, hash_n_buckets(hash) * 2); @@ -414,33 +452,33 @@ int hash_add_unique(struct hash *hash, const void *key, const void *value) return 0; } -static inline struct hash_entry * +static inline struct hash_entry hash_find_entry(const struct hash *hash, const char *key, unsigned int hashval) { unsigned int pos = hashval & hash->n_buckets_mask; const struct hash_bucket *bucket = hash->buckets + pos; - struct hash_entry *entry, *entry_end; - entry = bucket->entries; - entry_end = entry + bucket->used; - for (; entry < entry_end; entry++) { - if (hashval != entry->hashval) + for (unsigned int entry = 0; entry < bucket->used; entry++) { + if (hashval != bucket->hashvals[entry]) continue; - if (hash->key_compare(key, entry->key) == 0) - return entry; + if (!hash->key_compare(key, bucket->keys[entry])) { + return (struct hash_entry){ + .key = &bucket->keys[entry], + .value = &bucket->values[entry], + .hashval = &bucket->hashvals[entry], + }; + } } - return NULL; + return (struct hash_entry){}; } void *hash_find(const struct hash *hash, const void *key) { - const struct hash_entry *entry; + struct hash_entry entry = + hash_find_entry(hash, key, hash->hash_value(key)); - entry = hash_find_entry(hash, key, hash->hash_value(key)); - if (entry) - return (void *)entry->value; - return NULL; + return entry.key ? *entry.value : NULL; } int hash_del(struct hash *hash, const void *key) @@ -448,14 +486,13 @@ int hash_del(struct hash *hash, const void *key) unsigned int hashval = hash->hash_value(key); unsigned int pos = hashval & hash->n_buckets_mask; struct hash_bucket *bucket = hash->buckets + pos; - struct hash_entry *entry; - entry = hash_find_entry(hash, key, hashval); - if (entry == NULL) + struct hash_entry entry = hash_find_entry(hash, key, hashval); + if (entry.key == NULL) return -ENOENT; - hash->free_value((void *)entry->value); - hash->free_key((void *)entry->key); + hash->free_value(*entry.value); + hash->free_key(*entry.key); if (bucket->used > 1) { /* Instead of compacting the bucket array by moving elements, just copy @@ -463,10 +500,14 @@ int hash_del(struct hash *hash, const void *key) * changes the ordering inside the bucket array, but it's much more * efficient, as it always has to copy exactly at most 1 element instead * of potentially bucket->used elements. */ - struct hash_entry *entry_last = bucket->entries + bucket->used - 1; + void *last_key = &bucket->keys[bucket->used - 1]; - if (entry != entry_last) - memcpy(entry, entry_last, sizeof(*entry)); + /* FIXME: Is comparing these pointers UB after calling free_key()? */ + if (entry.key != last_key) { + *entry.key = last_key; + *entry.value = bucket->values[bucket->used - 1]; + *entry.hashval = bucket->hashvals[bucket->used - 1]; + } } bucket->used--; @@ -479,12 +520,12 @@ int hash_del(struct hash *hash, const void *key) unsigned int steps_total = bucket->total / STEPS; if (steps_used + 1 < steps_total) { - struct hash_entry *tmp = reallocarray( - bucket->entries, steps_used + 1, STEPS * sizeof(*tmp)); - if (tmp) { - bucket->entries = tmp; - bucket->total = (steps_used + 1) * STEPS; - } + unsigned int new_total; + + if (__builtin_add_overflow(steps_used, 1, &new_total)) + return -EOVERFLOW; + + resize_bucket(bucket, new_total); } } @@ -505,7 +546,6 @@ bool hash_iter_next(struct hash_iter *iter, const void **value) { const struct hash_bucket *b = iter->hash->buckets + iter->bucket; - const struct hash_entry *e; iter->entry++; @@ -525,12 +565,10 @@ bool hash_iter_next(struct hash_iter *iter, return false; } - e = b->entries + iter->entry; - if (value != NULL) - *value = e->value; + *value = b->values[iter->entry]; if (key != NULL) - *key = e->key; + *key = b->keys[iter->entry]; return true; } From d64433aff7661731e813ec744e2397d562c2931d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 20 Jan 2021 19:14:12 -0800 Subject: [PATCH 1627/2505] Simplify key comparison function in hash table No need for a full comparison function; just provide an equality comparison function. Simplifies the integer key comparison function. --- src/lib/hash.c | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/lib/hash.c b/src/lib/hash.c index 5a5ad7d7c..9228d270e 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -50,7 +50,7 @@ struct hash { unsigned int n_buckets_mask; unsigned (*hash_value)(const void *key); - int (*key_compare)(const void *k1, const void *k2); + int (*key_equal)(const void *k1, const void *k2); void (*free_value)(void *value); void (*free_key)(void *value); @@ -212,19 +212,16 @@ __attribute__((constructor(65535))) static void initialize_odd_constant(void) #endif } -static inline int hash_int_key_cmp(const void *k1, const void *k2) +static inline int hash_int_key_equal(const void *k1, const void *k2) { - intptr_t a = (intptr_t)k1; - intptr_t b = (intptr_t)k2; - - return (a > b) - (a < b); + return k1 == k2; } static void no_op(void *arg __attribute__((unused))) {} static struct hash * hash_internal_new(unsigned int (*hash_value)(const void *key), - int (*key_compare)(const void *k1, const void *k2), + int (*key_equal)(const void *k1, const void *k2), void (*free_key)(void *value), void (*free_value)(void *value)) { @@ -240,7 +237,7 @@ hash_internal_new(unsigned int (*hash_value)(const void *key), } hash->hash_value = hash_value; - hash->key_compare = key_compare; + hash->key_equal = key_equal; hash->free_value = free_value; hash->free_key = free_key; @@ -254,7 +251,7 @@ hash_internal_new(unsigned int (*hash_value)(const void *key), struct hash *hash_int_new(void (*free_key)(void *value), void (*free_value)(void *value)) { - return hash_internal_new(hash_int, hash_int_key_cmp, + return hash_internal_new(hash_int, hash_int_key_equal, free_key ? free_key : no_op, free_value ? free_value : no_op); } @@ -263,7 +260,7 @@ struct hash *hash_str_new(void (*free_key)(void *value), void (*free_value)(void *value)) { return hash_internal_new( - hash_str, (int (*)(const void *, const void *))strcmp, + hash_str, (int (*)(const void *, const void *))streq, free_key ? free_key : no_op, free_value ? free_value : no_op); } @@ -317,7 +314,7 @@ static struct hash_entry hash_add_entry_hashed(struct hash *hash, for (entry = 0; entry < bucket->used; entry++) { if (hashval != bucket->hashvals[entry]) continue; - if (!hash->key_compare(key, bucket->keys[entry])) { + if (hash->key_equal(key, bucket->keys[entry])) { existing = true; goto done; } @@ -461,7 +458,7 @@ hash_find_entry(const struct hash *hash, const char *key, unsigned int hashval) for (unsigned int entry = 0; entry < bucket->used; entry++) { if (hashval != bucket->hashvals[entry]) continue; - if (!hash->key_compare(key, bucket->keys[entry])) { + if (hash->key_equal(key, bucket->keys[entry])) { return (struct hash_entry){ .key = &bucket->keys[entry], .value = &bucket->values[entry], From 15c123e765210318df4226309189f8cf36d54fb2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 20 Jan 2021 23:15:05 -0800 Subject: [PATCH 1628/2505] Fix crash on startup due to hash table changes This fixes a double free when adding elements to the hash table. --- src/lib/hash.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/lib/hash.c b/src/lib/hash.c index 9228d270e..fe61aff22 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -419,9 +419,6 @@ int hash_add(struct hash *hash, const void *key, const void *value) return -errno; hash->free_value(*entry.value); - hash->free_key(*entry.key); - - *entry.key = (void *)key; *entry.value = (void *)value; if (hash->count > hash->n_buckets_mask) @@ -440,7 +437,6 @@ int hash_add_unique(struct hash *hash, const void *key, const void *value) if (entry.existing) return -EEXIST; - *entry.key = (void *)key; *entry.value = (void *)value; if (hash->count > hash->n_buckets_mask) From 69f623f72bbd22ce4014d9c755f04ea9552a51fd Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 20 Jan 2021 23:32:16 -0800 Subject: [PATCH 1629/2505] Free memory leak while adding items to hash table --- src/lib/hash.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/hash.c b/src/lib/hash.c index fe61aff22..605013acc 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -322,7 +322,7 @@ static struct hash_entry hash_add_entry_hashed(struct hash *hash, entry = bucket->used; - bucket->keys[entry] = (void *)key; + bucket->keys[entry] = NULL; bucket->values[entry] = NULL; bucket->hashvals[entry] = hashval; @@ -418,7 +418,10 @@ int hash_add(struct hash *hash, const void *key, const void *value) if (!entry.key) return -errno; + hash->free_key(*entry.key); hash->free_value(*entry.value); + + *entry.key = (void *)key; *entry.value = (void *)value; if (hash->count > hash->n_buckets_mask) @@ -437,6 +440,7 @@ int hash_add_unique(struct hash *hash, const void *key, const void *value) if (entry.existing) return -EEXIST; + *entry.key = (void *)key; *entry.value = (void *)value; if (hash->count > hash->n_buckets_mask) From c36b1578c538d8cd7ec45429d26eb487835b3a4f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 20 Jan 2021 23:34:26 -0800 Subject: [PATCH 1630/2505] Avoid indirect calls when adding new items to hash table --- src/lib/hash.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib/hash.c b/src/lib/hash.c index 605013acc..edfd70cc4 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -417,9 +417,10 @@ int hash_add(struct hash *hash, const void *key, const void *value) if (!entry.key) return -errno; - - hash->free_key(*entry.key); - hash->free_value(*entry.value); + if (entry.existing) { + hash->free_key(*entry.key); + hash->free_value(*entry.value); + } *entry.key = (void *)key; *entry.value = (void *)value; From ae21449c8acb3be00b34b23ac92a7baf3e5c34a5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 22 Jan 2021 21:50:55 -0800 Subject: [PATCH 1631/2505] Factor heuristics to rehash table to their own functions --- src/lib/hash.c | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/lib/hash.c b/src/lib/hash.c index edfd70cc4..a55133358 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -405,6 +405,29 @@ static struct hash_entry hash_add_entry(struct hash *hash, const void *key) return hash_add_entry_hashed(hash, key, hashval); } +static inline bool need_rehash_grow(const struct hash *hash) +{ + /* The heuristic to rehash and grow the number of buckets is if there's + * more than 16 entries per bucket on average. This is the number of + * elements in the hashvals array that would fit in a single cache line. */ + return hash->count > hash_n_buckets(hash) * 16; +} + +static inline bool need_rehash_shrink(const struct hash *hash) +{ + /* A hash table will be shrunk if, on average, more than 50% of its + * buckets are empty, but will never have less than MIN_BUCKETS buckets. */ + const unsigned int n_buckets = hash_n_buckets(hash); + + if (n_buckets <= MIN_BUCKETS) + return false; + + if (hash->count > n_buckets / 2) + return false; + + return true; +} + /* * add or replace key in hash map. * @@ -425,7 +448,7 @@ int hash_add(struct hash *hash, const void *key, const void *value) *entry.key = (void *)key; *entry.value = (void *)value; - if (hash->count > hash->n_buckets_mask) + if (need_rehash_grow(hash)) rehash(hash, hash_n_buckets(hash) * 2); return 0; @@ -444,7 +467,7 @@ int hash_add_unique(struct hash *hash, const void *key, const void *value) *entry.key = (void *)key; *entry.value = (void *)value; - if (hash->count > hash->n_buckets_mask) + if (need_rehash_grow(hash)) rehash(hash, hash_n_buckets(hash) * 2); return 0; @@ -511,7 +534,7 @@ int hash_del(struct hash *hash, const void *key) bucket->used--; hash->count--; - if (hash->n_buckets_mask > (MIN_BUCKETS - 1) && hash->count < hash->n_buckets_mask / 2) { + if (need_rehash_shrink(hash)) { rehash(hash, hash_n_buckets(hash) / 2); } else { unsigned int steps_used = bucket->used / STEPS; From 135ab41d4761411c3a5be64fc4a1b7371e0a324c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 22 Jan 2021 21:51:36 -0800 Subject: [PATCH 1632/2505] Ensure client is closed even if couldn't create coroutine --- src/lib/lwan-thread.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 53ba7dba6..467406c8d 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -332,8 +332,16 @@ static ALWAYS_INLINE void spawn_coro(struct lwan_connection *conn, .thread = t, }; if (UNLIKELY(!conn->coro)) { + /* FIXME: send a "busy" response to this client? we don't have a coroutine + * at this point, can't use lwan_send() here */ + lwan_status_error("Could not create coroutine, dropping connection"); + conn->flags = 0; - lwan_status_error("Could not create coroutine"); + + int fd = lwan_connection_get_fd(tq->lwan, conn); + shutdown(fd, SHUT_RDWR); + close(fd); + return; } From e5be5ce6b065f5378fadd7f63c108d090a5621c4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 23 Jan 2021 11:54:38 -0800 Subject: [PATCH 1633/2505] Use lwan_send() in lwan_writev() if iov_count is 1 --- src/lib/lwan-io-wrappers.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index eb39618fb..b689ff83a 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -39,16 +39,22 @@ lwan_writev(struct lwan_request *request, struct iovec *iov, int iov_count) int flags = (request->conn->flags & CONN_CORK) ? MSG_MORE : 0; for (int tries = MAX_FAILED_TRIES; tries;) { + const int remaining_len = (int)(iov_count - curr_iov); ssize_t written; + if (remaining_len == 1) { + const struct iovec *vec = &iov[curr_iov]; + return lwan_send(request, vec->iov_base, vec->iov_len, flags); + } + if (flags) { struct msghdr hdr = { .msg_iov = iov + curr_iov, - .msg_iovlen = (size_t)(iov_count - curr_iov), + .msg_iovlen = (size_t)remaining_len, }; written = sendmsg(request->fd, &hdr, flags); } else { - written = writev(request->fd, iov + curr_iov, iov_count - curr_iov); + written = writev(request->fd, iov + curr_iov, remaining_len); } if (UNLIKELY(written < 0)) { From d9d37001a6eb8c816fdd2670a1d3a0e8a4677cbc Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 30 Jan 2021 10:06:49 -0800 Subject: [PATCH 1634/2505] Allow fuzz-test corpus generator to perform partial writes --- src/lib/lwan-request.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 107529dbb..b68463312 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -751,7 +751,7 @@ static void save_to_corpus_for_fuzzing(struct lwan_value buffer) if (fd < 0) goto try_another_file_name; - for (ssize_t total_written = 0; total_written != (ssize_t)buffer.len;) { + while (buffer.len) { ssize_t r = write(fd, buffer.value, buffer.len); if (r < 0) { @@ -763,7 +763,8 @@ static void save_to_corpus_for_fuzzing(struct lwan_value buffer) goto try_another_file_name; } - total_written += r; + buffer.value += r; + buffer.len -= r; } close(fd); From f890556b48217e018c73c87bbbd6d685e6decb68 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 30 Jan 2021 10:07:29 -0800 Subject: [PATCH 1635/2505] Remove superfluous usage of ptrdiff_t Difference of two pointers always yield this type, so casting to it is useless. --- src/lib/lwan-request.c | 4 ++-- src/lib/lwan-tq.c | 2 +- src/lib/missing.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index b68463312..56c9a8654 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1147,7 +1147,7 @@ get_remaining_post_data_length(struct lwan_request *request, char *buffer_end = helper->buffer->value + helper->buffer->len; - *have = (size_t)(ptrdiff_t)(buffer_end - helper->next_request); + *have = (size_t)(buffer_end - helper->next_request); if (*have < *total) return HTTP_PARTIAL_CONTENT; @@ -1530,7 +1530,7 @@ const char *lwan_request_get_header(struct lwan_request *request, ALWAYS_INLINE int lwan_connection_get_fd(const struct lwan *lwan, const struct lwan_connection *conn) { - return (int)(ptrdiff_t)(conn - lwan->conns); + return (int)(intptr_t)(conn - lwan->conns); } const char * diff --git a/src/lib/lwan-tq.c b/src/lib/lwan-tq.c index e6c4618f0..8e602380c 100644 --- a/src/lib/lwan-tq.c +++ b/src/lib/lwan-tq.c @@ -26,7 +26,7 @@ static inline int timeout_queue_node_to_idx(struct timeout_queue *tq, struct lwan_connection *conn) { - return (conn == &tq->head) ? -1 : (int)(ptrdiff_t)(conn - tq->conns); + return (conn == &tq->head) ? -1 : (int)(intptr_t)(conn - tq->conns); } static inline struct lwan_connection * diff --git a/src/lib/missing.c b/src/lib/missing.c index 8f459ca00..ebdf5d001 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -49,7 +49,7 @@ void *memrchr(const void *s, int c, size_t n) const char *prev = NULL; for (const char *cur = s; cur <= end; prev = cur++) { - cur = (const char *)memchr(cur, c, (size_t)(ptrdiff_t)(end - cur)); + cur = (const char *)memchr(cur, c, (size_t)(end - cur)); if (!cur) break; } From 61fe8515d3a212f25b1b053a490ab82c186b5693 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 31 Jan 2021 13:38:13 -0800 Subject: [PATCH 1636/2505] Don't destroy parameter to save_to_corpus_for_fuzzing() until it succeeds --- src/lib/lwan-request.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 56c9a8654..1772b1816 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -734,8 +734,9 @@ static ALWAYS_INLINE void parse_connection_header(struct lwan_request *request) } #if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) -static void save_to_corpus_for_fuzzing(struct lwan_value buffer) +static void save_to_corpus_for_fuzzing(const struct lwan_value buffer) { + struct lwan_value buffer_copy; char corpus_name[PATH_MAX]; const char *crlfcrlf; int fd; @@ -745,14 +746,16 @@ static void save_to_corpus_for_fuzzing(struct lwan_value buffer) buffer.len = (size_t)(crlfcrlf - buffer.value + 4); try_another_file_name: + buffer_copy = buffer; + snprintf(corpus_name, sizeof(corpus_name), "corpus-request-%d", rand()); fd = open(corpus_name, O_WRONLY | O_CLOEXEC | O_CREAT | O_EXCL, 0644); if (fd < 0) goto try_another_file_name; - while (buffer.len) { - ssize_t r = write(fd, buffer.value, buffer.len); + while (buffer_copy.len) { + ssize_t r = write(fd, buffer_copy.value, buffer_copy.len); if (r < 0) { if (errno == EAGAIN || errno == EINTR) @@ -763,8 +766,8 @@ static void save_to_corpus_for_fuzzing(struct lwan_value buffer) goto try_another_file_name; } - buffer.value += r; - buffer.len -= r; + buffer_copy.value += r; + buffer_copy.len -= r; } close(fd); From ede413ec0b7a4fd972f2df63888656a6c7fa4298 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 4 Feb 2021 17:45:42 -0800 Subject: [PATCH 1637/2505] Provide better support for libucontext For some reason, the Makefile in libucontext is broken -- building it with meson fixes the generated libraries, which do not export the symbols and as a result, no symbol interposition ends up taking place. To force those symbols to be used, use the `libucontext_` prefixed versions; at least we'll get a build error in Lwan if libucontext wasn't built correctly rather than silently building and then using the libc ucontext functions and definitions. I have confirmed that Lwan doesn't make calls to block/unblock the signal mask on AARCH64, which happens when using the libc ucontext functions. This should improve performance on non-x86 systems where libucontext is supported. --- CMakeLists.txt | 1 + README.md | 1 + src/cmake/lwan-build-config.h.cmake | 1 + src/lib/lwan-coro.c | 13 +++++++++++++ src/lib/lwan-coro.h | 3 +++ 5 files changed, 19 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f58ee12e..2072f10de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,6 +83,7 @@ if (CMAKE_SYSTEM_PROCESSOR MATCHES "^arm") if (LIBUCONTEXT_LIBRARY) message(STATUS "libucontext found, using it for context switching routines") list(APPEND ADDITIONAL_LIBRARIES "${LIBUCONTEXT_LIBRARY}") + set(HAVE_LIBUCONTEXT 1) else () message(STATUS "Trying to use libc context switching; this might not work!") endif () diff --git a/README.md b/README.md index 5a3ec16f0..6c12a6c93 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ The build system will look for these libraries and enable/link if available. - [Valgrind](http://valgrind.org) - [Brotli](https://github.com/google/brotli) - [ZSTD](https://github.com/facebook/zstd) + - [libucontext](https://github.com/kaniini/libucontext) is **recommended** on non-x86 targets - Alternative memory allocators can be used by passing `-DUSE_ALTERNATIVE_MALLOC` to CMake with the following values: - ["mimalloc"](https://github.com/microsoft/mimalloc) - ["jemalloc"](http://jemalloc.net/) diff --git a/src/cmake/lwan-build-config.h.cmake b/src/cmake/lwan-build-config.h.cmake index ecb6b3286..c5f1b045d 100644 --- a/src/cmake/lwan-build-config.h.cmake +++ b/src/cmake/lwan-build-config.h.cmake @@ -61,6 +61,7 @@ #cmakedefine HAVE_LUA #cmakedefine HAVE_BROTLI #cmakedefine HAVE_ZSTD +#cmakedefine HAVE_LIBUCONTEXT /* Valgrind support for coroutines */ #cmakedefine HAVE_VALGRIND diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 87250af68..c75582e62 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -199,6 +199,8 @@ asm(".text\n\t" "movl 0xc(%eax),%ebp\n\t" /* EBP */ "movl 0x1c(%eax),%ecx\n\t" /* ECX */ "ret\n\t"); +#elif defined(HAVE_LIBUCONTEXT) +#define coro_swapcontext(cur, oth) libucontext_swapcontext(cur, oth) #else #define coro_swapcontext(cur, oth) swapcontext(cur, oth) #endif @@ -291,16 +293,27 @@ void coro_reset(struct coro *coro, coro_function_t func, void *data) #define STACK_PTR 6 coro->context[STACK_PTR] = (uintptr_t)stack; #else + +#if defined(HAVE_LIBUCONTEXT) + libucontext_getcontext(&coro->context); +#else getcontext(&coro->context); +#endif coro->context.uc_stack.ss_sp = stack; coro->context.uc_stack.ss_size = CORO_STACK_SIZE; coro->context.uc_stack.ss_flags = 0; coro->context.uc_link = NULL; +#if defined(HAVE_LIBUCONTEXT) + libucontext_makecontext(&coro->context, (void (*)())coro_entry_point, 3, + coro, func, data); +#else makecontext(&coro->context, (void (*)())coro_entry_point, 3, coro, func, data); #endif + +#endif } ALWAYS_INLINE struct coro * diff --git a/src/lib/lwan-coro.h b/src/lib/lwan-coro.h index b3cbe4286..e477e9f87 100644 --- a/src/lib/lwan-coro.h +++ b/src/lib/lwan-coro.h @@ -27,6 +27,9 @@ typedef uintptr_t coro_context[10]; #elif defined(__i386__) typedef uintptr_t coro_context[7]; +#elif defined(HAS_LIBUCONTEXT) +#include +typedef libucontext_ucontext_t coro_context; #else #include typedef ucontext_t coro_context; From d128e4cba2325745864080f9175739a343bdd8a2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 4 Feb 2021 17:49:02 -0800 Subject: [PATCH 1638/2505] README.md tweaks --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 6c12a6c93..c0ecccad5 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,8 @@ Alternative memory allocators can be selected as well. Lwan currently supports [TCMalloc](https://github.com/gperftools/gperftools), [mimalloc](https://github.com/microsoft/mimalloc), and [jemalloc](http://jemalloc.net/) out of the box. To use either one of them, -pass `-DALTERNATIVE_MALLOC=ON` to the CMake invocation line. +pass `-DALTERNATIVE_MALLOC=name` to the CMake invocation line, using the +names provided in the "Optional dependencies" section. ### Tests @@ -234,8 +235,8 @@ Some examples can be found in `lwan.conf` and `techempower.conf`. #### Time Intervals Time fields can be specified using multipliers. Multiple can be specified, they're -just added together; for instance, "1M 1w" specifies "1 month and 1 week". The following -table lists all known multipliers: +just added together; for instance, "1M 1w" specifies "1 month and 1 week" +(37 days). The following table lists all known multipliers: | Multiplier | Description | |------------|-------------| @@ -243,9 +244,9 @@ table lists all known multipliers: | `m` | Minutes | | `h` | Hours | | `d` | Days | -| `w` | Weeks | -| `M` | Months | -| `y` | Years | +| `w` | 7-day Weeks | +| `M` | 30-day Months | +| `y` | 365-day Years | A number with a multiplier not in this table is ignored; a warning is issued while reading the configuration file. No spaces must exist between the number and its From 2fff0fc6c9d3e26add51e30c5fbe1b890688883e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 4 Feb 2021 17:49:12 -0800 Subject: [PATCH 1639/2505] Fixes fuzzing build after d9d37001 Fixes https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=30276 --- src/lib/lwan-request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 1772b1816..eae79e08f 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -734,7 +734,7 @@ static ALWAYS_INLINE void parse_connection_header(struct lwan_request *request) } #if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) -static void save_to_corpus_for_fuzzing(const struct lwan_value buffer) +static void save_to_corpus_for_fuzzing(struct lwan_value buffer) { struct lwan_value buffer_copy; char corpus_name[PATH_MAX]; From 41936f01a1622396fd37b407118e18b83e71cb36 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 4 Feb 2021 17:50:00 -0800 Subject: [PATCH 1640/2505] Trim request string buffer between requests/websocket messages While this was fine for the usual request side of things, a WebSockets client could potentially uncontrollably make Lwan allocate memory until malloc would exhaust because the string buffer never trimmed the allocated buffer (it only changed the buffer length, never its capacity/allocated buffer). Fix this by introducing a new lwan_strbuf_reset_trim() API, which works pretty much like lwan_strbuf_reset(), but takes a size_t parameter that forces the old buffer to freed and a new buffer to be allocated with that size *if* the old capacity exceeds the parameter. With this, each request will now trim the string buffer to 2KB between requests in the same connection, and, by default, each WebSockets message read will force the buffer to be 1KB or smaller before potentially reallocating; for WebSockets, though, it's possible to tune the hint parameter if the expected packet should be larger than 1KB. --- src/lib/lwan-strbuf.c | 18 ++++++++++++++++++ src/lib/lwan-strbuf.h | 1 + src/lib/lwan-thread.c | 4 +++- src/lib/lwan-websocket.c | 15 +++++++++++++-- src/lib/lwan.h | 1 + 5 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-strbuf.c b/src/lib/lwan-strbuf.c index 2c3f76654..b7c1e6cc8 100644 --- a/src/lib/lwan-strbuf.c +++ b/src/lib/lwan-strbuf.c @@ -262,6 +262,24 @@ void lwan_strbuf_reset(struct lwan_strbuf *s) s->used = 0; } +void lwan_strbuf_reset_trim(struct lwan_strbuf *s, size_t trim_thresh) +{ + if (s->flags & BUFFER_MALLOCD && s->capacity > trim_thresh) { + /* Not using realloc() here because we don't care about the contents + * of this buffer after reset is called, but we want to maintain a + * buffer already allocated of up to trim_thresh bytes. */ + void *tmp = malloc(trim_thresh); + + if (tmp) { + free(s->buffer); + s->buffer = tmp; + s->capacity = trim_thresh; + } + } + + return lwan_strbuf_reset(s); +} + /* This function is quite dangerous, so the prototype is only in lwan-private.h */ char *lwan_strbuf_extend_unsafe(struct lwan_strbuf *s, size_t by) { diff --git a/src/lib/lwan-strbuf.h b/src/lib/lwan-strbuf.h index ffcee5dce..b34bd611f 100644 --- a/src/lib/lwan-strbuf.h +++ b/src/lib/lwan-strbuf.h @@ -41,6 +41,7 @@ struct lwan_strbuf *lwan_strbuf_new(void); void lwan_strbuf_free(struct lwan_strbuf *s); void lwan_strbuf_reset(struct lwan_strbuf *s); +void lwan_strbuf_reset_trim(struct lwan_strbuf *s, size_t trim_thresh); bool lwan_strbuf_append_char(struct lwan_strbuf *s, const char c); diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 467406c8d..74a25738d 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -153,7 +153,9 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, coro_yield(coro, CONN_CORO_WANT_READ); } - lwan_strbuf_reset(&strbuf); + /* Ensure string buffer is reset between requests, and that the backing + * store isn't over 2KB. */ + lwan_strbuf_reset_trim(&strbuf, 2048); /* Only allow flags from config. */ flags = request.flags & (REQUEST_PROXIED | REQUEST_ALLOW_CORS); diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index 8998b09da..dc6b9759b 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -210,7 +210,7 @@ static void unmask(char *msg, size_t msg_len, char mask[static 4]) } } -int lwan_response_websocket_read(struct lwan_request *request) +int lwan_response_websocket_read_hint(struct lwan_request *request, size_t size_hint) { enum ws_opcode opcode = WS_OPCODE_INVALID; enum ws_opcode last_opcode; @@ -220,7 +220,7 @@ int lwan_response_websocket_read(struct lwan_request *request) if (!(request->conn->flags & CONN_IS_WEBSOCKET)) return ENOTCONN; - lwan_strbuf_reset(request->response.buffer); + lwan_strbuf_reset_trim(request->response.buffer, size_hint); next_frame: last_opcode = opcode; @@ -299,3 +299,14 @@ int lwan_response_websocket_read(struct lwan_request *request) return (request->conn->flags & CONN_IS_WEBSOCKET) ? 0 : ECONNRESET; } + +inline int lwan_response_websocket_read(struct lwan_request *request) +{ + /* Ensure that a rogue client won't keep increasing the memory usage in an + * uncontrolled manner by curbing the backing store to 1KB at most by default. + * If an application expects messages to be larger than 1024 bytes on average, + * they can call lwan_response_websocket_read_hint() directly with a larger + * value to avoid malloc chatter (things should still work, but will be + * slightly more inefficient). */ + return lwan_response_websocket_read_hint(request, 1024); +} diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 1fe6c6a50..692aa6bb7 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -586,6 +586,7 @@ enum lwan_http_status lwan_request_websocket_upgrade(struct lwan_request *request); void lwan_response_websocket_write(struct lwan_request *request); int lwan_response_websocket_read(struct lwan_request *request); +int lwan_response_websocket_read_hint(struct lwan_request *request, size_t size_hint); void lwan_request_await_read(struct lwan_request *r, int fd); void lwan_request_await_write(struct lwan_request *r, int fd); From 4c56498ccf405ff5918dfdd82648be28b79e668c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 4 Feb 2021 17:54:25 -0800 Subject: [PATCH 1641/2505] Fix checking of tmpfs temporary directories on Linux --- src/lib/missing/sys/vfs.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/missing/sys/vfs.h b/src/lib/missing/sys/vfs.h index 2539fe8cf..a55dfb2d5 100644 --- a/src/lib/missing/sys/vfs.h +++ b/src/lib/missing/sys/vfs.h @@ -23,6 +23,7 @@ #include #elif defined(__linux__) #include_next +#include #endif #ifndef _MISSING_VFS_H_ From 95db853763fabe78fe525a3367d2396642aaf27b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 5 Feb 2021 22:27:54 -0800 Subject: [PATCH 1642/2505] Build libucontext if building on non-x86 platforms Use CMake's ExternalProject module to fetch the latest libucontext, build it, and statically link with Lwan. Tested on a Raspberry Pi running Raspbian. --- CMakeLists.txt | 19 ++++--------------- src/lib/CMakeLists.txt | 29 ++++++++++++++++++++++++++++- src/lib/lwan-coro.h | 2 +- 3 files changed, 33 insertions(+), 17 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2072f10de..f7556aa8c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,21 +76,6 @@ if (ZSTD_FOUND) set(HAVE_ZSTD 1) endif () -if (CMAKE_SYSTEM_PROCESSOR MATCHES "^arm") - message(STATUS "${CMAKE_SYSTEM_PROCESSOR} processor detected, looking for libucontext") - - find_library(LIBUCONTEXT_LIBRARY NAMES libucontext.a) - if (LIBUCONTEXT_LIBRARY) - message(STATUS "libucontext found, using it for context switching routines") - list(APPEND ADDITIONAL_LIBRARIES "${LIBUCONTEXT_LIBRARY}") - set(HAVE_LIBUCONTEXT 1) - else () - message(STATUS "Trying to use libc context switching; this might not work!") - endif () -else () - message(STATUS "Using built-in context switching routines for ${CMAKE_SYSTEM_PROCESSOR} processors") -endif () - option(USE_ALTERNATIVE_MALLOC "Use alternative malloc implementations" "OFF") if (USE_ALTERNATIVE_MALLOC) unset(ALTMALLOC_LIBS CACHE) @@ -294,6 +279,10 @@ else () set(LWAN_COMMON_LIBS -Wl,-whole-archive lwan-static -Wl,-no-whole-archive) endif () +if (NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^i[3456]86|x86_64") + set(HAVE_LIBUCONTEXT 1) +endif () + include_directories(src/lib) include_directories(BEFORE src/lib/missing) diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index 6c5b438ba..22fd7fc4f 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -106,13 +106,40 @@ add_custom_target(generate_auto_index_icons ) add_dependencies(lwan-static generate_auto_index_icons) - include_directories(${CMAKE_BINARY_DIR}) if (NOT HAVE_BUILTIN_FPCLASSIFY) set(ADDITIONAL_LIBRARIES ${ADDITIONAL_LIBRARIES} -lm PARENT_SCOPE) endif () +if (HAVE_LIBUCONTEXT) + message(STATUS "Using libucontext/${CMAKE_SYSTEM_PROCESSOR} for coroutine context switching") + + include(ExternalProject) + + ExternalProject_Add(libucontext + GIT_REPOSITORY https://github.com/kaniini/libucontext + + BUILD_IN_SOURCE ON + + CONFIGURE_COMMAND "" + BUILD_COMMAND make + INSTALL_COMMAND make install DESTDIR=${CMAKE_BINARY_DIR} + + BUILD_BYPRODUCTS ${CMAKE_BINARY_DIR}/lib/libucontext.a + + BUILD_ALWAYS OFF + UPDATE_DISCONNECTED ON + ) + add_dependencies(lwan-static libucontext) + + set(ADDITIONAL_LIBRARIES ${CMAKE_BINARY_DIR}/lib/libucontext.a ${ADDITIONAL_LIBRARIES} PARENT_SCOPE) + include_directories(${CMAKE_BINARY_DIR}/usr/include) +else () + message(STATUS "Using built-in context switching routines for ${CMAKE_SYSTEM_PROCESSOR} processors") +endif () + + install( TARGETS lwan-static lwan-shared DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR} diff --git a/src/lib/lwan-coro.h b/src/lib/lwan-coro.h index e477e9f87..9edfa16cd 100644 --- a/src/lib/lwan-coro.h +++ b/src/lib/lwan-coro.h @@ -27,7 +27,7 @@ typedef uintptr_t coro_context[10]; #elif defined(__i386__) typedef uintptr_t coro_context[7]; -#elif defined(HAS_LIBUCONTEXT) +#elif defined(HAVE_LIBUCONTEXT) #include typedef libucontext_ucontext_t coro_context; #else From 7a1c0ecb519b037893f708f0158c777055eb64d4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 5 Feb 2021 22:28:54 -0800 Subject: [PATCH 1643/2505] Remove compatibility with libc-provided ucontext functions Since libucontext is now used on everything that's not supported by the inlined implementation in Lwan, with the dependency being built alongside Lwan itself, supporting this doesn't make any sense anymore. --- src/lib/lwan-coro.c | 14 ++------------ src/lib/lwan-coro.h | 3 +-- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index c75582e62..081b42686 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -202,7 +202,7 @@ asm(".text\n\t" #elif defined(HAVE_LIBUCONTEXT) #define coro_swapcontext(cur, oth) libucontext_swapcontext(cur, oth) #else -#define coro_swapcontext(cur, oth) swapcontext(cur, oth) +#error Unsupported platform. #endif __attribute__((used, visibility("internal"))) @@ -292,26 +292,16 @@ void coro_reset(struct coro *coro, coro_function_t func, void *data) #define STACK_PTR 6 coro->context[STACK_PTR] = (uintptr_t)stack; -#else - -#if defined(HAVE_LIBUCONTEXT) +#elif defined(HAVE_LIBUCONTEXT) libucontext_getcontext(&coro->context); -#else - getcontext(&coro->context); -#endif coro->context.uc_stack.ss_sp = stack; coro->context.uc_stack.ss_size = CORO_STACK_SIZE; coro->context.uc_stack.ss_flags = 0; coro->context.uc_link = NULL; -#if defined(HAVE_LIBUCONTEXT) libucontext_makecontext(&coro->context, (void (*)())coro_entry_point, 3, coro, func, data); -#else - makecontext(&coro->context, (void (*)())coro_entry_point, 3, coro, func, - data); -#endif #endif } diff --git a/src/lib/lwan-coro.h b/src/lib/lwan-coro.h index 9edfa16cd..ee39a6acc 100644 --- a/src/lib/lwan-coro.h +++ b/src/lib/lwan-coro.h @@ -31,8 +31,7 @@ typedef uintptr_t coro_context[7]; #include typedef libucontext_ucontext_t coro_context; #else -#include -typedef ucontext_t coro_context; +#error Unsupported platform. #endif struct coro; From 6f58009d9717ef068f8b238e34528f0ddeaa019f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 6 Feb 2021 09:23:29 -0800 Subject: [PATCH 1644/2505] Mention in README that libucontext is now downloaded/built on non-x86 --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c0ecccad5..b420647c9 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,6 @@ The build system will look for these libraries and enable/link if available. - [Valgrind](http://valgrind.org) - [Brotli](https://github.com/google/brotli) - [ZSTD](https://github.com/facebook/zstd) - - [libucontext](https://github.com/kaniini/libucontext) is **recommended** on non-x86 targets - Alternative memory allocators can be used by passing `-DUSE_ALTERNATIVE_MALLOC` to CMake with the following values: - ["mimalloc"](https://github.com/microsoft/mimalloc) - ["jemalloc"](http://jemalloc.net/) @@ -53,6 +52,8 @@ The build system will look for these libraries and enable/link if available. - Client libraries for either [MySQL](https://dev.mysql.com) or [MariaDB](https://mariadb.org) - [SQLite 3](http://sqlite.org) +On non-x86 systems, [libucontext](https://github.com/kaniini/libucontext) +will be downloaded and built alongside Lwan. ### Common operating system package names From 390a58708b188bff31595bd44da5031cc51175a2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 3 Mar 2021 08:27:08 -0800 Subject: [PATCH 1645/2505] No need to increment line_len while padding base64 output --- src/lib/base64.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/base64.c b/src/lib/base64.c index d85e04e02..66e002aa3 100644 --- a/src/lib/base64.c +++ b/src/lib/base64.c @@ -112,7 +112,6 @@ base64_encode(const unsigned char *src, size_t len, size_t *out_len) *pos++ = base64_table[(in[1] & 0x0f) << 2]; } *pos++ = '='; - line_len += 4; } *pos = '\0'; From 0d3bd6a2d8b0e2d7cfa6efad1a1d86823cd0b731 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 6 Mar 2021 00:59:02 -0800 Subject: [PATCH 1646/2505] Don't call sscanf() while parsing Range headers --- src/lib/lwan-request.c | 67 ++++++++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index eae79e08f..be23eac61 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -606,6 +606,28 @@ static void parse_if_modified_since(struct lwan_request_parser_helper *helper) helper->if_modified_since.parsed = parsed; } +static bool +parse_off_without_sign(const char *ptr, char **end, off_t *off) +{ + unsigned long long val; + + static_assert(sizeof(val) >= sizeof(off_t), + "off_t fits in a long long"); + + errno = 0; + + val = strtoull(ptr, end, 10); + if (UNLIKELY(val == 0 && *end == ptr)) + return false; + if (UNLIKELY(errno != 0)) + return false; + if (UNLIKELY(val > OFF_MAX)) + return false; + + *off = (off_t)val; + return true; +} + static void parse_range(struct lwan_request_parser_helper *helper) { @@ -617,31 +639,40 @@ parse_range(struct lwan_request_parser_helper *helper) return; range += sizeof("bytes=") - 1; - uint64_t from, to; - if (sscanf(range, "%"SCNu64"-%"SCNu64, &from, &to) == 2) { - if (UNLIKELY(from > OFF_MAX || to > OFF_MAX)) - goto invalid_range; + off_t from, to; + char *end; - helper->range.from = (off_t)from; - helper->range.to = (off_t)to; - } else if (sscanf(range, "-%"SCNu64, &to) == 1) { - if (UNLIKELY(to > OFF_MAX)) - goto invalid_range; + if (*range == '-') { + from = 0; - helper->range.from = 0; - helper->range.to = (off_t)to; - } else if (sscanf(range, "%"SCNu64"-", &from) == 1) { - if (UNLIKELY(from > OFF_MAX)) + if (!parse_off_without_sign(range + 1, &end, &to)) goto invalid_range; - - helper->range.from = (off_t)from; - helper->range.to = -1; + if (*end != '\0') + goto invalid_range; + } else if (lwan_char_isdigit(*range)) { + if (!parse_off_without_sign(range, &end, &from)) + goto invalid_range; + switch (*end) { + case '\0': + to = -1; + break; + case '-': + if (!parse_off_without_sign(end + 1, &end, &to)) + goto invalid_range; + if (*end != '\0') + goto invalid_range; + break; + default: + goto invalid_range; + } } else { invalid_range: - helper->range.from = -1; - helper->range.to = -1; + to = from = -1; } + + helper->range.from = from; + helper->range.to = to; } static void From 297970719d4626412900cebaef86773b6a9bc1e1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 6 Mar 2021 00:59:17 -0800 Subject: [PATCH 1647/2505] No need to always call strlen() when calculating the patch of index.html --- src/lib/lwan-mod-serve-files.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 389412a59..44fe2f020 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -767,11 +767,14 @@ static const struct cache_funcs *get_funcs(struct serve_files_priv *priv, char *index_html_path = index_html_path_buf; if (S_ISDIR(st->st_mode)) { + size_t index_html_path_len; + /* It is a directory. It might be the root directory (empty key), or * something else. In either case, tack priv->index_html to the * path. */ if (*key == '\0') { index_html_path = (char *)priv->index_html; + index_html_path_len = strlen(index_html_path); } else { /* Redirect /path to /path/. This is to help cases where there's * something like , so that actually @@ -784,6 +787,8 @@ static const struct cache_funcs *get_funcs(struct serve_files_priv *priv, priv->index_html); if (UNLIKELY(ret < 0 || ret >= PATH_MAX)) return NULL; + + index_html_path_len = (size_t)ret; } /* See if it exists. */ @@ -805,10 +810,7 @@ static const struct cache_funcs *get_funcs(struct serve_files_priv *priv, return NULL; /* If it does, we want its full path. */ - /* FIXME: Use strlcpy() here instead of calling strlen()? */ - if (UNLIKELY(priv->root_path_len + - strlen(index_html_path) + 1 >= - PATH_MAX)) + if (UNLIKELY(priv->root_path_len + index_html_path_len + 1 >= PATH_MAX)) return NULL; strncpy(full_path + priv->root_path_len, index_html_path, From 867cf8d04eb08720547e41e7792b74e73dc70d34 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 6 Mar 2021 02:11:13 -0800 Subject: [PATCH 1648/2505] Fix parsing of Range headers without upper bound This fixes 0d3bd6a2. --- src/lib/lwan-request.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index be23eac61..0e1ae6333 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -653,18 +653,17 @@ parse_range(struct lwan_request_parser_helper *helper) } else if (lwan_char_isdigit(*range)) { if (!parse_off_without_sign(range, &end, &from)) goto invalid_range; - switch (*end) { - case '\0': + if (*end != '-') + goto invalid_range; + + range = end + 1; + if (*range == '\0') { to = -1; - break; - case '-': - if (!parse_off_without_sign(end + 1, &end, &to)) + } else { + if (!parse_off_without_sign(range, &end, &to)) goto invalid_range; if (*end != '\0') goto invalid_range; - break; - default: - goto invalid_range; } } else { invalid_range: From cc6d2f1348ac330afa3c367f0d667d7f8d57b35d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 6 Mar 2021 02:45:04 -0800 Subject: [PATCH 1649/2505] If read_request() fails, just abort the coroutine after responding If read_request() returns any error at this point, it's probably better to just send an error response and abort the coroutine and let the client handle the error instead: we don't have information to even log the request because it has not been parsed yet at this stage. Even if there are other requests waiting in the pipeline, this seems like the safer thing to do. --- src/lib/lwan-request.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 0e1ae6333..b0f692564 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1454,14 +1454,12 @@ void lwan_process_request(struct lwan *l, struct lwan_request *request) status = read_request(request); if (UNLIKELY(status != HTTP_OK)) { - /* This request was bad, but maybe there's a good one in the - * pipeline. */ - if (status == HTTP_BAD_REQUEST && request->helper->next_request) - return; - - /* Response here can be: HTTP_TOO_LARGE, HTTP_BAD_REQUEST (without - * next request), or HTTP_TIMEOUT. Nothing to do, just abort the - * coroutine. */ + /* If read_request() returns any error at this point, it's probably + * better to just send an error response and abort the coroutine and + * let the client handle the error instead: we don't have + * information to even log the request because it has not been + * parsed yet at this stage. Even if there are other requests waiting + * in the pipeline, this seems like the safer thing to do. */ lwan_default_response(request, status); coro_yield(request->conn->coro, CONN_CORO_ABORT); __builtin_unreachable(); From f51cd6cc6f929107c283ec3dfda9bab431a14d87 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 22 Mar 2021 18:07:44 -0700 Subject: [PATCH 1650/2505] Provide better errors when lexing/parsing config files --- src/lib/lwan-config.c | 88 +++++++++++++++++++++++-------------------- 1 file changed, 48 insertions(+), 40 deletions(-) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index dbf621fde..84365de1d 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -39,9 +39,23 @@ #include "ringbuffer.h" -#define FOR_EACH_LEXEME(X) \ - X(ERROR) X(STRING) X(EQUAL) X(OPEN_BRACKET) X(CLOSE_BRACKET) X(LINEFEED) \ - X(VARIABLE) X(VARIABLE_DEFAULT) X(EOF) +#define LEX_ERROR(lexer, fmt, ...) \ + ({ \ + config_error(config_from_lexer(lexer), "Syntax error: " fmt, \ + ##__VA_ARGS__); \ + NULL; \ + }) + +#define PARSER_ERROR(parser, fmt, ...) \ + ({ \ + config_error(config_from_parser(parser), "Parsing error: " fmt, \ + ##__VA_ARGS__); \ + NULL; \ + }) + +#define FOR_EACH_LEXEME(X) \ + X(STRING) X(EQUAL) X(OPEN_BRACKET) X(CLOSE_BRACKET) X(LINEFEED) X(VARIABLE) \ + X(VARIABLE_DEFAULT) X(EOF) #define GENERATE_ENUM(id) LEXEME_ ## id, #define GENERATE_ARRAY_ITEM(id) [LEXEME_ ## id] = #id, @@ -286,19 +300,16 @@ static void *lex_string(struct lexer *lexer) return lex_config; } -static void *lex_error(struct lexer *lexer, const char *msg) +static struct config *config_from_parser(struct parser *parser) { - struct lexeme lexeme = { - .type = LEXEME_ERROR, - .value = { - .value = msg, - .len = strlen(msg) - } - }; + return container_of(parser, struct config, parser); +} - emit_lexeme(lexer, &lexeme); +static struct config *config_from_lexer(struct lexer *lexer) +{ + struct parser *parser = container_of(lexer, struct parser, lexer); - return NULL; + return config_from_parser(parser); } static bool lex_streq(struct lexer *lexer, const char *str, size_t s) @@ -324,7 +335,7 @@ static void *lex_multiline_string(struct lexer *lexer) } } while (next(lexer) != '\0'); - return lex_error(lexer, "EOF while scanning multiline string"); + return LEX_ERROR(lexer, "EOF while scanning multiline string"); } static bool is_variable(int chr) @@ -349,7 +360,7 @@ static void *lex_variable_default(struct lexer *lexer) } } while (chr != '\0'); - return lex_error(lexer, "EOF while scanning for end of variable"); + return LEX_ERROR(lexer, "EOF while scanning for end of variable"); } static void *lex_variable(struct lexer *lexer) @@ -377,7 +388,7 @@ static void *lex_variable(struct lexer *lexer) } } while (is_variable(chr)); - return lex_error(lexer, "EOF while scanning for end of variable"); + return LEX_ERROR(lexer, "EOF while scanning for end of variable"); } static bool is_comment(int chr) @@ -440,7 +451,7 @@ static void *lex_config(struct lexer *lexer) if (is_string(chr)) return lex_string; - return lex_error(lexer, "Invalid character"); + return LEX_ERROR(lexer, "Invalid character: '%c'", chr); } emit(lexer, LEXEME_LINEFEED); @@ -467,11 +478,12 @@ static void *parse_config(struct parser *parser); #define ENV_VAR_NAME_LEN_MAX 64 -static __attribute__((noinline)) const char *secure_getenv_len(const char *key, size_t len) +static __attribute__((noinline)) const char * +secure_getenv_len(struct parser *parser, const char *key, size_t len) { if (UNLIKELY(len > ENV_VAR_NAME_LEN_MAX)) { - lwan_status_error("Variable name exceeds %d bytes", ENV_VAR_NAME_LEN_MAX); - return NULL; + return PARSER_ERROR(parser, "Variable name \"%.*s\" exceeds %d bytes", + (int)len, key, ENV_VAR_NAME_LEN_MAX); } return secure_getenv(strndupa(key, len)); @@ -500,13 +512,13 @@ static void *parse_key_value(struct parser *parser) case LEXEME_VARIABLE: { const char *value; - value = secure_getenv_len(lexeme->value.value, lexeme->value.len); + value = secure_getenv_len(parser, lexeme->value.value, + lexeme->value.len); if (lexeme->type == LEXEME_VARIABLE) { if (!value) { - lwan_status_error( - "Variable '$%.*s' not defined in environment", + return PARSER_ERROR( + parser, "Variable '$%.*s' not defined in environment", (int)lexeme->value.len, lexeme->value.value); - return NULL; } else { lwan_strbuf_append_strz(&parser->strbuf, value); } @@ -514,15 +526,14 @@ static void *parse_key_value(struct parser *parser) const struct lexeme *var_name = lexeme; if (!(lexeme = lex_next(&parser->lexer))) { - lwan_status_error( - "Default value for variable '$%.*s' not given", + return PARSER_ERROR( + parser, "Default value for variable '$%.*s' not given", (int)var_name->value.len, var_name->value.value); - return NULL; } if (lexeme->type != LEXEME_STRING) { - lwan_status_error("Wrong format for default value"); - return NULL; + return PARSER_ERROR(parser, + "Wrong format for default value"); } if (!value) { @@ -565,16 +576,15 @@ static void *parse_key_value(struct parser *parser) : NULL; default: - lwan_status_error("Unexpected token while parsing key-value: %s", - lexeme_type_str[lexeme->type]); - return NULL; + return PARSER_ERROR(parser, + "Unexpected token while parsing key-value: %s", + lexeme_type_str[lexeme->type]); } last_lexeme = lexeme->type; } - lwan_status_error("EOF while parsing key-value"); - return NULL; + return PARSER_ERROR(parser, "EOF while parsing key-value"); } static void *parse_section(struct parser *parser) @@ -647,22 +657,20 @@ static void *parse_config(struct parser *parser) return parse_config; case LEXEME_CLOSE_BRACKET: { - struct config_line line = { .type = CONFIG_LINE_TYPE_SECTION_END }; + struct config_line line = {.type = CONFIG_LINE_TYPE_SECTION_END}; if (config_ring_buffer_try_put(&parser->items, &line)) return parse_config; - lwan_status_error("Could not parse section end"); - return NULL; + return PARSER_ERROR(parser, "Could not parse section"); } case LEXEME_EOF: return NULL; default: - lwan_status_error("Unexpected lexeme type: %s", - lexeme_type_str[lexeme->type]); - return NULL; + return PARSER_ERROR(parser, "Unexpected lexeme type: %s", + lexeme_type_str[lexeme->type]); } } From 4b9430ac38c9d9a3efb4b3854e389aca7a53c42f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 22 Mar 2021 21:15:51 -0700 Subject: [PATCH 1651/2505] config, template: mmap() with MAP_PRIVATE --- src/lib/lwan-config.c | 2 +- src/lib/lwan-template.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 84365de1d..314f06cd6 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -709,7 +709,7 @@ config_open_path(const char *path, void **data, size_t *size) return NULL; } - mapped = mmap(NULL, (size_t)st.st_size, PROT_READ, MAP_SHARED, fd, 0); + mapped = mmap(NULL, (size_t)st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); close(fd); if (mapped == MAP_FAILED) return NULL; diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index df9d6c7b7..b77ba5199 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -1394,7 +1394,7 @@ lwan_tpl_compile_file(const char *filename, if (fstat(fd, &st) < 0) goto close_file; - mapped = mmap(NULL, (size_t)st.st_size, PROT_READ, MAP_SHARED, fd, 0); + mapped = mmap(NULL, (size_t)st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); if (mapped == MAP_FAILED) goto close_file; From 2af9644b4dd5871dd2237173efa4b37f85f9f190 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 22 Mar 2021 21:20:36 -0700 Subject: [PATCH 1652/2505] config: Better error message if using a variable in top level context --- src/lib/lwan-config.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 314f06cd6..1da459bb7 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -669,8 +669,15 @@ static void *parse_config(struct parser *parser) return NULL; default: - return PARSER_ERROR(parser, "Unexpected lexeme type: %s", - lexeme_type_str[lexeme->type]); + switch (lexeme->type) { + case LEXEME_VARIABLE: + case LEXEME_VARIABLE_DEFAULT: + return PARSER_ERROR(parser, "Variable '%.*s' can't be used here", + (int)lexeme->value.len, lexeme->value.value); + default: + return PARSER_ERROR(parser, "Unexpected lexeme type: %s", + lexeme_type_str[lexeme->type]); + } } } From 9db2e236fc4180330424c07f1c30c98962623d5d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 28 Mar 2021 14:43:19 -0700 Subject: [PATCH 1653/2505] Better error when EOF is found when looking for default value for variable --- src/lib/lwan-config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 1da459bb7..598c3e2aa 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -360,7 +360,7 @@ static void *lex_variable_default(struct lexer *lexer) } } while (chr != '\0'); - return LEX_ERROR(lexer, "EOF while scanning for end of variable"); + return LEX_ERROR(lexer, "EOF while scanning for default value for variable"); } static void *lex_variable(struct lexer *lexer) From 5b3b17f7cc8cd6c55a9ded75dee9dac3296a882f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 28 Mar 2021 14:43:46 -0700 Subject: [PATCH 1654/2505] Consider EOF=linefeed when parsing key/value --- src/lib/lwan-config.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 598c3e2aa..3a3bf3c57 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -568,6 +568,7 @@ static void *parse_key_value(struct parser *parser) backup(&parser->lexer); /* fallthrough */ + case LEXEME_EOF: case LEXEME_LINEFEED: line.key = lwan_strbuf_get_buffer(&parser->strbuf); line.value = line.key + key_size + 1; From afbe66de088c6615aa7b8a75679f5b567affce4a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 28 Mar 2021 14:51:19 -0700 Subject: [PATCH 1655/2505] Get rid of lexeme_type_str in config parser --- src/lib/lwan-config.c | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 3a3bf3c57..6c5d82ace 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -58,19 +58,13 @@ X(VARIABLE_DEFAULT) X(EOF) #define GENERATE_ENUM(id) LEXEME_ ## id, -#define GENERATE_ARRAY_ITEM(id) [LEXEME_ ## id] = #id, enum lexeme_type { FOR_EACH_LEXEME(GENERATE_ENUM) TOTAL_LEXEMES }; -static const char *lexeme_type_str[TOTAL_LEXEMES] = { - FOR_EACH_LEXEME(GENERATE_ARRAY_ITEM) -}; - #undef GENERATE_ENUM -#undef GENERATE_ARRAY_ITEM struct lexeme { enum lexeme_type type; @@ -576,10 +570,11 @@ static void *parse_key_value(struct parser *parser) ? parse_config : NULL; - default: - return PARSER_ERROR(parser, - "Unexpected token while parsing key-value: %s", - lexeme_type_str[lexeme->type]); + case LEXEME_OPEN_BRACKET: + return PARSER_ERROR(parser, "Open bracket not expected here"); + + case TOTAL_LEXEMES: + __builtin_unreachable(); } last_lexeme = lexeme->type; @@ -669,17 +664,16 @@ static void *parse_config(struct parser *parser) case LEXEME_EOF: return NULL; - default: - switch (lexeme->type) { - case LEXEME_VARIABLE: - case LEXEME_VARIABLE_DEFAULT: - return PARSER_ERROR(parser, "Variable '%.*s' can't be used here", - (int)lexeme->value.len, lexeme->value.value); - default: - return PARSER_ERROR(parser, "Unexpected lexeme type: %s", - lexeme_type_str[lexeme->type]); - } + case LEXEME_VARIABLE: + case LEXEME_VARIABLE_DEFAULT: + return PARSER_ERROR(parser, "Variable '%.*s' can't be used here", + (int)lexeme->value.len, lexeme->value.value); + + case TOTAL_LEXEMES: + __builtin_unreachable(); } + + return NULL; } static const struct config_line *parser_next(struct parser *parser) From 56381e749a00c159c889ad3afae6324f9b40e9a9 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 28 Mar 2021 15:25:25 -0700 Subject: [PATCH 1656/2505] Print configuration file parser error in main function --- src/bin/tools/configdump.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/bin/tools/configdump.c b/src/bin/tools/configdump.c index 4e9eb6081..fd86e9e81 100644 --- a/src/bin/tools/configdump.c +++ b/src/bin/tools/configdump.c @@ -69,12 +69,6 @@ dump(struct config *config, int indent_level) break; } } - - if (config_last_error(config)) { - lwan_status_critical("Error while reading configuration file (line %d): %s", - config_cur_line(config), - config_last_error(config)); - } } int main(int argc, char *argv[]) @@ -95,6 +89,12 @@ int main(int argc, char *argv[]) dump(config, 0); + if (config_last_error(config)) { + lwan_status_critical("Error while reading configuration file (line %d): %s\n", + config_cur_line(config), + config_last_error(config)); + } + config_close(config); return 0; From 337332a41cfb716b24b0f1273130350d84f31954 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 28 Mar 2021 15:26:35 -0700 Subject: [PATCH 1657/2505] Strengthen parsing of sections Configuration file parser would just stop, without signaling an error, when encountering a section without a name. For instance, adding "{}" to a configuration file in its own line would break it. --- src/lib/lwan-config.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 6c5d82ace..7ea2c8009 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -588,8 +588,9 @@ static void *parse_section(struct parser *parser) const struct lexeme *lexeme; size_t name_len; - if (!(lexeme = lexeme_ring_buffer_get_ptr_or_null(&parser->buffer))) - return NULL; + lexeme = lexeme_ring_buffer_get_ptr_or_null(&parser->buffer); + if (!lexeme || lexeme->type != LEXEME_STRING) + return PARSER_ERROR(parser, "Expecting a string"); lwan_strbuf_append_str(&parser->strbuf, lexeme->value.value, lexeme->value.len); @@ -597,6 +598,9 @@ static void *parse_section(struct parser *parser) lwan_strbuf_append_char(&parser->strbuf, '\0'); while ((lexeme = lexeme_ring_buffer_get_ptr_or_null(&parser->buffer))) { + if (lexeme->type != LEXEME_STRING) + return PARSER_ERROR(parser, "Expecting a string"); + lwan_strbuf_append_str(&parser->strbuf, lexeme->value.value, lexeme->value.len); From dda197b81c389563b29f162ff1f264d2196d848e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 28 Mar 2021 15:31:04 -0700 Subject: [PATCH 1658/2505] Better error handling for some unlikely conditions --- src/lib/lwan-config.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 7ea2c8009..95f8f1a89 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -633,10 +633,13 @@ static void *parse_section_shorthand(struct parser *parser) static void *parse_config(struct parser *parser) { - const struct lexeme *lexeme; + const struct lexeme *lexeme = lex_next(&parser->lexer); - if (!(lexeme = lex_next(&parser->lexer))) - return NULL; + if (!lexeme) { + /* EOF is signaled by a LEXEME_EOF from the parser, so + * this should never happen. */ + return PARSER_ERROR(parser, "Internal error: Could not obtain lexeme"); + } switch (lexeme->type) { case LEXEME_EQUAL: @@ -652,21 +655,22 @@ static void *parse_config(struct parser *parser) return parse_config; case LEXEME_STRING: - lexeme_ring_buffer_try_put(&parser->buffer, lexeme); + if (!lexeme_ring_buffer_try_put(&parser->buffer, lexeme)) + return PARSER_ERROR(parser, "Internal error: could not store string in ring buffer"); return parse_config; case LEXEME_CLOSE_BRACKET: { struct config_line line = {.type = CONFIG_LINE_TYPE_SECTION_END}; - if (config_ring_buffer_try_put(&parser->items, &line)) - return parse_config; + if (!config_ring_buffer_try_put(&parser->items, &line)) + return PARSER_ERROR(parser, "Internal error: could not store section end in ring buffer"); - return PARSER_ERROR(parser, "Could not parse section"); + return parse_config; } case LEXEME_EOF: - return NULL; + break; case LEXEME_VARIABLE: case LEXEME_VARIABLE_DEFAULT: From ae594ed865473b15d52f5a9b176f9dc5c4a03b2d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 28 Mar 2021 16:54:03 -0700 Subject: [PATCH 1659/2505] Ensure all key components are strings when concatenating --- src/lib/lwan-config.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 95f8f1a89..895a94551 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -491,6 +491,9 @@ static void *parse_key_value(struct parser *parser) size_t key_size; while ((lexeme = lexeme_ring_buffer_get_ptr_or_null(&parser->buffer))) { + if (lexeme->type != LEXEME_STRING) + return PARSER_ERROR(parser, "Expecting string"); + lwan_strbuf_append_str(&parser->strbuf, lexeme->value.value, lexeme->value.len); From 28edd3af99829fa177987b6f70bcbb1fb68b1576 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 28 Mar 2021 16:54:28 -0700 Subject: [PATCH 1660/2505] No lexemes should be waiting when encountering a close bracket This would not trigger an error, while it should: foo bar } baz = 1 2 3 --- src/lib/lwan-config.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 895a94551..2ce92bfc2 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -666,6 +666,9 @@ static void *parse_config(struct parser *parser) case LEXEME_CLOSE_BRACKET: { struct config_line line = {.type = CONFIG_LINE_TYPE_SECTION_END}; + if (!lexeme_ring_buffer_empty(&parser->buffer)) + return PARSER_ERROR(parser, "Not expecting a close bracket here"); + if (!config_ring_buffer_try_put(&parser->items, &line)) return PARSER_ERROR(parser, "Internal error: could not store section end in ring buffer"); From 4b6c26cfd10cb0f788ae61f68d8002cb6085e497 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 28 Mar 2021 17:00:29 -0700 Subject: [PATCH 1661/2505] Error out on key/value pairs and sections with empty names --- src/lib/lwan-config.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 2ce92bfc2..2cf2f6336 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -646,9 +646,15 @@ static void *parse_config(struct parser *parser) switch (lexeme->type) { case LEXEME_EQUAL: + if (lexeme_ring_buffer_empty(&parser->buffer)) + return PARSER_ERROR(parser, "Keys can´t be empty"); + return parse_key_value; case LEXEME_OPEN_BRACKET: + if (lexeme_ring_buffer_empty(&parser->buffer)) + return PARSER_ERROR(parser, "Section names can´t be empty"); + return parse_section; case LEXEME_LINEFEED: From 1d61a19584da7f9cb2a9cb7be227717773575917 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 28 Mar 2021 17:00:51 -0700 Subject: [PATCH 1662/2505] Ensure that premature EOF is an error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Couldn´t trigger this by crafting a bad configuration file. --- src/lib/lwan-config.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 2cf2f6336..691ab6a23 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -682,6 +682,9 @@ static void *parse_config(struct parser *parser) } case LEXEME_EOF: + if (!lexeme_ring_buffer_empty(&parser->buffer)) + return PARSER_ERROR(parser, "Internal error: Premature EOF"); + break; case LEXEME_VARIABLE: From 12ca457a4b7ab90a9f206339d268755110f50934 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 28 Mar 2021 17:03:47 -0700 Subject: [PATCH 1663/2505] Add another internal error message to config reader --- src/lib/lwan-config.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 691ab6a23..c2f63bf98 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -629,6 +629,8 @@ static void *parse_section_shorthand(struct parser *parser) if (config_ring_buffer_try_put(&parser->items, &line)) return next_state; + + return PARSER_ERROR(parser, "Internal error: couldn´t append line to internal ring buffer"); } return NULL; From 1d10155f0faf54ff3ed0fba1faf928e06c47ae79 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 29 Mar 2021 22:00:55 -0700 Subject: [PATCH 1664/2505] Do not require space before open bracket on sections --- src/lib/lwan-config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index c2f63bf98..c4d4e2fcb 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -268,7 +268,7 @@ static void *lex_variable(struct lexer *lexer); static bool is_string(int chr) { - return chr && !isspace(chr) && chr != '=' && chr != '#'; + return chr && !isspace(chr) && chr != '=' && chr != '#' && chr != '{' && chr != '}'; } static void *lex_string(struct lexer *lexer) From fd3f421827b9f7a2c73ed48e9276a6166b0f2bb8 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 1 Apr 2021 17:29:25 -0700 Subject: [PATCH 1665/2505] Do not require linefeed before a section close bracket --- src/lib/lwan-config.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index c4d4e2fcb..f51a39fb8 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -422,6 +422,12 @@ static void *lex_config(struct lexer *lexer) } if (chr == '}') { + /* Emitting a linefeed lexeme before a close bracket lexeme + * simplifies the parser and allows for situations where a + * section is closed when declaring a key/value pair + * (e.g. "section{key=value}" all in a single line). + */ + emit(lexer, LEXEME_LINEFEED); emit(lexer, LEXEME_CLOSE_BRACKET); return lex_config; } From 0e2572f16dc5455743199a89e69fa8188b6f313b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 1 Apr 2021 17:29:57 -0700 Subject: [PATCH 1666/2505] Error out if config ring buffer is full when pushing key/value --- src/lib/lwan-config.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index f51a39fb8..f8cc62fe6 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -572,12 +572,16 @@ static void *parse_key_value(struct parser *parser) /* fallthrough */ case LEXEME_EOF: + /* FIXME: EOF while in a global context is fine, but when in a section + * it's not. */ case LEXEME_LINEFEED: line.key = lwan_strbuf_get_buffer(&parser->strbuf); line.value = line.key + key_size + 1; - return config_ring_buffer_try_put(&parser->items, &line) - ? parse_config - : NULL; + + if (config_ring_buffer_try_put(&parser->items, &line)) + return parse_config; + + return PARSER_ERROR(parser, "Could not add key/value to ring buffer"); case LEXEME_OPEN_BRACKET: return PARSER_ERROR(parser, "Open bracket not expected here"); From 2218627b19ddece3efe5ff492ab2729cfd0d75d6 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 4 Apr 2021 09:53:06 -0700 Subject: [PATCH 1667/2505] Ensure sections aren't closed before they're opened --- src/lib/lwan-config.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index f8cc62fe6..c94fdd548 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -99,6 +99,7 @@ struct config { void *addr; size_t sz; } mapped; + int opened_brackets; }; unsigned int parse_time_period(const char *str, unsigned int default_value) @@ -667,6 +668,8 @@ static void *parse_config(struct parser *parser) if (lexeme_ring_buffer_empty(&parser->buffer)) return PARSER_ERROR(parser, "Section names can´t be empty"); + config_from_parser(parser)->opened_brackets++; + return parse_section; case LEXEME_LINEFEED: @@ -683,6 +686,10 @@ static void *parse_config(struct parser *parser) case LEXEME_CLOSE_BRACKET: { struct config_line line = {.type = CONFIG_LINE_TYPE_SECTION_END}; + struct config *config = config_from_parser(parser); + + if (!config->opened_brackets) + return PARSER_ERROR(parser, "Section closed before it opened"); if (!lexeme_ring_buffer_empty(&parser->buffer)) return PARSER_ERROR(parser, "Not expecting a close bracket here"); @@ -690,10 +697,15 @@ static void *parse_config(struct parser *parser) if (!config_ring_buffer_try_put(&parser->items, &line)) return PARSER_ERROR(parser, "Internal error: could not store section end in ring buffer"); + config->opened_brackets--; + return parse_config; } case LEXEME_EOF: + if (config_from_parser(parser)->opened_brackets) + return PARSER_ERROR(parser, "EOF while looking for a close bracket"); + if (!lexeme_ring_buffer_empty(&parser->buffer)) return PARSER_ERROR(parser, "Internal error: Premature EOF"); @@ -779,6 +791,7 @@ config_init_data(struct config *config, const void *data, size_t len) }; config->error_message = NULL; + config->opened_brackets = 0; lwan_strbuf_init(&config->parser.strbuf); config_ring_buffer_init(&config->parser.items); @@ -874,6 +887,7 @@ struct config *config_isolate_section(struct config *current_conf, isolated->mapped.addr = NULL; isolated->mapped.sz = 0; + /* Keep opened_brackets from the original */ lexer = &isolated->parser.lexer; lexer->start = lexer->pos; From f26941356571460f56b6855217387d6522baf7e2 Mon Sep 17 00:00:00 2001 From: pontscho Date: Sat, 10 Apr 2021 15:11:14 -0700 Subject: [PATCH 1668/2505] Add support for PUT method --- src/lib/lwan-mod-lua.c | 1 + src/lib/lwan-private.h | 4 +-- src/lib/lwan-request.c | 71 +++++++++++++++++++++++++---------------- src/lib/lwan-response.c | 2 +- src/lib/lwan.c | 17 ++++++++-- src/lib/lwan.h | 9 ++++-- 6 files changed, 69 insertions(+), 35 deletions(-) diff --git a/src/lib/lwan-mod-lua.c b/src/lib/lwan-mod-lua.c index dd252cdaf..183c692e4 100644 --- a/src/lib/lwan-mod-lua.c +++ b/src/lib/lwan-mod-lua.c @@ -307,6 +307,7 @@ static const struct lwan_module module = { .create_from_hash = lua_create_from_hash, .destroy = lua_destroy, .handle_request = lua_handle_request, + .flags = HANDLER_HAS_BODY_DATA, }; LWAN_REGISTER_MODULE(lua, &module); diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index fa7e0a347..cb3e73cb5 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -35,8 +35,8 @@ struct lwan_request_parser_helper { struct lwan_value query_string; /* Stuff after ? and before # */ - struct lwan_value post_data; /* Request body for POST */ - struct lwan_value content_type; /* Content-Type: for POST */ + struct lwan_value body_data; /* Request body for POST and PUT */ + struct lwan_value content_type; /* Content-Type: for POST and PUT */ struct lwan_value content_length; /* Content-Length: */ struct lwan_value connection; /* Connection: */ diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index b0f692564..29251a24c 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -406,7 +406,7 @@ static void parse_post_data(struct lwan_request *request) sizeof(content_type) - 1))) return; - parse_key_values(request, &helper->post_data, &helper->post_params, + parse_key_values(request, &helper->body_data, &helper->post_params, url_decode, '&'); } @@ -952,7 +952,7 @@ read_request(struct lwan_request *request) } static enum lwan_read_finalizer -post_data_finalizer(const struct lwan_value *buffer, +body_data_finalizer(const struct lwan_value *buffer, size_t want_to_read, const struct lwan_request *request, int n_packets) @@ -1020,7 +1020,7 @@ static const char *is_dir_good_for_tmp(const char *v) } static const char *temp_dir; -static const size_t post_buffer_temp_file_thresh = 1<<20; +static const size_t body_buffer_temp_file_thresh = 1<<20; static const char * get_temp_dir(void) @@ -1048,8 +1048,8 @@ get_temp_dir(void) return tmpdir; lwan_status_warning("Temporary directory could not be determined. POST " - "requests over %zu bytes will fail.", - post_buffer_temp_file_thresh); + "or PUT requests over %zu bytes bytes will fail.", + body_buffer_temp_file_thresh); return NULL; } @@ -1096,7 +1096,7 @@ struct file_backed_buffer { }; static void -free_post_buffer(void *data) +free_body_buffer(void *data) { struct file_backed_buffer *buf = data; @@ -1105,13 +1105,13 @@ free_post_buffer(void *data) } static void* -alloc_post_buffer(struct coro *coro, size_t size, bool allow_file) +alloc_body_buffer(struct coro *coro, size_t size, bool allow_file) { struct file_backed_buffer *buf; void *ptr = (void *)MAP_FAILED; int fd; - if (LIKELY(size < post_buffer_temp_file_thresh)) { + if (LIKELY(size < body_buffer_temp_file_thresh)) { ptr = coro_malloc(coro, size); if (LIKELY(ptr)) @@ -1140,7 +1140,7 @@ alloc_post_buffer(struct coro *coro, size_t size, bool allow_file) if (UNLIKELY(ptr == MAP_FAILED)) return NULL; - buf = coro_malloc_full(coro, sizeof(*buf), free_post_buffer); + buf = coro_malloc_full(coro, sizeof(*buf), free_body_buffer); if (UNLIKELY(!buf)) { munmap(ptr, size); return NULL; @@ -1152,7 +1152,7 @@ alloc_post_buffer(struct coro *coro, size_t size, bool allow_file) } static enum lwan_http_status -get_remaining_post_data_length(struct lwan_request *request, +get_remaining_body_data_length(struct lwan_request *request, const size_t max_size, size_t *total, size_t *have) @@ -1185,13 +1185,14 @@ get_remaining_post_data_length(struct lwan_request *request, if (*have < *total) return HTTP_PARTIAL_CONTENT; - helper->post_data.value = helper->next_request; - helper->post_data.len = *total; + helper->body_data.value = helper->next_request; + helper->body_data.len = *total; helper->next_request += *total; return HTTP_OK; } -static enum lwan_http_status read_post_data(struct lwan_request *request) +static enum lwan_http_status read_body_data(struct lwan_request *request, size_t max_data_size, + bool allow_temp_file) { /* Holy indirection, Batman! */ const struct lwan_config *config = &request->conn->thread->lwan->config; @@ -1200,18 +1201,25 @@ static enum lwan_http_status read_post_data(struct lwan_request *request) size_t total, have; char *new_buffer; - status = get_remaining_post_data_length(request, config->max_post_data_size, - &total, &have); + status = get_remaining_body_data_length(request, max_data_size, &total, &have); if (status != HTTP_PARTIAL_CONTENT) return status; - new_buffer = alloc_post_buffer(request->conn->coro, total + 1, - config->allow_post_temp_file); + const char *expect = lwan_request_get_header(request, "Expect"); + if (expect && strncmp(expect, "100-", 4) == 0) { + char headers[DEFAULT_HEADERS_SIZE]; + size_t header_len = (size_t)snprintf(headers, sizeof(headers), "HTTP/1.%c 100 Continue\r\n\r\n", + request->flags & REQUEST_IS_HTTP_1_0 ? '0' : '1'); + lwan_send(request, headers, header_len, 0); + } + + new_buffer = alloc_body_buffer(request->conn->coro, total + 1, + allow_temp_file); if (UNLIKELY(!new_buffer)) return HTTP_INTERNAL_ERROR; - helper->post_data.value = new_buffer; - helper->post_data.len = total; + helper->body_data.value = new_buffer; + helper->body_data.len = total; if (have) { new_buffer = mempcpy(new_buffer, helper->next_request, have); total -= have; @@ -1222,17 +1230,17 @@ static enum lwan_http_status read_post_data(struct lwan_request *request) helper->error_when_n_packets = lwan_calculate_n_packets(total); struct lwan_value buffer = {.value = new_buffer, .len = total}; - return client_read(request, &buffer, total, post_data_finalizer); + return client_read(request, &buffer, total, body_data_finalizer); } -static enum lwan_http_status discard_post_data(struct lwan_request *request) +static enum lwan_http_status discard_body_data(struct lwan_request *request) { /* Holy indirection, Batman! */ const struct lwan_config *config = &request->conn->thread->lwan->config; enum lwan_http_status status; size_t total, have; - status = get_remaining_post_data_length(request, config->max_post_data_size, + status = get_remaining_body_data_length(request, config->max_post_data_size, &total, &have); if (status != HTTP_PARTIAL_CONTENT) return status; @@ -1387,11 +1395,20 @@ static enum lwan_http_status prepare_for_response(struct lwan_url_map *url_map, request->url.len--; } - if (lwan_request_get_method(request) == REQUEST_METHOD_POST) { - if (url_map->flags & HANDLER_HAS_POST_DATA) - return read_post_data(request); + switch (lwan_request_get_method(request)) { + default: break; + + case REQUEST_METHOD_POST: + case REQUEST_METHOD_PUT: + if (url_map->flags & HANDLER_HAS_BODY_DATA) { + const struct lwan_config *config = &request->conn->thread->lwan->config; + + return lwan_request_get_method(request) == REQUEST_METHOD_POST + ? read_body_data(request, config->max_post_data_size, config->allow_post_temp_file) + : read_body_data(request, config->max_put_data_size, config->allow_put_temp_file); + } - enum lwan_http_status status = discard_post_data(request); + enum lwan_http_status status = discard_body_data(request); return (status == HTTP_OK) ? HTTP_NOT_ALLOWED : status; } @@ -1671,7 +1688,7 @@ lwan_request_get_if_modified_since(struct lwan_request *request, time_t *value) ALWAYS_INLINE const struct lwan_value * lwan_request_get_request_body(struct lwan_request *request) { - return &request->helper->post_data; + return &request->helper->body_data; } ALWAYS_INLINE const struct lwan_value * diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 4b939d1be..21b9b3bfe 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -322,7 +322,7 @@ size_t lwan_prepare_response_header_full( if (UNLIKELY(request->flags & REQUEST_ALLOW_CORS)) { APPEND_CONSTANT( "\r\nAccess-Control-Allow-Origin: *" - "\r\nAccess-Control-Allow-Methods: GET, POST, OPTIONS" + "\r\nAccess-Control-Allow-Methods: GET, POST, PUT, OPTIONS" "\r\nAccess-Control-Allow-Credentials: true" "\r\nAccess-Control-Allow-Headers: Origin, Accept, Content-Type"); } diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 7606970e6..07bb537af 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -63,6 +63,8 @@ static const struct lwan_config default_config = { .n_threads = 0, .max_post_data_size = 10 * DEFAULT_BUFFER_SIZE, .allow_post_temp_file = false, + .max_put_data_size = 10 * DEFAULT_BUFFER_SIZE, + .allow_put_temp_file = false, }; LWAN_HANDLER(brew_coffee) @@ -544,9 +546,20 @@ static bool setup_from_config(struct lwan *lwan, const char *path) config_error(conf, "Maximum post data can't be over 128MiB"); lwan->config.max_post_data_size = (size_t)max_post_data_size; + } else if (streq(line->key, "max_put_data_size")) { + long max_put_data_size = parse_long( + line->value, (long)default_config.max_put_data_size); + if (max_put_data_size < 0) + config_error(conf, "Negative maximum put data size"); + else if (max_put_data_size > 128 * (1 << 20)) + config_error(conf, + "Maximum put data can't be over 128MiB"); + lwan->config.max_put_data_size = (size_t)max_put_data_size; } else if (streq(line->key, "allow_temp_files")) { - lwan->config.allow_post_temp_file = - !!strstr(line->value, "post"); + if (strstr(line->value, "post")) + lwan->config.allow_post_temp_file = true; + if (strstr(line->value, "put")) + lwan->config.allow_put_temp_file = true; } else { config_error(conf, "Unknown config key: %s", line->key); } diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 692aa6bb7..1873a42d3 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -210,12 +210,12 @@ enum lwan_http_status { #undef GENERATE_ENUM_ITEM enum lwan_handler_flags { - HANDLER_HAS_POST_DATA = 1 << 0, + HANDLER_HAS_BODY_DATA = 1 << 0, HANDLER_MUST_AUTHORIZE = 1 << 1, HANDLER_CAN_REWRITE_URL = 1 << 2, HANDLER_DATA_IS_HASH_TABLE = 1 << 3, - HANDLER_PARSE_MASK = HANDLER_HAS_POST_DATA, + HANDLER_PARSE_MASK = HANDLER_HAS_BODY_DATA, }; /* 1<<0 set: response has body; see has_response_body() in lwan-response.c */ @@ -224,7 +224,8 @@ enum lwan_handler_flags { X(POST, post, (1 << 1 | 1 << 0), STR4_INT('P', 'O', 'S', 'T')) \ X(HEAD, head, (1 << 1), STR4_INT('H', 'E', 'A', 'D')) \ X(OPTIONS, options, (1 << 2), STR4_INT('O', 'P', 'T', 'I')) \ - X(DELETE, delete, (1 << 1 | 1 << 2), STR4_INT('D', 'E', 'L', 'E')) + X(DELETE, delete, (1 << 1 | 1 << 2), STR4_INT('D', 'E', 'L', 'E')) \ + X(PUT, put, (1 << 2 | 1 << 0), STR4_INT('P', 'U', 'T', ' ')) #define SELECT_MASK(upper, lower, mask, constant) mask | #define GENERATE_ENUM_ITEM(upper, lower, mask, constant) REQUEST_METHOD_##upper = mask, @@ -463,6 +464,7 @@ struct lwan_config { char *config_file_path; size_t max_post_data_size; + size_t max_put_data_size; unsigned int keep_alive_timeout; unsigned int expires; @@ -473,6 +475,7 @@ struct lwan_config { bool proxy_protocol; bool allow_cors; bool allow_post_temp_file; + bool allow_put_temp_file; }; struct lwan { From e79a84eed980fe68b652f7551f9bce1daef26727 Mon Sep 17 00:00:00 2001 From: pontscho Date: Sat, 10 Apr 2021 15:11:51 -0700 Subject: [PATCH 1669/2505] Add more methods to the Lua request metatable --- src/lib/lwan-lua.c | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index da129bd3f..1faadb510 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -93,11 +93,38 @@ static int request_param_getter(lua_State *L, return 1; } +LWAN_LUA_METHOD(remote_address) +{ + struct lwan_request *request = lwan_lua_get_request_from_userdata(L); + char ip_buffer[INET6_ADDRSTRLEN]; + lua_pushstring(L, lwan_request_get_remote_address(request, ip_buffer)); + return 1; +} + + LWAN_LUA_METHOD(header) { return request_param_getter(L, lwan_request_get_header); } +LWAN_LUA_METHOD(path) +{ + struct lwan_request *request = lwan_lua_get_request_from_userdata(L); + lua_pushlstring(L, request->url.value, request->url.len); + return 1; +} + +LWAN_LUA_METHOD(query_string) +{ + struct lwan_request *request = lwan_lua_get_request_from_userdata(L); + if (request->helper->query_string.len) { + lua_pushlstring(L, request->helper->query_string.value, request->helper->query_string.len); + } else { + lua_pushlstring(L, "", 0); + } + return 1; +} + LWAN_LUA_METHOD(query_param) { return request_param_getter(L, lwan_request_get_query_param); @@ -113,6 +140,17 @@ LWAN_LUA_METHOD(cookie) return request_param_getter(L, lwan_request_get_cookie); } +LWAN_LUA_METHOD(body) +{ + struct lwan_request *request = lwan_lua_get_request_from_userdata(L); + if (request->helper->body_data.len) { + lua_pushlstring(L, request->helper->body_data.value, request->helper->body_data.len); + } else { + lua_pushlstring(L, "", 0); + } + return 1; +} + LWAN_LUA_METHOD(ws_upgrade) { struct lwan_request *request = lwan_lua_get_request_from_userdata(L); From 31edd46fe2b7baa5af024eb630777b36f69c28c1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 10 Apr 2021 15:22:34 -0700 Subject: [PATCH 1670/2505] Document new setting and request metatable methods --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index b420647c9..a122ef441 100644 --- a/README.md +++ b/README.md @@ -277,6 +277,7 @@ can be decided automatically, so some configuration options are provided. | `threads` | `int` | `0` | Number of I/O threads. Default (0) is the number of online CPUs | | `proxy_protocol` | `bool` | `false` | Enables the [PROXY protocol](https://www.haproxy.com/blog/haproxy/proxy-protocol/). Versions 1 and 2 are supported. Only enable this setting if using Lwan behind a proxy, and the proxy supports this protocol; otherwise, this allows anybody to spoof origin IP addresses | | `max_post_data_size` | `int` | `40960` | Sets the maximum number of data size for POST requests, in bytes | +| `max_put_data_size` | `int` | `40960` | Sets the maximum number of data size for PUT requests, in bytes | ### Straitjacket @@ -426,6 +427,10 @@ information from the request, or to set the response, as seen below: - `req:ws_upgrade()` returns `1` if the connection could be upgraded to a WebSocket; `0` otherwise - `req:ws_write(str)` sends `str` through the WebSocket-upgraded connection - `req:ws_read()` returns a string with the contents of the last WebSocket frame, or a number indicating an status (ENOTCONN/107 on Linux if it has been disconnected; EAGAIN/11 on Linux if nothing was available; ENOMSG/42 on Linux otherwise). The return value here might change in the future for something more Lua-like. + - `req:remote_address()` returns a string with the remote IP address. + - `req:path()` returns a string with the request path. + - `req:query_string()` returns a string with the query string (empty string if no query string present). + - `req:body()` returns the request body (POST/PUT requests). Handler functions may return either `nil` (in which case, a `200 OK` response is generated), or a number matching an HTTP status code. Attempting to return From a65a00cc29f813e4b4dc1f8b25733b9c7711ee43 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 10 Apr 2021 15:29:45 -0700 Subject: [PATCH 1671/2505] Simplify check for "do we expect request body for this request?" --- src/lib/lwan-request.c | 48 ++++++++++++++++++++++---------------- src/lib/lwan.h | 53 +++++++++++++++++++++--------------------- 2 files changed, 55 insertions(+), 46 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 29251a24c..fabe5d6db 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1191,30 +1191,40 @@ get_remaining_body_data_length(struct lwan_request *request, return HTTP_OK; } -static enum lwan_http_status read_body_data(struct lwan_request *request, size_t max_data_size, - bool allow_temp_file) +static enum lwan_http_status read_body_data(struct lwan_request *request) { /* Holy indirection, Batman! */ const struct lwan_config *config = &request->conn->thread->lwan->config; struct lwan_request_parser_helper *helper = request->helper; enum lwan_http_status status; - size_t total, have; + size_t total, have, max_data_size; + bool allow_temp_file; char *new_buffer; - status = get_remaining_body_data_length(request, max_data_size, &total, &have); + if (lwan_request_get_method(request) == REQUEST_METHOD_POST) { + allow_temp_file = config->allow_post_temp_file; + max_data_size = config->max_post_data_size; + } else { + allow_temp_file = config->allow_put_temp_file; + max_data_size = config->max_put_data_size; + } + + status = + get_remaining_body_data_length(request, max_data_size, &total, &have); if (status != HTTP_PARTIAL_CONTENT) return status; const char *expect = lwan_request_get_header(request, "Expect"); if (expect && strncmp(expect, "100-", 4) == 0) { char headers[DEFAULT_HEADERS_SIZE]; - size_t header_len = (size_t)snprintf(headers, sizeof(headers), "HTTP/1.%c 100 Continue\r\n\r\n", - request->flags & REQUEST_IS_HTTP_1_0 ? '0' : '1'); + size_t header_len = (size_t)snprintf( + headers, sizeof(headers), "HTTP/1.%c 100 Continue\r\n\r\n", + request->flags & REQUEST_IS_HTTP_1_0 ? '0' : '1'); lwan_send(request, headers, header_len, 0); } - new_buffer = alloc_body_buffer(request->conn->coro, total + 1, - allow_temp_file); + new_buffer = + alloc_body_buffer(request->conn->coro, total + 1, allow_temp_file); if (UNLIKELY(!new_buffer)) return HTTP_INTERNAL_ERROR; @@ -1379,6 +1389,13 @@ lwan_request_websocket_upgrade(struct lwan_request *request) return HTTP_SWITCHING_PROTOCOLS; } +static inline bool request_has_body(const struct lwan_request *request) +{ + /* 3rd bit set in method: request method has body. See lwan.h, + * definition of FOR_EACH_REQUEST_METHOD() for more info. */ + return lwan_request_get_method(request) & 1 << 3; +} + static enum lwan_http_status prepare_for_response(struct lwan_url_map *url_map, struct lwan_request *request) { @@ -1395,18 +1412,9 @@ static enum lwan_http_status prepare_for_response(struct lwan_url_map *url_map, request->url.len--; } - switch (lwan_request_get_method(request)) { - default: break; - - case REQUEST_METHOD_POST: - case REQUEST_METHOD_PUT: - if (url_map->flags & HANDLER_HAS_BODY_DATA) { - const struct lwan_config *config = &request->conn->thread->lwan->config; - - return lwan_request_get_method(request) == REQUEST_METHOD_POST - ? read_body_data(request, config->max_post_data_size, config->allow_post_temp_file) - : read_body_data(request, config->max_put_data_size, config->allow_put_temp_file); - } + if (request_has_body(request)) { + if (url_map->flags & HANDLER_HAS_BODY_DATA) + return read_body_data(request); enum lwan_http_status status = discard_body_data(request); return (status == HTTP_OK) ? HTTP_NOT_ALLOWED : status; diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 1873a42d3..76b097411 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -219,13 +219,14 @@ enum lwan_handler_flags { }; /* 1<<0 set: response has body; see has_response_body() in lwan-response.c */ +/* 1<<3 set: request has body; see request_has_body() in lwan-request.c */ #define FOR_EACH_REQUEST_METHOD(X) \ X(GET, get, (1 << 0), STR4_INT('G', 'E', 'T', ' ')) \ - X(POST, post, (1 << 1 | 1 << 0), STR4_INT('P', 'O', 'S', 'T')) \ + X(POST, post, (1 << 3 | 1 << 1 | 1 << 0), STR4_INT('P', 'O', 'S', 'T')) \ X(HEAD, head, (1 << 1), STR4_INT('H', 'E', 'A', 'D')) \ X(OPTIONS, options, (1 << 2), STR4_INT('O', 'P', 'T', 'I')) \ X(DELETE, delete, (1 << 1 | 1 << 2), STR4_INT('D', 'E', 'L', 'E')) \ - X(PUT, put, (1 << 2 | 1 << 0), STR4_INT('P', 'U', 'T', ' ')) + X(PUT, put, (1 << 3 | 1 << 2 | 1 << 0), STR4_INT('P', 'U', 'T', ' ')) #define SELECT_MASK(upper, lower, mask, constant) mask | #define GENERATE_ENUM_ITEM(upper, lower, mask, constant) REQUEST_METHOD_##upper = mask, @@ -236,30 +237,30 @@ enum lwan_request_flags { REQUEST_METHOD_MASK = FOR_EACH_REQUEST_METHOD(SELECT_MASK) 0, FOR_EACH_REQUEST_METHOD(GENERATE_ENUM_ITEM) - REQUEST_ACCEPT_DEFLATE = 1 << 3, - REQUEST_ACCEPT_GZIP = 1 << 4, - REQUEST_ACCEPT_BROTLI = 1 << 5, - REQUEST_ACCEPT_ZSTD = 1 << 6, - REQUEST_ACCEPT_MASK = 1 << 3 | 1 << 4 | 1 << 5 | 1 << 6, - - REQUEST_IS_HTTP_1_0 = 1 << 7, - REQUEST_ALLOW_PROXY_REQS = 1 << 8, - REQUEST_PROXIED = 1 << 9, - REQUEST_ALLOW_CORS = 1 << 10, - - RESPONSE_SENT_HEADERS = 1 << 11, - RESPONSE_CHUNKED_ENCODING = 1 << 12, - RESPONSE_NO_CONTENT_LENGTH = 1 << 13, - RESPONSE_URL_REWRITTEN = 1 << 14, - - RESPONSE_STREAM = 1 << 15, - - REQUEST_PARSED_QUERY_STRING = 1 << 16, - REQUEST_PARSED_IF_MODIFIED_SINCE = 1 << 17, - REQUEST_PARSED_RANGE = 1 << 18, - REQUEST_PARSED_POST_DATA = 1 << 19, - REQUEST_PARSED_COOKIES = 1 << 20, - REQUEST_PARSED_ACCEPT_ENCODING = 1 << 21, + REQUEST_ACCEPT_DEFLATE = 1 << 4, + REQUEST_ACCEPT_GZIP = 1 << 5, + REQUEST_ACCEPT_BROTLI = 1 << 6, + REQUEST_ACCEPT_ZSTD = 1 << 7, + REQUEST_ACCEPT_MASK = 1 << 4 | 1 << 5 | 1 << 6 | 1 << 7, + + REQUEST_IS_HTTP_1_0 = 1 << 8, + REQUEST_ALLOW_PROXY_REQS = 1 << 9, + REQUEST_PROXIED = 1 << 10, + REQUEST_ALLOW_CORS = 1 << 11, + + RESPONSE_SENT_HEADERS = 1 << 12, + RESPONSE_CHUNKED_ENCODING = 1 << 13, + RESPONSE_NO_CONTENT_LENGTH = 1 << 14, + RESPONSE_URL_REWRITTEN = 1 << 15, + + RESPONSE_STREAM = 1 << 16, + + REQUEST_PARSED_QUERY_STRING = 1 << 17, + REQUEST_PARSED_IF_MODIFIED_SINCE = 1 << 18, + REQUEST_PARSED_RANGE = 1 << 19, + REQUEST_PARSED_POST_DATA = 1 << 20, + REQUEST_PARSED_COOKIES = 1 << 21, + REQUEST_PARSED_ACCEPT_ENCODING = 1 << 22, }; #undef SELECT_MASK From 5e78de64a9472570646f6e01866232e14a10c26d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 10 Apr 2021 15:35:58 -0700 Subject: [PATCH 1672/2505] Expect header will only be present on HTTP/1.1 requests So remove the call to snprintf() to simplify things a little bit. Add a link to the RFC2616 that explains how this header should be used. --- src/lib/lwan-request.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index fabe5d6db..2414bf59d 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1214,13 +1214,15 @@ static enum lwan_http_status read_body_data(struct lwan_request *request) if (status != HTTP_PARTIAL_CONTENT) return status; - const char *expect = lwan_request_get_header(request, "Expect"); - if (expect && strncmp(expect, "100-", 4) == 0) { - char headers[DEFAULT_HEADERS_SIZE]; - size_t header_len = (size_t)snprintf( - headers, sizeof(headers), "HTTP/1.%c 100 Continue\r\n\r\n", - request->flags & REQUEST_IS_HTTP_1_0 ? '0' : '1'); - lwan_send(request, headers, header_len, 0); + if (!(request->flags & REQUEST_IS_HTTP_1_0)) { + /* §8.2.3 https://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html */ + const char *expect = lwan_request_get_header(request, "Expect"); + + if (expect && strncmp(expect, "100-", 4) == 0) { + static const char continue_header[] = "HTTP/1.1 100 Continue\r\n\r\n"; + + lwan_send(request, continue_header, sizeof(continue_header) - 1, 0); + } } new_buffer = From 704c9fff82ea26e19a5bfcb365ae2fc771363e95 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 10 Apr 2021 20:17:11 -0700 Subject: [PATCH 1673/2505] Remove most calls to memmem() when servicing pipelined requests This gets rid of ~12% of the overall cycles when servicing simple requests in pipelined mode (gets rid of 1 branch misprediction/request when testing for "is this a pipelined request", and around 6 cycles/request). --- src/lib/lwan-request.c | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 2414bf59d..7806a6a6a 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -895,16 +895,10 @@ read_request_finalizer(const struct lwan_value *buffer, MIN_REQUEST_SIZE + sizeof(struct proxy_header_v2); struct lwan_request_parser_helper *helper = request->helper; - if (!(request->conn->flags & CONN_CORK)) { - /* CONN_CORK is set on pipelined requests. For non-pipelined requests, - * try looking at the last four bytes to see if we have a complete - * request in the buffer as a fast path. (The memmem() below appears - * in profiles with measurable impact.) */ - if (LIKELY(buffer->len >= MIN_REQUEST_SIZE)) { - STRING_SWITCH (buffer->value + buffer->len - 4) { - case STR4_INT('\r', '\n', '\r', '\n'): - return FINALIZER_DONE; - } + if (LIKELY(buffer->len >= MIN_REQUEST_SIZE)) { + STRING_SWITCH (buffer->value + buffer->len - 4) { + case STR4_INT('\r', '\n', '\r', '\n'): + return FINALIZER_DONE; } } From ddf3952d4277e3c8c44086625872b356b9224579 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 10 Apr 2021 20:19:18 -0700 Subject: [PATCH 1674/2505] Avoid calculating crlfcrlf_to_base if next_request is set --- src/lib/lwan-request.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 7806a6a6a..ea9f4a46c 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -904,13 +904,12 @@ read_request_finalizer(const struct lwan_value *buffer, char *crlfcrlf = memmem(buffer->value, buffer->len, "\r\n\r\n", 4); if (LIKELY(crlfcrlf)) { - const size_t crlfcrlf_to_base = (size_t)(crlfcrlf - buffer->value); - if (LIKELY(helper->next_request)) { helper->next_request = NULL; return FINALIZER_DONE; } + const size_t crlfcrlf_to_base = (size_t)(crlfcrlf - buffer->value); if (crlfcrlf_to_base >= MIN_REQUEST_SIZE - 4) return FINALIZER_DONE; From beadce52243f9ed1ccd39fc2d72db5f88d690c52 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 11 Apr 2021 08:20:31 -0700 Subject: [PATCH 1675/2505] Accept "all" for the global "allow_temp_files" setting Also document this configuration option, which was surprisingly undocumented. --- README.md | 1 + src/lib/lwan.c | 15 +++++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a122ef441..951843f7f 100644 --- a/README.md +++ b/README.md @@ -278,6 +278,7 @@ can be decided automatically, so some configuration options are provided. | `proxy_protocol` | `bool` | `false` | Enables the [PROXY protocol](https://www.haproxy.com/blog/haproxy/proxy-protocol/). Versions 1 and 2 are supported. Only enable this setting if using Lwan behind a proxy, and the proxy supports this protocol; otherwise, this allows anybody to spoof origin IP addresses | | `max_post_data_size` | `int` | `40960` | Sets the maximum number of data size for POST requests, in bytes | | `max_put_data_size` | `int` | `40960` | Sets the maximum number of data size for PUT requests, in bytes | +| `allow_temp_files` | `str` | `""` | Use temporary files; set to `post` for POST requests, `put` for PUT requests, or `all` (equivalent to setting to `post put`) for both.| ### Straitjacket diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 07bb537af..2d5d07a76 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -556,10 +556,17 @@ static bool setup_from_config(struct lwan *lwan, const char *path) "Maximum put data can't be over 128MiB"); lwan->config.max_put_data_size = (size_t)max_put_data_size; } else if (streq(line->key, "allow_temp_files")) { - if (strstr(line->value, "post")) - lwan->config.allow_post_temp_file = true; - if (strstr(line->value, "put")) - lwan->config.allow_put_temp_file = true; + bool has_post, has_put; + + if (strstr(line->value, "all")) { + has_post = has_put = true; + } else { + has_post = !!strstr(line->value, "post"); + has_put = !!strstr(line->value, "put"); + } + + lwan->config.allow_post_temp_file = has_post; + lwan->config.allow_put_temp_file = has_put; } else { config_error(conf, "Unknown config key: %s", line->key); } From 387dca98f2c60d10f66d7a7ed3246cc96e3e68ef Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 11 Apr 2021 08:27:47 -0700 Subject: [PATCH 1676/2505] LEXEME_LINEFEED will always be generated by the lexer before EOF So LEXEME_EOF shouldn't appear while reading a key/value. --- src/lib/lwan-config.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index c94fdd548..8f23fb03f 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -569,12 +569,10 @@ static void *parse_key_value(struct parser *parser) break; case LEXEME_CLOSE_BRACKET: + /* FIXME: backup() can´t be called by the parser! This is broken! */ backup(&parser->lexer); /* fallthrough */ - case LEXEME_EOF: - /* FIXME: EOF while in a global context is fine, but when in a section - * it's not. */ case LEXEME_LINEFEED: line.key = lwan_strbuf_get_buffer(&parser->strbuf); line.value = line.key + key_size + 1; @@ -587,6 +585,9 @@ static void *parse_key_value(struct parser *parser) case LEXEME_OPEN_BRACKET: return PARSER_ERROR(parser, "Open bracket not expected here"); + case LEXEME_EOF: + return PARSER_ERROR(parser, "Internal error: EOF found while parsing key/value"); + case TOTAL_LEXEMES: __builtin_unreachable(); } From 664d87b8bc0c3f4f16e29830c3528243959025fc Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 11 Apr 2021 08:33:56 -0700 Subject: [PATCH 1677/2505] backup() can't be called by the parser This would only work if the lexer's ring buffer is empty; otherwise, tokens would be produced in the wrong order. Remove the call and extract the code that handles a closing bracket to its own function, to act as the next state from both parse_config() and parse_key_value(). --- src/lib/lwan-config.c | 57 ++++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 8f23fb03f..a8afe833d 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -476,6 +476,7 @@ static const struct lexeme *lex_next(struct lexer *lexer) } static void *parse_config(struct parser *parser); +static void *parse_section_end(struct parser *parser); #define ENV_VAR_NAME_LEN_MAX 64 @@ -569,24 +570,24 @@ static void *parse_key_value(struct parser *parser) break; case LEXEME_CLOSE_BRACKET: - /* FIXME: backup() can´t be called by the parser! This is broken! */ - backup(&parser->lexer); - /* fallthrough */ - case LEXEME_LINEFEED: line.key = lwan_strbuf_get_buffer(&parser->strbuf); line.value = line.key + key_size + 1; - if (config_ring_buffer_try_put(&parser->items, &line)) - return parse_config; + if (config_ring_buffer_try_put(&parser->items, &line)) { + return lexeme->type == LEXEME_LINEFEED ? parse_config + : parse_section_end; + } - return PARSER_ERROR(parser, "Could not add key/value to ring buffer"); + return PARSER_ERROR(parser, + "Could not add key/value to ring buffer"); case LEXEME_OPEN_BRACKET: return PARSER_ERROR(parser, "Open bracket not expected here"); case LEXEME_EOF: - return PARSER_ERROR(parser, "Internal error: EOF found while parsing key/value"); + return PARSER_ERROR( + parser, "Internal error: EOF found while parsing key/value"); case TOTAL_LEXEMES: __builtin_unreachable(); @@ -648,6 +649,27 @@ static void *parse_section_shorthand(struct parser *parser) return NULL; } +static void *parse_section_end(struct parser *parser) +{ + struct config_line line = {.type = CONFIG_LINE_TYPE_SECTION_END}; + struct config *config = config_from_parser(parser); + + if (!config->opened_brackets) + return PARSER_ERROR(parser, "Section closed before it opened"); + + if (!lexeme_ring_buffer_empty(&parser->buffer)) + return PARSER_ERROR(parser, "Not expecting a close bracket here"); + + if (!config_ring_buffer_try_put(&parser->items, &line)) { + return PARSER_ERROR(parser, + "Internal error: could not store section end in ring buffer"); + } + + config->opened_brackets--; + + return parse_config; +} + static void *parse_config(struct parser *parser) { const struct lexeme *lexeme = lex_next(&parser->lexer); @@ -685,23 +707,8 @@ static void *parse_config(struct parser *parser) return parse_config; - case LEXEME_CLOSE_BRACKET: { - struct config_line line = {.type = CONFIG_LINE_TYPE_SECTION_END}; - struct config *config = config_from_parser(parser); - - if (!config->opened_brackets) - return PARSER_ERROR(parser, "Section closed before it opened"); - - if (!lexeme_ring_buffer_empty(&parser->buffer)) - return PARSER_ERROR(parser, "Not expecting a close bracket here"); - - if (!config_ring_buffer_try_put(&parser->items, &line)) - return PARSER_ERROR(parser, "Internal error: could not store section end in ring buffer"); - - config->opened_brackets--; - - return parse_config; - } + case LEXEME_CLOSE_BRACKET: + return parse_section_end; case LEXEME_EOF: if (config_from_parser(parser)->opened_brackets) From c83ecc1fa54cfea74d18522903d0e4dfffec7f2c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 11 Apr 2021 08:39:25 -0700 Subject: [PATCH 1678/2505] The lexer will always generate a LINEFEED before a CLOSE_BRACKET So this simplifies things when parsing key/values. --- src/lib/lwan-config.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index a8afe833d..25e07837c 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -569,15 +569,12 @@ static void *parse_key_value(struct parser *parser) break; - case LEXEME_CLOSE_BRACKET: case LEXEME_LINEFEED: line.key = lwan_strbuf_get_buffer(&parser->strbuf); line.value = line.key + key_size + 1; - if (config_ring_buffer_try_put(&parser->items, &line)) { - return lexeme->type == LEXEME_LINEFEED ? parse_config - : parse_section_end; - } + if (config_ring_buffer_try_put(&parser->items, &line)) + return parse_config; return PARSER_ERROR(parser, "Could not add key/value to ring buffer"); @@ -585,6 +582,10 @@ static void *parse_key_value(struct parser *parser) case LEXEME_OPEN_BRACKET: return PARSER_ERROR(parser, "Open bracket not expected here"); + case LEXEME_CLOSE_BRACKET: + return PARSER_ERROR( + parser, "Internal error: Close bracket found while parsing key/value"); + case LEXEME_EOF: return PARSER_ERROR( parser, "Internal error: EOF found while parsing key/value"); From 1f750be425d5ed410b1029f8b1e73e2d3cd34e20 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 11 Apr 2021 08:44:21 -0700 Subject: [PATCH 1679/2505] Simplify how LEXEME_VARIABLE_DEFAULT and LEXEME_VARIABLE are parsed --- src/lib/lwan-config.c | 71 +++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 25e07837c..8af1bc660 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -513,44 +513,43 @@ static void *parse_key_value(struct parser *parser) while ((lexeme = lex_next(&parser->lexer))) { switch (lexeme->type) { - case LEXEME_VARIABLE_DEFAULT: case LEXEME_VARIABLE: { - const char *value; - - value = secure_getenv_len(parser, lexeme->value.value, - lexeme->value.len); - if (lexeme->type == LEXEME_VARIABLE) { - if (!value) { - return PARSER_ERROR( - parser, "Variable '$%.*s' not defined in environment", - (int)lexeme->value.len, lexeme->value.value); - } else { - lwan_strbuf_append_strz(&parser->strbuf, value); - } + const char *value = secure_getenv_len(parser, lexeme->value.value, + lexeme->value.len); + if (!value) { + return PARSER_ERROR( + parser, "Variable '$%.*s' not defined in environment", + (int)lexeme->value.len, lexeme->value.value); + } + + lwan_strbuf_append_strz(&parser->strbuf, value); + + break; + } + + case LEXEME_VARIABLE_DEFAULT: { + const char *value = secure_getenv_len(parser, lexeme->value.value, + lexeme->value.len); + const struct lexeme *var_name = lexeme; + + if (!(lexeme = lex_next(&parser->lexer))) { + return PARSER_ERROR( + parser, "Default value for variable '$%.*s' not given", + (int)var_name->value.len, var_name->value.value); + } + + if (lexeme->type != LEXEME_STRING) + return PARSER_ERROR(parser, "Wrong format for default value"); + + if (!value) { + lwan_status_debug( + "Using default value of '%.*s' for variable '${%.*s}'", + (int)lexeme->value.len, lexeme->value.value, + (int)var_name->value.len, var_name->value.value); + lwan_strbuf_append_str(&parser->strbuf, lexeme->value.value, + lexeme->value.len); } else { - const struct lexeme *var_name = lexeme; - - if (!(lexeme = lex_next(&parser->lexer))) { - return PARSER_ERROR( - parser, "Default value for variable '$%.*s' not given", - (int)var_name->value.len, var_name->value.value); - } - - if (lexeme->type != LEXEME_STRING) { - return PARSER_ERROR(parser, - "Wrong format for default value"); - } - - if (!value) { - lwan_status_debug( - "Using default value of '%.*s' for variable '${%.*s}'", - (int)lexeme->value.len, lexeme->value.value, - (int)var_name->value.len, var_name->value.value); - lwan_strbuf_append_str(&parser->strbuf, lexeme->value.value, - lexeme->value.len); - } else { - lwan_strbuf_append_strz(&parser->strbuf, value); - } + lwan_strbuf_append_strz(&parser->strbuf, value); } break; From ec0e6aad3667227ae3de75373e707b719a0e421d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 11 Apr 2021 10:50:24 -0700 Subject: [PATCH 1680/2505] Better validation for HTTP status code in mod-response --- src/lib/lwan-mod-response.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/lib/lwan-mod-response.c b/src/lib/lwan-mod-response.c index dbcf4d704..f39c1131a 100644 --- a/src/lib/lwan-mod-response.c +++ b/src/lib/lwan-mod-response.c @@ -49,15 +49,21 @@ static void *response_create_from_hash(const char *prefix, return NULL; } - struct lwan_response_settings settings = { - .code = (enum lwan_http_status)parse_int(code, 999) - }; + int code_as_int = parse_int(code, -1); + if (code_as_int < 0) { + lwan_status_error("Couldn't parse `code` as an integer"); + return NULL; + } - if (settings.code == 999) { - lwan_status_error("Unknown error code: %s", code); + const char *valid_code = lwan_http_status_as_string_with_code(code_as_int); + if (!strncmp(valid_code, "999 ", 4)) { + lwan_status_error("Code %d isn't a known HTTP status code", code_as_int); return NULL; } + struct lwan_response_settings settings = { + .code = (enum lwan_http_status)code_as_int + }; return response_create(prefix, &settings); } From b556637a61c742bc7e52ce641889d57b45dc6269 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 11 Apr 2021 12:54:08 -0700 Subject: [PATCH 1681/2505] Rename REQUEST_PARSED_POST_DATA to REQUEST_PARSED_FORM_DATA PUT requests may also have form data, so rename this for consistency. --- src/lib/lwan-request.c | 8 ++++---- src/lib/lwan.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index ea9f4a46c..d81f3d9e2 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -395,7 +395,7 @@ static void parse_query_string(struct lwan_request *request) url_decode, '&'); } -static void parse_post_data(struct lwan_request *request) +static void parse_form_data(struct lwan_request *request) { struct lwan_request_parser_helper *helper = request->helper; static const char content_type[] = "application/x-www-form-urlencoded"; @@ -1725,9 +1725,9 @@ lwan_request_get_query_params(struct lwan_request *request) ALWAYS_INLINE const struct lwan_key_value_array * lwan_request_get_post_params(struct lwan_request *request) { - if (!(request->flags & REQUEST_PARSED_POST_DATA)) { - parse_post_data(request); - request->flags |= REQUEST_PARSED_POST_DATA; + if (!(request->flags & REQUEST_PARSED_FORM_DATA)) { + parse_form_data(request); + request->flags |= REQUEST_PARSED_FORM_DATA; } return &request->helper->post_params; diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 76b097411..0529f3aa6 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -258,7 +258,7 @@ enum lwan_request_flags { REQUEST_PARSED_QUERY_STRING = 1 << 17, REQUEST_PARSED_IF_MODIFIED_SINCE = 1 << 18, REQUEST_PARSED_RANGE = 1 << 19, - REQUEST_PARSED_POST_DATA = 1 << 20, + REQUEST_PARSED_FORM_DATA = 1 << 20, REQUEST_PARSED_COOKIES = 1 << 21, REQUEST_PARSED_ACCEPT_ENCODING = 1 << 22, }; From b61d3eb2f18952766ca44c5eb4f7e3bc2067edeb Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 11 Apr 2021 14:22:31 -0700 Subject: [PATCH 1682/2505] Add a FIXME for failure when reading request bodies --- src/lib/lwan-request.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index d81f3d9e2..e1f27e9e9 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1408,6 +1408,10 @@ static enum lwan_http_status prepare_for_response(struct lwan_url_map *url_map, } if (request_has_body(request)) { + /* FIXME: if read_body_data() fails somehow (e.g. buffer + * allocation failed, can't use file, etc), then + * discard_body_data() isn't called. */ + if (url_map->flags & HANDLER_HAS_BODY_DATA) return read_body_data(request); From 25cbebdc1620862c86a897c2d666ac159847e770 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 11 Apr 2021 14:42:24 -0700 Subject: [PATCH 1683/2505] Add function to parse long long integers --- src/lib/lwan-config.c | 16 +++++++++++++--- src/lib/lwan-config.h | 1 + 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 8af1bc660..00e45abdb 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -131,16 +131,16 @@ unsigned int parse_time_period(const char *str, unsigned int default_value) return total ? total : default_value; } -long parse_long(const char *value, long default_value) +long long parse_long_long(const char *value, long long default_value) { char *endptr; - long parsed; + long long parsed; if (!value) return default_value; errno = 0; - parsed = strtol(value, &endptr, 0); + parsed = strtoll(value, &endptr, 0); if (errno != 0) return default_value; @@ -151,6 +151,16 @@ long parse_long(const char *value, long default_value) return parsed; } +long parse_long(const char *value, long default_value) +{ + long long long_long_value = parse_long_long(value, default_value); + + if ((long long)(long)long_long_value != long_long_value) + return default_value; + + return (long)long_long_value; +} + int parse_int(const char *value, int default_value) { long long_value = parse_long(value, default_value); diff --git a/src/lib/lwan-config.h b/src/lib/lwan-config.h index cc775da15..378ea49ed 100644 --- a/src/lib/lwan-config.h +++ b/src/lib/lwan-config.h @@ -65,6 +65,7 @@ bool config_skip_section(struct config *conf, const struct config_line *line); bool parse_bool(const char *value, bool default_value); long parse_long(const char *value, long default_value); +long long parse_long_long(const char *value, long long default_value); int parse_int(const char *value, int default_value); unsigned int parse_time_period(const char *str, unsigned int default_value); From 67b346b754ddfca0fbc0cdf7e1c591aa9065885e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 11 Apr 2021 14:47:06 -0700 Subject: [PATCH 1684/2505] Discard post data even if buffer allocation fails If the buffer allocation to read the request body failed, Lwan would fail to discard the body, causing next requests to fail. --- src/lib/lwan-request.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index e1f27e9e9..288fb1431 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1184,7 +1184,7 @@ get_remaining_body_data_length(struct lwan_request *request, return HTTP_OK; } -static enum lwan_http_status read_body_data(struct lwan_request *request) +static int read_body_data(struct lwan_request *request) { /* Holy indirection, Batman! */ const struct lwan_config *config = &request->conn->thread->lwan->config; @@ -1205,7 +1205,7 @@ static enum lwan_http_status read_body_data(struct lwan_request *request) status = get_remaining_body_data_length(request, max_data_size, &total, &have); if (status != HTTP_PARTIAL_CONTENT) - return status; + return -(int)status; if (!(request->flags & REQUEST_IS_HTTP_1_0)) { /* §8.2.3 https://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html */ @@ -1221,7 +1221,7 @@ static enum lwan_http_status read_body_data(struct lwan_request *request) new_buffer = alloc_body_buffer(request->conn->coro, total + 1, allow_temp_file); if (UNLIKELY(!new_buffer)) - return HTTP_INTERNAL_ERROR; + return -HTTP_INTERNAL_ERROR; helper->body_data.value = new_buffer; helper->body_data.len = total; @@ -1408,14 +1408,15 @@ static enum lwan_http_status prepare_for_response(struct lwan_url_map *url_map, } if (request_has_body(request)) { - /* FIXME: if read_body_data() fails somehow (e.g. buffer - * allocation failed, can't use file, etc), then - * discard_body_data() isn't called. */ + int status = 0; - if (url_map->flags & HANDLER_HAS_BODY_DATA) - return read_body_data(request); + if (url_map->flags & HANDLER_HAS_BODY_DATA) { + status = read_body_data(request); + if (status > 0) + return status; + } - enum lwan_http_status status = discard_body_data(request); + status = discard_body_data(request); return (status == HTTP_OK) ? HTTP_NOT_ALLOWED : status; } From 36e99c3c43fdb22bde65c5df5acac0bdad327a73 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 11 Apr 2021 14:47:29 -0700 Subject: [PATCH 1685/2505] Discarding post data shouldn't cap body size based on configuration Otherwise, it's possible to make discard_post_data() to fail before it even starts reading the request, and use one request body as part of the next request. This could potentially be used to bypass some kinds of ACLs in reverse proxies. --- src/lib/lwan-request.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 288fb1431..5f98ce91a 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1151,15 +1151,15 @@ get_remaining_body_data_length(struct lwan_request *request, size_t *have) { struct lwan_request_parser_helper *helper = request->helper; - long parsed_size; + long long parsed_size; if (UNLIKELY(!helper->content_length.value)) return HTTP_BAD_REQUEST; - parsed_size = parse_long(helper->content_length.value, -1); + parsed_size = parse_long_long(helper->content_length.value, -1); if (UNLIKELY(parsed_size < 0)) return HTTP_BAD_REQUEST; - if (UNLIKELY(parsed_size >= (long)max_size)) + if (UNLIKELY((size_t)parsed_size >= max_size)) return HTTP_TOO_LARGE; if (UNLIKELY(!parsed_size)) return HTTP_OK; @@ -1240,12 +1240,16 @@ static int read_body_data(struct lwan_request *request) static enum lwan_http_status discard_body_data(struct lwan_request *request) { - /* Holy indirection, Batman! */ - const struct lwan_config *config = &request->conn->thread->lwan->config; enum lwan_http_status status; size_t total, have; - status = get_remaining_body_data_length(request, config->max_post_data_size, + /* SIZE_MAX is passed to get_remaining_body_data_length() since + * this won't allocate a buffer big enough for the whole body, but + * it has to be fully read regardless of what the configuration + * says. Ideally this would send a "request too large" response + * and close the connection to avoid the client from hogging the + * server. */ + status = get_remaining_body_data_length(request, SIZE_MAX, &total, &have); if (status != HTTP_PARTIAL_CONTENT) return status; From cb26d125138387496a683ab6e8c69a5a807e6218 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 11 Apr 2021 17:11:41 -0700 Subject: [PATCH 1686/2505] Gracefully close connections when sending timeout responses Unset the keep-alive flag so that not only the "Connection: close" header is sent in the response, but also, the connection is closed after it's serviced. --- src/lib/lwan-response.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 21b9b3bfe..0c2384a96 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -102,7 +102,7 @@ static inline bool has_response_body(enum lwan_request_flags method, return (method & 1 << 0) || status != HTTP_NOT_MODIFIED; } -void lwan_response(struct lwan_request *request, enum lwan_http_status status) +static void lwan_response_internal(struct lwan_request *request, enum lwan_http_status status) { const struct lwan_response *response = &request->response; char headers[DEFAULT_HEADERS_SIZE]; @@ -166,6 +166,18 @@ void lwan_response(struct lwan_request *request, enum lwan_http_status status) return (void)lwan_writev(request, response_vec, N_ELEMENTS(response_vec)); } +void lwan_response(struct lwan_request *request, enum lwan_http_status status) +{ + if (UNLIKELY(status == HTTP_TIMEOUT)) { + /* If a timeout has been reached, it's safer to just gracefully + * close the connection instead of figuring out what would be + * the current state of the request buffer. */ + request->conn->flags &= ~CONN_IS_KEEP_ALIVE; + } + + return lwan_response_internal(request, status); +} + void lwan_default_response(struct lwan_request *request, enum lwan_http_status status) { From a361f50b10645c0efb9bf614d5e1b47b37eb7f47 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 11 Apr 2021 17:14:04 -0700 Subject: [PATCH 1687/2505] Put an upper bound in the number of reads to discard body data --- src/lib/lwan-request.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 5f98ce91a..b3b5379b8 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1254,17 +1254,18 @@ static enum lwan_http_status discard_body_data(struct lwan_request *request) if (status != HTTP_PARTIAL_CONTENT) return status; - /* FIXME: set/use error_when_*? */ total -= have; - while (total) { + int n_packets = lwan_calculate_n_packets(total); + while (total && n_packets) { char buffer[DEFAULT_BUFFER_SIZE]; ssize_t r; r = lwan_recv(request, buffer, LWAN_MIN(sizeof(buffer), total), 0); total -= (size_t)r; + n_packets--; } - return HTTP_OK; + return n_packets ? HTTP_OK : HTTP_TIMEOUT; } static char * From 52435270e8bdbb70383fa812fe20466ef3bb8896 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 12 Apr 2021 17:59:12 -0700 Subject: [PATCH 1688/2505] Validate code in mod_response even when creating with config struct --- src/lib/lwan-mod-response.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/lib/lwan-mod-response.c b/src/lib/lwan-mod-response.c index f39c1131a..7dcf89167 100644 --- a/src/lib/lwan-mod-response.c +++ b/src/lib/lwan-mod-response.c @@ -14,11 +14,12 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ -#include #include +#include #include "lwan.h" #include "lwan-mod-response.h" @@ -36,6 +37,14 @@ static void *response_create(const char *prefix __attribute__((unused)), { struct lwan_response_settings *settings = instance; + const char *valid_code = + lwan_http_status_as_string_with_code(settings->code); + if (!strncmp(valid_code, "999 ", 4)) { + lwan_status_error("Code %d isn't a known HTTP status code", + settings->code); + return NULL; + } + return (void *)settings->code; } @@ -55,14 +64,8 @@ static void *response_create_from_hash(const char *prefix, return NULL; } - const char *valid_code = lwan_http_status_as_string_with_code(code_as_int); - if (!strncmp(valid_code, "999 ", 4)) { - lwan_status_error("Code %d isn't a known HTTP status code", code_as_int); - return NULL; - } - struct lwan_response_settings settings = { - .code = (enum lwan_http_status)code_as_int + .code = (enum lwan_http_status)code_as_int, }; return response_create(prefix, &settings); } From 67ea502ac6c0252e452b7351a4f214c2e9ee9999 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 12 Apr 2021 17:59:48 -0700 Subject: [PATCH 1689/2505] Gracefully close connection to discard request body Instead of reading from the socket, which could potentially take a long time and use a lot of networking resources, it's best to just close the connection as if it were a "Connection: close" request and use the mechanism already in place for gracefully closing connections (which should make sure that the peer has received the response from us). --- src/lib/lwan-request.c | 38 ++++++-------------------------------- src/lib/lwan-response.c | 14 +------------- 2 files changed, 7 insertions(+), 45 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index b3b5379b8..3a096413e 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1238,36 +1238,6 @@ static int read_body_data(struct lwan_request *request) return client_read(request, &buffer, total, body_data_finalizer); } -static enum lwan_http_status discard_body_data(struct lwan_request *request) -{ - enum lwan_http_status status; - size_t total, have; - - /* SIZE_MAX is passed to get_remaining_body_data_length() since - * this won't allocate a buffer big enough for the whole body, but - * it has to be fully read regardless of what the configuration - * says. Ideally this would send a "request too large" response - * and close the connection to avoid the client from hogging the - * server. */ - status = get_remaining_body_data_length(request, SIZE_MAX, - &total, &have); - if (status != HTTP_PARTIAL_CONTENT) - return status; - - total -= have; - int n_packets = lwan_calculate_n_packets(total); - while (total && n_packets) { - char buffer[DEFAULT_BUFFER_SIZE]; - ssize_t r; - - r = lwan_recv(request, buffer, LWAN_MIN(sizeof(buffer), total), 0); - total -= (size_t)r; - n_packets--; - } - - return n_packets ? HTTP_OK : HTTP_TIMEOUT; -} - static char * parse_proxy_protocol(struct lwan_request *request, char *buffer) { @@ -1421,8 +1391,12 @@ static enum lwan_http_status prepare_for_response(struct lwan_url_map *url_map, return status; } - status = discard_body_data(request); - return (status == HTTP_OK) ? HTTP_NOT_ALLOWED : status; + /* Instead of trying to read the body here, which will require + * us to allocate and read potentially a lot of bytes, force + * this connection to be closed as soon as we send a "not allowed" + * response. */ + request->conn->flags &= ~CONN_IS_KEEP_ALIVE; + return status < 0 ? -status : HTTP_NOT_ALLOWED; } return HTTP_OK; diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 0c2384a96..21b9b3bfe 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -102,7 +102,7 @@ static inline bool has_response_body(enum lwan_request_flags method, return (method & 1 << 0) || status != HTTP_NOT_MODIFIED; } -static void lwan_response_internal(struct lwan_request *request, enum lwan_http_status status) +void lwan_response(struct lwan_request *request, enum lwan_http_status status) { const struct lwan_response *response = &request->response; char headers[DEFAULT_HEADERS_SIZE]; @@ -166,18 +166,6 @@ static void lwan_response_internal(struct lwan_request *request, enum lwan_http_ return (void)lwan_writev(request, response_vec, N_ELEMENTS(response_vec)); } -void lwan_response(struct lwan_request *request, enum lwan_http_status status) -{ - if (UNLIKELY(status == HTTP_TIMEOUT)) { - /* If a timeout has been reached, it's safer to just gracefully - * close the connection instead of figuring out what would be - * the current state of the request buffer. */ - request->conn->flags &= ~CONN_IS_KEEP_ALIVE; - } - - return lwan_response_internal(request, status); -} - void lwan_default_response(struct lwan_request *request, enum lwan_http_status status) { From 657921737356217474f16d1ce8eedd63828ffb8a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 19 Apr 2021 21:56:47 -0700 Subject: [PATCH 1690/2505] Simplify turn_timer_wheel() --- src/lib/lwan-thread.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 74a25738d..b3e8752de 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -421,6 +421,7 @@ static bool process_pending_timers(struct timeout_queue *tq, static int turn_timer_wheel(struct timeout_queue *tq, struct lwan_thread *t, int epoll_fd) { + const int infinite_timeout = -1; timeout_t wheel_timeout; struct timespec now; @@ -430,23 +431,21 @@ turn_timer_wheel(struct timeout_queue *tq, struct lwan_thread *t, int epoll_fd) timeouts_update(t->wheel, (timeout_t)(now.tv_sec * 1000 + now.tv_nsec / 1000000)); + /* Check if there's an expired timer. */ wheel_timeout = timeouts_timeout(t->wheel); - if (UNLIKELY((int64_t)wheel_timeout < 0)) - goto infinite_timeout; - - if (wheel_timeout == 0) { - if (!process_pending_timers(tq, t, epoll_fd)) - goto infinite_timeout; - - wheel_timeout = timeouts_timeout(t->wheel); - if (wheel_timeout == 0) - goto infinite_timeout; + if (wheel_timeout > 0) { + return (int)wheel_timeout; /* No, but will soon. Wake us up in + wheel_timeout ms. */ } - return (int)wheel_timeout; + if (UNLIKELY((int64_t)wheel_timeout < 0)) + return infinite_timeout; /* None found. */ + + if (!process_pending_timers(tq, t, epoll_fd)) + return infinite_timeout; /* No more timers to process. */ -infinite_timeout: - return -1; + /* After processing pending timers, determine when to wake up. */ + return (int)timeouts_timeout(t->wheel); } static void *thread_io_loop(void *data) From f964a8967f2d0288d2120537593dc506a51525ee Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 19 Apr 2021 22:49:51 -0700 Subject: [PATCH 1691/2505] Deduplicate some substrings in the configuration parser --- src/lib/lwan-config.c | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 00e45abdb..f9a22b260 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -41,15 +41,22 @@ #define LEX_ERROR(lexer, fmt, ...) \ ({ \ - config_error(config_from_lexer(lexer), "Syntax error: " fmt, \ - ##__VA_ARGS__); \ + config_error(config_from_lexer(lexer), "%s" fmt, \ + "Syntax error: ", ##__VA_ARGS__); \ NULL; \ }) #define PARSER_ERROR(parser, fmt, ...) \ ({ \ - config_error(config_from_parser(parser), "Parsing error: " fmt, \ - ##__VA_ARGS__); \ + config_error(config_from_parser(parser), "%s" fmt, \ + "Parsing error: ", ##__VA_ARGS__); \ + NULL; \ + }) + +#define INTERNAL_ERROR(parser, fmt, ...) \ + ({ \ + config_error(config_from_parser(parser), "%s" fmt, \ + "Internal error: ", ##__VA_ARGS__); \ NULL; \ }) @@ -592,12 +599,12 @@ static void *parse_key_value(struct parser *parser) return PARSER_ERROR(parser, "Open bracket not expected here"); case LEXEME_CLOSE_BRACKET: - return PARSER_ERROR( - parser, "Internal error: Close bracket found while parsing key/value"); + return INTERNAL_ERROR( + parser, "Close bracket found while parsing key/value"); case LEXEME_EOF: - return PARSER_ERROR( - parser, "Internal error: EOF found while parsing key/value"); + return INTERNAL_ERROR( + parser, "EOF found while parsing key/value"); case TOTAL_LEXEMES: __builtin_unreachable(); @@ -653,7 +660,7 @@ static void *parse_section_shorthand(struct parser *parser) if (config_ring_buffer_try_put(&parser->items, &line)) return next_state; - return PARSER_ERROR(parser, "Internal error: couldn´t append line to internal ring buffer"); + return INTERNAL_ERROR(parser, "couldn't append line to internal ring buffer"); } return NULL; @@ -671,8 +678,8 @@ static void *parse_section_end(struct parser *parser) return PARSER_ERROR(parser, "Not expecting a close bracket here"); if (!config_ring_buffer_try_put(&parser->items, &line)) { - return PARSER_ERROR(parser, - "Internal error: could not store section end in ring buffer"); + return INTERNAL_ERROR(parser, + "could not store section end in ring buffer"); } config->opened_brackets--; @@ -687,7 +694,7 @@ static void *parse_config(struct parser *parser) if (!lexeme) { /* EOF is signaled by a LEXEME_EOF from the parser, so * this should never happen. */ - return PARSER_ERROR(parser, "Internal error: Could not obtain lexeme"); + return INTERNAL_ERROR(parser, "could not obtain lexeme"); } switch (lexeme->type) { @@ -713,7 +720,7 @@ static void *parse_config(struct parser *parser) case LEXEME_STRING: if (!lexeme_ring_buffer_try_put(&parser->buffer, lexeme)) - return PARSER_ERROR(parser, "Internal error: could not store string in ring buffer"); + return INTERNAL_ERROR(parser, "could not store string in ring buffer"); return parse_config; @@ -725,7 +732,7 @@ static void *parse_config(struct parser *parser) return PARSER_ERROR(parser, "EOF while looking for a close bracket"); if (!lexeme_ring_buffer_empty(&parser->buffer)) - return PARSER_ERROR(parser, "Internal error: Premature EOF"); + return INTERNAL_ERROR(parser, "premature EOF"); break; From fb6992b735484ebe629195c55c6d476ac2c8cba5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 26 Apr 2021 18:17:49 -0700 Subject: [PATCH 1692/2505] Document req:header() --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 951843f7f..f3db71087 100644 --- a/README.md +++ b/README.md @@ -424,6 +424,7 @@ information from the request, or to set the response, as seen below: - `req:send_event(event, str)` sends an event (using server-sent events) - `req:cookie(param)` returns the cookie named `param`, or `nil` is not found - `req:set_headers(tbl)` sets the response headers from the table `tbl`; a header may be specified multiple times by using a table, rather than a string, in the table value (`{'foo'={'bar', 'baz'}}`); must be called before sending any response with `say()` or `send_event()` + - `req:header(name)` obtains the header from the request with the given name or `nil` if not found - `req:sleep(ms)` pauses the current handler for the specified amount of milliseconds - `req:ws_upgrade()` returns `1` if the connection could be upgraded to a WebSocket; `0` otherwise - `req:ws_write(str)` sends `str` through the WebSocket-upgraded connection From 69c102e0f2ae98b2b994e7f8609a58dd3c610000 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 26 Apr 2021 18:18:17 -0700 Subject: [PATCH 1693/2505] Immediately free websockets handshake There's no need to keep it around as it'll never be needed again. --- src/lib/lwan-request.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 3a096413e..4d42ba3ce 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1323,11 +1323,7 @@ prepare_websocket_handshake(struct lwan_request *request, char **encoded) sha1_finalize(&ctx, digest); *encoded = (char *)base64_encode(digest, sizeof(digest), NULL); - if (UNLIKELY(!*encoded)) - return HTTP_INTERNAL_ERROR; - coro_defer(request->conn->coro, free, *encoded); - - return HTTP_SWITCHING_PROTOCOLS; + return LIKELY(*encoded) ? HTTP_SWITCHING_PROTOCOLS : HTTP_INTERNAL_ERROR; } enum lwan_http_status @@ -1350,6 +1346,7 @@ lwan_request_websocket_upgrade(struct lwan_request *request) {.key = "Upgrade", .value = "websocket"}, {}, }); + free(encoded); if (UNLIKELY(!header_buf_len)) return HTTP_INTERNAL_ERROR; From 578a6db41282d3f4b892867c3fc97c11cfa874b0 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 26 Apr 2021 18:18:59 -0700 Subject: [PATCH 1694/2505] Fix build warning --- src/lib/lwan-request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 4d42ba3ce..ffb8db085 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1235,7 +1235,7 @@ static int read_body_data(struct lwan_request *request) helper->error_when_n_packets = lwan_calculate_n_packets(total); struct lwan_value buffer = {.value = new_buffer, .len = total}; - return client_read(request, &buffer, total, body_data_finalizer); + return (int)client_read(request, &buffer, total, body_data_finalizer); } static char * From 5214eba8000c210b52e620935029c486de6bb35a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 26 Apr 2021 18:19:38 -0700 Subject: [PATCH 1695/2505] Factor reading of body data outside of prepare_for_response() --- src/lib/lwan-mod-lua.c | 2 +- src/lib/lwan-request.c | 55 +++++++++++++++++++++++++----------------- src/lib/lwan.h | 4 +-- 3 files changed, 36 insertions(+), 25 deletions(-) diff --git a/src/lib/lwan-mod-lua.c b/src/lib/lwan-mod-lua.c index 183c692e4..b20bc9355 100644 --- a/src/lib/lwan-mod-lua.c +++ b/src/lib/lwan-mod-lua.c @@ -307,7 +307,7 @@ static const struct lwan_module module = { .create_from_hash = lua_create_from_hash, .destroy = lua_destroy, .handle_request = lua_handle_request, - .flags = HANDLER_HAS_BODY_DATA, + .flags = HANDLER_EXPECTS_BODY_DATA, }; LWAN_REGISTER_MODULE(lua, &module); diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index ffb8db085..ce0846475 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1363,39 +1363,50 @@ static inline bool request_has_body(const struct lwan_request *request) return lwan_request_get_method(request) & 1 << 3; } -static enum lwan_http_status prepare_for_response(struct lwan_url_map *url_map, - struct lwan_request *request) +static enum lwan_http_status +maybe_read_body_data(const struct lwan_url_map *url_map, + struct lwan_request *request) { - request->url.value += url_map->prefix_len; - request->url.len -= url_map->prefix_len; + int status = 0; - if (UNLIKELY(url_map->flags & HANDLER_MUST_AUTHORIZE)) { - if (!lwan_http_authorize_urlmap(request, url_map)) - return HTTP_NOT_AUTHORIZED; + if (url_map->flags & HANDLER_EXPECTS_BODY_DATA) { + status = read_body_data(request); + if (status > 0) + return (enum lwan_http_status)status; + } + + /* Instead of trying to read the body here, which will require + * us to allocate and read potentially a lot of bytes, force + * this connection to be closed as soon as we send a "not allowed" + * response. */ + request->conn->flags &= ~CONN_IS_KEEP_ALIVE; + + if (status < 0) { + status = -status; + return (enum lwan_http_status)status; } + return HTTP_NOT_ALLOWED; +} + +static enum lwan_http_status prepare_for_response(const struct lwan_url_map *url_map, + struct lwan_request *request) +{ + request->url.value += url_map->prefix_len; + request->url.len -= url_map->prefix_len; while (*request->url.value == '/' && request->url.len > 0) { request->url.value++; request->url.len--; } - if (request_has_body(request)) { - int status = 0; - - if (url_map->flags & HANDLER_HAS_BODY_DATA) { - status = read_body_data(request); - if (status > 0) - return status; - } - - /* Instead of trying to read the body here, which will require - * us to allocate and read potentially a lot of bytes, force - * this connection to be closed as soon as we send a "not allowed" - * response. */ - request->conn->flags &= ~CONN_IS_KEEP_ALIVE; - return status < 0 ? -status : HTTP_NOT_ALLOWED; + if (UNLIKELY(url_map->flags & HANDLER_MUST_AUTHORIZE)) { + if (!lwan_http_authorize_urlmap(request, url_map)) + return HTTP_NOT_AUTHORIZED; } + if (UNLIKELY(request_has_body(request))) + return maybe_read_body_data(url_map, request); + return HTTP_OK; } diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 0529f3aa6..c13649b89 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -210,12 +210,12 @@ enum lwan_http_status { #undef GENERATE_ENUM_ITEM enum lwan_handler_flags { - HANDLER_HAS_BODY_DATA = 1 << 0, + HANDLER_EXPECTS_BODY_DATA = 1 << 0, HANDLER_MUST_AUTHORIZE = 1 << 1, HANDLER_CAN_REWRITE_URL = 1 << 2, HANDLER_DATA_IS_HASH_TABLE = 1 << 3, - HANDLER_PARSE_MASK = HANDLER_HAS_BODY_DATA, + HANDLER_PARSE_MASK = HANDLER_EXPECTS_BODY_DATA, }; /* 1<<0 set: response has body; see has_response_body() in lwan-response.c */ From b40212579ab0482234b5a56f829ae8682aa26286 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 26 Apr 2021 18:20:19 -0700 Subject: [PATCH 1696/2505] Comment about capacity/used in strbuf --- src/lib/lwan-strbuf.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/lib/lwan-strbuf.h b/src/lib/lwan-strbuf.h index b34bd611f..8e5e1c66f 100644 --- a/src/lib/lwan-strbuf.h +++ b/src/lib/lwan-strbuf.h @@ -26,7 +26,14 @@ struct lwan_strbuf { char *buffer; + + /* `capacity` used to be derived from `used` by aligning it to the next + * power of two, but this resulted in re-allocations after this strbuf + * been reset between requests. It now always contains the capacity + * allocated by `buffer`; resetting essentially only resets `used` and + * writes `\0` to buffer[0]. */ size_t capacity, used; + unsigned int flags; }; From 2ca0ef5913fe5d2194ecffd30c21823afe01f354 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 26 Apr 2021 22:12:00 -0700 Subject: [PATCH 1697/2505] Simplify coro yield values/flags for timers/asyncawait No need to have separate flags for both; a single "suspend"/"resume" pair is sufficient. --- src/lib/lwan-request.c | 6 ++++-- src/lib/lwan-thread.c | 10 ++++------ src/lib/lwan.h | 13 ++++--------- 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index ce0846475..01668e49f 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1616,12 +1616,14 @@ lwan_request_get_remote_address(struct lwan_request *request, static void remove_sleep(void *data1, void *data2) { + static const enum lwan_connection_flags suspended_sleep = + CONN_SUSPENDED | CONN_HAS_REMOVE_SLEEP_DEFER; struct timeouts *wheel = data1; struct timeout *timeout = data2; struct lwan_request *request = container_of(timeout, struct lwan_request, timeout); - if (request->conn->flags & CONN_SUSPENDED_TIMER) + if ((request->conn->flags & suspended_sleep) == suspended_sleep) timeouts_del(wheel, timeout); request->conn->flags &= ~CONN_HAS_REMOVE_SLEEP_DEFER; @@ -1640,7 +1642,7 @@ void lwan_request_sleep(struct lwan_request *request, uint64_t ms) conn->flags |= CONN_HAS_REMOVE_SLEEP_DEFER; } - coro_yield(conn->coro, CONN_CORO_SUSPEND_TIMER); + coro_yield(conn->coro, CONN_CORO_SUSPEND); } ALWAYS_INLINE int diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index b3e8752de..e45c7d3a9 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -195,8 +195,7 @@ static void update_epoll_flags(int fd, * or EPOLLOUT events. We still want to track this fd in epoll, though, * so unset both so that only EPOLLRDHUP (plus the implicitly-set ones) * are set. */ - [CONN_CORO_SUSPEND_TIMER] = CONN_SUSPENDED_TIMER, - [CONN_CORO_SUSPEND_ASYNC_AWAIT] = CONN_SUSPENDED_ASYNC_AWAIT, + [CONN_CORO_SUSPEND] = CONN_SUSPENDED, /* Ideally, when suspending a coroutine, the current flags&CONN_EVENTS_MASK * would have to be stored and restored -- however, resuming as if the @@ -214,8 +213,7 @@ static void update_epoll_flags(int fd, [CONN_CORO_WANT_READ] = ~CONN_EVENTS_WRITE, [CONN_CORO_WANT_WRITE] = ~CONN_EVENTS_READ, - [CONN_CORO_SUSPEND_TIMER] = ~(CONN_EVENTS_READ_WRITE | CONN_SUSPENDED_ASYNC_AWAIT), - [CONN_CORO_SUSPEND_ASYNC_AWAIT] = ~(CONN_EVENTS_READ_WRITE | CONN_SUSPENDED_TIMER), + [CONN_CORO_SUSPEND] = ~CONN_EVENTS_READ_WRITE, [CONN_CORO_RESUME] = ~CONN_SUSPENDED, }; enum lwan_connection_flags prev_flags = conn->flags; @@ -267,7 +265,7 @@ resume_async(struct timeout_queue *tq, struct lwan_connection *await_fd_conn = &tq->lwan->conns[await_fd]; if (LIKELY(await_fd_conn->flags & CONN_ASYNC_AWAIT)) { if (LIKELY((await_fd_conn->flags & CONN_EVENTS_MASK) == flags)) - return CONN_CORO_SUSPEND_ASYNC_AWAIT; + return CONN_CORO_SUSPEND; op = EPOLL_CTL_MOD; } else { @@ -281,7 +279,7 @@ resume_async(struct timeout_queue *tq, if (LIKELY(!epoll_ctl(epoll_fd, op, await_fd, &event))) { await_fd_conn->flags &= ~CONN_EVENTS_MASK; await_fd_conn->flags |= flags; - return CONN_CORO_SUSPEND_ASYNC_AWAIT; + return CONN_CORO_SUSPEND; } return CONN_CORO_ABORT; diff --git a/src/lib/lwan.h b/src/lib/lwan.h index c13649b89..5918b206b 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -282,19 +282,15 @@ enum lwan_connection_flags { /* This is only used to determine if timeout_del() is necessary when * the connection coro ends. */ - CONN_SUSPENDED_TIMER = 1 << 5, + CONN_SUSPENDED = 1 << 5, CONN_HAS_REMOVE_SLEEP_DEFER = 1 << 6, - CONN_SUSPENDED_ASYNC_AWAIT = 1 << 7, - - CONN_SUSPENDED = CONN_SUSPENDED_TIMER | CONN_SUSPENDED_ASYNC_AWAIT, - - CONN_CORK = 1 << 8, + CONN_CORK = 1 << 7, /* Set only on file descriptors being watched by async/await to determine * which epoll operation to use when suspending/resuming (ADD/MOD). Reset * whenever associated client connection is closed. */ - CONN_ASYNC_AWAIT = 1 << 9, + CONN_ASYNC_AWAIT = 1 << 8, }; enum lwan_connection_coro_yield { @@ -306,8 +302,7 @@ enum lwan_connection_coro_yield { CONN_CORO_WANT_WRITE, CONN_CORO_WANT_READ_WRITE, - CONN_CORO_SUSPEND_TIMER, - CONN_CORO_SUSPEND_ASYNC_AWAIT, + CONN_CORO_SUSPEND, CONN_CORO_RESUME, /* Group async stuff together to make it easier to check if a connection From 52bc7ff85aa3fa2ac87132aa87866d159b7168a1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 28 Apr 2021 08:29:28 -0700 Subject: [PATCH 1698/2505] Don't accept websockets handshakes with empty keys Although RFC6455 do not mention empty keys, let's interpret it to mean the string is malformed and reject the handshake if that's the case. --- src/lib/lwan-request.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 01668e49f..cc2231876 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1314,6 +1314,8 @@ prepare_websocket_handshake(struct lwan_request *request, char **encoded) if (UNLIKELY(!sec_websocket_key)) return HTTP_BAD_REQUEST; const size_t sec_websocket_key_len = strlen(sec_websocket_key); + if (sec_websocket_key_len == 0) + return HTTP_BAD_REQUEST; if (UNLIKELY(!base64_validate((void *)sec_websocket_key, sec_websocket_key_len))) return HTTP_BAD_REQUEST; From 2d22e417e5604644f16e1c31c66b94f6e180e67b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 28 Apr 2021 08:47:07 -0700 Subject: [PATCH 1699/2505] Plug memory leak when fuzzing Fixes https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=33772 --- src/lib/lwan-request.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index cc2231876..42dcb3ed2 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1823,7 +1823,10 @@ __attribute__((used)) int fuzz_parse_http_request(const uint8_t *data, lwan_request_get_if_modified_since(&request, &trash2); LWAN_NO_DISCARD(trash2); - prepare_websocket_handshake(&request, &trash3); + if (prepare_websocket_handshake(&request, &trash3) == + HTTP_SWITCHING_PROTOCOLS) { + free(trash3); + } LWAN_NO_DISCARD(trash3); LWAN_NO_DISCARD( From ec14ea0f2708759eef10148fc28f0d89e2f51be9 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 28 Apr 2021 19:24:35 -0700 Subject: [PATCH 1700/2505] Sec-WebSocket-Key is always, decoded, 16 bytes long From the RFC : A |Sec-WebSocket-Key| header field with a base64-encoded (see Section 4 of [RFC4648]) value that, when decoded, is 16 bytes in length. --- src/lib/base64.h | 6 ++++++ src/lib/lwan-request.c | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/lib/base64.h b/src/lib/base64.h index dc6ccb71f..94a28022f 100644 --- a/src/lib/base64.h +++ b/src/lib/base64.h @@ -9,3 +9,9 @@ unsigned char *base64_decode(const unsigned char *src, size_t len, size_t *out_len); bool base64_validate(const unsigned char *src, size_t len); + +static inline size_t base64_encoded_len(size_t decoded_len) +{ + /* This counts the padding bytes (by rounding to the next multiple of 4). */ + return ((4u * decoded_len) + 3u) & ~3u; +} diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 42dcb3ed2..55e30b41a 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1313,8 +1313,9 @@ prepare_websocket_handshake(struct lwan_request *request, char **encoded) lwan_request_get_header(request, "Sec-WebSocket-Key"); if (UNLIKELY(!sec_websocket_key)) return HTTP_BAD_REQUEST; + const size_t sec_websocket_key_len = strlen(sec_websocket_key); - if (sec_websocket_key_len == 0) + if (base64_encoded_len(16) != sec_websocket_key_len) return HTTP_BAD_REQUEST; if (UNLIKELY(!base64_validate((void *)sec_websocket_key, sec_websocket_key_len))) return HTTP_BAD_REQUEST; From c0c1b6c6871e0b2314147c93d8d7aa132fba53b4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 28 Apr 2021 19:59:44 -0700 Subject: [PATCH 1701/2505] Fix calculation of decoded base64 length --- src/lib/base64.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/base64.h b/src/lib/base64.h index 94a28022f..191b54f21 100644 --- a/src/lib/base64.h +++ b/src/lib/base64.h @@ -13,5 +13,5 @@ bool base64_validate(const unsigned char *src, size_t len); static inline size_t base64_encoded_len(size_t decoded_len) { /* This counts the padding bytes (by rounding to the next multiple of 4). */ - return ((4u * decoded_len) + 3u) & ~3u; + return ((4u * decoded_len / 3u) + 3u) & ~3u; } From 79dd4983a8ef00d6c55d393d25bbe7fdb8ce00a1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 1 May 2021 09:13:51 -0700 Subject: [PATCH 1702/2505] Do not encode newlines in base64-encoded data --- src/lib/base64.c | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/lib/base64.c b/src/lib/base64.c index 66e002aa3..1535d9569 100644 --- a/src/lib/base64.c +++ b/src/lib/base64.c @@ -74,13 +74,11 @@ base64_encode(const unsigned char *src, size_t len, size_t *out_len) unsigned char *out, *pos; const unsigned char *end, *in; size_t olen; - int line_len; - olen = len * 4 / 3 + 4; /* 3-byte blocks to 4-byte */ - olen += olen / 72; /* line feeds */ - olen++; /* nul termination */ + olen = base64_encoded_len(len) + 1 /* for NUL termination */; if (olen < len) return NULL; /* integer overflow */ + out = malloc(olen); if (out == NULL) return NULL; @@ -88,18 +86,12 @@ base64_encode(const unsigned char *src, size_t len, size_t *out_len) end = src + len; in = src; pos = out; - line_len = 0; while (end - in >= 3) { *pos++ = base64_table[in[0] >> 2]; *pos++ = base64_table[((in[0] & 0x03) << 4) | (in[1] >> 4)]; *pos++ = base64_table[((in[1] & 0x0f) << 2) | (in[2] >> 6)]; *pos++ = base64_table[in[2] & 0x3f]; in += 3; - line_len += 4; - if (line_len >= 72) { - *pos++ = '\n'; - line_len = 0; - } } if (end - in) { From abf0d8e7520d482839f620203e3589e56b5b1e68 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 8 May 2021 10:36:36 -0700 Subject: [PATCH 1703/2505] Get rid of pending_fds queue This is in preparation for the usage of SO_REUSEPORT, so that threads can schedule connections in other threads. --- src/lib/CMakeLists.txt | 1 - src/lib/lwan-thread.c | 47 +++++------------ src/lib/lwan.h | 2 - src/lib/queue.c | 115 ----------------------------------------- src/lib/queue.h | 34 ------------ 5 files changed, 13 insertions(+), 186 deletions(-) delete mode 100644 src/lib/queue.c delete mode 100644 src/lib/queue.h diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index 22fd7fc4f..518ea4807 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -39,7 +39,6 @@ set(SOURCES missing-pthread.c murmur3.c patterns.c - queue.c realpathat.c sd-daemon.c sha1.c diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index e45c7d3a9..c213195e5 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -313,7 +313,7 @@ static void update_date_cache(struct lwan_thread *thread) thread->date.expires); } -static ALWAYS_INLINE void spawn_coro(struct lwan_connection *conn, +static ALWAYS_INLINE bool spawn_coro(struct lwan_connection *conn, struct coro_switcher *switcher, struct timeout_queue *tq) { @@ -342,10 +342,11 @@ static ALWAYS_INLINE void spawn_coro(struct lwan_connection *conn, shutdown(fd, SHUT_RDWR); close(fd); - return; + return false; } timeout_queue_insert(tq, conn); + return true; } static void accept_nudge(int pipe_fd, @@ -356,24 +357,11 @@ static void accept_nudge(int pipe_fd, int epoll_fd) { uint64_t event; - int new_fd; /* Errors are ignored here as pipe_fd serves just as a way to wake the * thread from epoll_wait(). It's fine to consume the queue at this * point, regardless of the error type. */ (void)read(pipe_fd, &event, sizeof(event)); - - while (spsc_queue_pop(&t->pending_fds, &new_fd)) { - struct lwan_connection *conn = &conns[new_fd]; - struct epoll_event ev = { - .data.ptr = conn, - .events = conn_flags_to_epoll_events(CONN_EVENTS_READ), - }; - - if (LIKELY(!epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_fd, &ev))) - spawn_coro(conn, switcher, tq); - } - timeouts_add(t->wheel, &tq->timeout, 1000); } @@ -497,6 +485,11 @@ static void *thread_io_loop(void *data) continue; } + if (!conn->coro) { + if (UNLIKELY(!spawn_coro(conn, &switcher, &tq))) + continue; + } + resume_coro(&tq, conn, epoll_fd); timeout_queue_move_to_last(&tq, conn); } @@ -555,11 +548,6 @@ static void create_thread(struct lwan *l, struct lwan_thread *thread, if (pthread_attr_destroy(&attr)) lwan_status_critical_perror("pthread_attr_destroy"); - - if (spsc_queue_init(&thread->pending_fds, n_queue_fds) < 0) { - lwan_status_critical("Could not initialize pending fd " - "queue width %zu elements", n_queue_fds); - } } void lwan_thread_nudge(struct lwan_thread *t) @@ -572,19 +560,11 @@ void lwan_thread_nudge(struct lwan_thread *t) void lwan_thread_add_client(struct lwan_thread *t, int fd) { - for (int i = 0; i < 10; i++) { - bool pushed = spsc_queue_push(&t->pending_fds, fd); - - if (LIKELY(pushed)) - return; - - /* Queue is full; nudge the thread to consume it. */ - lwan_thread_nudge(t); - } - - lwan_status_error("Dropping connection %d", fd); - /* FIXME: send "busy" response now, even without receiving request? */ - close(fd); + struct epoll_event ev = { + .data.ptr = &t->lwan->conns[fd], + .events = conn_flags_to_epoll_events(CONN_EVENTS_READ), + }; + epoll_ctl(t->epoll_fd, EPOLL_CTL_ADD, fd, &ev); } #if defined(__linux__) && defined(__x86_64__) @@ -808,7 +788,6 @@ void lwan_thread_shutdown(struct lwan *l) #endif pthread_join(l->thread.threads[i].self, NULL); - spsc_queue_free(&t->pending_fds); timeouts_close(t->wheel); } diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 5918b206b..3a37f3d3f 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -31,7 +31,6 @@ extern "C" { #include #include "hash.h" -#include "queue.h" #include "timeout.h" #include "lwan-array.h" #include "lwan-config.h" @@ -437,7 +436,6 @@ struct lwan_thread { char date[30]; char expires[30]; } date; - struct spsc_queue pending_fds; struct timeouts *wheel; int epoll_fd; int pipe_fd[2]; diff --git a/src/lib/queue.c b/src/lib/queue.c deleted file mode 100644 index bd48dba40..000000000 --- a/src/lib/queue.c +++ /dev/null @@ -1,115 +0,0 @@ -/* - * SPSC Bounded Queue - * Based on public domain C++ version by mstump[1]. Released under - * the same license terms. - * - * [1] - * https://github.com/mstump/queues/blob/master/include/spsc-bounded-queue.hpp - */ - -#include -#include -#include -#include -#include - -#include "queue.h" -#include "lwan-private.h" - -#if !defined(ATOMIC_RELAXED) - -#define ATOMIC_RELAXED __ATOMIC_RELAXED -#define ATOMIC_ACQUIRE __ATOMIC_ACQUIRE -#define ATOMIC_RELEASE __ATOMIC_RELEASE - -#endif - -#if defined(__GNUC__) - -#if (__GNUC__ * 100 + __GNUC_MINOR__ >= 470) -#define HAS_GCC_ATOMIC 1 -#else -#define HAS_SYNC_ATOMIC 1 -#endif - -#endif - -#if HAS_GCC_ATOMIC - -#define ATOMIC_INIT(P, V) \ - do { \ - (P) = (V); \ - } while (0) - -#define ATOMIC_LOAD(P, O) __atomic_load_n((P), (O)) -#define ATOMIC_STORE(P, V, O) __atomic_store_n((P), (V), (O)) - -#elif HAS_SYNC_ATOMIC - -#define ATOMIC_INIT(P, V) \ - do { \ - (P) = (V); \ - } while (0) - -#define ATOMIC_LOAD(P, O) __sync_fetch_and_add((P), 0) -#define ATOMIC_STORE(P, V, O) \ - ({ \ - __sync_synchronize(); \ - __sync_lock_test_and_set((P), (V)); \ - }) - -#else - -#error Unsupported compiler. - -#endif - -int spsc_queue_init(struct spsc_queue *q, size_t size) -{ - if (size == 0) - return -EINVAL; - - size = lwan_nextpow2(size); - q->buffer = calloc(1 + size, sizeof(int)); - if (!q->buffer) - return -errno; - - ATOMIC_INIT(q->head, 0); - ATOMIC_INIT(q->tail, 0); - - q->size = size; - q->mask = size - 1; - - return 0; -} - -void spsc_queue_free(struct spsc_queue *q) { free(q->buffer); } - -bool spsc_queue_push(struct spsc_queue *q, int input) -{ - const size_t head = ATOMIC_LOAD(&q->head, ATOMIC_RELAXED); - - if (((ATOMIC_LOAD(&q->tail, ATOMIC_ACQUIRE) - (head + 1)) & q->mask) >= 1) { - q->buffer[head & q->mask] = input; - ATOMIC_STORE(&q->head, head + 1, ATOMIC_RELEASE); - - return true; - } - - return false; -} - -bool spsc_queue_pop(struct spsc_queue *q, int *output) -{ - const size_t tail = ATOMIC_LOAD(&q->tail, ATOMIC_RELAXED); - - if (((ATOMIC_LOAD(&q->head, ATOMIC_ACQUIRE) - tail) & q->mask) >= 1) { - *output = q->buffer[tail & q->mask]; - - ATOMIC_STORE(&q->tail, tail + 1, ATOMIC_RELEASE); - - return true; - } - - return false; -} diff --git a/src/lib/queue.h b/src/lib/queue.h deleted file mode 100644 index f0b68292b..000000000 --- a/src/lib/queue.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * SPSC Bounded Queue - * Based on public domain C++ version by mstump[1]. Released under - * the same license terms. - * - * [1] - * https://github.com/mstump/queues/blob/master/include/spsc-bounded-queue.hpp - */ - -#pragma once - -#include -#include - -struct spsc_queue { - size_t size; - size_t mask; - int *buffer; - char cache_line_pad0[64 - sizeof(size_t) + sizeof(size_t) + sizeof(void *)]; - - size_t head; - char cache_line_pad1[64 - sizeof(size_t)]; - - size_t tail; - char cache_line_pad2[64 - sizeof(size_t)]; -}; - -int spsc_queue_init(struct spsc_queue *q, size_t size); - -void spsc_queue_free(struct spsc_queue *q); - -bool spsc_queue_push(struct spsc_queue *q, int input); - -bool spsc_queue_pop(struct spsc_queue *q, int *output); From cc43e8a93352ed4d22df1d0286f486d986d1781f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 8 May 2021 13:07:37 -0700 Subject: [PATCH 1704/2505] Make all threads accept connections from clients This moves the responsibility of accepting connections from the main thread to the various worker threads that are spawned to handle connections. A single accepting thread was a bottleneck and would compete with busy worker threads; now all worker threads share this responsibility. Requires, for the moment, SO_REUSEPORT support; support for systems where this is disabled or not available will come later. --- README.md | 1 - lwan.conf | 3 - src/bin/testrunner/testrunner.conf | 3 - src/lib/lwan-job.c | 8 +-- src/lib/lwan-private.h | 4 +- src/lib/lwan-socket.c | 16 ++--- src/lib/lwan-thread.c | 109 +++++++++++++++++++++++++++-- src/lib/lwan.c | 102 +-------------------------- src/lib/lwan.h | 3 - 9 files changed, 115 insertions(+), 134 deletions(-) diff --git a/README.md b/README.md index f3db71087..9835f35da 100644 --- a/README.md +++ b/README.md @@ -272,7 +272,6 @@ can be decided automatically, so some configuration options are provided. |--------|------|---------|-------------| | `keep_alive_timeout` | `time` | `15` | Timeout to keep a connection alive | | `quiet` | `bool` | `false` | Set to true to not print any debugging messages. Only effective in release builds. | -| `reuse_port` | `bool` | `false` | Sets `SO_REUSEPORT` to `1` in the master socket | | `expires` | `time` | `1M 1w` | Value of the "Expires" header. Default is 1 month and 1 week | | `threads` | `int` | `0` | Number of I/O threads. Default (0) is the number of online CPUs | | `proxy_protocol` | `bool` | `false` | Enables the [PROXY protocol](https://www.haproxy.com/blog/haproxy/proxy-protocol/). Versions 1 and 2 are supported. Only enable this setting if using Lwan behind a proxy, and the proxy supports this protocol; otherwise, this allows anybody to spoof origin IP addresses | diff --git a/lwan.conf b/lwan.conf index 77b70df87..1af606547 100644 --- a/lwan.conf +++ b/lwan.conf @@ -5,9 +5,6 @@ keep_alive_timeout = 15 # release builds.) quiet = false -# Set SO_REUSEPORT=1 in the master socket. -reuse_port = false - # Value of "Expires" header. Default is 1 month and 1 week. expires = 1M 1w diff --git a/src/bin/testrunner/testrunner.conf b/src/bin/testrunner/testrunner.conf index e9fd7cb3f..d77b0fc73 100644 --- a/src/bin/testrunner/testrunner.conf +++ b/src/bin/testrunner/testrunner.conf @@ -5,9 +5,6 @@ keep_alive_timeout = ${KEEP_ALIVE_TIMEOUT:15} # release builds.) quiet = false -# Set SO_REUSEPORT=1 in the master socket. -reuse_port = false - # Value of "Expires" header. Default is 1 month and 1 week. expires = 1M 1w diff --git a/src/lib/lwan-job.c b/src/lib/lwan-job.c index 1a4d56918..0c754dcb1 100644 --- a/src/lib/lwan-job.c +++ b/src/lib/lwan-job.c @@ -63,8 +63,7 @@ timedwait(bool had_job) pthread_cond_timedwait(&job_wait_cond, &job_wait_mutex, &rgtp); } -static void* -job_thread(void *data __attribute__((unused))) +void lwan_job_thread_main_loop(void) { /* Idle priority for the calling thread. Magic value of `7` obtained from * sample program in linux/Documentation/block/ioprio.txt. This is a no-op @@ -93,8 +92,6 @@ job_thread(void *data __attribute__((unused))) if (pthread_mutex_unlock(&job_wait_mutex)) lwan_status_critical("Could not lock job wait mutex"); - - return NULL; } void lwan_job_thread_init(void) @@ -105,9 +102,8 @@ void lwan_job_thread_init(void) list_head_init(&jobs); + self = pthread_self(); running = true; - if (pthread_create(&self, NULL, job_thread, NULL) < 0) - lwan_status_critical_perror("pthread_create"); #ifdef SCHED_IDLE struct sched_param sched_param = { diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index cb3e73cb5..ba001e555 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -85,8 +85,7 @@ void lwan_set_thread_name(const char *name); void lwan_response_init(struct lwan *l); void lwan_response_shutdown(struct lwan *l); -void lwan_socket_init(struct lwan *l); -void lwan_socket_shutdown(struct lwan *l); +int lwan_create_listen_socket(struct lwan *l); void lwan_thread_init(struct lwan *l); void lwan_thread_shutdown(struct lwan *l); @@ -97,6 +96,7 @@ void lwan_status_init(struct lwan *l); void lwan_status_shutdown(struct lwan *l); void lwan_job_thread_init(void); +void lwan_job_thread_main_loop(void); void lwan_job_thread_shutdown(void); void lwan_job_add(bool (*cb)(void *data), void *data); void lwan_job_del(bool (*cb)(void *data), void *data); diff --git a/src/lib/lwan-socket.c b/src/lib/lwan-socket.c index 41df9eaf7..a2ac45eea 100644 --- a/src/lib/lwan-socket.c +++ b/src/lib/lwan-socket.c @@ -184,7 +184,7 @@ static int listen_addrinfo(int fd, const struct addrinfo *addr) lwan_status_warning("%s not supported by the kernel", #_option); \ } while (0) -static int bind_and_listen_addrinfos(struct addrinfo *addrs, bool reuse_port) +static int bind_and_listen_addrinfos(struct addrinfo *addrs) { const struct addrinfo *addr; @@ -198,11 +198,9 @@ static int bind_and_listen_addrinfos(struct addrinfo *addrs, bool reuse_port) SET_SOCKET_OPTION(SOL_SOCKET, SO_REUSEADDR, (int[]){1}); #ifdef SO_REUSEPORT - SET_SOCKET_OPTION_MAY_FAIL(SOL_SOCKET, SO_REUSEPORT, - (int[]){reuse_port}); + SET_SOCKET_OPTION(SOL_SOCKET, SO_REUSEPORT, (int[]){1}); #else - if (reuse_port) - lwan_status_warning("reuse_port not supported by the OS"); + lwan_status_critical("SO_REUSEPORT not supported by the OS"); #endif if (!bind(fd, addr->ai_addr, addr->ai_addrlen)) @@ -219,6 +217,7 @@ static int setup_socket_normally(struct lwan *l) char *node, *port; char *listener = strdupa(l->config.listener); sa_family_t family = parse_listener(listener, &node, &port); + if (family == AF_MAX) { lwan_status_critical("Could not parse listener: %s", l->config.listener); @@ -233,12 +232,12 @@ static int setup_socket_normally(struct lwan *l) if (ret) lwan_status_critical("getaddrinfo: %s", gai_strerror(ret)); - int fd = bind_and_listen_addrinfos(addrs, l->config.reuse_port); + int fd = bind_and_listen_addrinfos(addrs); freeaddrinfo(addrs); return fd; } -void lwan_socket_init(struct lwan *l) +int lwan_create_listen_socket(struct lwan *l) { int fd, n; @@ -262,13 +261,14 @@ void lwan_socket_init(struct lwan *l) #define TCP_FASTOPEN 23 #endif + SET_SOCKET_OPTION_MAY_FAIL(SOL_SOCKET, SO_REUSEADDR, (int[]){1}); SET_SOCKET_OPTION_MAY_FAIL(SOL_TCP, TCP_FASTOPEN, (int[]){5}); SET_SOCKET_OPTION_MAY_FAIL(SOL_TCP, TCP_QUICKACK, (int[]){0}); SET_SOCKET_OPTION_MAY_FAIL(SOL_TCP, TCP_DEFER_ACCEPT, (int[]){(int)l->config.keep_alive_timeout}); #endif - l->main_socket = fd; + return fd; } #undef SET_SOCKET_OPTION diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index c213195e5..9fe858456 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -349,12 +349,9 @@ static ALWAYS_INLINE bool spawn_coro(struct lwan_connection *conn, return true; } -static void accept_nudge(int pipe_fd, - struct lwan_thread *t, - struct lwan_connection *conns, +static void accept_nudge(struct lwan_thread *t, struct timeout_queue *tq, - struct coro_switcher *switcher, - int epoll_fd) + int pipe_fd) { uint64_t event; @@ -434,6 +431,96 @@ turn_timer_wheel(struct timeout_queue *tq, struct lwan_thread *t, int epoll_fd) return (int)timeouts_timeout(t->wheel); } +enum herd_accept { HERD_MORE = 0, HERD_GONE = -1, HERD_SHUTDOWN = 1 }; + +struct core_bitmap { + uint64_t bitmap[4]; +}; + +static ALWAYS_INLINE int schedule_client(struct lwan *l, int fd) +{ + struct lwan_thread *thread = l->conns[fd].thread; + + lwan_thread_add_client(thread, fd); + + return (int)(thread - l->thread.threads); +} + +static ALWAYS_INLINE enum herd_accept +accept_one(struct lwan *l, int listen_fd, struct core_bitmap *cores) +{ + int fd = accept4(listen_fd, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC); + + if (LIKELY(fd >= 0)) { + int core = schedule_client(l, fd); + + cores->bitmap[core / 64] |= UINT64_C(1)<<(core % 64); + + return HERD_MORE; + } + + switch (errno) { + case EAGAIN: + return HERD_GONE; + + case EBADF: + case ECONNABORTED: + case EINVAL: + lwan_status_info("Listening socket closed"); + return HERD_SHUTDOWN; + + default: + lwan_status_perror("accept"); + return HERD_MORE; + } +} + +static bool try_accept_connections(struct lwan_thread *t, int listen_fd) +{ + struct lwan *lwan = t->lwan; + struct core_bitmap cores = {}; + enum herd_accept ha; + + ha = accept_one(lwan, listen_fd, &cores); + if (ha == HERD_MORE) { + do { + ha = accept_one(lwan, listen_fd, &cores); + } while (ha == HERD_MORE); + } + if (UNLIKELY(ha > HERD_MORE)) + return false; + + for (size_t i = 0; i < N_ELEMENTS(cores.bitmap); i++) { + for (uint64_t c = cores.bitmap[i]; c; c ^= c & -c) { + size_t core = (size_t)__builtin_ctzl(c); + struct lwan_thread *cur_thread = &lwan->thread.threads[i * 64 + core]; + + if (cur_thread != t) + lwan_thread_nudge(cur_thread); + } + } + + return true; +} + +static int create_listen_socket(struct lwan_thread *t) +{ + int listen_fd; + + listen_fd = lwan_create_listen_socket(t->lwan); + if (listen_fd < 0) + lwan_status_critical("Could not create listen_fd"); + + struct epoll_event event = { + .events = EPOLLIN | EPOLLET | EPOLLHUP | EPOLLERR | EPOLLEXCLUSIVE, + .data.ptr = NULL, + }; + if (epoll_ctl(t->epoll_fd, EPOLL_CTL_ADD, listen_fd, &event) < 0) + lwan_status_critical_perror("Could not add socket to epoll"); + + return listen_fd; +} + static void *thread_io_loop(void *data) { struct lwan_thread *t = data; @@ -444,11 +531,14 @@ static void *thread_io_loop(void *data) struct epoll_event *events; struct coro_switcher switcher; struct timeout_queue tq; + int listen_fd; lwan_status_debug("Worker thread #%zd starting", t - t->lwan->thread.threads + 1); lwan_set_thread_name("worker"); + listen_fd = create_listen_socket(t); + events = calloc((size_t)max_events, sizeof(*events)); if (UNLIKELY(!events)) lwan_status_critical("Could not allocate memory for events"); @@ -473,8 +563,12 @@ static void *thread_io_loop(void *data) struct lwan_connection *conn; if (UNLIKELY(!event->data.ptr)) { - accept_nudge(read_pipe_fd, t, lwan->conns, &tq, &switcher, - epoll_fd); + accept_nudge(t, &tq, read_pipe_fd); + if (UNLIKELY(!try_accept_connections(t, listen_fd))) { + close(epoll_fd); + epoll_fd = -1; + break; + } continue; } @@ -499,6 +593,7 @@ static void *thread_io_loop(void *data) timeout_queue_expire_all(&tq); free(events); + close(listen_fd); return NULL; } diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 2d5d07a76..49792902f 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -56,7 +56,6 @@ static const struct lwan_config default_config = { .listener = "localhost:8080", .keep_alive_timeout = 15, .quiet = false, - .reuse_port = false, .proxy_protocol = false, .allow_cors = false, .expires = 1 * ONE_WEEK, @@ -515,9 +514,6 @@ static bool setup_from_config(struct lwan *lwan, const char *path) } else if (streq(line->key, "quiet")) { lwan->config.quiet = parse_bool(line->value, default_config.quiet); - } else if (streq(line->key, "reuse_port")) { - lwan->config.reuse_port = - parse_bool(line->value, default_config.reuse_port); } else if (streq(line->key, "proxy_protocol")) { lwan->config.proxy_protocol = parse_bool(line->value, default_config.proxy_protocol); @@ -759,7 +755,6 @@ void lwan_init_with_config(struct lwan *l, const struct lwan_config *config) lwan_readahead_init(); lwan_thread_init(l); - lwan_socket_init(l); lwan_http_authorize_init(); } @@ -787,106 +782,11 @@ void lwan_shutdown(struct lwan *l) lwan_readahead_shutdown(); } -static ALWAYS_INLINE int schedule_client(struct lwan *l, int fd) -{ - struct lwan_thread *thread = l->conns[fd].thread; - - lwan_thread_add_client(thread, fd); - - return (int)(thread - l->thread.threads); -} - -static volatile sig_atomic_t main_socket = -1; - -static_assert(sizeof(main_socket) >= sizeof(int), - "size of sig_atomic_t > size of int"); - -static void sigint_handler(int signal_number __attribute__((unused))) -{ - if (main_socket < 0) - return; - - shutdown((int)main_socket, SHUT_RDWR); - close((int)main_socket); - - main_socket = -1; -} - -enum herd_accept { HERD_MORE = 0, HERD_GONE = -1, HERD_SHUTDOWN = 1 }; - -struct core_bitmap { - uint64_t bitmap[4]; -}; - -static ALWAYS_INLINE enum herd_accept -accept_one(struct lwan *l, struct core_bitmap *cores) -{ - int fd = accept4((int)main_socket, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC); - - if (LIKELY(fd >= 0)) { - int core = schedule_client(l, fd); - - cores->bitmap[core / 64] |= UINT64_C(1)<<(core % 64); - - return HERD_MORE; - } - - switch (errno) { - case EAGAIN: - return HERD_GONE; - - case EBADF: - case ECONNABORTED: - case EINVAL: - if (main_socket < 0) { - lwan_status_info("Signal 2 (Interrupt) received"); - } else { - lwan_status_info("Main socket closed for unknown reasons"); - } - return HERD_SHUTDOWN; - - default: - lwan_status_perror("accept"); - return HERD_MORE; - } -} - void lwan_main_loop(struct lwan *l) { - struct core_bitmap cores = {}; - - assert(main_socket == -1); - main_socket = l->main_socket; - - if (signal(SIGINT, sigint_handler) == SIG_ERR) - lwan_status_critical("Could not set signal handler"); - lwan_status_info("Ready to serve"); - while (true) { - enum herd_accept ha; - - fcntl(l->main_socket, F_SETFL, 0); - ha = accept_one(l, &cores); - if (ha == HERD_MORE) { - fcntl(l->main_socket, F_SETFL, O_NONBLOCK); - - do { - ha = accept_one(l, &cores); - } while (ha == HERD_MORE); - } - - if (UNLIKELY(ha > HERD_MORE)) - break; - - for (size_t i = 0; i < N_ELEMENTS(cores.bitmap); i++) { - for (uint64_t c = cores.bitmap[i]; c; c ^= c & -c) { - size_t core = (size_t)__builtin_ctzl(c); - lwan_thread_nudge(&l->thread.threads[i * 64 + core]); - } - } - cores = (struct core_bitmap){}; - } + lwan_job_thread_main_loop(); } #ifdef CLOCK_MONOTONIC_COARSE diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 3a37f3d3f..d2d3bcfbf 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -465,7 +465,6 @@ struct lwan_config { unsigned int n_threads; bool quiet; - bool reuse_port; bool proxy_protocol; bool allow_cors; bool allow_post_temp_file; @@ -487,8 +486,6 @@ struct lwan { struct lwan_config config; struct coro_switcher switcher; - int main_socket; - unsigned int online_cpus; unsigned int available_cpus; }; From 3bdab3a140f024d4dad6ef9026e70227be3663c8 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 8 May 2021 14:18:37 -0700 Subject: [PATCH 1705/2505] n_queue_fds parameter isn't used any longer --- src/lib/lwan-thread.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 9fe858456..51bfc7bc8 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -598,8 +598,7 @@ static void *thread_io_loop(void *data) return NULL; } -static void create_thread(struct lwan *l, struct lwan_thread *thread, - const size_t n_queue_fds) +static void create_thread(struct lwan *l, struct lwan_thread *thread) { int ignore; pthread_attr_t attr; @@ -814,11 +813,8 @@ void lwan_thread_init(struct lwan *l) if (!l->thread.threads) lwan_status_critical("Could not allocate memory for threads"); - const size_t n_queue_fds = LWAN_MIN(l->thread.max_fd / l->thread.count, - (size_t)(2 * lwan_socket_get_backlog_size())); - lwan_status_debug("Pending client file descriptor queue has %zu items", n_queue_fds); for (unsigned int i = 0; i < l->thread.count; i++) - create_thread(l, &l->thread.threads[i], n_queue_fds); + create_thread(l, &l->thread.threads[i]); const unsigned int total_conns = l->thread.max_fd * l->thread.count; #ifdef __x86_64__ From 5f8c3c2c595cc7753c329fe5a9653883205acb3e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 8 May 2021 14:26:36 -0700 Subject: [PATCH 1706/2505] EPOLLEXCLUSIVE has a LIFO order, so maybe not use it Thanks to Paul Khuong for the heads up --- src/lib/lwan-thread.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 51bfc7bc8..36aa6fc93 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -512,7 +512,7 @@ static int create_listen_socket(struct lwan_thread *t) lwan_status_critical("Could not create listen_fd"); struct epoll_event event = { - .events = EPOLLIN | EPOLLET | EPOLLHUP | EPOLLERR | EPOLLEXCLUSIVE, + .events = EPOLLIN | EPOLLET | EPOLLERR, .data.ptr = NULL, }; if (epoll_ctl(t->epoll_fd, EPOLL_CTL_ADD, listen_fd, &event) < 0) From b823169538ca920436f38676fbbb2d3e5eaa3f00 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 9 May 2021 09:02:14 -0700 Subject: [PATCH 1707/2505] Get rid of the eventfd used for nudging threads This doesn't seem to be necessary anymore, given that the burden of accepting connections is now spread among all worker threads. --- CMakeLists.txt | 1 - src/cmake/lwan-build-config.h.cmake | 1 - src/lib/lwan-private.h | 1 - src/lib/lwan-thread.c | 95 +++-------------------------- src/lib/lwan.h | 1 - 5 files changed, 10 insertions(+), 89 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f7556aa8c..17d2ee61e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -145,7 +145,6 @@ check_function_exists(mkostemp HAVE_MKOSTEMP) check_function_exists(clock_gettime HAVE_CLOCK_GETTIME) check_function_exists(pthread_barrier_init HAVE_PTHREADBARRIER) check_function_exists(pthread_set_name_np HAVE_PTHREAD_SET_NAME_NP) -check_function_exists(eventfd HAVE_EVENTFD) check_function_exists(posix_fadvise HAVE_POSIX_FADVISE) check_function_exists(getentropy HAVE_GETENTROPY) check_function_exists(fwrite_unlocked HAVE_FWRITE_UNLOCKED) diff --git a/src/cmake/lwan-build-config.h.cmake b/src/cmake/lwan-build-config.h.cmake index c5f1b045d..0abb2d9f7 100644 --- a/src/cmake/lwan-build-config.h.cmake +++ b/src/cmake/lwan-build-config.h.cmake @@ -33,7 +33,6 @@ #cmakedefine HAVE_RAWMEMCHR #cmakedefine HAVE_READAHEAD #cmakedefine HAVE_REALLOCARRAY -#cmakedefine HAVE_EVENTFD #cmakedefine HAVE_EPOLL #cmakedefine HAVE_KQUEUE #cmakedefine HAVE_DLADDR diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index ba001e555..76f39e7f8 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -90,7 +90,6 @@ int lwan_create_listen_socket(struct lwan *l); void lwan_thread_init(struct lwan *l); void lwan_thread_shutdown(struct lwan *l); void lwan_thread_add_client(struct lwan_thread *t, int fd); -void lwan_thread_nudge(struct lwan_thread *t); void lwan_status_init(struct lwan *l); void lwan_status_shutdown(struct lwan *l); diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 36aa6fc93..3e2acae1a 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -31,10 +31,6 @@ #include #include -#if defined(HAVE_EVENTFD) -#include -#endif - #include "lwan-private.h" #include "lwan-tq.h" #include "list.h" @@ -349,19 +345,6 @@ static ALWAYS_INLINE bool spawn_coro(struct lwan_connection *conn, return true; } -static void accept_nudge(struct lwan_thread *t, - struct timeout_queue *tq, - int pipe_fd) -{ - uint64_t event; - - /* Errors are ignored here as pipe_fd serves just as a way to wake the - * thread from epoll_wait(). It's fine to consume the queue at this - * point, regardless of the error type. */ - (void)read(pipe_fd, &event, sizeof(event)); - timeouts_add(t->wheel, &tq->timeout, 1000); -} - static bool process_pending_timers(struct timeout_queue *tq, struct lwan_thread *t, int epoll_fd) @@ -433,29 +416,13 @@ turn_timer_wheel(struct timeout_queue *tq, struct lwan_thread *t, int epoll_fd) enum herd_accept { HERD_MORE = 0, HERD_GONE = -1, HERD_SHUTDOWN = 1 }; -struct core_bitmap { - uint64_t bitmap[4]; -}; - -static ALWAYS_INLINE int schedule_client(struct lwan *l, int fd) -{ - struct lwan_thread *thread = l->conns[fd].thread; - - lwan_thread_add_client(thread, fd); - - return (int)(thread - l->thread.threads); -} - static ALWAYS_INLINE enum herd_accept -accept_one(struct lwan *l, int listen_fd, struct core_bitmap *cores) +accept_one(struct lwan *l, int listen_fd) { int fd = accept4(listen_fd, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC); if (LIKELY(fd >= 0)) { - int core = schedule_client(l, fd); - - cores->bitmap[core / 64] |= UINT64_C(1)<<(core % 64); - + lwan_thread_add_client(l->conns[fd].thread, fd); return HERD_MORE; } @@ -478,27 +445,13 @@ accept_one(struct lwan *l, int listen_fd, struct core_bitmap *cores) static bool try_accept_connections(struct lwan_thread *t, int listen_fd) { struct lwan *lwan = t->lwan; - struct core_bitmap cores = {}; enum herd_accept ha; - ha = accept_one(lwan, listen_fd, &cores); - if (ha == HERD_MORE) { - do { - ha = accept_one(lwan, listen_fd, &cores); - } while (ha == HERD_MORE); + while ((ha = accept_one(lwan, listen_fd)) == HERD_MORE) { } - if (UNLIKELY(ha > HERD_MORE)) - return false; - for (size_t i = 0; i < N_ELEMENTS(cores.bitmap); i++) { - for (uint64_t c = cores.bitmap[i]; c; c ^= c & -c) { - size_t core = (size_t)__builtin_ctzl(c); - struct lwan_thread *cur_thread = &lwan->thread.threads[i * 64 + core]; - - if (cur_thread != t) - lwan_thread_nudge(cur_thread); - } - } + if (ha > HERD_MORE) + return false; return true; } @@ -525,7 +478,6 @@ static void *thread_io_loop(void *data) { struct lwan_thread *t = data; int epoll_fd = t->epoll_fd; - const int read_pipe_fd = t->pipe_fd[0]; const int max_events = LWAN_MIN((int)t->lwan->thread.max_fd, 1024); struct lwan *lwan = t->lwan; struct epoll_event *events; @@ -562,13 +514,13 @@ static void *thread_io_loop(void *data) for (struct epoll_event *event = events; n_fds--; event++) { struct lwan_connection *conn; - if (UNLIKELY(!event->data.ptr)) { - accept_nudge(t, &tq, read_pipe_fd); + if (!event->data.ptr) { if (UNLIKELY(!try_accept_connections(t, listen_fd))) { close(epoll_fd); epoll_fd = -1; break; } + timeouts_add(t->wheel, &tq.timeout, 1000); continue; } @@ -622,21 +574,6 @@ static void create_thread(struct lwan *l, struct lwan_thread *thread) if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE)) lwan_status_critical_perror("pthread_attr_setdetachstate"); -#if defined(HAVE_EVENTFD) - int efd = eventfd(0, EFD_NONBLOCK | EFD_SEMAPHORE | EFD_CLOEXEC); - if (efd < 0) - lwan_status_critical_perror("eventfd"); - - thread->pipe_fd[0] = thread->pipe_fd[1] = efd; -#else - if (pipe2(thread->pipe_fd, O_NONBLOCK | O_CLOEXEC) < 0) - lwan_status_critical_perror("pipe"); -#endif - - struct epoll_event event = { .events = EPOLLIN, .data.ptr = NULL }; - if (epoll_ctl(thread->epoll_fd, EPOLL_CTL_ADD, thread->pipe_fd[0], &event) < 0) - lwan_status_critical_perror("epoll_ctl"); - if (pthread_create(&thread->self, &attr, thread_io_loop, thread)) lwan_status_critical_perror("pthread_create"); @@ -644,14 +581,6 @@ static void create_thread(struct lwan *l, struct lwan_thread *thread) lwan_status_critical_perror("pthread_attr_destroy"); } -void lwan_thread_nudge(struct lwan_thread *t) -{ - uint64_t event = 1; - - if (UNLIKELY(write(t->pipe_fd[1], &event, sizeof(event)) < 0)) - lwan_status_perror("write"); -} - void lwan_thread_add_client(struct lwan_thread *t, int fd) { struct epoll_event ev = { @@ -862,9 +791,10 @@ void lwan_thread_shutdown(struct lwan *l) for (unsigned int i = 0; i < l->thread.count; i++) { struct lwan_thread *t = &l->thread.threads[i]; + int epoll_fd = t->epoll_fd; - close(t->epoll_fd); - lwan_thread_nudge(t); + t->epoll_fd = -1; + close(epoll_fd); } pthread_barrier_wait(&l->thread.barrier); @@ -873,11 +803,6 @@ void lwan_thread_shutdown(struct lwan *l) for (unsigned int i = 0; i < l->thread.count; i++) { struct lwan_thread *t = &l->thread.threads[i]; - close(t->pipe_fd[0]); -#if !defined(HAVE_EVENTFD) - close(t->pipe_fd[1]); -#endif - pthread_join(l->thread.threads[i].self, NULL); timeouts_close(t->wheel); } diff --git a/src/lib/lwan.h b/src/lib/lwan.h index d2d3bcfbf..82fd9016d 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -438,7 +438,6 @@ struct lwan_thread { } date; struct timeouts *wheel; int epoll_fd; - int pipe_fd[2]; pthread_t self; }; From d1be6c0627fcaea26f1984de299eee95fb9f80d3 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 9 May 2021 09:27:29 -0700 Subject: [PATCH 1708/2505] Only print one "Listening on http://..." message --- src/lib/lwan-private.h | 2 +- src/lib/lwan-socket.c | 39 +++++++++++++++++++++------------------ src/lib/lwan-thread.c | 31 ++++++++++++++++++------------- src/lib/lwan.h | 1 + 4 files changed, 41 insertions(+), 32 deletions(-) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 76f39e7f8..bf9ea6f81 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -85,7 +85,7 @@ void lwan_set_thread_name(const char *name); void lwan_response_init(struct lwan *l); void lwan_response_shutdown(struct lwan *l); -int lwan_create_listen_socket(struct lwan *l); +int lwan_create_listen_socket(struct lwan *l, bool print_listening_msg); void lwan_thread_init(struct lwan *l); void lwan_thread_shutdown(struct lwan *l); diff --git a/src/lib/lwan-socket.c b/src/lib/lwan-socket.c index a2ac45eea..537991fba 100644 --- a/src/lib/lwan-socket.c +++ b/src/lib/lwan-socket.c @@ -150,22 +150,25 @@ static sa_family_t parse_listener(char *listener, char **node, char **port) return parse_listener_ipv4(listener, node, port); } -static int listen_addrinfo(int fd, const struct addrinfo *addr) +static int +listen_addrinfo(int fd, const struct addrinfo *addr, bool print_listening_msg) { if (listen(fd, lwan_socket_get_backlog_size()) < 0) lwan_status_critical_perror("listen"); - char host_buf[NI_MAXHOST], serv_buf[NI_MAXSERV]; - int ret = getnameinfo(addr->ai_addr, addr->ai_addrlen, host_buf, - sizeof(host_buf), serv_buf, sizeof(serv_buf), - NI_NUMERICHOST | NI_NUMERICSERV); - if (ret) - lwan_status_critical("getnameinfo: %s", gai_strerror(ret)); - - if (addr->ai_family == AF_INET6) - lwan_status_info("Listening on http://[%s]:%s", host_buf, serv_buf); - else - lwan_status_info("Listening on http://%s:%s", host_buf, serv_buf); + if (print_listening_msg) { + char host_buf[NI_MAXHOST], serv_buf[NI_MAXSERV]; + int ret = getnameinfo(addr->ai_addr, addr->ai_addrlen, host_buf, + sizeof(host_buf), serv_buf, sizeof(serv_buf), + NI_NUMERICHOST | NI_NUMERICSERV); + if (ret) + lwan_status_critical("getnameinfo: %s", gai_strerror(ret)); + + if (addr->ai_family == AF_INET6) + lwan_status_info("Listening on http://[%s]:%s", host_buf, serv_buf); + else + lwan_status_info("Listening on http://%s:%s", host_buf, serv_buf); + } return set_socket_flags(fd); } @@ -184,7 +187,7 @@ static int listen_addrinfo(int fd, const struct addrinfo *addr) lwan_status_warning("%s not supported by the kernel", #_option); \ } while (0) -static int bind_and_listen_addrinfos(struct addrinfo *addrs) +static int bind_and_listen_addrinfos(const struct addrinfo *addrs, bool print_listening_msg) { const struct addrinfo *addr; @@ -204,7 +207,7 @@ static int bind_and_listen_addrinfos(struct addrinfo *addrs) #endif if (!bind(fd, addr->ai_addr, addr->ai_addrlen)) - return listen_addrinfo(fd, addr); + return listen_addrinfo(fd, addr, print_listening_msg); close(fd); } @@ -212,7 +215,7 @@ static int bind_and_listen_addrinfos(struct addrinfo *addrs) lwan_status_critical("Could not bind socket"); } -static int setup_socket_normally(struct lwan *l) +static int setup_socket_normally(struct lwan *l, bool print_listening_msg) { char *node, *port; char *listener = strdupa(l->config.listener); @@ -232,12 +235,12 @@ static int setup_socket_normally(struct lwan *l) if (ret) lwan_status_critical("getaddrinfo: %s", gai_strerror(ret)); - int fd = bind_and_listen_addrinfos(addrs); + int fd = bind_and_listen_addrinfos(addrs, print_listening_msg); freeaddrinfo(addrs); return fd; } -int lwan_create_listen_socket(struct lwan *l) +int lwan_create_listen_socket(struct lwan *l, bool print_listening_msg) { int fd, n; @@ -249,7 +252,7 @@ int lwan_create_listen_socket(struct lwan *l) } else if (n == 1) { fd = setup_socket_from_systemd(); } else { - fd = setup_socket_normally(l); + fd = setup_socket_normally(l, print_listening_msg); } SET_SOCKET_OPTION(SOL_SOCKET, SO_LINGER, diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 3e2acae1a..75b33f233 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -417,9 +417,9 @@ turn_timer_wheel(struct timeout_queue *tq, struct lwan_thread *t, int epoll_fd) enum herd_accept { HERD_MORE = 0, HERD_GONE = -1, HERD_SHUTDOWN = 1 }; static ALWAYS_INLINE enum herd_accept -accept_one(struct lwan *l, int listen_fd) +accept_one(struct lwan *l, const struct lwan_thread *t) { - int fd = accept4(listen_fd, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC); + int fd = accept4(t->listen_fd, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC); if (LIKELY(fd >= 0)) { lwan_thread_add_client(l->conns[fd].thread, fd); @@ -442,12 +442,12 @@ accept_one(struct lwan *l, int listen_fd) } } -static bool try_accept_connections(struct lwan_thread *t, int listen_fd) +static bool try_accept_connections(const struct lwan_thread *t) { struct lwan *lwan = t->lwan; enum herd_accept ha; - while ((ha = accept_one(lwan, listen_fd)) == HERD_MORE) { + while ((ha = accept_one(lwan, t)) == HERD_MORE) { } if (ha > HERD_MORE) @@ -456,11 +456,11 @@ static bool try_accept_connections(struct lwan_thread *t, int listen_fd) return true; } -static int create_listen_socket(struct lwan_thread *t) +static int create_listen_socket(struct lwan_thread *t, bool print_listening_msg) { int listen_fd; - listen_fd = lwan_create_listen_socket(t->lwan); + listen_fd = lwan_create_listen_socket(t->lwan, print_listening_msg); if (listen_fd < 0) lwan_status_critical("Could not create listen_fd"); @@ -483,14 +483,11 @@ static void *thread_io_loop(void *data) struct epoll_event *events; struct coro_switcher switcher; struct timeout_queue tq; - int listen_fd; lwan_status_debug("Worker thread #%zd starting", t - t->lwan->thread.threads + 1); lwan_set_thread_name("worker"); - listen_fd = create_listen_socket(t); - events = calloc((size_t)max_events, sizeof(*events)); if (UNLIKELY(!events)) lwan_status_critical("Could not allocate memory for events"); @@ -515,7 +512,7 @@ static void *thread_io_loop(void *data) struct lwan_connection *conn; if (!event->data.ptr) { - if (UNLIKELY(!try_accept_connections(t, listen_fd))) { + if (UNLIKELY(!try_accept_connections(t))) { close(epoll_fd); epoll_fd = -1; break; @@ -545,7 +542,6 @@ static void *thread_io_loop(void *data) timeout_queue_expire_all(&tq); free(events); - close(listen_fd); return NULL; } @@ -742,8 +738,14 @@ void lwan_thread_init(struct lwan *l) if (!l->thread.threads) lwan_status_critical("Could not allocate memory for threads"); - for (unsigned int i = 0; i < l->thread.count; i++) - create_thread(l, &l->thread.threads[i]); + for (unsigned int i = 0; i < l->thread.count; i++) { + struct lwan_thread *thread = &l->thread.threads[i]; + + create_thread(l, thread); + + if ((thread->listen_fd = create_listen_socket(thread, i == 0)) < 0) + lwan_status_critical_perror("Could not create listening socket"); + } const unsigned int total_conns = l->thread.max_fd * l->thread.count; #ifdef __x86_64__ @@ -792,9 +794,12 @@ void lwan_thread_shutdown(struct lwan *l) for (unsigned int i = 0; i < l->thread.count; i++) { struct lwan_thread *t = &l->thread.threads[i]; int epoll_fd = t->epoll_fd; + int listen_fd = t->listen_fd; + t->listen_fd = -1; t->epoll_fd = -1; close(epoll_fd); + close(listen_fd); } pthread_barrier_wait(&l->thread.barrier); diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 82fd9016d..cf61c7e94 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -438,6 +438,7 @@ struct lwan_thread { } date; struct timeouts *wheel; int epoll_fd; + int listen_fd; pthread_t self; }; From 06785fb960b841d4d75c337cfbf41ebaebd0f2f9 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 9 May 2021 12:37:59 -0700 Subject: [PATCH 1709/2505] Fix timers after getting rid of eventfd in b8231695 Previous to the change in b8231695, a thread's epoll would wake up from a nudge, have all coroutines for new connections created, client file descriptors added to the epoll set, and then it would wait for epoll_wait to notify of events to handle. With the change, coroutines would be resumed right after being created, and would not give an opportunity to turn the timer wheel. So requests that happened right after the server started would consider the amount of time that had passed since the thread boot time to the sleep time. Move to the previous behavior by not resuming the coroutine off the bat, and give an opportunity for the timer wheel to turn. --- src/lib/lwan-thread.c | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 75b33f233..127e83027 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -309,7 +309,7 @@ static void update_date_cache(struct lwan_thread *thread) thread->date.expires); } -static ALWAYS_INLINE bool spawn_coro(struct lwan_connection *conn, +static ALWAYS_INLINE void spawn_coro(struct lwan_connection *conn, struct coro_switcher *switcher, struct timeout_queue *tq) { @@ -327,22 +327,20 @@ static ALWAYS_INLINE bool spawn_coro(struct lwan_connection *conn, .time_to_expire = tq->current_time + tq->move_to_last_bump, .thread = t, }; - if (UNLIKELY(!conn->coro)) { - /* FIXME: send a "busy" response to this client? we don't have a coroutine - * at this point, can't use lwan_send() here */ - lwan_status_error("Could not create coroutine, dropping connection"); + if (LIKELY(conn->coro)) { + timeout_queue_insert(tq, conn); + return; + } - conn->flags = 0; + /* FIXME: send a "busy" response to this client? we don't have a coroutine + * at this point, can't use lwan_send() here */ + lwan_status_error("Could not create coroutine, dropping connection"); - int fd = lwan_connection_get_fd(tq->lwan, conn); - shutdown(fd, SHUT_RDWR); - close(fd); + conn->flags = 0; - return false; - } - - timeout_queue_insert(tq, conn); - return true; + int fd = lwan_connection_get_fd(tq->lwan, conn); + shutdown(fd, SHUT_RDWR); + close(fd); } static bool process_pending_timers(struct timeout_queue *tq, @@ -529,8 +527,8 @@ static void *thread_io_loop(void *data) } if (!conn->coro) { - if (UNLIKELY(!spawn_coro(conn, &switcher, &tq))) - continue; + spawn_coro(conn, &switcher, &tq); + continue; } resume_coro(&tq, conn, epoll_fd); From 8a6f6b3bdab346bf66788722338e5bdb71ab2579 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 9 May 2021 21:17:39 -0700 Subject: [PATCH 1710/2505] lwan_thread_add_client() doesn't need to exist anymore --- src/lib/lwan-private.h | 1 - src/lib/lwan-thread.c | 16 ++++++---------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index bf9ea6f81..f117cbe1b 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -89,7 +89,6 @@ int lwan_create_listen_socket(struct lwan *l, bool print_listening_msg); void lwan_thread_init(struct lwan *l); void lwan_thread_shutdown(struct lwan *l); -void lwan_thread_add_client(struct lwan_thread *t, int fd); void lwan_status_init(struct lwan *l); void lwan_status_shutdown(struct lwan *l); diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 127e83027..73de7d9e1 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -420,7 +420,12 @@ accept_one(struct lwan *l, const struct lwan_thread *t) int fd = accept4(t->listen_fd, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC); if (LIKELY(fd >= 0)) { - lwan_thread_add_client(l->conns[fd].thread, fd); + struct epoll_event ev = { + .data.ptr = &l->conns[fd], + .events = conn_flags_to_epoll_events(CONN_EVENTS_READ), + }; + epoll_ctl(t->epoll_fd, EPOLL_CTL_ADD, fd, &ev); + return HERD_MORE; } @@ -575,15 +580,6 @@ static void create_thread(struct lwan *l, struct lwan_thread *thread) lwan_status_critical_perror("pthread_attr_destroy"); } -void lwan_thread_add_client(struct lwan_thread *t, int fd) -{ - struct epoll_event ev = { - .data.ptr = &t->lwan->conns[fd], - .events = conn_flags_to_epoll_events(CONN_EVENTS_READ), - }; - epoll_ctl(t->epoll_fd, EPOLL_CTL_ADD, fd, &ev); -} - #if defined(__linux__) && defined(__x86_64__) static bool read_cpu_topology(struct lwan *l, uint32_t siblings[]) { From c80b426138c454e8dfb1e51ff2b30124f568995d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 10 May 2021 08:44:00 -0700 Subject: [PATCH 1711/2505] Don't re-arm timeout queue timeout every time a new connection comes in --- src/lib/lwan-thread.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 73de7d9e1..959bd2164 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -504,6 +504,7 @@ static void *thread_io_loop(void *data) for (;;) { int timeout = turn_timer_wheel(&tq, t, epoll_fd); int n_fds = epoll_wait(epoll_fd, events, max_events, timeout); + bool accepted_connection = false; if (UNLIKELY(n_fds < 0)) { if (errno == EBADF || errno == EINVAL) @@ -515,13 +516,13 @@ static void *thread_io_loop(void *data) struct lwan_connection *conn; if (!event->data.ptr) { - if (UNLIKELY(!try_accept_connections(t))) { - close(epoll_fd); - epoll_fd = -1; - break; + if (LIKELY(try_accept_connections(t)) { + accepted_connections = true; + continue; } - timeouts_add(t->wheel, &tq.timeout, 1000); - continue; + close(epoll_fd); + epoll_fd = -1; + break; } conn = event->data.ptr; @@ -539,6 +540,11 @@ static void *thread_io_loop(void *data) resume_coro(&tq, conn, epoll_fd); timeout_queue_move_to_last(&tq, conn); } + + if (accepted_connection) { + timeouts_add(t->wheel, &tq.timeout, 1000); + accepted_connection = false; + } } pthread_barrier_wait(&lwan->thread.barrier); From 29e0ca2c021b61e755728865b4e2747cba81ddf1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 10 May 2021 08:44:29 -0700 Subject: [PATCH 1712/2505] Connections may be dropped before coroutine was created for it A connection might receive a EPOLLHUP|EPOLLRDHUP event before we spawn a coroutine to handle that client. Even though the connection has been reset by the peer, the file descriptor is still alive. Since timeout_queue_expire() will only be called whenever we have a file descriptor already, we can always close the connection, regardless if there's a coroutine or not. This has not been tested. I also do not remember why that check was there in the first place, but this is very old and part of the initial implementation of the timeout queue. This might need to be reversed in the future if we re-discover the reason it was like that in the first place. --- src/lib/lwan-tq.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-tq.c b/src/lib/lwan-tq.c index 8e602380c..b6f65f23e 100644 --- a/src/lib/lwan-tq.c +++ b/src/lib/lwan-tq.c @@ -91,9 +91,9 @@ void timeout_queue_expire(struct timeout_queue *tq, if (LIKELY(conn->coro)) { coro_free(conn->coro); conn->coro = NULL; - - close(lwan_connection_get_fd(tq->lwan, conn)); } + + close(lwan_connection_get_fd(tq->lwan, conn)); } void timeout_queue_expire_waiting(struct timeout_queue *tq) From 1b63e201f2a75a622f6035e7a32018b6d73bc3a1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 10 May 2021 08:53:42 -0700 Subject: [PATCH 1713/2505] Fix build after c80b426 --- src/lib/lwan-thread.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 959bd2164..ed7acaa0f 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -504,7 +504,7 @@ static void *thread_io_loop(void *data) for (;;) { int timeout = turn_timer_wheel(&tq, t, epoll_fd); int n_fds = epoll_wait(epoll_fd, events, max_events, timeout); - bool accepted_connection = false; + bool accepted_connections = false; if (UNLIKELY(n_fds < 0)) { if (errno == EBADF || errno == EINVAL) @@ -516,7 +516,7 @@ static void *thread_io_loop(void *data) struct lwan_connection *conn; if (!event->data.ptr) { - if (LIKELY(try_accept_connections(t)) { + if (LIKELY(try_accept_connections(t))) { accepted_connections = true; continue; } @@ -541,9 +541,9 @@ static void *thread_io_loop(void *data) timeout_queue_move_to_last(&tq, conn); } - if (accepted_connection) { + if (accepted_connections) { timeouts_add(t->wheel, &tq.timeout, 1000); - accepted_connection = false; + accepted_connections = false; } } From 8fc11e8687291dd1941aa69595fe79c1f61b0727 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 10 May 2021 18:37:27 -0700 Subject: [PATCH 1714/2505] Remove outdated debug message --- src/lib/lwan-socket.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib/lwan-socket.c b/src/lib/lwan-socket.c index 537991fba..a9e331e3b 100644 --- a/src/lib/lwan-socket.c +++ b/src/lib/lwan-socket.c @@ -244,8 +244,6 @@ int lwan_create_listen_socket(struct lwan *l, bool print_listening_msg) { int fd, n; - lwan_status_debug("Initializing sockets"); - n = sd_listen_fds(1); if (n > 1) { lwan_status_critical("Too many file descriptors received"); From 835cde8e9aba4fe64a6e6cac2b69fc5f12939954 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 10 May 2021 18:37:49 -0700 Subject: [PATCH 1715/2505] Assert CONN_ASYNC_AWAIT isn't set when spawning coroutine --- src/lib/lwan-thread.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index ed7acaa0f..9c5b4e1e4 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -316,6 +316,7 @@ static ALWAYS_INLINE void spawn_coro(struct lwan_connection *conn, struct lwan_thread *t = conn->thread; assert(!conn->coro); + assert(!(conn->flags & CONN_ASYNC_AWAIT)); assert(t); assert((uintptr_t)t >= (uintptr_t)tq->lwan->thread.threads); assert((uintptr_t)t < From 34dae3f1cf44f7819b11f83a9c743deb25d08758 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 10 May 2021 18:38:59 -0700 Subject: [PATCH 1716/2505] Assert that the data cache line is 64 bytes long --- src/lib/lwan-thread.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 9c5b4e1e4..1de29ab4e 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -752,6 +752,9 @@ void lwan_thread_init(struct lwan *l) #ifdef __x86_64__ static_assert(sizeof(struct lwan_connection) == 32, "Two connections per cache line"); +#ifdef _SC_LEVEL1_DCACHE_LINESIZE + assert(sysconf(_SC_LEVEL1_DCACHE_LINESIZE) == 64); +#endif lwan_status_debug("%d CPUs of %d are online. " "Reading topology to pre-schedule clients", From a6e56ed5b54c7ecbddc0d5ab0fb3ddf8a2a2115c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 10 May 2021 18:39:42 -0700 Subject: [PATCH 1717/2505] Use SO_INCOMING_CPU on Linux to coincide with worker thread affinity --- src/lib/lwan-thread.c | 122 +++++++++++++++++++++++++----------------- src/lib/lwan.h | 1 + 2 files changed, 73 insertions(+), 50 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 1de29ab4e..c09f611e5 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -413,51 +413,57 @@ turn_timer_wheel(struct timeout_queue *tq, struct lwan_thread *t, int epoll_fd) return (int)timeouts_timeout(t->wheel); } -enum herd_accept { HERD_MORE = 0, HERD_GONE = -1, HERD_SHUTDOWN = 1 }; - -static ALWAYS_INLINE enum herd_accept -accept_one(struct lwan *l, const struct lwan_thread *t) +static bool accept_waiting_clients(const struct lwan_thread *t) { - int fd = accept4(t->listen_fd, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC); - - if (LIKELY(fd >= 0)) { - struct epoll_event ev = { - .data.ptr = &l->conns[fd], - .events = conn_flags_to_epoll_events(CONN_EVENTS_READ), - }; - epoll_ctl(t->epoll_fd, EPOLL_CTL_ADD, fd, &ev); + const struct lwan_connection *conns = t->lwan->conns; - return HERD_MORE; - } + while (true) { + int fd = + accept4(t->listen_fd, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC); + + if (LIKELY(fd >= 0)) { + const struct lwan_connection *conn = &conns[fd]; + struct epoll_event ev = { + .data.ptr = (void *)conn, + .events = conn_flags_to_epoll_events(CONN_EVENTS_READ), + }; + int r = epoll_ctl(conn->thread->epoll_fd, EPOLL_CTL_ADD, fd, &ev); + + if (UNLIKELY(r < 0)) { + /* FIXME: send a "busy" response here? No coroutine has been + * created at this point to use the usual stuff, though. */ + lwan_status_perror("Could not add file descriptor %d to epoll " + "set %d. Dropping connection", + fd, conn->thread->epoll_fd); + shutdown(fd, SHUT_RDWR); + close(fd); + } - switch (errno) { - case EAGAIN: - return HERD_GONE; +#if __linux__ && defined(__x86_64__) + /* Ignore errors here, as this is just a hint */ + (void)setsockopt(fd, SOL_SOCKET, SO_INCOMING_CPU, &t->cpu, sizeof(t->cpu)); +#endif - case EBADF: - case ECONNABORTED: - case EINVAL: - lwan_status_info("Listening socket closed"); - return HERD_SHUTDOWN; + continue; + } - default: - lwan_status_perror("accept"); - return HERD_MORE; - } -} + switch (errno) { + default: + lwan_status_perror("Unexpected error while accepting connections"); + /* fallthrough */ -static bool try_accept_connections(const struct lwan_thread *t) -{ - struct lwan *lwan = t->lwan; - enum herd_accept ha; + case EAGAIN: + return true; - while ((ha = accept_one(lwan, t)) == HERD_MORE) { + case EBADF: + case ECONNABORTED: + case EINVAL: + lwan_status_info("Listening socket closed"); + return false; + } } - if (ha > HERD_MORE) - return false; - - return true; + __builtin_unreachable(); } static int create_listen_socket(struct lwan_thread *t, bool print_listening_msg) @@ -468,6 +474,11 @@ static int create_listen_socket(struct lwan_thread *t, bool print_listening_msg) if (listen_fd < 0) lwan_status_critical("Could not create listen_fd"); +#if __linux__ && defined(__x86_64__) + /* Ignore errors here, as this is just a hint */ + (void)setsockopt(listen_fd, SOL_SOCKET, SO_INCOMING_CPU, &t->cpu, sizeof(t->cpu)); +#endif + struct epoll_event event = { .events = EPOLLIN | EPOLLET | EPOLLERR, .data.ptr = NULL, @@ -517,7 +528,7 @@ static void *thread_io_loop(void *data) struct lwan_connection *conn; if (!event->data.ptr) { - if (LIKELY(try_accept_connections(t))) { + if (LIKELY(accept_waiting_clients(t))) { accepted_connections = true; continue; } @@ -561,7 +572,6 @@ static void create_thread(struct lwan *l, struct lwan_thread *thread) int ignore; pthread_attr_t attr; - memset(thread, 0, sizeof(*thread)); thread->lwan = l; thread->wheel = timeouts_open(&ignore); @@ -728,6 +738,8 @@ adjust_threads_affinity(struct lwan *l, uint32_t *schedtbl, uint32_t n) void lwan_thread_init(struct lwan *l) { + const unsigned int total_conns = l->thread.max_fd * l->thread.count; + if (pthread_barrier_init(&l->thread.barrier, NULL, (unsigned)l->thread.count + 1)) lwan_status_critical("Could not create barrier"); @@ -739,16 +751,6 @@ void lwan_thread_init(struct lwan *l) if (!l->thread.threads) lwan_status_critical("Could not allocate memory for threads"); - for (unsigned int i = 0; i < l->thread.count; i++) { - struct lwan_thread *thread = &l->thread.threads[i]; - - create_thread(l, thread); - - if ((thread->listen_fd = create_listen_socket(thread, i == 0)) < 0) - lwan_status_critical_perror("Could not create listening socket"); - } - - const unsigned int total_conns = l->thread.max_fd * l->thread.count; #ifdef __x86_64__ static_assert(sizeof(struct lwan_connection) == 32, "Two connections per cache line"); @@ -776,16 +778,36 @@ void lwan_thread_init(struct lwan *l) n_threads--; /* Transform count into mask for AND below */ - if (adj_affinity) - adjust_threads_affinity(l, schedtbl, n_threads); + if (adj_affinity) { + /* Save which CPU this tread will be pinned at so we can use + * SO_INCOMING_CPU later. */ + for (unsigned int i = 0; i < l->thread.count; i++) + l->thread.threads[i].cpu = schedtbl[i & n_threads]; + } for (unsigned int i = 0; i < total_conns; i++) l->conns[i].thread = &l->thread.threads[schedtbl[i & n_threads]]; #else + for (unsigned int i = 0; i < l->thread.count; i++) + l->thread.threads[i].cpu = i % l->thread.count; for (unsigned int i = 0; i < total_conns; i++) l->conns[i].thread = &l->thread.threads[i % l->thread.count]; #endif + for (unsigned int i = 0; i < l->thread.count; i++) { + struct lwan_thread *thread = &l->thread.threads[i]; + + create_thread(l, thread); + + if ((thread->listen_fd = create_listen_socket(thread, i == 0)) < 0) + lwan_status_critical_perror("Could not create listening socket"); + } + +#ifdef __x86_64__ + if (adj_affinity) + adjust_threads_affinity(l, schedtbl, n_threads); +#endif + pthread_barrier_wait(&l->thread.barrier); lwan_status_debug("Worker threads created and ready to serve"); diff --git a/src/lib/lwan.h b/src/lib/lwan.h index cf61c7e94..73a690370 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -439,6 +439,7 @@ struct lwan_thread { struct timeouts *wheel; int epoll_fd; int listen_fd; + unsigned int cpu; pthread_t self; }; From 87b904e97b5195b9441d0ae4c9fb4ab8cb7c2d06 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 10 May 2021 18:45:07 -0700 Subject: [PATCH 1718/2505] Move #ifdefs for SO_INCOMING_CPU to missing/sys/socket.h --- src/lib/lwan-thread.c | 16 ++++++++-------- src/lib/missing/sys/socket.h | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index c09f611e5..f1460c433 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -439,10 +439,10 @@ static bool accept_waiting_clients(const struct lwan_thread *t) close(fd); } -#if __linux__ && defined(__x86_64__) - /* Ignore errors here, as this is just a hint */ - (void)setsockopt(fd, SOL_SOCKET, SO_INCOMING_CPU, &t->cpu, sizeof(t->cpu)); -#endif + if (SO_INCOMING_CPU_SUPPORTED) { + /* Ignore errors here, as this is just a hint */ + (void)setsockopt(fd, SOL_SOCKET, SO_INCOMING_CPU, &t->cpu, sizeof(t->cpu)); + } continue; } @@ -474,10 +474,10 @@ static int create_listen_socket(struct lwan_thread *t, bool print_listening_msg) if (listen_fd < 0) lwan_status_critical("Could not create listen_fd"); -#if __linux__ && defined(__x86_64__) - /* Ignore errors here, as this is just a hint */ - (void)setsockopt(listen_fd, SOL_SOCKET, SO_INCOMING_CPU, &t->cpu, sizeof(t->cpu)); -#endif + if (SO_INCOMING_CPU_SUPPORTED) { + /* Ignore errors here, as this is just a hint */ + (void)setsockopt(listen_fd, SOL_SOCKET, SO_INCOMING_CPU, &t->cpu, sizeof(t->cpu)); + } struct epoll_event event = { .events = EPOLLIN | EPOLLET | EPOLLERR, diff --git a/src/lib/missing/sys/socket.h b/src/lib/missing/sys/socket.h index cc3812b57..7739f44bd 100644 --- a/src/lib/missing/sys/socket.h +++ b/src/lib/missing/sys/socket.h @@ -31,6 +31,21 @@ #define SOCK_CLOEXEC 0 #endif +#ifdef __linux__ +#ifndef SO_INCOMING_CPU +#define SO_INCOMING_CPU 49 /* Build with old kernel headers */ +#endif +#ifdef __x86_64__ +#define SO_INCOMING_CPU_SUPPORTED 1 +#endif +#endif +#ifndef SO_INCOMING_CPU +#define SO_INCOMING_CPU 0 +#endif +#ifndef SO_INCOMING_CPU_SUPPORTED +#define SO_INCOMING_CPU_SUPPORTED 0 +#endif + #ifndef SOCK_NONBLOCK #define SOCK_NONBLOCK 00004000 #endif From f9f02fdaad9d292195f874d8d0f05b3e168d9d48 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 11 May 2021 08:07:41 -0700 Subject: [PATCH 1719/2505] Use SO_ATTACH_REUSEPORT_CBPF instead of SO_INCOMING_CPU if supported --- CMakeLists.txt | 5 +++- src/cmake/lwan-build-config.h.cmake | 1 + src/lib/lwan-thread.c | 38 ++++++++++++++++++++--------- 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 17d2ee61e..bd9c499c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -181,7 +181,10 @@ check_c_source_compiles("int main(void) { __builtin_fpclassify(0, 0, 0, 0, 0, 0. check_c_source_compiles("int main(void) { unsigned long long p; (void)__builtin_mul_overflow(0, 0, &p); }" HAVE_BUILTIN_MUL_OVERFLOW) check_c_source_compiles("int main(void) { unsigned long long p; (void)__builtin_add_overflow(0, 0, &p); }" HAVE_BUILTIN_ADD_OVERFLOW) check_c_source_compiles("int main(void) { _Static_assert(1, \"\"); }" HAVE_STATIC_ASSERT) - +check_c_source_compiles("#include +#include +#include +int main(void) { setsockopt(0, SOL_SOCKET, SO_ATTACH_REUSEPORT_CBPF, NULL, 0); }" HAVE_SO_ATTACH_REUSEPORT_CBPF) # # Look for Valgrind header diff --git a/src/cmake/lwan-build-config.h.cmake b/src/cmake/lwan-build-config.h.cmake index 0abb2d9f7..6dcceb537 100644 --- a/src/cmake/lwan-build-config.h.cmake +++ b/src/cmake/lwan-build-config.h.cmake @@ -44,6 +44,7 @@ #cmakedefine HAVE_GETTID #cmakedefine HAVE_SECURE_GETENV #cmakedefine HAVE_STATFS +#cmakedefine HAVE_SO_ATTACH_REUSEPORT_CBPF /* Compiler builtins for specific CPU instruction support */ #cmakedefine HAVE_BUILTIN_CLZLL diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index f1460c433..eff0426a0 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -31,6 +31,10 @@ #include #include +#if defined(HAVE_SO_ATTACH_REUSEPORT_CBPF) +#include +#endif + #include "lwan-private.h" #include "lwan-tq.h" #include "list.h" @@ -439,10 +443,10 @@ static bool accept_waiting_clients(const struct lwan_thread *t) close(fd); } - if (SO_INCOMING_CPU_SUPPORTED) { - /* Ignore errors here, as this is just a hint */ - (void)setsockopt(fd, SOL_SOCKET, SO_INCOMING_CPU, &t->cpu, sizeof(t->cpu)); - } +#if defined(HAVE_SO_INCOMING_CPU) + /* Ignore errors here, as this is just a hint */ + (void)setsockopt(fd, SOL_SOCKET, SO_INCOMING_CPU, &t->cpu, sizeof(t->cpu)); +#endif continue; } @@ -466,18 +470,30 @@ static bool accept_waiting_clients(const struct lwan_thread *t) __builtin_unreachable(); } -static int create_listen_socket(struct lwan_thread *t, bool print_listening_msg) +static int create_listen_socket(struct lwan_thread *t, unsigned int socket_num) { int listen_fd; - listen_fd = lwan_create_listen_socket(t->lwan, print_listening_msg); + listen_fd = lwan_create_listen_socket(t->lwan, socket_num == 0); if (listen_fd < 0) lwan_status_critical("Could not create listen_fd"); - if (SO_INCOMING_CPU_SUPPORTED) { - /* Ignore errors here, as this is just a hint */ - (void)setsockopt(listen_fd, SOL_SOCKET, SO_INCOMING_CPU, &t->cpu, sizeof(t->cpu)); - } + /* Ignore errors here, as this is just a hint */ +#if defined(HAVE_SO_ATTACH_REUSEPORT_CBPF) + /* From socket(7): "The BPF program must return an index between 0 and + * N-1 representing the socket which should receive the packet (where N + * is the number of sockets in the group)." */ + struct sock_filter filter[] = { + {BPF_LD | BPF_W | BPF_IMM, 0, 0, socket_num}, /* A = socket_num */ + {BPF_RET | BPF_A, 0, 0, 0}, /* return A */ + }; + struct sock_fprog fprog = {.filter = filter, .len = N_ELEMENTS(filter)}; + (void)setsockopt(listen_fd, SOL_SOCKET, SO_ATTACH_REUSEPORT_CBPF, &fprog, + sizeof(fprog)); +#elif defined(HAVE_SO_INCOMING_CPU) + (void)setsockopt(listen_fd, SOL_SOCKET, SO_INCOMING_CPU, &t->cpu, + sizeof(t->cpu)); +#endif struct epoll_event event = { .events = EPOLLIN | EPOLLET | EPOLLERR, @@ -799,7 +815,7 @@ void lwan_thread_init(struct lwan *l) create_thread(l, thread); - if ((thread->listen_fd = create_listen_socket(thread, i == 0)) < 0) + if ((thread->listen_fd = create_listen_socket(thread, i)) < 0) lwan_status_critical_perror("Could not create listening socket"); } From 00641b1b81bd57cc0514515cd8ff0503b6c609a9 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 11 May 2021 08:07:59 -0700 Subject: [PATCH 1720/2505] Cache TCP listen backlog value --- src/lib/lwan-socket.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-socket.c b/src/lib/lwan-socket.c index a9e331e3b..d5248b4b8 100644 --- a/src/lib/lwan-socket.c +++ b/src/lib/lwan-socket.c @@ -37,7 +37,10 @@ int lwan_socket_get_backlog_size(void) { - int backlog = SOMAXCONN; + static int backlog = 0; + + if (backlog) + return backlog; #ifdef __linux__ FILE *somaxconn; @@ -51,6 +54,9 @@ int lwan_socket_get_backlog_size(void) } #endif + if (!backlog) + backlog = SOMAXCONN; + return backlog; } From f8cd8ea6403053a520e6326fcde7d1d2c076b2ae Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 11 May 2021 08:11:15 -0700 Subject: [PATCH 1721/2505] Better checks for availability of SO_INCOMING_CPU --- CMakeLists.txt | 9 ++++++++- src/cmake/lwan-build-config.h.cmake | 1 + src/lib/lwan-thread.c | 4 ++-- src/lib/missing/sys/socket.h | 15 --------------- 4 files changed, 11 insertions(+), 18 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bd9c499c2..24dab3b18 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -184,7 +184,14 @@ check_c_source_compiles("int main(void) { _Static_assert(1, \"\"); }" HAVE_STATI check_c_source_compiles("#include #include #include -int main(void) { setsockopt(0, SOL_SOCKET, SO_ATTACH_REUSEPORT_CBPF, NULL, 0); }" HAVE_SO_ATTACH_REUSEPORT_CBPF) +int main(void) { + setsockopt(0, SOL_SOCKET, SO_ATTACH_REUSEPORT_CBPF, NULL, 0); +}" HAVE_SO_ATTACH_REUSEPORT_CBPF) +check_c_source_compiles("#include +#include +int main(void) { + setsockopt(0, SOL_SOCKET, SO_INCOMING_CPU, NULL, 0); +}" HAVE_SO_INCOMING_CPU) # # Look for Valgrind header diff --git a/src/cmake/lwan-build-config.h.cmake b/src/cmake/lwan-build-config.h.cmake index 6dcceb537..4ff476307 100644 --- a/src/cmake/lwan-build-config.h.cmake +++ b/src/cmake/lwan-build-config.h.cmake @@ -45,6 +45,7 @@ #cmakedefine HAVE_SECURE_GETENV #cmakedefine HAVE_STATFS #cmakedefine HAVE_SO_ATTACH_REUSEPORT_CBPF +#cmakedefine HAVE_SO_INCOMING_CPU /* Compiler builtins for specific CPU instruction support */ #cmakedefine HAVE_BUILTIN_CLZLL diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index eff0426a0..6c414129c 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -443,7 +443,7 @@ static bool accept_waiting_clients(const struct lwan_thread *t) close(fd); } -#if defined(HAVE_SO_INCOMING_CPU) +#if defined(HAVE_SO_INCOMING_CPU) && defined(__x86_64__) /* Ignore errors here, as this is just a hint */ (void)setsockopt(fd, SOL_SOCKET, SO_INCOMING_CPU, &t->cpu, sizeof(t->cpu)); #endif @@ -490,7 +490,7 @@ static int create_listen_socket(struct lwan_thread *t, unsigned int socket_num) struct sock_fprog fprog = {.filter = filter, .len = N_ELEMENTS(filter)}; (void)setsockopt(listen_fd, SOL_SOCKET, SO_ATTACH_REUSEPORT_CBPF, &fprog, sizeof(fprog)); -#elif defined(HAVE_SO_INCOMING_CPU) +#elif defined(HAVE_SO_INCOMING_CPU) && defined(__x86_64__) (void)setsockopt(listen_fd, SOL_SOCKET, SO_INCOMING_CPU, &t->cpu, sizeof(t->cpu)); #endif diff --git a/src/lib/missing/sys/socket.h b/src/lib/missing/sys/socket.h index 7739f44bd..cc3812b57 100644 --- a/src/lib/missing/sys/socket.h +++ b/src/lib/missing/sys/socket.h @@ -31,21 +31,6 @@ #define SOCK_CLOEXEC 0 #endif -#ifdef __linux__ -#ifndef SO_INCOMING_CPU -#define SO_INCOMING_CPU 49 /* Build with old kernel headers */ -#endif -#ifdef __x86_64__ -#define SO_INCOMING_CPU_SUPPORTED 1 -#endif -#endif -#ifndef SO_INCOMING_CPU -#define SO_INCOMING_CPU 0 -#endif -#ifndef SO_INCOMING_CPU_SUPPORTED -#define SO_INCOMING_CPU_SUPPORTED 0 -#endif - #ifndef SOCK_NONBLOCK #define SOCK_NONBLOCK 00004000 #endif From e681fa44832cd9137347b008f6e6de73df48288f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 11 May 2021 22:59:10 -0700 Subject: [PATCH 1722/2505] BPF programs attached with setsockopt() are per-group Attach the program only once and make sure no other filter is set by issuing setsockopt(SOL_SOCKET, SO_LOCK_FILTER) right after installing the CBPF program. --- src/lib/lwan-thread.c | 47 ++++++++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 6c414129c..954ad69e9 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -470,26 +470,45 @@ static bool accept_waiting_clients(const struct lwan_thread *t) __builtin_unreachable(); } -static int create_listen_socket(struct lwan_thread *t, unsigned int socket_num) +static int create_listen_socket(struct lwan_thread *t, + unsigned int num, + unsigned int num_sockets) { int listen_fd; - listen_fd = lwan_create_listen_socket(t->lwan, socket_num == 0); + listen_fd = lwan_create_listen_socket(t->lwan, num == 0); if (listen_fd < 0) lwan_status_critical("Could not create listen_fd"); - /* Ignore errors here, as this is just a hint */ + /* Ignore errors here, as this is just a hint */ #if defined(HAVE_SO_ATTACH_REUSEPORT_CBPF) - /* From socket(7): "The BPF program must return an index between 0 and - * N-1 representing the socket which should receive the packet (where N - * is the number of sockets in the group)." */ - struct sock_filter filter[] = { - {BPF_LD | BPF_W | BPF_IMM, 0, 0, socket_num}, /* A = socket_num */ - {BPF_RET | BPF_A, 0, 0, 0}, /* return A */ - }; - struct sock_fprog fprog = {.filter = filter, .len = N_ELEMENTS(filter)}; - (void)setsockopt(listen_fd, SOL_SOCKET, SO_ATTACH_REUSEPORT_CBPF, &fprog, - sizeof(fprog)); + /* From socket(7): "These options may be set repeatedly at any time on + * any socket in the group to replace the current BPF program used by + * all sockets in the group." */ + if (num == 0) { + /* From socket(7): "The BPF program must return an index between 0 and + * N-1 representing the socket which should receive the packet (where N + * is the number of sockets in the group)." */ + const uint32_t cpu_ad_off = (uint32_t)SKF_AD_OFF + SKF_AD_CPU; + struct sock_filter filter[] = { + {BPF_LD | BPF_W | BPF_ABS, 0, 0, cpu_ad_off}, /* A = curr_cpu_index */ + {BPF_ALU | BPF_MOD | BPF_K, 0, 0, num_sockets}, /* A %= num_sockets */ + {BPF_RET | BPF_A, 0, 0, 0}, /* return A */ + }; + struct sock_fprog fprog = {.filter = filter, .len = N_ELEMENTS(filter)}; + + if (!(num_sockets & (num_sockets - 1))) { + /* FIXME: Is this strength reduction already made by the kernel? */ + filter[1].code &= (uint16_t)~BPF_MOD; + filter[1].code |= BPF_AND; + filter[1].k = num_sockets - 1; + } + + (void)setsockopt(listen_fd, SOL_SOCKET, SO_ATTACH_REUSEPORT_CBPF, + &fprog, sizeof(fprog)); + (void)setsockopt(listen_fd, SOL_SOCKET, SO_LOCK_FILTER, + (int[]){1}, sizeof(int)); + } #elif defined(HAVE_SO_INCOMING_CPU) && defined(__x86_64__) (void)setsockopt(listen_fd, SOL_SOCKET, SO_INCOMING_CPU, &t->cpu, sizeof(t->cpu)); @@ -815,7 +834,7 @@ void lwan_thread_init(struct lwan *l) create_thread(l, thread); - if ((thread->listen_fd = create_listen_socket(thread, i)) < 0) + if ((thread->listen_fd = create_listen_socket(thread, i, l->thread.count)) < 0) lwan_status_critical_perror("Could not create listening socket"); } From afeeb690d8120b3a8dea291f8e06459909bc589c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 13 May 2021 08:44:50 -0700 Subject: [PATCH 1723/2505] Use online_cpu count instead of thread count to mod CPU index --- src/lib/lwan-thread.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 954ad69e9..aa017d11c 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -824,7 +824,7 @@ void lwan_thread_init(struct lwan *l) l->conns[i].thread = &l->thread.threads[schedtbl[i & n_threads]]; #else for (unsigned int i = 0; i < l->thread.count; i++) - l->thread.threads[i].cpu = i % l->thread.count; + l->thread.threads[i].cpu = i % l->online_cpus; for (unsigned int i = 0; i < total_conns; i++) l->conns[i].thread = &l->thread.threads[i % l->thread.count]; #endif From 0810045674cd29dc9b923e885ebb2c2c0c964ce4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 13 May 2021 08:45:12 -0700 Subject: [PATCH 1724/2505] Use MSG_TRUNC if available to discard unsolicited PONG websockets frames --- src/lib/lwan-websocket.c | 6 ++++-- src/lib/missing/sys/socket.h | 4 ++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index dc6b9759b..c84a133e9 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -161,8 +161,10 @@ static void discard_frame(struct lwan_request *request, uint16_t header) { size_t len = get_frame_length(request, header); - for (char buffer[128]; len;) - len -= (size_t)lwan_recv(request, buffer, sizeof(buffer), 0); + for (char buffer[1024]; len;) { + const size_t to_read = LWAN_MIN(len, sizeof(buffer)); + len -= (size_t)lwan_recv(request, buffer, to_read, MSG_TRUNC); + } } static void unmask(char *msg, size_t msg_len, char mask[static 4]) diff --git a/src/lib/missing/sys/socket.h b/src/lib/missing/sys/socket.h index cc3812b57..b433d5a80 100644 --- a/src/lib/missing/sys/socket.h +++ b/src/lib/missing/sys/socket.h @@ -27,6 +27,10 @@ #define MSG_MORE 0 #endif +#ifndef MSG_TRUNC +#define MSG_TRUNC 0 +#endif + #ifndef SOCK_CLOEXEC #define SOCK_CLOEXEC 0 #endif From 09ed7654b40121ea9a1c519bd93e2a10b4cca553 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 13 May 2021 08:45:52 -0700 Subject: [PATCH 1725/2505] Plug possible memory leak in fuzz_parse_http_request() --- src/lib/lwan-request.c | 85 ++++++++++++++++++++++-------------------- 1 file changed, 44 insertions(+), 41 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 55e30b41a..7da1764b4 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1787,54 +1787,57 @@ __attribute__((used)) int fuzz_parse_http_request(const uint8_t *data, /* If the finalizer isn't happy with a request, there's no point in * going any further with parsing it. */ - if (read_request_finalizer(&buffer, sizeof(data_copy), &request, 1) != - FINALIZER_DONE) + enum lwan_read_finalizer finalizer = + read_request_finalizer(&buffer, sizeof(data_copy), &request, 1); + if (finalizer != FINALIZER_DONE) return 0; /* client_read() NUL-terminates the string */ data_copy[length - 1] = '\0'; - if (parse_http_request(&request) == HTTP_OK) { - off_t trash1; - time_t trash2; - char *trash3; - size_t gen = coro_deferred_get_generation(coro); - - /* Only pointers were set in helper struct; actually parse them here. */ - parse_accept_encoding(&request); - - /* Requesting these items will force them to be parsed, and also - * exercise the lookup function. */ - LWAN_NO_DISCARD( - lwan_request_get_header(&request, "Non-Existing-Header")); - LWAN_NO_DISCARD(lwan_request_get_header( - &request, "Host")); /* Usually existing short header */ - LWAN_NO_DISCARD( - lwan_request_get_cookie(&request, "Non-Existing-Cookie")); - LWAN_NO_DISCARD( - lwan_request_get_cookie(&request, "FOO")); /* Set by some tests */ - LWAN_NO_DISCARD( - lwan_request_get_query_param(&request, "Non-Existing-Query-Param")); - LWAN_NO_DISCARD( - lwan_request_get_post_param(&request, "Non-Existing-Post-Param")); - - lwan_request_get_range(&request, &trash1, &trash1); - LWAN_NO_DISCARD(trash1); - - lwan_request_get_if_modified_since(&request, &trash2); - LWAN_NO_DISCARD(trash2); - - if (prepare_websocket_handshake(&request, &trash3) == - HTTP_SWITCHING_PROTOCOLS) { - free(trash3); - } - LWAN_NO_DISCARD(trash3); + if (parse_http_request(&request) != HTTP_OK) + return 0; - LWAN_NO_DISCARD( - lwan_http_authorize(&request, "Fuzzy Realm", "/dev/null")); + off_t trash1; + time_t trash2; + char *trash3; + size_t gen = coro_deferred_get_generation(coro); - coro_deferred_run(coro, gen); - } + /* Only pointers were set in helper struct; actually parse them here. */ + parse_accept_encoding(&request); + + /* Requesting these items will force them to be parsed, and also + * exercise the lookup function. */ + LWAN_NO_DISCARD(lwan_request_get_header(&request, "Non-Existing-Header")); + + /* Usually existing short header */ + LWAN_NO_DISCARD(lwan_request_get_header(&request, "Host")); + + LWAN_NO_DISCARD(lwan_request_get_cookie(&request, "Non-Existing-Cookie")); + /* Set by some tests */ + LWAN_NO_DISCARD(lwan_request_get_cookie(&request, "FOO")); + + LWAN_NO_DISCARD( + lwan_request_get_query_param(&request, "Non-Existing-Query-Param")); + + LWAN_NO_DISCARD( + lwan_request_get_post_param(&request, "Non-Existing-Post-Param")); + + lwan_request_get_range(&request, &trash1, &trash1); + LWAN_NO_DISCARD(trash1); + + lwan_request_get_if_modified_since(&request, &trash2); + LWAN_NO_DISCARD(trash2); + + enum lwan_http_response handshake = + prepare_websocket_handshake(&request, &trash3); + LWAN_NO_DISCARD(trash3); + if (handshake == HTTP_SWITCHING_PROTOCOLS) + free(trash3); + + LWAN_NO_DISCARD(lwan_http_authorize(&request, "Fuzzy Realm", "/dev/null")); + + coro_deferred_run(coro, gen); return 0; } From 89e950dec67d170f58cb4a4d405c3e5c051a0ecd Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 13 May 2021 08:48:20 -0700 Subject: [PATCH 1726/2505] Move LWAN_NO_DISCARD macro to lwan-private.h --- src/lib/lwan-http-authorize.c | 1 + src/lib/lwan-private.h | 10 ++++++++++ src/lib/lwan.h | 9 --------- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/lib/lwan-http-authorize.c b/src/lib/lwan-http-authorize.c index e7ef3ca67..c077b1431 100644 --- a/src/lib/lwan-http-authorize.c +++ b/src/lib/lwan-http-authorize.c @@ -24,6 +24,7 @@ #include #include "base64.h" +#include "lwan-private.h" #include "lwan-cache.h" #include "lwan-config.h" #include "lwan-http-authorize.h" diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index f117cbe1b..ece5d2af8 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -161,6 +161,16 @@ void lwan_lua_state_push_request(lua_State *L, struct lwan_request *request); const char *lwan_lua_state_last_error(lua_State *L); #endif +/* This macro is used as an attempt to convince the compiler that it should + * never elide an expression -- for instance, when writing fuzz-test or + * micro-benchmarks. */ +#define LWAN_NO_DISCARD(...) \ + do { \ + __typeof__(__VA_ARGS__) no_discard_ = __VA_ARGS__; \ + __asm__ __volatile__("" ::"g"(no_discard_) : "memory"); \ + } while (0) + + #ifdef __APPLE__ #define SECTION_START(name_) __start_##name_[] __asm("section$start$__DATA$" #name_) #define SECTION_END(name_) __stop_##name_[] __asm("section$end$__DATA$" #name_) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 73a690370..ddd04595b 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -51,18 +51,9 @@ extern "C" { __typeof__(array), __typeof__(&(array)[0]))])) #endif - #define N_ELEMENTS(array) \ (ZERO_IF_IS_ARRAY(array) | sizeof(array) / sizeof(array[0])) -/* This macro is used as an attempt to convince the compiler that it should - * never elide an expression -- for instance, when writing fuzz-test or - * micro-benchmarks. */ -#define LWAN_NO_DISCARD(...) \ - do { \ - __typeof__(__VA_ARGS__) no_discard_ = __VA_ARGS__; \ - __asm__ __volatile__("" ::"g"(no_discard_) : "memory"); \ - } while (0) #ifdef __APPLE__ #define LWAN_SECTION_NAME(name_) "__DATA," #name_ From b5fe2df073f4c8a1d54cd7275759318d8c044a05 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 13 May 2021 21:37:45 -0700 Subject: [PATCH 1727/2505] lwan_nextpow2() should be pure --- src/lib/lwan-private.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index ece5d2af8..7571e2f6a 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -126,7 +126,7 @@ uint8_t lwan_char_isspace(char ch) __attribute__((pure)); uint8_t lwan_char_isxdigit(char ch) __attribute__((pure)); uint8_t lwan_char_isdigit(char ch) __attribute__((pure)); -static ALWAYS_INLINE size_t lwan_nextpow2(size_t number) +static ALWAYS_INLINE __attribute__((pure)) size_t lwan_nextpow2(size_t number) { #if defined(HAVE_BUILTIN_CLZLL) static const int size_bits = (int)sizeof(number) * CHAR_BIT; From 70b62c07facc86e2dd571e132509ba3c5cbad9d9 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 13 May 2021 21:43:48 -0700 Subject: [PATCH 1728/2505] Extend fallback implementation of lwan_pow2() to handle 64-bit --- src/lib/lwan-private.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 7571e2f6a..8f4810ab7 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -148,6 +148,9 @@ static ALWAYS_INLINE __attribute__((pure)) size_t lwan_nextpow2(size_t number) number |= number >> 4; number |= number >> 8; number |= number >> 16; +#if __SIZE_WIDTH__ == 64 + number |= number >> 32; +#endif return number + 1; } From 0b2fd4fff60a11d5e1d836727115aae7374657ba Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 13 May 2021 21:44:14 -0700 Subject: [PATCH 1729/2505] Do not use strtol() to parse dates Use the specialized function to parse two digits instead. --- src/lib/lwan-time.c | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/lib/lwan-time.c b/src/lib/lwan-time.c index b676529a2..8830ba65d 100644 --- a/src/lib/lwan-time.c +++ b/src/lib/lwan-time.c @@ -25,27 +25,30 @@ #include "lwan-private.h" #include "int-to-str.h" -static int -parse_2_digit_num(const char *str, const char end_chr, unsigned int max) +static int parse_2_digit_num_no_end_check(const char *str, unsigned int max) { - unsigned int val; + static const unsigned int tens[] = {0, 10, 20, 30, 40, 50, 60, 70, 80, 90}; - if (UNLIKELY(!lwan_char_isdigit(*str))) - return -EINVAL; - if (UNLIKELY(!lwan_char_isdigit(*(str + 1)))) + if (UNLIKELY(!lwan_char_isdigit(str[0]))) return -EINVAL; - if (UNLIKELY(*(str + 2) != end_chr)) + if (UNLIKELY(!lwan_char_isdigit(str[1]))) return -EINVAL; - val = (unsigned int)((*str - '0') * 10); - val += (unsigned int)(*(str + 1) - '0'); - + unsigned int val = tens[str[0] - '0'] + (unsigned int)(str[1] - '0'); if (UNLIKELY(val > max)) return -EINVAL; return (int)val; } +static int +parse_2_digit_num(const char *str, const char end_chr, unsigned int max) +{ + if (UNLIKELY(str[2] != end_chr)) + return -EINVAL; + return parse_2_digit_num_no_end_check(str, max); +} + int lwan_parse_rfc_time(const char in[static 30], time_t *out) { /* This function is used instead of strptime() because locale @@ -88,10 +91,11 @@ int lwan_parse_rfc_time(const char in[static 30], time_t *out) } str += 4; - tm.tm_year = parse_int(strndupa(str, 4), -1); - if (UNLIKELY(tm.tm_year < 0)) + int year_hundreds = parse_2_digit_num_no_end_check(str, 21); + int year_ones = parse_2_digit_num_no_end_check(str + 2, 99); + if (UNLIKELY(year_hundreds < 0 || year_ones < 0)) return -EINVAL; - tm.tm_year -= 1900; + tm.tm_year = (year_hundreds * 100 + year_ones) - 1900; if (UNLIKELY(tm.tm_year < 0 || tm.tm_year > 1000)) return -EINVAL; str += 5; From e9e3a71af490784c5400b478369d9612443b80e6 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 13 May 2021 21:44:56 -0700 Subject: [PATCH 1730/2505] MSG_TRUNC discards data from TCP packets only on Linux, apparently --- src/lib/lwan-websocket.c | 11 ++++++++++- src/lib/missing/sys/socket.h | 4 ---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index c84a133e9..6de54055a 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -160,10 +160,19 @@ static size_t get_frame_length(struct lwan_request *request, uint16_t header) static void discard_frame(struct lwan_request *request, uint16_t header) { size_t len = get_frame_length(request, header); +#if defined(__linux__) + /* MSG_TRUNC for TCP sockets is only supported the way we need here + * on Linux. */ + int flags = MSG_TRUNC; +#else + /* On other OSes, we need to actually read into the buffer in order + * to discard the data. */ + int flags = 0; +#endif for (char buffer[1024]; len;) { const size_t to_read = LWAN_MIN(len, sizeof(buffer)); - len -= (size_t)lwan_recv(request, buffer, to_read, MSG_TRUNC); + len -= (size_t)lwan_recv(request, buffer, to_read, flags); } } diff --git a/src/lib/missing/sys/socket.h b/src/lib/missing/sys/socket.h index b433d5a80..cc3812b57 100644 --- a/src/lib/missing/sys/socket.h +++ b/src/lib/missing/sys/socket.h @@ -27,10 +27,6 @@ #define MSG_MORE 0 #endif -#ifndef MSG_TRUNC -#define MSG_TRUNC 0 -#endif - #ifndef SOCK_CLOEXEC #define SOCK_CLOEXEC 0 #endif From a40d6184a08b720ae79723fac6d0afebfe5594e2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 13 May 2021 21:45:29 -0700 Subject: [PATCH 1731/2505] Fallback strndupa() should use strncpy() strncpy() stops copying when it finds a NUL byte --- src/lib/missing/string.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/missing/string.h b/src/lib/missing/string.h index 0c8b1508e..8134882f8 100644 --- a/src/lib/missing/string.h +++ b/src/lib/missing/string.h @@ -27,7 +27,7 @@ ({ \ char *strndupa_tmp_s = alloca(l + 1); \ strndupa_tmp_s[l] = '\0'; \ - memcpy(strndupa_tmp_s, s, l); \ + strncpy(strndupa_tmp_s, s, l); \ }) #ifndef strndupa From b789e4d1a4f1f9912ae88bd5a0c5f33e433d293f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 13 May 2021 22:50:04 -0700 Subject: [PATCH 1732/2505] Better format for `lwan --help' --- src/bin/lwan/main.c | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/bin/lwan/main.c b/src/bin/lwan/main.c index 6f6ba9236..62f607bb5 100644 --- a/src/bin/lwan/main.c +++ b/src/bin/lwan/main.c @@ -65,27 +65,28 @@ print_help(const char *argv0, const struct lwan_config *config) const char *config_file = lwan_get_config_path(path_buf, sizeof(path_buf)); printf("Usage: %s [--root /path/to/root/dir] [--listen addr:port]\n", argv0); - printf("\t[--config] [--user username] [--chroot] [--modules|--handlers]\n"); + printf(" [--config] [--user username] [--chroot] [--modules|--handlers]\n"); + printf("\n"); printf("Serve files through HTTP.\n\n"); printf("Options:\n"); - printf("\t-r, --root Path to serve files from (default: ./wwwroot).\n"); - printf("\t-l, --listen Listener (default: %s).\n", config->listener); - printf("\t-c, --config Path to config file path.\n"); - printf("\t-u, --user Username to drop privileges to (root required).\n"); - printf("\t-C, --chroot Chroot to path passed to --root (root required).\n"); - printf("\t-m, --modules Print information about available modules.\n"); - printf("\t-H, --handlers Print information about available handlers.\n"); - printf("\t-h, --help This.\n"); + printf(" -r, --root Path to serve files from (default: ./wwwroot).\n"); + printf(" -l, --listen Listener (default: %s).\n", config->listener); + printf(" -c, --config Path to config file path.\n"); + printf(" -u, --user Username to drop privileges to (root required).\n"); + printf(" -C, --chroot Chroot to path passed to --root (root required).\n"); + printf(" -m, --modules Print information about available modules.\n"); + printf(" -H, --handlers Print information about available handlers.\n"); + printf(" -h, --help This.\n"); printf("\n"); printf("Examples:\n"); printf(" Serve system-wide documentation:\n"); - printf(" %s -r /usr/share/doc\n", argv0); + printf(" %s -r /usr/share/doc\n", argv0); printf(" Serve on a different port:\n"); - printf(" %s -l '*:1337'\n", argv0); + printf(" %s -l '*:1337'\n", argv0); printf(" Use %s from %s:\n", config_file, current_dir); - printf(" %s\n", argv0); + printf(" %s\n", argv0); printf(" Use /etc/%s:\n", config_file); - printf(" %s -c /etc/%s\n", argv0, config_file); + printf(" %s -c /etc/%s\n", argv0, config_file); printf("\n"); printf("Report bugs at .\n"); From 9351c113b6a0b0dc739e729e12865d60b7742cf3 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 13 May 2021 22:50:28 -0700 Subject: [PATCH 1733/2505] Only send "Connection: keep-alive" in the first response Still send "Connection: close" (and close the connection gracefully) if such header/value pair is present. But on all keep-alive connections, send the header only in the first response, and leave it implicit for the subsequent ones. According to RFC2616: If either the client or the server sends the close token in the Connection header, that request becomes the last one for the connection. Clients and servers SHOULD NOT assume that a persistent connection is maintained for HTTP versions less than 1.1 unless it is explicitly signaled. See section 19.6.2 for more information on backward compatibility with HTTP/1.0 clients. --- src/lib/lwan-request.c | 8 +++++--- src/lib/lwan-response.c | 24 ++++++++++++++---------- src/lib/lwan.h | 2 ++ 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 7da1764b4..7e8cca6c8 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -757,10 +757,12 @@ static ALWAYS_INLINE void parse_connection_header(struct lwan_request *request) if (LIKELY(!(request->flags & REQUEST_IS_HTTP_1_0))) has_keep_alive = !has_close; - if (has_keep_alive) + if (has_keep_alive) { request->conn->flags |= CONN_IS_KEEP_ALIVE; - else - request->conn->flags &= ~CONN_IS_KEEP_ALIVE; + } else { + request->conn->flags &= + ~(CONN_IS_KEEP_ALIVE | CONN_SENT_CONNECTION_HEADER); + } } #if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 21b9b3bfe..31382e3c4 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -231,22 +231,24 @@ size_t lwan_prepare_response_header_full( char buffer[INT_TO_STR_BUFFER_SIZE]; bool date_overridden = false; bool expires_overridden = false; + const enum lwan_request_flags request_flags = request->flags; + const enum lwan_connection_flags conn_flags = request->conn->flags; assert(request->global_response_headers); p_headers = headers; - if (UNLIKELY(request->flags & REQUEST_IS_HTTP_1_0)) + if (UNLIKELY(request_flags & REQUEST_IS_HTTP_1_0)) APPEND_CONSTANT("HTTP/1.0 "); else APPEND_CONSTANT("HTTP/1.1 "); APPEND_STRING(lwan_http_status_as_string_with_code(status)); - if (UNLIKELY(request->flags & RESPONSE_CHUNKED_ENCODING)) { + if (UNLIKELY(request_flags & RESPONSE_CHUNKED_ENCODING)) { APPEND_CONSTANT("\r\nTransfer-Encoding: chunked"); - } else if (UNLIKELY(request->flags & RESPONSE_NO_CONTENT_LENGTH)) { + } else if (UNLIKELY(request_flags & RESPONSE_NO_CONTENT_LENGTH)) { /* Do nothing. */ - } else if (!(request->flags & RESPONSE_STREAM)) { + } else if (!(request_flags & RESPONSE_STREAM)) { APPEND_CONSTANT("\r\nContent-Length: "); APPEND_UINT(lwan_strbuf_get_length(request->response.buffer)); } @@ -290,17 +292,19 @@ size_t lwan_prepare_response_header_full( } } - if (UNLIKELY(request->conn->flags & CONN_IS_UPGRADE)) { + if (UNLIKELY(conn_flags & CONN_IS_UPGRADE)) { APPEND_CONSTANT("\r\nConnection: Upgrade"); /* Lie that the Expires header has ben overriden just so that we * don't send them when performing a websockets handhsake. */ expires_overridden = true; } else { - if (LIKELY(request->conn->flags & CONN_IS_KEEP_ALIVE)) { - APPEND_CONSTANT("\r\nConnection: keep-alive"); - } else { - APPEND_CONSTANT("\r\nConnection: close"); + if (!(conn_flags & CONN_SENT_CONNECTION_HEADER)) { + if (LIKELY(conn_flags & CONN_IS_KEEP_ALIVE)) + APPEND_CONSTANT("\r\nConnection: keep-alive"); + else + APPEND_CONSTANT("\r\nConnection: close"); + request->conn->flags |= CONN_SENT_CONNECTION_HEADER; } if (LIKELY(request->response.mime_type)) { @@ -319,7 +323,7 @@ size_t lwan_prepare_response_header_full( APPEND_STRING_LEN(request->conn->thread->date.expires, 29); } - if (UNLIKELY(request->flags & REQUEST_ALLOW_CORS)) { + if (UNLIKELY(request_flags & REQUEST_ALLOW_CORS)) { APPEND_CONSTANT( "\r\nAccess-Control-Allow-Origin: *" "\r\nAccess-Control-Allow-Methods: GET, POST, PUT, OPTIONS" diff --git a/src/lib/lwan.h b/src/lib/lwan.h index ddd04595b..abd817dd7 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -281,6 +281,8 @@ enum lwan_connection_flags { * which epoll operation to use when suspending/resuming (ADD/MOD). Reset * whenever associated client connection is closed. */ CONN_ASYNC_AWAIT = 1 << 8, + + CONN_SENT_CONNECTION_HEADER = 1 << 9, }; enum lwan_connection_coro_yield { From d1dcdff99992a4f0084a303a889fde4263d50740 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 13 May 2021 23:16:55 -0700 Subject: [PATCH 1734/2505] Group unlikely branches together when buildingn response headers --- src/lib/lwan-response.c | 83 +++++++++++++++++++++++++---------------- 1 file changed, 51 insertions(+), 32 deletions(-) diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 31382e3c4..0420be78b 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -216,6 +216,22 @@ void lwan_default_response(struct lwan_request *request, #define APPEND_CONSTANT(const_str_) \ APPEND_STRING_LEN((const_str_), sizeof(const_str_) - 1) +enum uncommon_overrides { + OVERRIDE_DATE = 1 << 0, + OVERRIDE_EXPIRES = 1 << 1, +}; + +static ALWAYS_INLINE bool has_content_length(enum lwan_request_flags v) +{ + return !(v & (RESPONSE_NO_CONTENT_LENGTH | RESPONSE_STREAM | + RESPONSE_CHUNKED_ENCODING)); +} + +static ALWAYS_INLINE bool has_uncommon_response_headers(enum lwan_request_flags v) +{ + return v & (REQUEST_ALLOW_CORS | RESPONSE_CHUNKED_ENCODING); +} + size_t lwan_prepare_response_header_full( struct lwan_request *request, enum lwan_http_status status, @@ -223,14 +239,13 @@ size_t lwan_prepare_response_header_full( size_t headers_buf_size, const struct lwan_key_value *additional_headers) { - /* NOTE: If new response headers are added here, update can_override_header() - * in lwan.c */ + /* NOTE: If new response headers are added here, update + * can_override_header() in lwan.c */ char *p_headers; char *p_headers_end = headers + headers_buf_size; char buffer[INT_TO_STR_BUFFER_SIZE]; - bool date_overridden = false; - bool expires_overridden = false; + enum uncommon_overrides overrides = 0; const enum lwan_request_flags request_flags = request->flags; const enum lwan_connection_flags conn_flags = request->conn->flags; @@ -244,31 +259,25 @@ size_t lwan_prepare_response_header_full( APPEND_CONSTANT("HTTP/1.1 "); APPEND_STRING(lwan_http_status_as_string_with_code(status)); - if (UNLIKELY(request_flags & RESPONSE_CHUNKED_ENCODING)) { - APPEND_CONSTANT("\r\nTransfer-Encoding: chunked"); - } else if (UNLIKELY(request_flags & RESPONSE_NO_CONTENT_LENGTH)) { - /* Do nothing. */ - } else if (!(request_flags & RESPONSE_STREAM)) { - APPEND_CONSTANT("\r\nContent-Length: "); - APPEND_UINT(lwan_strbuf_get_length(request->response.buffer)); - } + if (!additional_headers) + goto skip_additional_headers; - if (LIKELY((status < HTTP_BAD_REQUEST && additional_headers))) { + if (LIKELY((status < HTTP_BAD_REQUEST))) { const struct lwan_key_value *header; for (header = additional_headers; header->key; header++) { - STRING_SWITCH_L(header->key) { + STRING_SWITCH_L (header->key) { case STR4_INT_L('S', 'e', 'r', 'v'): if (LIKELY(streq(header->key + 4, "er"))) continue; break; case STR4_INT_L('D', 'a', 't', 'e'): if (LIKELY(*(header->key + 4) == '\0')) - date_overridden = true; + overrides |= OVERRIDE_DATE; break; case STR4_INT_L('E', 'x', 'p', 'i'): if (LIKELY(streq(header->key + 4, "res"))) - expires_overridden = true; + overrides |= OVERRIDE_EXPIRES; break; } @@ -280,7 +289,7 @@ size_t lwan_prepare_response_header_full( APPEND_CHAR_NOCHECK(' '); APPEND_STRING(header->value); } - } else if (UNLIKELY(status == HTTP_NOT_AUTHORIZED) && additional_headers) { + } else if (UNLIKELY(status == HTTP_NOT_AUTHORIZED)) { const struct lwan_key_value *header; for (header = additional_headers; header->key; header++) { @@ -292,12 +301,13 @@ size_t lwan_prepare_response_header_full( } } +skip_additional_headers: if (UNLIKELY(conn_flags & CONN_IS_UPGRADE)) { APPEND_CONSTANT("\r\nConnection: Upgrade"); /* Lie that the Expires header has ben overriden just so that we * don't send them when performing a websockets handhsake. */ - expires_overridden = true; + overrides |= OVERRIDE_EXPIRES; } else { if (!(conn_flags & CONN_SENT_CONNECTION_HEADER)) { if (LIKELY(conn_flags & CONN_IS_KEEP_ALIVE)) @@ -313,22 +323,31 @@ size_t lwan_prepare_response_header_full( } } - if (LIKELY(!date_overridden)) { - APPEND_CONSTANT("\r\nDate: "); - APPEND_STRING_LEN(request->conn->thread->date.date, 29); - } - - if (LIKELY(!expires_overridden)) { - APPEND_CONSTANT("\r\nExpires: "); - APPEND_STRING_LEN(request->conn->thread->date.expires, 29); + if (UNLIKELY(overrides)) { + if (overrides & OVERRIDE_DATE) { + APPEND_CONSTANT("\r\nDate: "); + APPEND_STRING_LEN(request->conn->thread->date.date, 29); + } + if (overrides & OVERRIDE_EXPIRES) { + APPEND_CONSTANT("\r\nExpires: "); + APPEND_STRING_LEN(request->conn->thread->date.expires, 29); + } } - if (UNLIKELY(request_flags & REQUEST_ALLOW_CORS)) { - APPEND_CONSTANT( - "\r\nAccess-Control-Allow-Origin: *" - "\r\nAccess-Control-Allow-Methods: GET, POST, PUT, OPTIONS" - "\r\nAccess-Control-Allow-Credentials: true" - "\r\nAccess-Control-Allow-Headers: Origin, Accept, Content-Type"); + if (LIKELY(has_content_length(request_flags))) { + APPEND_CONSTANT("\r\nContent-Length: "); + APPEND_UINT(lwan_strbuf_get_length(request->response.buffer)); + } else if (UNLIKELY(has_uncommon_response_headers(request_flags))) { + if (request_flags & REQUEST_ALLOW_CORS) { + APPEND_CONSTANT( + "\r\nAccess-Control-Allow-Origin: *" + "\r\nAccess-Control-Allow-Methods: GET, POST, PUT, OPTIONS" + "\r\nAccess-Control-Allow-Credentials: true" + "\r\nAccess-Control-Allow-Headers: Origin, Accept, " + "Content-Type"); + } + if (request_flags & RESPONSE_CHUNKED_ENCODING) + APPEND_CONSTANT("\r\nTransfer-Encoding: chunked"); } APPEND_STRING_LEN(lwan_strbuf_get_buffer(request->global_response_headers), From f06355e7de17c6d8b167c31fc8540efb329a58bc Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 13 May 2021 23:23:49 -0700 Subject: [PATCH 1735/2505] Use HTTP_CLASS__CLIENT_ERROR when choosing to send additional headers --- src/lib/lwan-response.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 0420be78b..1a0fa9100 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -262,7 +262,7 @@ size_t lwan_prepare_response_header_full( if (!additional_headers) goto skip_additional_headers; - if (LIKELY((status < HTTP_BAD_REQUEST))) { + if (LIKELY((status < HTTP_CLASS__CLIENT_ERROR))) { const struct lwan_key_value *header; for (header = additional_headers; header->key; header++) { From 54f54eefe70a9303d314c698f031ed1403574518 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 14 May 2021 00:10:14 -0700 Subject: [PATCH 1736/2505] Clean up the Date/Expires header override code --- src/lib/lwan-response.c | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 1a0fa9100..1a0b20113 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -216,11 +216,6 @@ void lwan_default_response(struct lwan_request *request, #define APPEND_CONSTANT(const_str_) \ APPEND_STRING_LEN((const_str_), sizeof(const_str_) - 1) -enum uncommon_overrides { - OVERRIDE_DATE = 1 << 0, - OVERRIDE_EXPIRES = 1 << 1, -}; - static ALWAYS_INLINE bool has_content_length(enum lwan_request_flags v) { return !(v & (RESPONSE_NO_CONTENT_LENGTH | RESPONSE_STREAM | @@ -245,9 +240,10 @@ size_t lwan_prepare_response_header_full( char *p_headers; char *p_headers_end = headers + headers_buf_size; char buffer[INT_TO_STR_BUFFER_SIZE]; - enum uncommon_overrides overrides = 0; const enum lwan_request_flags request_flags = request->flags; const enum lwan_connection_flags conn_flags = request->conn->flags; + bool date_override = false; + bool expires_override = false; assert(request->global_response_headers); @@ -259,7 +255,7 @@ size_t lwan_prepare_response_header_full( APPEND_CONSTANT("HTTP/1.1 "); APPEND_STRING(lwan_http_status_as_string_with_code(status)); - if (!additional_headers) + if (LIKELY(!additional_headers)) goto skip_additional_headers; if (LIKELY((status < HTTP_CLASS__CLIENT_ERROR))) { @@ -273,11 +269,11 @@ size_t lwan_prepare_response_header_full( break; case STR4_INT_L('D', 'a', 't', 'e'): if (LIKELY(*(header->key + 4) == '\0')) - overrides |= OVERRIDE_DATE; + date_override = true; break; case STR4_INT_L('E', 'x', 'p', 'i'): if (LIKELY(streq(header->key + 4, "res"))) - overrides |= OVERRIDE_EXPIRES; + expires_override = true; break; } @@ -289,6 +285,9 @@ size_t lwan_prepare_response_header_full( APPEND_CHAR_NOCHECK(' '); APPEND_STRING(header->value); } + + if (date_override) + goto skip_date_header; } else if (UNLIKELY(status == HTTP_NOT_AUTHORIZED)) { const struct lwan_key_value *header; @@ -302,12 +301,12 @@ size_t lwan_prepare_response_header_full( } skip_additional_headers: + APPEND_CONSTANT("\r\nDate: "); + APPEND_STRING_LEN(request->conn->thread->date.date, 29); + +skip_date_header: if (UNLIKELY(conn_flags & CONN_IS_UPGRADE)) { APPEND_CONSTANT("\r\nConnection: Upgrade"); - - /* Lie that the Expires header has ben overriden just so that we - * don't send them when performing a websockets handhsake. */ - overrides |= OVERRIDE_EXPIRES; } else { if (!(conn_flags & CONN_SENT_CONNECTION_HEADER)) { if (LIKELY(conn_flags & CONN_IS_KEEP_ALIVE)) @@ -321,14 +320,8 @@ size_t lwan_prepare_response_header_full( APPEND_CONSTANT("\r\nContent-Type: "); APPEND_STRING(request->response.mime_type); } - } - if (UNLIKELY(overrides)) { - if (overrides & OVERRIDE_DATE) { - APPEND_CONSTANT("\r\nDate: "); - APPEND_STRING_LEN(request->conn->thread->date.date, 29); - } - if (overrides & OVERRIDE_EXPIRES) { + if (!expires_override) { APPEND_CONSTANT("\r\nExpires: "); APPEND_STRING_LEN(request->conn->thread->date.expires, 29); } From 3719e2757248c73b689cdb4f4396300925257409 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 14 May 2021 00:22:26 -0700 Subject: [PATCH 1737/2505] Do not send "Expires" headers for benchmarks --- src/lib/lwan-response.c | 2 +- src/lib/lwan.h | 17 +++++++++-------- src/samples/techempower/techempower.c | 10 +++++++++- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 1a0b20113..b079c73f2 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -243,7 +243,7 @@ size_t lwan_prepare_response_header_full( const enum lwan_request_flags request_flags = request->flags; const enum lwan_connection_flags conn_flags = request->conn->flags; bool date_override = false; - bool expires_override = false; + bool expires_override = !!(request->flags & RESPONSE_NO_EXPIRES); assert(request->global_response_headers); diff --git a/src/lib/lwan.h b/src/lib/lwan.h index abd817dd7..003672052 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -241,16 +241,17 @@ enum lwan_request_flags { RESPONSE_SENT_HEADERS = 1 << 12, RESPONSE_CHUNKED_ENCODING = 1 << 13, RESPONSE_NO_CONTENT_LENGTH = 1 << 14, - RESPONSE_URL_REWRITTEN = 1 << 15, + RESPONSE_NO_EXPIRES = 1 << 15, + RESPONSE_URL_REWRITTEN = 1 << 16, - RESPONSE_STREAM = 1 << 16, + RESPONSE_STREAM = 1 << 17, - REQUEST_PARSED_QUERY_STRING = 1 << 17, - REQUEST_PARSED_IF_MODIFIED_SINCE = 1 << 18, - REQUEST_PARSED_RANGE = 1 << 19, - REQUEST_PARSED_FORM_DATA = 1 << 20, - REQUEST_PARSED_COOKIES = 1 << 21, - REQUEST_PARSED_ACCEPT_ENCODING = 1 << 22, + REQUEST_PARSED_QUERY_STRING = 1 << 18, + REQUEST_PARSED_IF_MODIFIED_SINCE = 1 << 19, + REQUEST_PARSED_RANGE = 1 << 20, + REQUEST_PARSED_FORM_DATA = 1 << 21, + REQUEST_PARSED_COOKIES = 1 << 22, + REQUEST_PARSED_ACCEPT_ENCODING = 1 << 23, }; #undef SELECT_MASK diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index a8ef46359..fc713d132 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -188,6 +188,8 @@ LWAN_HANDLER(json) { struct hello_world_json j = {.message = hello_world}; + request->flags |= RESPONSE_NO_EXPIRES; + return json_response_obj(response, hello_world_json_desc, N_ELEMENTS(hello_world_json_desc), &j); } @@ -232,6 +234,8 @@ LWAN_HANDLER(db) if (!queried) return HTTP_INTERNAL_ERROR; + request->flags |= RESPONSE_NO_EXPIRES; + return json_response_obj(response, db_json_desc, N_ELEMENTS(db_json_desc), &db_json); } @@ -262,8 +266,9 @@ LWAN_HANDLER(queries) * so this is a good approximation. */ lwan_strbuf_grow_to(response->buffer, (size_t)(32l * queries)); - ret = json_response_arr(response, &queries_array_desc, &qj); + request->flags |= RESPONSE_NO_EXPIRES; + ret = json_response_arr(response, &queries_array_desc, &qj); out: db_stmt_finalize(stmt); @@ -370,6 +375,7 @@ LWAN_HANDLER(cached_queries) * so this is a good approximation. */ lwan_strbuf_grow_to(response->buffer, (size_t)(32l * queries)); + request->flags |= RESPONSE_NO_EXPIRES; return json_response_arr(response, &queries_array_desc, &qj); } @@ -378,6 +384,7 @@ LWAN_HANDLER(plaintext) lwan_strbuf_set_static(response->buffer, hello_world, sizeof(hello_world) - 1); + request->flags |= RESPONSE_NO_EXPIRES; response->mime_type = "text/plain"; return HTTP_OK; } @@ -461,6 +468,7 @@ LWAN_HANDLER(fortunes) &fortune))) return HTTP_INTERNAL_ERROR; + request->flags |= RESPONSE_NO_EXPIRES; response->mime_type = "text/html; charset=UTF-8"; return HTTP_OK; } From dcedbd09c122f1df5b68072a3afbfab172956406 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 14 May 2021 08:31:57 -0700 Subject: [PATCH 1738/2505] Fix fuzzing builds --- src/lib/lwan-request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 7e8cca6c8..6da8d5b64 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1831,7 +1831,7 @@ __attribute__((used)) int fuzz_parse_http_request(const uint8_t *data, lwan_request_get_if_modified_since(&request, &trash2); LWAN_NO_DISCARD(trash2); - enum lwan_http_response handshake = + enum lwan_http_status handshake = prepare_websocket_handshake(&request, &trash3); LWAN_NO_DISCARD(trash3); if (handshake == HTTP_SWITCHING_PROTOCOLS) From a4ef9e1abea531962e4cfd4817aee0372645a8b2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 14 May 2021 08:35:38 -0700 Subject: [PATCH 1739/2505] No need to reset accepted_connections --- src/lib/lwan-thread.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index aa017d11c..2328ae8e4 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -588,10 +588,8 @@ static void *thread_io_loop(void *data) timeout_queue_move_to_last(&tq, conn); } - if (accepted_connections) { + if (accepted_connections) timeouts_add(t->wheel, &tq.timeout, 1000); - accepted_connections = false; - } } pthread_barrier_wait(&lwan->thread.barrier); From 8ccafc6d43c04a7b8b13c8e3d713b34b1a0c41d8 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 14 May 2021 08:51:36 -0700 Subject: [PATCH 1740/2505] If next pipelined request has 0 bytes, don't bother yielding --- src/lib/lwan-request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 6da8d5b64..433c3ce5c 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -825,7 +825,7 @@ client_read(struct lwan_request *request, if (__builtin_sub_overflow(buffer->len, next_request_len, &new_len)) { helper->next_request = NULL; - } else { + } else if (new_len) { /* FIXME: This memmove() could be eventually removed if a better * stucture (maybe a ringbuffer, reading with readv(), and each * pointer is coro_strdup() if they wrap around?) were used for From 0bf48c7beaef0791135fc23a7828e714614b84ad Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 14 May 2021 19:27:18 -0700 Subject: [PATCH 1741/2505] Read request right away after spawning coroutine No need to have an epoll_wait() roundtrip. --- src/lib/lwan-request.c | 9 +++++++++ src/lib/lwan-thread.c | 9 +++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 433c3ce5c..9b8d408bc 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1638,6 +1638,15 @@ void lwan_request_sleep(struct lwan_request *request, uint64_t ms) { struct lwan_connection *conn = request->conn; struct timeouts *wheel = conn->thread->wheel; + struct timespec now; + + /* We need to update the timer wheel right now because + * a request might have requested to sleep a long time + * before it was being serviced -- causing the timeout + * to essentially be a no-op. */ + if (UNLIKELY(clock_gettime(monotonic_clock_id, &now) < 0)) + lwan_status_critical("Could not get monotonic time"); + timeouts_update(wheel, (timeout_t)(now.tv_sec * 1000 + now.tv_nsec / 1000000)); request->timeout = (struct timeout) {}; timeouts_add(wheel, &request->timeout, ms); diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 2328ae8e4..f78b03a42 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -313,7 +313,7 @@ static void update_date_cache(struct lwan_thread *thread) thread->date.expires); } -static ALWAYS_INLINE void spawn_coro(struct lwan_connection *conn, +static ALWAYS_INLINE bool spawn_coro(struct lwan_connection *conn, struct coro_switcher *switcher, struct timeout_queue *tq) { @@ -334,7 +334,7 @@ static ALWAYS_INLINE void spawn_coro(struct lwan_connection *conn, }; if (LIKELY(conn->coro)) { timeout_queue_insert(tq, conn); - return; + return true; } /* FIXME: send a "busy" response to this client? we don't have a coroutine @@ -346,6 +346,7 @@ static ALWAYS_INLINE void spawn_coro(struct lwan_connection *conn, int fd = lwan_connection_get_fd(tq->lwan, conn); shutdown(fd, SHUT_RDWR); close(fd); + return false; } static bool process_pending_timers(struct timeout_queue *tq, @@ -580,8 +581,8 @@ static void *thread_io_loop(void *data) } if (!conn->coro) { - spawn_coro(conn, &switcher, &tq); - continue; + if (UNLIKELY(!spawn_coro(conn, &switcher, &tq))) + continue; } resume_coro(&tq, conn, epoll_fd); From 1087173ce1e93552953c4c5257c96083806060ee Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 14 May 2021 19:28:03 -0700 Subject: [PATCH 1742/2505] Chunked responses are uncommon and incompatible with Content-Length --- src/lib/lwan-response.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index b079c73f2..8c1d972c7 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -216,13 +216,15 @@ void lwan_default_response(struct lwan_request *request, #define APPEND_CONSTANT(const_str_) \ APPEND_STRING_LEN((const_str_), sizeof(const_str_) - 1) -static ALWAYS_INLINE bool has_content_length(enum lwan_request_flags v) +static ALWAYS_INLINE __attribute__((const)) bool +has_content_length(enum lwan_request_flags v) { return !(v & (RESPONSE_NO_CONTENT_LENGTH | RESPONSE_STREAM | RESPONSE_CHUNKED_ENCODING)); } -static ALWAYS_INLINE bool has_uncommon_response_headers(enum lwan_request_flags v) +static ALWAYS_INLINE __attribute__((const)) bool +has_uncommon_response_headers(enum lwan_request_flags v) { return v & (REQUEST_ALLOW_CORS | RESPONSE_CHUNKED_ENCODING); } @@ -242,7 +244,6 @@ size_t lwan_prepare_response_header_full( char buffer[INT_TO_STR_BUFFER_SIZE]; const enum lwan_request_flags request_flags = request->flags; const enum lwan_connection_flags conn_flags = request->conn->flags; - bool date_override = false; bool expires_override = !!(request->flags & RESPONSE_NO_EXPIRES); assert(request->global_response_headers); @@ -260,6 +261,7 @@ size_t lwan_prepare_response_header_full( if (LIKELY((status < HTTP_CLASS__CLIENT_ERROR))) { const struct lwan_key_value *header; + bool date_override = false; for (header = additional_headers; header->key; header++) { STRING_SWITCH_L (header->key) { @@ -330,7 +332,8 @@ size_t lwan_prepare_response_header_full( if (LIKELY(has_content_length(request_flags))) { APPEND_CONSTANT("\r\nContent-Length: "); APPEND_UINT(lwan_strbuf_get_length(request->response.buffer)); - } else if (UNLIKELY(has_uncommon_response_headers(request_flags))) { + } + if (UNLIKELY(has_uncommon_response_headers(request_flags))) { if (request_flags & REQUEST_ALLOW_CORS) { APPEND_CONSTANT( "\r\nAccess-Control-Allow-Origin: *" @@ -339,8 +342,10 @@ size_t lwan_prepare_response_header_full( "\r\nAccess-Control-Allow-Headers: Origin, Accept, " "Content-Type"); } - if (request_flags & RESPONSE_CHUNKED_ENCODING) + if (request_flags & RESPONSE_CHUNKED_ENCODING && + !has_content_length(request_flags)) { APPEND_CONSTANT("\r\nTransfer-Encoding: chunked"); + } } APPEND_STRING_LEN(lwan_strbuf_get_buffer(request->global_response_headers), From d27d16048425fb8d4d2cd336af6fcf3592f9ff4c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 16 May 2021 08:16:36 -0700 Subject: [PATCH 1743/2505] Send 503 response if coroutine couldn't be spawned --- src/lib/lwan-private.h | 2 ++ src/lib/lwan-response.c | 15 ++++++++---- src/lib/lwan-thread.c | 53 ++++++++++++++++++++++++++++++++++++----- 3 files changed, 59 insertions(+), 11 deletions(-) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 8f4810ab7..2d770cf84 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -117,6 +117,8 @@ size_t lwan_prepare_response_header_full(struct lwan_request *request, void lwan_response(struct lwan_request *request, enum lwan_http_status status); void lwan_default_response(struct lwan_request *request, enum lwan_http_status status); +void lwan_fill_default_response(struct lwan_strbuf *buffer, + enum lwan_http_status status); void lwan_straitjacket_enforce_from_config(struct config *c); diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 8c1d972c7..ee316d6fe 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -166,18 +166,23 @@ void lwan_response(struct lwan_request *request, enum lwan_http_status status) return (void)lwan_writev(request, response_vec, N_ELEMENTS(response_vec)); } -void lwan_default_response(struct lwan_request *request, - enum lwan_http_status status) +void lwan_fill_default_response(struct lwan_strbuf *buffer, + enum lwan_http_status status) { - request->response.mime_type = "text/html"; - lwan_tpl_apply_with_buffer( - error_template, request->response.buffer, + error_template, buffer, &(struct error_template){ .short_message = lwan_http_status_as_string(status), .long_message = lwan_http_status_as_descriptive_string(status), }); +} + +void lwan_default_response(struct lwan_request *request, + enum lwan_http_status status) +{ + request->response.mime_type = "text/html"; + lwan_fill_default_response(request->response.buffer, status); lwan_response(request, status); } diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index f78b03a42..92c9bdb60 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -313,6 +313,33 @@ static void update_date_cache(struct lwan_thread *thread) thread->date.expires); } +static bool send_buffer_without_coro(int fd, const char *buf, size_t buf_len) +{ + size_t total_sent = 0; + + for (int try = 0; try < 10; try++) { + size_t to_send = buf_len - total_sent; + if (!to_send) + return true; + + ssize_t sent = write(fd, buf + total_sent, to_send); + if (sent <= 0) { + if (errno == EINTR) + continue; + break; + } + + total_sent += (size_t)sent; + } + + return false; +} + +static bool send_string_without_coro(int fd, const char *str) +{ + return send_buffer_without_coro(fd, str, strlen(str)); +} + static ALWAYS_INLINE bool spawn_coro(struct lwan_connection *conn, struct coro_switcher *switcher, struct timeout_queue *tq) @@ -326,7 +353,7 @@ static ALWAYS_INLINE bool spawn_coro(struct lwan_connection *conn, assert((uintptr_t)t < (uintptr_t)(tq->lwan->thread.threads + tq->lwan->thread.count)); - *conn = (struct lwan_connection) { + *conn = (struct lwan_connection){ .coro = coro_new(switcher, process_request_coro, conn), .flags = CONN_EVENTS_READ, .time_to_expire = tq->current_time + tq->move_to_last_bump, @@ -336,14 +363,28 @@ static ALWAYS_INLINE bool spawn_coro(struct lwan_connection *conn, timeout_queue_insert(tq, conn); return true; } - - /* FIXME: send a "busy" response to this client? we don't have a coroutine - * at this point, can't use lwan_send() here */ - lwan_status_error("Could not create coroutine, dropping connection"); - conn->flags = 0; int fd = lwan_connection_get_fd(tq->lwan, conn); + + if (!send_string_without_coro(fd, "HTTP/1.0 503 Unavailable\r\n")) + goto out; + if (!send_string_without_coro(fd, "Connection: close")) + goto out; + if (send_buffer_without_coro(fd, lwan_strbuf_get_buffer(&tq->lwan->headers), + lwan_strbuf_get_length(&tq->lwan->headers))) { + struct lwan_strbuf buffer; + + lwan_strbuf_init(&buffer); + lwan_fill_default_response(&buffer, HTTP_UNAVAILABLE); + + send_buffer_without_coro(fd, lwan_strbuf_get_buffer(&buffer), + lwan_strbuf_get_length(&buffer)); + + lwan_strbuf_free(&buffer); + } + +out: shutdown(fd, SHUT_RDWR); close(fd); return false; From f1c5e71e33bd3c03fac0c83ecc68af1c8a6d4771 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 16 May 2021 08:59:29 -0700 Subject: [PATCH 1744/2505] Ensure 503 response is sent with proper content-type --- src/lib/lwan-thread.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 92c9bdb60..dc9e2b82d 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -363,13 +363,16 @@ static ALWAYS_INLINE bool spawn_coro(struct lwan_connection *conn, timeout_queue_insert(tq, conn); return true; } + conn->flags = 0; int fd = lwan_connection_get_fd(tq->lwan, conn); - if (!send_string_without_coro(fd, "HTTP/1.0 503 Unavailable\r\n")) + if (!send_string_without_coro(fd, "HTTP/1.0 503 Unavailable")) + goto out; + if (!send_string_without_coro(fd, "\r\nConnection: close")) goto out; - if (!send_string_without_coro(fd, "Connection: close")) + if (!send_string_without_coro(fd, "\r\nContent-Type: text/html")) goto out; if (send_buffer_without_coro(fd, lwan_strbuf_get_buffer(&tq->lwan->headers), lwan_strbuf_get_length(&tq->lwan->headers))) { From dee438f16db5a3b18f5e6a0e80ffc9bfc12f15d0 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 16 May 2021 09:10:21 -0700 Subject: [PATCH 1745/2505] Log when coroutines couldn't be spawned --- src/lib/lwan-thread.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index dc9e2b82d..6dfd26cfc 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -368,6 +368,8 @@ static ALWAYS_INLINE bool spawn_coro(struct lwan_connection *conn, int fd = lwan_connection_get_fd(tq->lwan, conn); + lwan_status_error("Couldn't spawn coroutine for file descriptor %d", fd); + if (!send_string_without_coro(fd, "HTTP/1.0 503 Unavailable")) goto out; if (!send_string_without_coro(fd, "\r\nConnection: close")) From 4560b7dee3938ce35e5f3fa3fec168052cc0299a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 16 May 2021 10:27:57 -0700 Subject: [PATCH 1746/2505] Remove string copy in lwan_request_get_header() --- src/lib/lwan-request.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 9b8d408bc..d4e899bbe 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -45,6 +45,7 @@ #include "lwan-io-wrappers.h" #include "sha1.h" +#define HEADER_VALUE_SEPARATOR_LEN (sizeof(": ") - 1) #define HEADER_TERMINATOR_LEN (sizeof("\r\n") - 1) #define MIN_REQUEST_SIZE (sizeof("GET / HTTP/1.1\r\n\r\n") - 1) @@ -1550,25 +1551,24 @@ const char *lwan_request_get_cookie(struct lwan_request *request, const char *lwan_request_get_header(struct lwan_request *request, const char *header) { - char name[64]; - int r; + const size_t header_len = strlen(header); + const size_t header_len_with_separator = header_len + HEADER_VALUE_SEPARATOR_LEN; assert(strchr(header, ':') == NULL); - r = snprintf(name, sizeof(name), "%s: ", header); - if (UNLIKELY(r < 0 || r >= (int)sizeof(name))) - return NULL; - for (size_t i = 0; i < request->helper->n_header_start; i++) { const char *start = request->helper->header_start[i]; char *end = request->helper->header_start[i + 1] - HEADER_TERMINATOR_LEN; - if (UNLIKELY(end - start < r)) + if (UNLIKELY((size_t)(end - start) < header_len_with_separator)) + continue; + + if (strncmp(start + header_len, ": ", HEADER_VALUE_SEPARATOR_LEN)) continue; - if (!strncasecmp(start, name, (size_t)r)) { + if (!strncasecmp(start, header, header_len)) { *end = '\0'; - return start + r; + return start + header_len_with_separator; } } From 6e4c797c30891386fb76332057e80f28faad9ad8 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 16 May 2021 10:28:55 -0700 Subject: [PATCH 1747/2505] Disable regression testing for some old fuzzing cases --- fuzz/disabled/README | 5 +++++ ...terfuzz-testcase-request_fuzzer-5091179189764096 | Bin ...terfuzz-testcase-request_fuzzer-5689112759107584 | Bin .../crash-61ff81c30aa3c76e78afea62b2e3bd1dfa49e854 | 0 .../crash-96cb33f2b70df83e63d2c6b2e571bd0c17b4adb3 | 0 .../crash-bd24b5eed9697284d0d67356e1b489b9e2e17ff6 | 0 6 files changed, 5 insertions(+) create mode 100644 fuzz/disabled/README rename fuzz/{ => disabled}/clusterfuzz-testcase-request_fuzzer-5091179189764096 (100%) rename fuzz/{ => disabled}/clusterfuzz-testcase-request_fuzzer-5689112759107584 (100%) rename fuzz/{ => disabled}/crash-61ff81c30aa3c76e78afea62b2e3bd1dfa49e854 (100%) rename fuzz/{ => disabled}/crash-96cb33f2b70df83e63d2c6b2e571bd0c17b4adb3 (100%) rename fuzz/{ => disabled}/crash-bd24b5eed9697284d0d67356e1b489b9e2e17ff6 (100%) diff --git a/fuzz/disabled/README b/fuzz/disabled/README new file mode 100644 index 000000000..e0a1ce6b7 --- /dev/null +++ b/fuzz/disabled/README @@ -0,0 +1,5 @@ +This directory contains some inputs that crashed Lwan previously, +but for reasons I can't investigate right now, are hanging the +test harness. + +They're still kept here for future investigations. diff --git a/fuzz/clusterfuzz-testcase-request_fuzzer-5091179189764096 b/fuzz/disabled/clusterfuzz-testcase-request_fuzzer-5091179189764096 similarity index 100% rename from fuzz/clusterfuzz-testcase-request_fuzzer-5091179189764096 rename to fuzz/disabled/clusterfuzz-testcase-request_fuzzer-5091179189764096 diff --git a/fuzz/clusterfuzz-testcase-request_fuzzer-5689112759107584 b/fuzz/disabled/clusterfuzz-testcase-request_fuzzer-5689112759107584 similarity index 100% rename from fuzz/clusterfuzz-testcase-request_fuzzer-5689112759107584 rename to fuzz/disabled/clusterfuzz-testcase-request_fuzzer-5689112759107584 diff --git a/fuzz/crash-61ff81c30aa3c76e78afea62b2e3bd1dfa49e854 b/fuzz/disabled/crash-61ff81c30aa3c76e78afea62b2e3bd1dfa49e854 similarity index 100% rename from fuzz/crash-61ff81c30aa3c76e78afea62b2e3bd1dfa49e854 rename to fuzz/disabled/crash-61ff81c30aa3c76e78afea62b2e3bd1dfa49e854 diff --git a/fuzz/crash-96cb33f2b70df83e63d2c6b2e571bd0c17b4adb3 b/fuzz/disabled/crash-96cb33f2b70df83e63d2c6b2e571bd0c17b4adb3 similarity index 100% rename from fuzz/crash-96cb33f2b70df83e63d2c6b2e571bd0c17b4adb3 rename to fuzz/disabled/crash-96cb33f2b70df83e63d2c6b2e571bd0c17b4adb3 diff --git a/fuzz/crash-bd24b5eed9697284d0d67356e1b489b9e2e17ff6 b/fuzz/disabled/crash-bd24b5eed9697284d0d67356e1b489b9e2e17ff6 similarity index 100% rename from fuzz/crash-bd24b5eed9697284d0d67356e1b489b9e2e17ff6 rename to fuzz/disabled/crash-bd24b5eed9697284d0d67356e1b489b9e2e17ff6 From 2acb2859714c2cad9e202780dabfcb3af2efb087 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 16 May 2021 10:38:00 -0700 Subject: [PATCH 1748/2505] Add one regression test file that was only local I don't know which commit fixed this, but this has been in the local regression testing since April 28, 2021. --- ...usterfuzz-testcase-minimized-request_fuzzer-5723694463844352 | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fuzz/clusterfuzz-testcase-minimized-request_fuzzer-5723694463844352 diff --git a/fuzz/clusterfuzz-testcase-minimized-request_fuzzer-5723694463844352 b/fuzz/clusterfuzz-testcase-minimized-request_fuzzer-5723694463844352 new file mode 100644 index 000000000..816cc8220 --- /dev/null +++ b/fuzz/clusterfuzz-testcase-minimized-request_fuzzer-5723694463844352 @@ -0,0 +1,2 @@ +GET /ws-write HTTP/1.1 Conn : Upg Upgrade: websocket Sec-WebSocket-Key: + From 998dec57738df448ebdaedcccbd336f5460b3002 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 16 May 2021 10:40:53 -0700 Subject: [PATCH 1749/2505] Move regression testing cases to fuzz/regression This should tidy up things a little bit --- fuzz/README | 4 ---- fuzz/regression/README | 4 ++++ ...stcase-minimized-request_fuzzer-5191405204406272 | 0 ...stcase-minimized-request_fuzzer-5636210766118912 | 0 ...stcase-minimized-request_fuzzer-5649134389821440 | 0 ...stcase-minimized-request_fuzzer-5652010562486272 | 0 ...stcase-minimized-request_fuzzer-5658117347475456 | 0 ...stcase-minimized-request_fuzzer-5717480481226752 | Bin ...stcase-minimized-request_fuzzer-5723694463844352 | 0 ...stcase-minimized-request_fuzzer-5729298679332864 | Bin ...terfuzz-testcase-request_fuzzer-5675545829834752 | Bin .../crash-9df31650ba7cf7fa3aae24d59b3e0a335ab6c23a | 0 src/scripts/testsuite.py | 4 ++-- 13 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 fuzz/regression/README rename fuzz/{ => regression}/clusterfuzz-testcase-minimized-request_fuzzer-5191405204406272 (100%) rename fuzz/{ => regression}/clusterfuzz-testcase-minimized-request_fuzzer-5636210766118912 (100%) rename fuzz/{ => regression}/clusterfuzz-testcase-minimized-request_fuzzer-5649134389821440 (100%) rename fuzz/{ => regression}/clusterfuzz-testcase-minimized-request_fuzzer-5652010562486272 (100%) rename fuzz/{ => regression}/clusterfuzz-testcase-minimized-request_fuzzer-5658117347475456 (100%) rename fuzz/{ => regression}/clusterfuzz-testcase-minimized-request_fuzzer-5717480481226752 (100%) rename fuzz/{ => regression}/clusterfuzz-testcase-minimized-request_fuzzer-5723694463844352 (100%) rename fuzz/{ => regression}/clusterfuzz-testcase-minimized-request_fuzzer-5729298679332864 (100%) rename fuzz/{ => regression}/clusterfuzz-testcase-request_fuzzer-5675545829834752 (100%) rename fuzz/{ => regression}/crash-9df31650ba7cf7fa3aae24d59b3e0a335ab6c23a (100%) diff --git a/fuzz/README b/fuzz/README index d4d1ba498..162c94cfe 100644 --- a/fuzz/README +++ b/fuzz/README @@ -1,6 +1,2 @@ -This directory contains files that managed to crash something in Lwan -after being fuzzed. They have been committed alongside the fix, so -it's easier to find them with git blame. - `http.dict' has been copied from the h2o web server project: https://github.com/h2o/h2o/blob/c14554e7/fuzz/http.dict diff --git a/fuzz/regression/README b/fuzz/regression/README new file mode 100644 index 000000000..84880c04d --- /dev/null +++ b/fuzz/regression/README @@ -0,0 +1,4 @@ +This directory contains files that managed to crash something in Lwan +after being fuzzed. They have been committed alongside the fix, so +it's easier to find them with git blame. + diff --git a/fuzz/clusterfuzz-testcase-minimized-request_fuzzer-5191405204406272 b/fuzz/regression/clusterfuzz-testcase-minimized-request_fuzzer-5191405204406272 similarity index 100% rename from fuzz/clusterfuzz-testcase-minimized-request_fuzzer-5191405204406272 rename to fuzz/regression/clusterfuzz-testcase-minimized-request_fuzzer-5191405204406272 diff --git a/fuzz/clusterfuzz-testcase-minimized-request_fuzzer-5636210766118912 b/fuzz/regression/clusterfuzz-testcase-minimized-request_fuzzer-5636210766118912 similarity index 100% rename from fuzz/clusterfuzz-testcase-minimized-request_fuzzer-5636210766118912 rename to fuzz/regression/clusterfuzz-testcase-minimized-request_fuzzer-5636210766118912 diff --git a/fuzz/clusterfuzz-testcase-minimized-request_fuzzer-5649134389821440 b/fuzz/regression/clusterfuzz-testcase-minimized-request_fuzzer-5649134389821440 similarity index 100% rename from fuzz/clusterfuzz-testcase-minimized-request_fuzzer-5649134389821440 rename to fuzz/regression/clusterfuzz-testcase-minimized-request_fuzzer-5649134389821440 diff --git a/fuzz/clusterfuzz-testcase-minimized-request_fuzzer-5652010562486272 b/fuzz/regression/clusterfuzz-testcase-minimized-request_fuzzer-5652010562486272 similarity index 100% rename from fuzz/clusterfuzz-testcase-minimized-request_fuzzer-5652010562486272 rename to fuzz/regression/clusterfuzz-testcase-minimized-request_fuzzer-5652010562486272 diff --git a/fuzz/clusterfuzz-testcase-minimized-request_fuzzer-5658117347475456 b/fuzz/regression/clusterfuzz-testcase-minimized-request_fuzzer-5658117347475456 similarity index 100% rename from fuzz/clusterfuzz-testcase-minimized-request_fuzzer-5658117347475456 rename to fuzz/regression/clusterfuzz-testcase-minimized-request_fuzzer-5658117347475456 diff --git a/fuzz/clusterfuzz-testcase-minimized-request_fuzzer-5717480481226752 b/fuzz/regression/clusterfuzz-testcase-minimized-request_fuzzer-5717480481226752 similarity index 100% rename from fuzz/clusterfuzz-testcase-minimized-request_fuzzer-5717480481226752 rename to fuzz/regression/clusterfuzz-testcase-minimized-request_fuzzer-5717480481226752 diff --git a/fuzz/clusterfuzz-testcase-minimized-request_fuzzer-5723694463844352 b/fuzz/regression/clusterfuzz-testcase-minimized-request_fuzzer-5723694463844352 similarity index 100% rename from fuzz/clusterfuzz-testcase-minimized-request_fuzzer-5723694463844352 rename to fuzz/regression/clusterfuzz-testcase-minimized-request_fuzzer-5723694463844352 diff --git a/fuzz/clusterfuzz-testcase-minimized-request_fuzzer-5729298679332864 b/fuzz/regression/clusterfuzz-testcase-minimized-request_fuzzer-5729298679332864 similarity index 100% rename from fuzz/clusterfuzz-testcase-minimized-request_fuzzer-5729298679332864 rename to fuzz/regression/clusterfuzz-testcase-minimized-request_fuzzer-5729298679332864 diff --git a/fuzz/clusterfuzz-testcase-request_fuzzer-5675545829834752 b/fuzz/regression/clusterfuzz-testcase-request_fuzzer-5675545829834752 similarity index 100% rename from fuzz/clusterfuzz-testcase-request_fuzzer-5675545829834752 rename to fuzz/regression/clusterfuzz-testcase-request_fuzzer-5675545829834752 diff --git a/fuzz/crash-9df31650ba7cf7fa3aae24d59b3e0a335ab6c23a b/fuzz/regression/crash-9df31650ba7cf7fa3aae24d59b3e0a335ab6c23a similarity index 100% rename from fuzz/crash-9df31650ba7cf7fa3aae24d59b3e0a335ab6c23a rename to fuzz/regression/crash-9df31650ba7cf7fa3aae24d59b3e0a335ab6c23a diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index 8ee37b3ca..504565243 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -972,7 +972,7 @@ def run_test(self, contents): @staticmethod def wrap(name): - with open(os.path.join("fuzz", name), "rb") as f: + with open(os.path.join("fuzz", "regression", name), "rb") as f: contents = str(f.read(), "latin-1") def run_test_wrapped(self): return self.run_test(contents) @@ -981,7 +981,7 @@ def run_test_wrapped(self): TestFuzzRegression = type('TestFuzzRegression', (TestFuzzRegressionBase,), { "test_" + name.replace("-", "_"): TestFuzzRegressionBase.wrap(name) for name in ( - cf for cf in os.listdir("fuzz") if cf.startswith(("clusterfuzz-", "crash-")) + cf for cf in os.listdir("fuzz/regression") if cf.startswith(("clusterfuzz-", "crash-")) ) }) From b4adf476a7be5f4997b3f3d4be61be71d6f80631 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 16 May 2021 11:27:30 -0700 Subject: [PATCH 1750/2505] Enable Reno TCP congestion protocol if available Does this propagate to accepted sockets? Need to test. --- src/lib/lwan-socket.c | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-socket.c b/src/lib/lwan-socket.c index d5248b4b8..447ac4fa5 100644 --- a/src/lib/lwan-socket.c +++ b/src/lib/lwan-socket.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -35,6 +36,35 @@ #include "int-to-str.h" #include "sd-daemon.h" +#ifdef __linux__ + +static bool reno_supported; +static void init_reno_supported(void) +{ + FILE *allowed; + + reno_supported = false; + + allowed = fopen("/proc/sys/net/ipv4/tcp_allowed_congestion_control", "re"); + if (!allowed) + return; + + char line[4096]; + if (fgets(line, sizeof(line), allowed)) { + if (strstr(line, "reno")) + reno_supported = true; + } + fclose(allowed); +} + +static bool is_reno_supported(void) +{ + static pthread_once_t reno_supported_once = PTHREAD_ONCE_INIT; + pthread_once(&reno_supported_once, init_reno_supported); + return reno_supported; +} +#endif + int lwan_socket_get_backlog_size(void) { static int backlog = 0; @@ -190,7 +220,7 @@ listen_addrinfo(int fd, const struct addrinfo *addr, bool print_listening_msg) do { \ const socklen_t _param_size_ = (socklen_t)sizeof(*(_param)); \ if (setsockopt(fd, (_domain), (_option), (_param), _param_size_) < 0) \ - lwan_status_warning("%s not supported by the kernel", #_option); \ + lwan_status_perror("%s not supported by the kernel", #_option); \ } while (0) static int bind_and_listen_addrinfos(const struct addrinfo *addrs, bool print_listening_msg) @@ -273,6 +303,9 @@ int lwan_create_listen_socket(struct lwan *l, bool print_listening_msg) SET_SOCKET_OPTION_MAY_FAIL(SOL_TCP, TCP_QUICKACK, (int[]){0}); SET_SOCKET_OPTION_MAY_FAIL(SOL_TCP, TCP_DEFER_ACCEPT, (int[]){(int)l->config.keep_alive_timeout}); + + if (is_reno_supported()) + SET_SOCKET_OPTION_MAY_FAIL(IPPROTO_TCP, TCP_CONGESTION, "reno"); #endif return fd; From e50b5f500c211eb4ec20ea064681dc0a0ce498b5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 20 May 2021 17:42:11 -0700 Subject: [PATCH 1751/2505] Move the last remaining uses of read() to recv() --- src/lib/lwan-request.c | 2 +- src/lib/lwan-thread.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index d4e899bbe..9ae7f479a 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -843,7 +843,7 @@ client_read(struct lwan_request *request, if (UNLIKELY(to_read == 0)) return HTTP_TOO_LARGE; - ssize_t n = read(request->fd, buffer->value + buffer->len, to_read); + ssize_t n = recv(request->fd, buffer->value + buffer->len, to_read, 0); if (UNLIKELY(n <= 0)) { if (n < 0) { switch (errno) { diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 6dfd26cfc..0396750f7 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -71,7 +71,7 @@ static void graceful_close(struct lwan *l, } for (int tries = 0; tries < 20; tries++) { - ssize_t r = read(fd, buffer, DEFAULT_BUFFER_SIZE); + ssize_t r = recv(fd, buffer, DEFAULT_BUFFER_SIZE, 0); if (!r) break; From 1f5139ddea2d7e3b8f56348d5cfbee2a608bada6 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 20 May 2021 17:43:00 -0700 Subject: [PATCH 1752/2505] Determine backlog size just once --- src/lib/lwan-socket.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/lib/lwan-socket.c b/src/lib/lwan-socket.c index 447ac4fa5..5c2f0a7d1 100644 --- a/src/lib/lwan-socket.c +++ b/src/lib/lwan-socket.c @@ -65,13 +65,9 @@ static bool is_reno_supported(void) } #endif -int lwan_socket_get_backlog_size(void) +static int backlog_size; +static void init_backlog_size(void) { - static int backlog = 0; - - if (backlog) - return backlog; - #ifdef __linux__ FILE *somaxconn; @@ -79,15 +75,20 @@ int lwan_socket_get_backlog_size(void) if (somaxconn) { int tmp; if (fscanf(somaxconn, "%d", &tmp) == 1) - backlog = tmp; + backlog_size = tmp; fclose(somaxconn); } #endif - if (!backlog) - backlog = SOMAXCONN; + if (!backlog_size) + backlog_size = SOMAXCONN; +} - return backlog; +int lwan_socket_get_backlog_size(void) +{ + static pthread_once_t backlog_size_once = PTHREAD_ONCE_INIT; + pthread_once(&backlog_size_once, init_backlog_size); + return backlog_size; } static int set_socket_flags(int fd) From d48644057d768511dba39691d50102a5361218d1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 20 May 2021 17:55:50 -0700 Subject: [PATCH 1753/2505] Fix link error in Release mode related to LTO usage This fixes a build error that happened when building with GCC 11.1.0 and GNU ld 2.36.1: /usr/bin/ld: /tmp/cclvCkU1.ltrans2.ltrans.o: warning: relocation against `.L934' in read-only section `.text' /usr/bin/ld: /tmp/cclvCkU1.ltrans2.ltrans.o: in function `bake_direct_addresses.constprop.0': :(.text+0x44ca): undefined reference to `.L934' --- CMakeLists.txt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 24dab3b18..3682a0850 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -226,6 +226,8 @@ else () HAVE_READ_ONLY_GOT) enable_c_flag_if_avail(-fno-plt CMAKE_C_FLAGS HAVE_NO_PLT) + enable_c_flag_if_avail(-no-pie CMAKE_C_FLAGS + HAVE_NO_PIE) enable_c_flag_if_avail(-Wl,-z,noexecstack CMAKE_EXE_LINKER_FLAGS HAVE_NOEXEC_STACK) endif () @@ -236,10 +238,7 @@ if (${CMAKE_BUILD_TYPE} MATCHES "Rel") enable_c_flag_if_avail(-malign-data=abi C_FLAGS_REL HAVE_ALIGN_DATA) enable_c_flag_if_avail(-fno-asynchronous-unwind-tables C_FLAGS_REL HAVE_NO_ASYNC_UNWIND_TABLES) - enable_c_flag_if_avail(-flto=jobserver C_FLAGS_REL HAVE_LTO_JOBSERVER) - if (NOT HAVE_LTO_JOBSERVER) - enable_c_flag_if_avail(-flto C_FLAGS_REL HAVE_LTO) - endif () + enable_c_flag_if_avail(-fPIC -flto C_FLAGS_REL HAVE_LTO) enable_c_flag_if_avail(-ffat-lto-objects C_FLAGS_REL HAVE_LTO_FAT_OBJS) enable_c_flag_if_avail(-mcrc32 C_FLAGS_REL HAVE_BUILTIN_IA32_CRC32) From 262289195603d0948dcd15f8a0c50abb165be8dd Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 22 May 2021 10:10:14 -0700 Subject: [PATCH 1754/2505] Add PRNG suitable for things like request IDs This PRNG is based off ot the work by D. H. Lehmer, "Mathematical methods in large-scale computing units", published in the Proceedings of a Second Symposium on Large Scale Digital Calculating Machinery; Annals of the Computation Laboratory, Harvard Univ. 26 (1951), pp. 141-146. and P L'Ecuyer, "Tables of linear congruential generators of different sizes and good lattice structure" published in Mathematics of Computation of the American Mathematical Society 68.225 (1999): 249-260. The implementation has been taken from Daniel Lemire's blog at, although it doesn't really use more than one generator as suggested (since this doesn't really need to pass the Big Crush test: https://lemire.me/blog/2019/03/19/the-fastest-conventional-random-number-generator-that-can-pass-big-crush/ --- src/lib/hash.c | 31 +++---------------------------- src/lib/lwan-private.h | 4 ++++ src/lib/lwan-thread.c | 22 ++++++++++++++++++++++ src/lib/missing.c | 31 +++++++++++++++++++++++++++++++ 4 files changed, 60 insertions(+), 28 deletions(-) diff --git a/src/lib/hash.c b/src/lib/hash.c index a55133358..f9b1509ff 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -111,33 +111,6 @@ static bool resize_bucket(struct hash_bucket *bucket, unsigned int new_size) return false; } -static unsigned int get_random_unsigned(void) -{ - unsigned int value = 0; - -#if defined(SYS_getrandom) - long int ret = syscall(SYS_getrandom, &value, sizeof(value), 0); - if (ret == sizeof(value)) - return value; -#elif defined(HAVE_GETENTROPY) - int ret = getentropy(&value, sizeof(value)); - if (ret == 0) - return value; -#endif - - int fd = open("/dev/urandom", O_CLOEXEC | O_RDONLY); - if (fd < 0) { - fd = open("/dev/random", O_CLOEXEC | O_RDONLY); - if (fd < 0) - return DEFAULT_ODD_CONSTANT; - } - if (read(fd, &value, sizeof(value)) != sizeof(value)) - value = DEFAULT_ODD_CONSTANT; - close(fd); - - return value; -} - static inline unsigned int hash_int_shift_mult(const void *keyptr) { /* http://www.concentric.net/~Ttwang/tech/inthash.htm */ @@ -200,7 +173,9 @@ __attribute__((constructor(65535))) static void initialize_odd_constant(void) { /* This constant is randomized in order to mitigate the DDoS attack * described by Crosby and Wallach in UsenixSec2003. */ - odd_constant = get_random_unsigned() | 1; + if (lwan_getentropy(&odd_constant, sizeof(odd_constant), 0) < 0) + odd_constant = DEFAULT_ODD_CONSTANT; + odd_constant |= 1; murmur3_set_seed(odd_constant); #if defined(HAVE_BUILTIN_CPU_INIT) && defined(HAVE_BUILTIN_IA32_CRC32) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 2d770cf84..0bcaf6b10 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -222,3 +222,7 @@ static ALWAYS_INLINE int lwan_calculate_n_packets(size_t total) * after ~2x number of expected packets to fully read the request body.*/ return LWAN_MAX(5, (int)(total / 740)); } + +long int lwan_getentropy(void *buffer, size_t buffer_len, int flags); +uint64_t lwan_random_uint64(); + diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 0396750f7..4ceac978f 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -93,6 +93,26 @@ static void graceful_close(struct lwan *l, /* close(2) will be called when the coroutine yields with CONN_CORO_ABORT */ } +static __thread __uint128_t lehmer64_state; + +static void lwan_random_seed_prng_for_thread(uint64_t fallback_seed) +{ + if (lwan_getentropy(&lehmer64_state, sizeof(lehmer64_state), 0) < 0) { + lwan_status_warning("Couldn't get proper entropy for PRNG, using fallback seed"); + for (int i = 0; i < 4; i++) { + lehmer64_state |= fallback_seed; + lehmer64_state <<= 32; + } + } +} + +uint64_t lwan_random_uint64() +{ + /* https://lemire.me/blog/2019/03/19/the-fastest-conventional-random-number-generator-that-can-pass-big-crush/ */ + lehmer64_state *= 0xda942042e4dd58b5ull; + return (uint64_t)(lehmer64_state >> 64); +} + __attribute__((noreturn)) static int process_request_coro(struct coro *coro, void *data) { @@ -593,6 +613,8 @@ static void *thread_io_loop(void *data) timeout_queue_init(&tq, lwan); + lwan_random_seed_prng_for_thread((uint64_t)(epoll_fd | time(NULL))); + pthread_barrier_wait(&lwan->thread.barrier); for (;;) { diff --git a/src/lib/missing.c b/src/lib/missing.c index ebdf5d001..33f37a080 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -612,3 +612,34 @@ int statfs(const char *path, struct statfs *buf) return -1; } #endif + +#if defined(SYS_getrandom) +long int lwan_getentropy(void *buffer, size_t buffer_len, int flags) +{ + return syscall(SYS_getrandom, buffer, buffer_len, flags); +} +#elif defined(HAVE_GETENTROPY) +long int lwan_getentropy(void *buffer, size_t buffer_len, int flags) +{ + (void)flags; + if (!getentropy(buffer, buffer_len)) + return buffer_len; + return -1; +} +#else +long int lwan_getentropy(void *buffer, size_t buffer_len, int flags) +{ + (void)flags; + + int fd = open("/dev/urandom", O_CLOEXEC | O_RDONLY); + if (fd < 0) { + fd = open("/dev/random", O_CLOEXEC | O_RDONLY); + if (fd < 0) + return -1; + } + ssize_t total_read = read(fd, buffer, buffer_len); + close(fd); + + return total_read == (ssize_t)buffer_len ? 0 : -1; +} +#endif From fec390572ce5faa023b6d2baed3a93778db7274d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 22 May 2021 10:28:31 -0700 Subject: [PATCH 1755/2505] Fallback random seed wasn't properly initialized It was looping 4 times when it needed just two (this function used to take an uint32_t as input and I forgot to change this when refactoring.) --- src/lib/lwan-thread.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 4ceac978f..15b5735db 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -99,10 +99,9 @@ static void lwan_random_seed_prng_for_thread(uint64_t fallback_seed) { if (lwan_getentropy(&lehmer64_state, sizeof(lehmer64_state), 0) < 0) { lwan_status_warning("Couldn't get proper entropy for PRNG, using fallback seed"); - for (int i = 0; i < 4; i++) { - lehmer64_state |= fallback_seed; - lehmer64_state <<= 32; - } + lehmer64_state |= fallback_seed; + lehmer64_state <<= 32; + lehmer64_state |= fallback_seed; } } From 12f1c0f7360792d75d1b49d20053fbbda999cf95 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 22 May 2021 11:02:35 -0700 Subject: [PATCH 1756/2505] Remove need to set SO_INCOMING_CPU when accepting clients When I originally wrote the code to use a CBPF program to direct the incoming sockets to the right CPU, it didn't seem to work as intended, especially if one used getsockopt(SO_INCOMING_CPU) to inspect the value and compare it against what thread->cpu gives you. The issue, that I realized only after reading [1], is that listening sockets must be created in a particular order if it is to use SKF_AD_CPU as the value returned by the CBPF program. I also realized while reading the same blog post, that my CBPF program was wrong, because it was MOD-ing the CPU ID with the total number of sockets, which is obviously wrong in retrospect. I also fixed this to only extract the SKF_AD_CPU field as I should've since the beginning. With this patch, threads are created serially, in the correct order. Calling getsockopt(SO_INCOMING_CPU) in a pinned worker thread will return the expected value, that coincides with thread->cpu and what getcpu() says. Because of this, setsockopt(SO_INCOMING_CPU) for each accepted socket isn't called any longer. [1] https://talawah.io/blog/extreme-http-performance-tuning-one-point-two-million/ --- src/lib/lwan-thread.c | 54 ++++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 15b5735db..af39862e7 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -509,11 +509,6 @@ static bool accept_waiting_clients(const struct lwan_thread *t) close(fd); } -#if defined(HAVE_SO_INCOMING_CPU) && defined(__x86_64__) - /* Ignore errors here, as this is just a hint */ - (void)setsockopt(fd, SOL_SOCKET, SO_INCOMING_CPU, &t->cpu, sizeof(t->cpu)); -#endif - continue; } @@ -537,8 +532,7 @@ static bool accept_waiting_clients(const struct lwan_thread *t) } static int create_listen_socket(struct lwan_thread *t, - unsigned int num, - unsigned int num_sockets) + unsigned int num) { int listen_fd; @@ -558,18 +552,10 @@ static int create_listen_socket(struct lwan_thread *t, const uint32_t cpu_ad_off = (uint32_t)SKF_AD_OFF + SKF_AD_CPU; struct sock_filter filter[] = { {BPF_LD | BPF_W | BPF_ABS, 0, 0, cpu_ad_off}, /* A = curr_cpu_index */ - {BPF_ALU | BPF_MOD | BPF_K, 0, 0, num_sockets}, /* A %= num_sockets */ {BPF_RET | BPF_A, 0, 0, 0}, /* return A */ }; struct sock_fprog fprog = {.filter = filter, .len = N_ELEMENTS(filter)}; - if (!(num_sockets & (num_sockets - 1))) { - /* FIXME: Is this strength reduction already made by the kernel? */ - filter[1].code &= (uint16_t)~BPF_MOD; - filter[1].code |= BPF_AND; - filter[1].k = num_sockets - 1; - } - (void)setsockopt(listen_fd, SOL_SOCKET, SO_ATTACH_REUSEPORT_CBPF, &fprog, sizeof(fprog)); (void)setsockopt(listen_fd, SOL_SOCKET, SO_LOCK_FILTER, @@ -841,10 +827,6 @@ void lwan_thread_init(struct lwan *l) { const unsigned int total_conns = l->thread.max_fd * l->thread.count; - if (pthread_barrier_init(&l->thread.barrier, NULL, - (unsigned)l->thread.count + 1)) - lwan_status_critical("Could not create barrier"); - lwan_status_debug("Initializing threads"); l->thread.threads = @@ -872,7 +854,8 @@ void lwan_thread_init(struct lwan *l) * use the CPU topology to group two connections per cache line in such * a way that false sharing is avoided. */ - uint32_t n_threads = (uint32_t)lwan_nextpow2((size_t)((l->thread.count - 1) * 2)); + uint32_t n_threads = + (uint32_t)lwan_nextpow2((size_t)((l->thread.count - 1) * 2)); uint32_t *schedtbl = alloca(n_threads * sizeof(uint32_t)); bool adj_affinity = topology_to_schedtbl(l, schedtbl, n_threads); @@ -893,15 +876,40 @@ void lwan_thread_init(struct lwan *l) l->thread.threads[i].cpu = i % l->online_cpus; for (unsigned int i = 0; i < total_conns; i++) l->conns[i].thread = &l->thread.threads[i % l->thread.count]; + uint32_t *schedtbl = NULL; #endif for (unsigned int i = 0; i < l->thread.count; i++) { - struct lwan_thread *thread = &l->thread.threads[i]; + struct lwan_thread *thread = NULL; + + if (schedtbl) { + /* This is not the most elegant thing, but this assures that the + * listening sockets are added to the SO_REUSEPORT group in a + * specific order, because that's what the CBPF program to direct + * the incoming connection to the right CPU will use. */ + for (uint32_t thread_id = 0; thread_id < l->thread.count; + thread_id++) { + if (schedtbl[thread_id & n_threads] == i) { + thread = &l->thread.threads[thread_id]; + break; + } + } + if (!thread) + lwan_status_critical( + "Could not figure out which CPU thread %d should go to", i); + } else { + thread = &l->thread.threads[i % l->thread.count]; + } + + if (pthread_barrier_init(&l->thread.barrier, NULL, 1)) + lwan_status_critical("Could not create barrier"); create_thread(l, thread); - if ((thread->listen_fd = create_listen_socket(thread, i, l->thread.count)) < 0) + if ((thread->listen_fd = create_listen_socket(thread, i)) < 0) lwan_status_critical_perror("Could not create listening socket"); + + pthread_barrier_wait(&l->thread.barrier); } #ifdef __x86_64__ @@ -909,8 +917,6 @@ void lwan_thread_init(struct lwan *l) adjust_threads_affinity(l, schedtbl, n_threads); #endif - pthread_barrier_wait(&l->thread.barrier); - lwan_status_debug("Worker threads created and ready to serve"); } From f9bfbbcffbeac07dfc80a0347434b6f6b4a3823d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 22 May 2021 11:43:59 -0700 Subject: [PATCH 1757/2505] SET_SOCKET_OPTION_MAY_FAIL() macro doesn't work with string args Just call setsockopt() directly for the TCP_CONGESTION flag and ignore errors. --- src/lib/lwan-socket.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-socket.c b/src/lib/lwan-socket.c index 5c2f0a7d1..15f399a3c 100644 --- a/src/lib/lwan-socket.c +++ b/src/lib/lwan-socket.c @@ -306,7 +306,7 @@ int lwan_create_listen_socket(struct lwan *l, bool print_listening_msg) (int[]){(int)l->config.keep_alive_timeout}); if (is_reno_supported()) - SET_SOCKET_OPTION_MAY_FAIL(IPPROTO_TCP, TCP_CONGESTION, "reno"); + setsockopt(fd, IPPROTO_TCP, TCP_CONGESTION, "reno", 4); #endif return fd; From d52c07bc0ad26c9034dbd4f04828720e571ef6cd Mon Sep 17 00:00:00 2001 From: "Leandro A. F. Pereira" Date: Thu, 27 May 2021 11:06:03 -0700 Subject: [PATCH 1758/2505] Update IRC network (Freenode -> Libera) --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9835f35da..1bc854a65 100644 --- a/README.md +++ b/README.md @@ -711,9 +711,8 @@ Without keep-alive, these numbers drop around 6-fold. IRC Channel ----------- -There is an IRC channel (`#lwan`) on [Freenode](http://freenode.net). A -standard IRC client can be used. A [web IRC gateway](http://webchat.freenode.net?channels=%23lwan&uio=d4) -is also available. +There is an IRC channel (`#lwan`) on [Libera](https://libera.chat). A +standard IRC client can be used. Lwan in the wild ---------------- From 5907503d38bb592c2d3cb8b8d8db6cc2814d8573 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 22 May 2021 15:03:51 -0700 Subject: [PATCH 1759/2505] Ensure lwan_getentropy uses /dev/[u]random as fallback --- src/lib/missing.c | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/src/lib/missing.c b/src/lib/missing.c index 33f37a080..702482bbf 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -613,33 +613,46 @@ int statfs(const char *path, struct statfs *buf) } #endif +static int lwan_getentropy_fallback(void *buffer, size_t buffer_len) +{ + int fd; + + fd = open("/dev/urandom", O_CLOEXEC | O_RDONLY); + if (fd < 0) { + fd = open("/dev/random", O_CLOEXEC | O_RDONLY); + if (fd < 0) + return -1; + } + ssize_t total_read = read(fd, buffer, buffer_len); + close(fd); + + return total_read == (ssize_t)buffer_len ? 0 : -1; +} + #if defined(SYS_getrandom) long int lwan_getentropy(void *buffer, size_t buffer_len, int flags) { - return syscall(SYS_getrandom, buffer, buffer_len, flags); + long r = syscall(SYS_getrandom, buffer, buffer_len, flags); + + if (r < 0) + return lwan_getentropy_fallback(buffer, buffer_len); + + return r; } #elif defined(HAVE_GETENTROPY) long int lwan_getentropy(void *buffer, size_t buffer_len, int flags) { (void)flags; + if (!getentropy(buffer, buffer_len)) return buffer_len; - return -1; + + return lwan_getentropy_fallback(buffer, buffer_len); } #else long int lwan_getentropy(void *buffer, size_t buffer_len, int flags) { (void)flags; - - int fd = open("/dev/urandom", O_CLOEXEC | O_RDONLY); - if (fd < 0) { - fd = open("/dev/random", O_CLOEXEC | O_RDONLY); - if (fd < 0) - return -1; - } - ssize_t total_read = read(fd, buffer, buffer_len); - close(fd); - - return total_read == (ssize_t)buffer_len ? 0 : -1; + return lwan_getentropy_fallback(buffer, buffer_len); } #endif From 40371a0a6af8444a590c356e261529f19beba815 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 22 May 2021 15:21:11 -0700 Subject: [PATCH 1760/2505] Accepted clients should be added to the same epoll set The thread affinity was not consistent with the pre-scheduled thread in each connection. --- src/lib/lwan-thread.c | 41 ++++++++++++++++++----------------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index af39862e7..46159295b 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -795,18 +795,15 @@ topology_to_schedtbl(struct lwan *l, uint32_t schedtbl[], uint32_t n_threads) } static void -adjust_threads_affinity(struct lwan *l, uint32_t *schedtbl, uint32_t mask) +adjust_thread_affinity(const struct lwan_thread *thread) { - for (uint32_t i = 0; i < l->thread.count; i++) { - cpu_set_t set; + cpu_set_t set; - CPU_ZERO(&set); - CPU_SET(schedtbl[i & mask], &set); + CPU_ZERO(&set); + CPU_SET(thread->cpu, &set); - if (pthread_setaffinity_np(l->thread.threads[i].self, sizeof(set), - &set)) - lwan_status_warning("Could not set affinity for thread %d", i); - } + if (pthread_setaffinity_np(thread->self, sizeof(set), &set)) + lwan_status_warning("Could not set thread affinity"); } #elif defined(__x86_64__) static bool @@ -818,8 +815,9 @@ topology_to_schedtbl(struct lwan *l, uint32_t schedtbl[], uint32_t n_threads) } static void -adjust_threads_affinity(struct lwan *l, uint32_t *schedtbl, uint32_t n) +adjust_thread_affinity(const struct lwan_thread *thread) { + (void)thread; } #endif @@ -862,13 +860,6 @@ void lwan_thread_init(struct lwan *l) n_threads--; /* Transform count into mask for AND below */ - if (adj_affinity) { - /* Save which CPU this tread will be pinned at so we can use - * SO_INCOMING_CPU later. */ - for (unsigned int i = 0; i < l->thread.count; i++) - l->thread.threads[i].cpu = schedtbl[i & n_threads]; - } - for (unsigned int i = 0; i < total_conns; i++) l->conns[i].thread = &l->thread.threads[schedtbl[i & n_threads]]; #else @@ -876,7 +867,9 @@ void lwan_thread_init(struct lwan *l) l->thread.threads[i].cpu = i % l->online_cpus; for (unsigned int i = 0; i < total_conns; i++) l->conns[i].thread = &l->thread.threads[i % l->thread.count]; + uint32_t *schedtbl = NULL; + const bool adj_affinity = false; #endif for (unsigned int i = 0; i < l->thread.count; i++) { @@ -894,9 +887,11 @@ void lwan_thread_init(struct lwan *l) break; } } - if (!thread) + if (!thread) { + /* FIXME: can this happen when we have a offline CPU? */ lwan_status_critical( "Could not figure out which CPU thread %d should go to", i); + } } else { thread = &l->thread.threads[i % l->thread.count]; } @@ -909,14 +904,14 @@ void lwan_thread_init(struct lwan *l) if ((thread->listen_fd = create_listen_socket(thread, i)) < 0) lwan_status_critical_perror("Could not create listening socket"); + if (adj_affinity) { + l->thread.threads[i].cpu = schedtbl[i & n_threads]; + adjust_thread_affinity(thread); + } + pthread_barrier_wait(&l->thread.barrier); } -#ifdef __x86_64__ - if (adj_affinity) - adjust_threads_affinity(l, schedtbl, n_threads); -#endif - lwan_status_debug("Worker threads created and ready to serve"); } From 016197cbdd9f4e10ac358a6a1ae3658b138d31e6 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 22 May 2021 18:11:08 -0700 Subject: [PATCH 1761/2505] Barrier should be initialized to 2 Otherwise we don't really wait for the thread to call barrier_wait(). --- src/lib/lwan-thread.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 46159295b..2654d8804 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -896,7 +896,7 @@ void lwan_thread_init(struct lwan *l) thread = &l->thread.threads[i % l->thread.count]; } - if (pthread_barrier_init(&l->thread.barrier, NULL, 1)) + if (pthread_barrier_init(&l->thread.barrier, NULL, 2)) lwan_status_critical("Could not create barrier"); create_thread(l, thread); From 2bd45c2044f1c80b141854f2bd84a5a543487c15 Mon Sep 17 00:00:00 2001 From: pontscho Date: Sat, 29 May 2021 14:55:59 -0700 Subject: [PATCH 1762/2505] Add request ID generation --- src/lib/lwan-lua.c | 7 +++++++ src/lib/lwan-thread.c | 1 + src/lib/lwan.h | 12 +++++++----- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index 1faadb510..5d98be97d 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -296,6 +296,13 @@ LWAN_LUA_METHOD(sleep) return 0; } +LWAN_LUA_METHOD(request_id) +{ + struct lwan_request *request = lwan_lua_get_request_from_userdata(L); + lua_pushfstring(L, "%016lx", request->request_id); + return 1; +} + DEFINE_ARRAY_TYPE(lwan_lua_method_array, luaL_reg) static struct lwan_lua_method_array lua_methods; diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 2654d8804..6aee99785 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -145,6 +145,7 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, struct lwan_request request = {.conn = conn, .global_response_headers = &lwan->headers, .fd = fd, + .request_id = lwan_random_uint64(), .response = {.buffer = &strbuf}, .flags = flags, .proxy = &proxy, diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 003672052..f5aca7eb4 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -362,16 +362,18 @@ struct lwan_request_parser_helper; struct lwan_request { enum lwan_request_flags flags; int fd; - struct lwan_value url; - struct lwan_value original_url; struct lwan_connection *conn; const struct lwan_strbuf *const global_response_headers; - struct lwan_proxy *proxy; - - struct timeout timeout; + uint64_t request_id; struct lwan_request_parser_helper *helper; + + struct lwan_value url; + struct lwan_value original_url; struct lwan_response response; + + struct lwan_proxy *proxy; + struct timeout timeout; }; struct lwan_module { From 2969a07efe9bd6e8f97195981cababd7b80381e8 Mon Sep 17 00:00:00 2001 From: pontscho Date: Sat, 29 May 2021 14:57:10 -0700 Subject: [PATCH 1763/2505] Add request:request_date() metamethod --- src/lib/lwan-lua.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index 5d98be97d..4471341cf 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -303,6 +303,13 @@ LWAN_LUA_METHOD(request_id) return 1; } +LWAN_LUA_METHOD(request_date) +{ + struct lwan_request *request = lwan_lua_get_request_from_userdata(L); + lua_pushstring(L, request->conn->thread->date.date); + return 1; +} + DEFINE_ARRAY_TYPE(lwan_lua_method_array, luaL_reg) static struct lwan_lua_method_array lua_methods; From b739f0f33f5d93438d7b4b7291a6ffa65603768c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 29 May 2021 14:58:33 -0700 Subject: [PATCH 1764/2505] Document the new request metamethods --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 1bc854a65..d8e39e5de 100644 --- a/README.md +++ b/README.md @@ -432,6 +432,8 @@ information from the request, or to set the response, as seen below: - `req:path()` returns a string with the request path. - `req:query_string()` returns a string with the query string (empty string if no query string present). - `req:body()` returns the request body (POST/PUT requests). + - `req:request_id()` returns a string containing the request ID. + - `req:request_date()` returns the date as it'll be written in the `Date` response header. Handler functions may return either `nil` (in which case, a `200 OK` response is generated), or a number matching an HTTP status code. Attempting to return From a8605e2c3bfa18ce57c36665c574b8208c2b163c Mon Sep 17 00:00:00 2001 From: pontscho Date: Thu, 20 May 2021 14:27:50 +0200 Subject: [PATCH 1765/2505] adding syslog() support --- CMakeLists.txt | 12 ++++ src/cmake/lwan-build-config.h.cmake | 1 + src/lib/lwan-status.c | 96 +++++++++++++++++++++++++++-- 3 files changed, 105 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3682a0850..38052eace 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -98,6 +98,16 @@ if (USE_ALTERNATIVE_MALLOC) endif () endif () +### +# syslog +option(USE_SYSLOG "Enable syslog" "OFF") +if (${USE_SYSLOG} STREQUAL "ON" AND HAVE_SYSLOG_FUNC) + set(HAVE_SYSLOG 1) +endif () +if (HAVE_SYSLOG) + message(STATUS "Using syslog/rsyslog for logging.") +endif () + # # Look for C library functions # @@ -111,6 +121,7 @@ set(CMAKE_EXTRA_INCLUDE_FILES time.h unistd.h dlfcn.h + syslog.h ) check_include_file(linux/capability.h HAVE_LINUX_CAPABILITY) check_include_file(sys/auxv.h HAVE_SYS_AUXV) @@ -151,6 +162,7 @@ check_function_exists(fwrite_unlocked HAVE_FWRITE_UNLOCKED) check_function_exists(gettid HAVE_GETTID) check_function_exists(secure_getenv HAVE_SECURE_GETENV) check_function_exists(statfs HAVE_STATFS) +check_function_exists(syslog HAVE_SYSLOG_FUNC) # This is available on -ldl in glibc, but some systems (such as OpenBSD) # will bundle these in the C library. This isn't required for glibc anyway, diff --git a/src/cmake/lwan-build-config.h.cmake b/src/cmake/lwan-build-config.h.cmake index 4ff476307..1af127ffa 100644 --- a/src/cmake/lwan-build-config.h.cmake +++ b/src/cmake/lwan-build-config.h.cmake @@ -46,6 +46,7 @@ #cmakedefine HAVE_STATFS #cmakedefine HAVE_SO_ATTACH_REUSEPORT_CBPF #cmakedefine HAVE_SO_INCOMING_CPU +#cmakedefine HAVE_SYSLOG /* Compiler builtins for specific CPU instruction support */ #cmakedefine HAVE_BUILTIN_CLZLL diff --git a/src/lib/lwan-status.c b/src/lib/lwan-status.c index 4b49ce9df..95d3a22c9 100644 --- a/src/lib/lwan-status.c +++ b/src/lib/lwan-status.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2013 Leandro A. F. Pereira + * Copyright (c) 2021 Leandro A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -17,7 +17,6 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA. */ - #define _GNU_SOURCE #include #include @@ -30,6 +29,7 @@ #include #include "lwan-private.h" +#include "lwan-status.h" enum lwan_status_type { STATUS_INFO = 0, @@ -42,11 +42,11 @@ enum lwan_status_type { STATUS_CRITICAL = 8, }; +static bool can_use_colors(void); + static volatile bool quiet = false; static bool use_colors; -static bool can_use_colors(void); - void lwan_status_init(struct lwan *l) { #ifdef NDEBUG @@ -134,6 +134,87 @@ static long gettid_cached(void) #define FORMAT_WITH_COLOR(fmt, color) "\033[" color "m" fmt "\033[0m" +#ifdef HAVE_SYSLOG + +#include + +static int status_to_syslog_prio[] = { + [STATUS_CRITICAL + STATUS_PERROR] = LOG_CRIT, + [STATUS_CRITICAL] = LOG_CRIT, + [STATUS_ERROR] = LOG_ERR, + [STATUS_WARNING] = LOG_WARNING, + [STATUS_INFO] = LOG_INFO, + [STATUS_DEBUG] = LOG_DEBUG, +}; + +#define APPEND(func, fmt, ...) \ + len = func(tmp + offs, (unsigned long)(log_len - offs), fmt, __VA_ARGS__); \ + if (len >= log_len - offs - 1) { \ + log_len *= 2; \ + continue; \ + } else if (len < 0) { \ + return; \ + } \ + offs += len; + +void lwan_syslog_status_out( +#ifndef NDEBUG + const char *file, + const int line, + const char *func, + const long tid, +#endif + enum lwan_status_type type, + int saved_errno, + const char *fmt, + va_list values) +{ + static volatile int log_len = 256; + char *tmp = NULL; + char *errmsg = NULL; + +#ifndef NDEBUG + char *base_name = basename(strdupa(file)); +#endif + + if (UNLIKELY(type & STATUS_PERROR)) { + char errbuf[64]; + errmsg = strerror_thunk_r(saved_errno, errbuf, sizeof(errbuf) - 1); + } + + do { + va_list copied_values; + va_copy(copied_values, values); + + tmp = alloca((size_t)log_len); + + int len = 0; + int offs = 0; + +#ifndef NDEBUG + APPEND(snprintf, "%ld %s:%d %s() ", tid, base_name, line, func) +#endif + + APPEND(vsnprintf, fmt, copied_values) + + if (errmsg) { + APPEND(snprintf, ": %s (error number %d)", errmsg, saved_errno) + } + } while (0); + + syslog(status_to_syslog_prio[type], "%s", tmp); +} + +#undef APPEND + +__attribute__((constructor)) static void register_lwan_to_syslog(void) +{ + openlog("lwan", LOG_NDELAY | LOG_PID | LOG_CONS, LOG_USER); +} +#else +#define lwan_syslog_status_out(...) +#endif + static void status_out( #ifndef NDEBUG const char *file, @@ -148,6 +229,13 @@ static void status_out( struct lwan_value end = end_color(); int saved_errno = errno; +#ifndef NDEBUG + lwan_syslog_status_out(file, line, func, gettid_cached(), type, saved_errno, + fmt, values); +#else + lwan_syslog_status_out(type, saved_errno, fmt, values); +#endif + flockfile(stdout); #ifndef NDEBUG From 6ba29d872bb945167ffdff0c5f3125bce18c898c Mon Sep 17 00:00:00 2001 From: pontscho Date: Thu, 20 May 2021 16:19:19 +0200 Subject: [PATCH 1766/2505] adding logger functions to lua --- src/lib/lwan-lua.c | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index 4471341cf..61945333f 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -310,6 +310,42 @@ LWAN_LUA_METHOD(request_date) return 1; } +#define IMPLEMENT_LOG_FUNCTION(name) \ + static int lwan_lua_log_##name(lua_State *L) \ + { \ + size_t log_str_len = 0; \ + const char *log_str = lua_tolstring(L, -1, &log_str_len); \ + if (log_str_len) \ + lwan_status_##name("%.*s", (int)log_str_len, log_str); \ + return 0; \ + } + +IMPLEMENT_LOG_FUNCTION(info) +IMPLEMENT_LOG_FUNCTION(warning) +IMPLEMENT_LOG_FUNCTION(error) +IMPLEMENT_LOG_FUNCTION(critical) + +#undef IMPLEMENT_LOG_FUNCTION + +static int luaopen_log(lua_State *L) +{ + static const char *metatable_name = "Lwan.log"; + static const struct luaL_Reg functions[] = { +#define LOG_FUNCTION(name) {#name, lwan_lua_log_##name} + LOG_FUNCTION(info), + LOG_FUNCTION(warning), + LOG_FUNCTION(error), + LOG_FUNCTION(critical), + {}, +#undef LOG_FUNCTION + }; + + luaL_newmetatable(L, metatable_name); + luaL_register(L, metatable_name, functions); + + return 0; +} + DEFINE_ARRAY_TYPE(lwan_lua_method_array, luaL_reg) static struct lwan_lua_method_array lua_methods; @@ -353,6 +389,7 @@ lua_State *lwan_lua_create_state(const char *script_file, const char *script) return NULL; luaL_openlibs(L); + luaopen_log(L); luaL_newmetatable(L, request_metatable_name); luaL_register(L, NULL, lwan_lua_method_array_get_array(&lua_methods)); From 58cd943d015e2b0d67f40358011416eee7aabf38 Mon Sep 17 00:00:00 2001 From: pontscho Date: Thu, 20 May 2021 16:19:46 +0200 Subject: [PATCH 1767/2505] Measure request processing time --- src/lib/lwan-request.c | 45 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 9ae7f479a..2a15d6e07 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1450,27 +1450,62 @@ static const char *get_request_method(struct lwan_request *request) } static void log_request(struct lwan_request *request, - enum lwan_http_status status) + enum lwan_http_status status, + double duration) { char ip_buffer[INET6_ADDRSTRLEN]; - lwan_status_debug("%s [%s] \"%s %s HTTP/%s\" %d %s", + lwan_status_debug("%s [%s] \"%s %s HTTP/%s\" %d %s %.3f ms", lwan_request_get_remote_address(request, ip_buffer), request->conn->thread->date.date, get_request_method(request), request->original_url.value, request->flags & REQUEST_IS_HTTP_1_0 ? "1.0" : "1.1", - status, request->response.mime_type); + status, request->response.mime_type, duration); } #else #define log_request(...) #endif +#ifndef NDEBUG +static struct timespec current_precise_monotonic_timespec(void) +{ + struct timespec now; + + if (UNLIKELY(clock_gettime(CLOCK_MONOTONIC, &now) < 0)) { + lwan_status_perror("clock_gettime"); + return (struct timespec){}; + } + + return now; +} + +static double elapsed_time_ms(const struct timespec then) +{ + const struct timespec now = current_precise_monotonic_timespec(); + struct timespec diff = { + .tv_sec = now.tv_sec - then.tv_sec, + .tv_nsec = now.tv_nsec - then.tv_nsec, + }; + + if (diff.tv_nsec < 0) { + diff.tv_nsec--; + diff.tv_nsec += 1000000000l; + } + + return (double)diff.tv_sec / 1000.0 + (double)diff.tv_nsec / 1000000.0; +} +#endif + void lwan_process_request(struct lwan *l, struct lwan_request *request) { enum lwan_http_status status; struct lwan_url_map *url_map; status = read_request(request); + +#ifndef NDEBUG + struct timespec request_begin_time = current_precise_monotonic_timespec(); +#endif if (UNLIKELY(status != HTTP_OK)) { /* If read_request() returns any error at this point, it's probably * better to just send an error response and abort the coroutine and @@ -1508,9 +1543,9 @@ void lwan_process_request(struct lwan *l, struct lwan_request *request) } log_and_return: - log_request(request, status); + lwan_response(request, status); - return (void)lwan_response(request, status); + log_request(request, status, elapsed_time_ms(request_begin_time)); } static inline void * From 3f22b6650a97b623e9dd9cae4f2dad478b8ea798 Mon Sep 17 00:00:00 2001 From: pontscho Date: Sat, 29 May 2021 15:37:36 -0700 Subject: [PATCH 1768/2505] Add request_id to output of log_request() function --- src/lib/lwan-request.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 2a15d6e07..b90a33ec5 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1455,9 +1455,9 @@ static void log_request(struct lwan_request *request, { char ip_buffer[INET6_ADDRSTRLEN]; - lwan_status_debug("%s [%s] \"%s %s HTTP/%s\" %d %s %.3f ms", + lwan_status_debug("%s [%s] %016lx \"%s %s HTTP/%s\" %d %s %.3f ms", lwan_request_get_remote_address(request, ip_buffer), - request->conn->thread->date.date, + request->conn->thread->date.date, request->request_id, get_request_method(request), request->original_url.value, request->flags & REQUEST_IS_HTTP_1_0 ? "1.0" : "1.1", status, request->response.mime_type, duration); From 3f02c8d3ec9bbee4536192a3904e6f26aedc888a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 29 May 2021 15:45:03 -0700 Subject: [PATCH 1769/2505] Document `-DUSE_SYSLOG=ON` --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index d8e39e5de..e5c87b60a 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,9 @@ supports [TCMalloc](https://github.com/gperftools/gperftools), pass `-DALTERNATIVE_MALLOC=name` to the CMake invocation line, using the names provided in the "Optional dependencies" section. +The `-DUSE_SYSLOG=ON` option can be passed to CMake to also log to the system log +in addition to the standard output. + ### Tests ~/lwan/build$ make testsuite From 5db746fb7e27d5def6398561c9323d9202944cb1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 29 May 2021 18:49:08 -0700 Subject: [PATCH 1770/2505] Add vprintf() versions to lwan_strbuf --- src/lib/lwan-strbuf.c | 14 ++++++++++++-- src/lib/lwan-strbuf.h | 5 +++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-strbuf.c b/src/lib/lwan-strbuf.c index b7c1e6cc8..2dbce0b9a 100644 --- a/src/lib/lwan-strbuf.c +++ b/src/lib/lwan-strbuf.c @@ -211,25 +211,35 @@ internal_printf(struct lwan_strbuf *s1, return success; } +bool lwan_strbuf_vprintf(struct lwan_strbuf *s, const char *fmt, va_list ap) +{ + return internal_printf(s, lwan_strbuf_set, fmt, ap); +} + bool lwan_strbuf_printf(struct lwan_strbuf *s, const char *fmt, ...) { bool could_printf; va_list values; va_start(values, fmt); - could_printf = internal_printf(s, lwan_strbuf_set, fmt, values); + could_printf = lwan_strbuf_vprintf(s, fmt, values); va_end(values); return could_printf; } +bool lwan_strbuf_append_vprintf(struct lwan_strbuf *s, const char *fmt, va_list ap) +{ + return internal_printf(s, lwan_strbuf_append_str, fmt, ap); +} + bool lwan_strbuf_append_printf(struct lwan_strbuf *s, const char *fmt, ...) { bool could_printf; va_list values; va_start(values, fmt); - could_printf = internal_printf(s, lwan_strbuf_append_str, fmt, values); + could_printf = lwan_strbuf_append_vprintf(s, fmt, values); va_end(values); return could_printf; diff --git a/src/lib/lwan-strbuf.h b/src/lib/lwan-strbuf.h index 8e5e1c66f..5825e01ea 100644 --- a/src/lib/lwan-strbuf.h +++ b/src/lib/lwan-strbuf.h @@ -20,6 +20,7 @@ #pragma once +#include #include #include #include @@ -74,9 +75,13 @@ static inline bool lwan_strbuf_setz(struct lwan_strbuf *s1, const char *s2) bool lwan_strbuf_append_printf(struct lwan_strbuf *s, const char *fmt, ...) __attribute__((format(printf, 2, 3))); +bool lwan_strbuf_append_vprintf(struct lwan_strbuf *s, + const char *fmt, + va_list v); bool lwan_strbuf_printf(struct lwan_strbuf *s1, const char *fmt, ...) __attribute__((format(printf, 2, 3))); +bool lwan_strbuf_vprintf(struct lwan_strbuf *s1, const char *fmt, va_list ap); bool lwan_strbuf_grow_to(struct lwan_strbuf *s, size_t new_size); bool lwan_strbuf_grow_by(struct lwan_strbuf *s, size_t offset); From 375e15ea7abf3e4dbc5c59ca67a1db6ff188826a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 29 May 2021 18:52:25 -0700 Subject: [PATCH 1771/2505] Don't use alloca() when outputting to syslog() Using alloca() with a potentially large allocation is very dangerous, so it's better to pay the price of a malloc/free roundtrip. Use lwan_strbuf so at least building the string is easier. --- src/lib/lwan-status.c | 54 +++++++++++++------------------------------ 1 file changed, 16 insertions(+), 38 deletions(-) diff --git a/src/lib/lwan-status.c b/src/lib/lwan-status.c index 95d3a22c9..08b737e03 100644 --- a/src/lib/lwan-status.c +++ b/src/lib/lwan-status.c @@ -136,6 +136,7 @@ static long gettid_cached(void) #ifdef HAVE_SYSLOG +#include #include static int status_to_syslog_prio[] = { @@ -147,16 +148,6 @@ static int status_to_syslog_prio[] = { [STATUS_DEBUG] = LOG_DEBUG, }; -#define APPEND(func, fmt, ...) \ - len = func(tmp + offs, (unsigned long)(log_len - offs), fmt, __VA_ARGS__); \ - if (len >= log_len - offs - 1) { \ - log_len *= 2; \ - continue; \ - } else if (len < 0) { \ - return; \ - } \ - offs += len; - void lwan_syslog_status_out( #ifndef NDEBUG const char *file, @@ -169,44 +160,31 @@ void lwan_syslog_status_out( const char *fmt, va_list values) { - static volatile int log_len = 256; - char *tmp = NULL; - char *errmsg = NULL; - -#ifndef NDEBUG - char *base_name = basename(strdupa(file)); -#endif - - if (UNLIKELY(type & STATUS_PERROR)) { - char errbuf[64]; - errmsg = strerror_thunk_r(saved_errno, errbuf, sizeof(errbuf) - 1); - } + va_list copied_values; + struct lwan_strbuf buf; - do { - va_list copied_values; - va_copy(copied_values, values); + va_copy(copied_values, values); - tmp = alloca((size_t)log_len); - - int len = 0; - int offs = 0; + lwan_strbuf_init(&buf); #ifndef NDEBUG - APPEND(snprintf, "%ld %s:%d %s() ", tid, base_name, line, func) + lwan_strbuf_append_printf(&buf, "%ld %s:%d %s() ", tid, + basename(strdupa(file)), line, func); #endif + lwan_strbuf_append_vprintf(&buf, fmt, copied_values); - APPEND(vsnprintf, fmt, copied_values) + if (type & STATUS_PERROR) { + char errbuf[128]; - if (errmsg) { - APPEND(snprintf, ": %s (error number %d)", errmsg, saved_errno) - } - } while (0); + lwan_strbuf_append_strz( + &buf, strerror_thunk_r(saved_errno, errbuf, sizeof(errbuf) - 1)); + } - syslog(status_to_syslog_prio[type], "%s", tmp); + syslog(status_to_syslog_prio[type], "%.*s", + (int)lwan_strbuf_get_length(&buf), lwan_strbuf_get_buffer(&buf)); + lwan_strbuf_free(&buf); } -#undef APPEND - __attribute__((constructor)) static void register_lwan_to_syslog(void) { openlog("lwan", LOG_NDELAY | LOG_PID | LOG_CONS, LOG_USER); From daa150efeff74bba06b6721898c1202375800805 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 29 May 2021 19:05:54 -0700 Subject: [PATCH 1772/2505] Allow a lwan_strbuf to use a user-allocated buffer This allows us to use lwan_strbuf for temporary strings, using a buffer in the caller frame's stack, avoiding unnecessary roundtrips to malloc. This also allows us to allocate dynamically a new strbuf with a buffer that can't grow beyond its capacity. --- src/lib/lwan-strbuf.c | 36 ++++++++++++++++++++++++++++++++++++ src/lib/lwan-strbuf.h | 4 ++++ 2 files changed, 40 insertions(+) diff --git a/src/lib/lwan-strbuf.c b/src/lib/lwan-strbuf.c index 2dbce0b9a..eccab9c98 100644 --- a/src/lib/lwan-strbuf.c +++ b/src/lib/lwan-strbuf.c @@ -28,6 +28,7 @@ static const unsigned int BUFFER_MALLOCD = 1 << 0; static const unsigned int STRBUF_MALLOCD = 1 << 1; +static const unsigned int BUFFER_FIXED = 1 << 2; static inline size_t align_size(size_t unaligned_size) { @@ -41,6 +42,9 @@ static inline size_t align_size(size_t unaligned_size) static bool grow_buffer_if_needed(struct lwan_strbuf *s, size_t size) { + if (s->flags & BUFFER_FIXED) + return size < s->capacity; + if (!(s->flags & BUFFER_MALLOCD)) { const size_t aligned_size = align_size(LWAN_MAX(size + 1, s->used)); if (UNLIKELY(!aligned_size)) @@ -93,6 +97,23 @@ bool lwan_strbuf_init_with_size(struct lwan_strbuf *s, size_t size) return true; } +bool lwan_strbuf_init_with_fixed_buffer(struct lwan_strbuf *s, + void *buffer, + size_t size) +{ + if (UNLIKELY(!s)) + return false; + + *s = (struct lwan_strbuf) { + .capacity = size, + .used = 0, + .buffer = buffer, + .flags = BUFFER_FIXED, + }; + + return true; +} + ALWAYS_INLINE bool lwan_strbuf_init(struct lwan_strbuf *s) { return lwan_strbuf_init_with_size(s, 0); @@ -113,6 +134,21 @@ struct lwan_strbuf *lwan_strbuf_new_with_size(size_t size) return s; } +struct lwan_strbuf *lwan_strbuf_new_with_fixed_buffer(size_t size) +{ + struct lwan_strbuf *s = malloc(sizeof(*s) + size + 1); + + if (UNLIKELY(!lwan_strbuf_init_with_fixed_buffer(s, s + 1, size))) { + free(s); + + return NULL; + } + + s->flags |= STRBUF_MALLOCD; + + return s; +} + ALWAYS_INLINE struct lwan_strbuf *lwan_strbuf_new(void) { return lwan_strbuf_new_with_size(0); diff --git a/src/lib/lwan-strbuf.h b/src/lib/lwan-strbuf.h index 5825e01ea..d3cef9145 100644 --- a/src/lib/lwan-strbuf.h +++ b/src/lib/lwan-strbuf.h @@ -41,10 +41,14 @@ struct lwan_strbuf { #define LWAN_STRBUF_STATIC_INIT \ (struct lwan_strbuf) { .buffer = "" } +bool lwan_strbuf_init_with_fixed_buffer(struct lwan_strbuf *buf, + void *buffer, + size_t size); bool lwan_strbuf_init_with_size(struct lwan_strbuf *buf, size_t size); bool lwan_strbuf_init(struct lwan_strbuf *buf); struct lwan_strbuf *lwan_strbuf_new_static(const char *str, size_t size); struct lwan_strbuf *lwan_strbuf_new_with_size(size_t size); +struct lwan_strbuf *lwan_strbuf_new_with_fixed_buffer(size_t size); struct lwan_strbuf *lwan_strbuf_new(void); void lwan_strbuf_free(struct lwan_strbuf *s); From 4795efde906322933623933d8be7fa612c867032 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 29 May 2021 19:07:23 -0700 Subject: [PATCH 1773/2505] Use stack buffer to build syslog messages --- src/lib/lwan-status.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-status.c b/src/lib/lwan-status.c index 08b737e03..9a326e72c 100644 --- a/src/lib/lwan-status.c +++ b/src/lib/lwan-status.c @@ -160,12 +160,13 @@ void lwan_syslog_status_out( const char *fmt, va_list values) { + char syslog_buffer[256]; va_list copied_values; struct lwan_strbuf buf; va_copy(copied_values, values); - lwan_strbuf_init(&buf); + lwan_strbuf_init_with_fixed_buffer(&buf, syslog_buffer, sizeof(syslog_buffer)); #ifndef NDEBUG lwan_strbuf_append_printf(&buf, "%ld %s:%d %s() ", tid, From e6ad03a9c19820cffe4b03db32e71770a1ce8fa8 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 29 May 2021 23:01:00 -0700 Subject: [PATCH 1774/2505] lwan_syslog_status_out() should bail if strbuf ops fail This function would, before changes to use strbuf, do the same thing: avoid logging to the syslog if any of the printf functions failed someshow or if the stack-allocated buffer wasn't large enough. --- src/lib/lwan-status.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/lib/lwan-status.c b/src/lib/lwan-status.c index 9a326e72c..1d2ac1748 100644 --- a/src/lib/lwan-status.c +++ b/src/lib/lwan-status.c @@ -164,25 +164,32 @@ void lwan_syslog_status_out( va_list copied_values; struct lwan_strbuf buf; - va_copy(copied_values, values); - - lwan_strbuf_init_with_fixed_buffer(&buf, syslog_buffer, sizeof(syslog_buffer)); + lwan_strbuf_init_with_fixed_buffer(&buf, syslog_buffer, + sizeof(syslog_buffer)); #ifndef NDEBUG - lwan_strbuf_append_printf(&buf, "%ld %s:%d %s() ", tid, - basename(strdupa(file)), line, func); + if (!lwan_strbuf_append_printf(&buf, "%ld %s:%d %s() ", tid, + basename(strdupa(file)), line, func)) + goto out; #endif - lwan_strbuf_append_vprintf(&buf, fmt, copied_values); + + va_copy(copied_values, values); + if (!lwan_strbuf_append_vprintf(&buf, fmt, copied_values)) + goto out; if (type & STATUS_PERROR) { char errbuf[128]; - lwan_strbuf_append_strz( - &buf, strerror_thunk_r(saved_errno, errbuf, sizeof(errbuf) - 1)); + if (!lwan_strbuf_append_strz( + &buf, + strerror_thunk_r(saved_errno, errbuf, sizeof(errbuf) - 1))) + goto out; } syslog(status_to_syslog_prio[type], "%.*s", (int)lwan_strbuf_get_length(&buf), lwan_strbuf_get_buffer(&buf)); + +out: lwan_strbuf_free(&buf); } From 2daae9b89863b5196b300b09ab79ddd68469c81f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 29 May 2021 23:02:55 -0700 Subject: [PATCH 1775/2505] Use binary OR consistently in status_to_syslog_prio table No difference in value. --- src/lib/lwan-status.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-status.c b/src/lib/lwan-status.c index 1d2ac1748..2c2c2efde 100644 --- a/src/lib/lwan-status.c +++ b/src/lib/lwan-status.c @@ -140,7 +140,7 @@ static long gettid_cached(void) #include static int status_to_syslog_prio[] = { - [STATUS_CRITICAL + STATUS_PERROR] = LOG_CRIT, + [STATUS_CRITICAL | STATUS_PERROR] = LOG_CRIT, [STATUS_CRITICAL] = LOG_CRIT, [STATUS_ERROR] = LOG_ERR, [STATUS_WARNING] = LOG_WARNING, From 199c99b53181e846b1cecd468e8d6a2fd5c3f6f8 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 30 May 2021 09:55:05 -0700 Subject: [PATCH 1776/2505] Use pointer to thread struct and epoll_fd as initial seed on failure This relies on ASLR to get some randomness; it's not great, but not only getentropy() is unlikely to fail, the PRNG we're seeding isn't the greatest anyway. --- src/lib/lwan-thread.c | 13 +++++++------ src/lib/murmur3.c | 6 +++--- src/lib/murmur3.h | 1 + 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 6aee99785..466af17ca 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -35,9 +35,10 @@ #include #endif +#include "list.h" +#include "murmur3.h" #include "lwan-private.h" #include "lwan-tq.h" -#include "list.h" static void lwan_strbuf_free_defer(void *data) { @@ -95,13 +96,13 @@ static void graceful_close(struct lwan *l, static __thread __uint128_t lehmer64_state; -static void lwan_random_seed_prng_for_thread(uint64_t fallback_seed) +static void lwan_random_seed_prng_for_thread(const struct lwan_thread *t) { if (lwan_getentropy(&lehmer64_state, sizeof(lehmer64_state), 0) < 0) { lwan_status_warning("Couldn't get proper entropy for PRNG, using fallback seed"); - lehmer64_state |= fallback_seed; - lehmer64_state <<= 32; - lehmer64_state |= fallback_seed; + lehmer64_state |= murmur3_fmix64((uint64_t)(uintptr_t)t); + lehmer64_state <<= 64; + lehmer64_state |= murmur3_fmix64((uint64_t)t->epoll_fd); } } @@ -599,7 +600,7 @@ static void *thread_io_loop(void *data) timeout_queue_init(&tq, lwan); - lwan_random_seed_prng_for_thread((uint64_t)(epoll_fd | time(NULL))); + lwan_random_seed_prng_for_thread(t); pthread_barrier_wait(&lwan->thread.barrier); diff --git a/src/lib/murmur3.c b/src/lib/murmur3.c index 6d9091c1a..01b301037 100644 --- a/src/lib/murmur3.c +++ b/src/lib/murmur3.c @@ -55,7 +55,7 @@ static FORCE_INLINE uint32_t fmix32(uint32_t h) #endif //---------- -static FORCE_INLINE uint64_t fmix64(uint64_t k) +FORCE_INLINE uint64_t murmur3_fmix64(uint64_t k) { k ^= k >> 33; k *= BIG_CONSTANT(0xff51afd7ed558ccd); @@ -319,8 +319,8 @@ MurmurHash3_x64_128(const void *key, const size_t len, const uint32_t seed, h2 ^= (uint64_t)len; h1 += h2; h2 += h1; - h1 = fmix64(h1); - h2 = fmix64(h2); + h1 = murmur3_fmix64(h1); + h2 = murmur3_fmix64(h2); h1 += h2; h2 += h1; ((uint64_t *) out)[0] = h1; diff --git a/src/lib/murmur3.h b/src/lib/murmur3.h index dd1d3dca6..314f5ceaf 100644 --- a/src/lib/murmur3.h +++ b/src/lib/murmur3.h @@ -9,6 +9,7 @@ //----------------------------------------------------------------------------- +uint64_t murmur3_fmix64(uint64_t k); void murmur3_set_seed(const uint32_t seed); unsigned int murmur3_simple(const void *key); From 474932417c6c6a9e804b9e20701a5a7ee9e7a5b6 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 30 May 2021 09:56:13 -0700 Subject: [PATCH 1777/2505] Get rid of useless ROT32 and ROT64 macros in murmur3 implementation --- src/lib/murmur3.c | 45 +++++++++++++++++++++------------------------ 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/src/lib/murmur3.c b/src/lib/murmur3.c index 01b301037..18beec759 100644 --- a/src/lib/murmur3.c +++ b/src/lib/murmur3.c @@ -33,9 +33,6 @@ static FORCE_INLINE uint64_t rotl64(uint64_t x, int8_t r) return (x << r) | (x >> (64 - r)); } -#define ROTL32(x,y) rotl32(x,y) -#define ROTL64(x,y) rotl64(x,y) - #define BIG_CONSTANT(x) (x##LLU) static uint32_t seed_value = 0xdeadbeef; @@ -87,10 +84,10 @@ MurmurHash3_x86_32(const void *key, size_t len, uint32_t seed, void *out) memcpy(&k1, blocks + i, sizeof(k1)); k1 *= c1; - k1 = ROTL32(k1, 15); + k1 = rotl32(k1, 15); k1 *= c2; h1 ^= k1; - h1 = ROTL32(h1, 13); + h1 = rotl32(h1, 13); h1 = h1 * 5 + 0xe6546b64; } @@ -108,7 +105,7 @@ MurmurHash3_x86_32(const void *key, size_t len, uint32_t seed, void *out) case 1: k1 ^= (uint32_t)tail[0]; k1 *= c1; - k1 = ROTL32(k1, 15); + k1 = rotl32(k1, 15); k1 *= c2; h1 ^= k1; }; @@ -152,14 +149,14 @@ FORCE_INLINE static void MurmurHash3_x86_128 (const void *key, const size_t len, memcpy(&k3, blocks + i * 4 + 2, sizeof(k3)); memcpy(&k4, blocks + i * 4 + 3, sizeof(k4)); - k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1; - h1 = ROTL32(h1,19); h1 += h2; h1 = h1*5+0x561ccd1b; - k2 *= c2; k2 = ROTL32(k2,16); k2 *= c3; h2 ^= k2; - h2 = ROTL32(h2,17); h2 += h3; h2 = h2*5+0x0bcaa747; - k3 *= c3; k3 = ROTL32(k3,17); k3 *= c4; h3 ^= k3; - h3 = ROTL32(h3,15); h3 += h4; h3 = h3*5+0x96cd1c35; - k4 *= c4; k4 = ROTL32(k4,18); k4 *= c1; h4 ^= k4; - h4 = ROTL32(h4,13); h4 += h1; h4 = h4*5+0x32ac3b17; + k1 *= c1; k1 = rotl32(k1,15); k1 *= c2; h1 ^= k1; + h1 = rotl32(h1,19); h1 += h2; h1 = h1*5+0x561ccd1b; + k2 *= c2; k2 = rotl32(k2,16); k2 *= c3; h2 ^= k2; + h2 = rotl32(h2,17); h2 += h3; h2 = h2*5+0x0bcaa747; + k3 *= c3; k3 = rotl32(k3,17); k3 *= c4; h3 ^= k3; + h3 = rotl32(h3,15); h3 += h4; h3 = h3*5+0x96cd1c35; + k4 *= c4; k4 = rotl32(k4,18); k4 *= c1; h4 ^= k4; + h4 = rotl32(h4,13); h4 += h1; h4 = h4*5+0x32ac3b17; } //---------- @@ -176,25 +173,25 @@ FORCE_INLINE static void MurmurHash3_x86_128 (const void *key, const size_t len, case 15: k4 ^= (uint32_t)tail[14] << 16; /* fallthrough */ case 14: k4 ^= (uint32_t)tail[13] << 8; /* fallthrough */ case 13: k4 ^= (uint32_t)tail[12] << 0; - k4 *= c4; k4 = ROTL32(k4,18); k4 *= c1; h4 ^= k4; + k4 *= c4; k4 = rotl32(k4,18); k4 *= c1; h4 ^= k4; /* fallthrough */ case 12: k3 ^= (uint32_t)tail[11] << 24; /* fallthrough */ case 11: k3 ^= (uint32_t)tail[10] << 16; /* fallthrough */ case 10: k3 ^= (uint32_t)tail[ 9] << 8; /* fallthrough */ case 9: k3 ^= (uint32_t)tail[ 8] << 0; - k3 *= c3; k3 = ROTL32(k3,17); k3 *= c4; h3 ^= k3; + k3 *= c3; k3 = rotl32(k3,17); k3 *= c4; h3 ^= k3; /* fallthrough */ case 8: k2 ^= (uint32_t)tail[ 7] << 24; /* fallthrough */ case 7: k2 ^= (uint32_t)tail[ 6] << 16; /* fallthrough */ case 6: k2 ^= (uint32_t)tail[ 5] << 8; /* fallthrough */ case 5: k2 ^= (uint32_t)tail[ 4] << 0; - k2 *= c2; k2 = ROTL32(k2,16); k2 *= c3; h2 ^= k2; + k2 *= c2; k2 = rotl32(k2,16); k2 *= c3; h2 ^= k2; /* fallthrough */ case 4: k1 ^= (uint32_t)tail[ 3] << 24; /* fallthrough */ case 3: k1 ^= (uint32_t)tail[ 2] << 16; /* fallthrough */ case 2: k1 ^= (uint32_t)tail[ 1] << 8; /* fallthrough */ case 1: k1 ^= (uint32_t)tail[ 0] << 0; - k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1; + k1 *= c1; k1 = rotl32(k1,15); k1 *= c2; h1 ^= k1; } //---------- @@ -244,17 +241,17 @@ MurmurHash3_x64_128(const void *key, const size_t len, const uint32_t seed, memcpy(&k2, blocks + i * 2 + 1, sizeof(k2)); k1 *= c1; - k1 = ROTL64(k1, 31); + k1 = rotl64(k1, 31); k1 *= c2; h1 ^= k1; - h1 = ROTL64(h1, 27); + h1 = rotl64(h1, 27); h1 += h2; h1 = h1 * 5 + 0x52dce729; k2 *= c2; - k2 = ROTL64(k2, 33); + k2 = rotl64(k2, 33); k2 *= c1; h2 ^= k2; - h2 = ROTL64(h2, 31); + h2 = rotl64(h2, 31); h2 += h1; h2 = h2 * 5 + 0x38495ab5; } @@ -280,7 +277,7 @@ MurmurHash3_x64_128(const void *key, const size_t len, const uint32_t seed, case 9: k2 ^= (uint64_t) (tail[8]) << 0; k2 *= c2; - k2 = ROTL64(k2, 33); + k2 = rotl64(k2, 33); k2 *= c1; h2 ^= k2; /* fallthrough */ @@ -308,7 +305,7 @@ MurmurHash3_x64_128(const void *key, const size_t len, const uint32_t seed, case 1: k1 ^= (uint64_t) (tail[0]) << 0; k1 *= c1; - k1 = ROTL64(k1, 31); + k1 = rotl64(k1, 31); k1 *= c2; h1 ^= k1; }; From 6eac0dbca124dafc0ee88638fe88b0b0e60d01d2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 30 May 2021 09:57:24 -0700 Subject: [PATCH 1778/2505] When diffing monotonic timespecs, decrement tv_sec if tv_nsec < 0 --- src/lib/lwan-request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index b90a33ec5..69604b840 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1488,7 +1488,7 @@ static double elapsed_time_ms(const struct timespec then) }; if (diff.tv_nsec < 0) { - diff.tv_nsec--; + diff.tv_sec--; diff.tv_nsec += 1000000000l; } From 17b15cf48b9b957d0680e27c63b3c830d7da06a3 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 30 May 2021 10:11:56 -0700 Subject: [PATCH 1779/2505] Include X-Request-Id header if RESPONSE_INCLUDE_REQUEST_ID flag is set --- src/bin/testrunner/main.c | 2 ++ src/lib/lwan-response.c | 10 +++++++++- src/lib/lwan.h | 2 ++ src/scripts/testsuite.py | 10 ++++++++++ 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/bin/testrunner/main.c b/src/bin/testrunner/main.c index 6a61c5332..833a56552 100644 --- a/src/bin/testrunner/main.c +++ b/src/bin/testrunner/main.c @@ -165,6 +165,8 @@ LWAN_HANDLER(hello_world) response->headers = headers; response->mime_type = "text/plain"; + request->flags |= RESPONSE_INCLUDE_REQUEST_ID; + const char *name = lwan_request_get_query_param(request, "name"); if (name) lwan_strbuf_printf(response->buffer, "Hello, %s!", name); diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index ee316d6fe..e29f0eded 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -231,7 +231,7 @@ has_content_length(enum lwan_request_flags v) static ALWAYS_INLINE __attribute__((const)) bool has_uncommon_response_headers(enum lwan_request_flags v) { - return v & (REQUEST_ALLOW_CORS | RESPONSE_CHUNKED_ENCODING); + return v & (RESPONSE_INCLUDE_REQUEST_ID | REQUEST_ALLOW_CORS | RESPONSE_CHUNKED_ENCODING); } size_t lwan_prepare_response_header_full( @@ -351,6 +351,14 @@ size_t lwan_prepare_response_header_full( !has_content_length(request_flags)) { APPEND_CONSTANT("\r\nTransfer-Encoding: chunked"); } + if (request_flags & RESPONSE_INCLUDE_REQUEST_ID) { + char request_id[] = "fill-with-req-id"; + APPEND_CONSTANT("\r\nX-Request-Id: "); + int r = snprintf(request_id, sizeof(request_id), "%016lx", request->request_id); + if (UNLIKELY(r < 0 || r >= (int)sizeof(request_id))) + return 0; + APPEND_STRING_LEN(request_id, (size_t)r); + } } APPEND_STRING_LEN(lwan_strbuf_get_buffer(request->global_response_headers), diff --git a/src/lib/lwan.h b/src/lib/lwan.h index f5aca7eb4..e50d2785a 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -252,6 +252,8 @@ enum lwan_request_flags { REQUEST_PARSED_FORM_DATA = 1 << 21, REQUEST_PARSED_COOKIES = 1 << 22, REQUEST_PARSED_ACCEPT_ENCODING = 1 << 23, + + RESPONSE_INCLUDE_REQUEST_ID = 1 << 24, }; #undef SELECT_MASK diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index 504565243..490ad2744 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -682,6 +682,16 @@ def test_valid_creds(self): class TestHelloWorld(LwanTest): + def test_request_id(self): + all_request_ids = set() + for i in range(20): + r = requests.get('http://127.0.0.1:8080/hello') + self.assertResponsePlain(r) + request_id = r.headers['x-request-id'] + self.assertFalse(request_id in all_request_ids) + self.assertTrue(re.match(r'^[a-f0-9]{16}$', request_id)) + all_request_ids.add(request_id) + def test_cookies(self): c = { 'SOMECOOKIE': '1c330301-89e4-408a-bf6c-ce107efe8a27', From d92cda57c89a3a9d270c427436e8feae5945cc1c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 31 May 2021 08:38:34 -0700 Subject: [PATCH 1780/2505] Add assertion for BUFFER_FIXED when freeing strbuf --- src/lib/lwan-strbuf.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-strbuf.c b/src/lib/lwan-strbuf.c index eccab9c98..c014b3fc1 100644 --- a/src/lib/lwan-strbuf.c +++ b/src/lib/lwan-strbuf.c @@ -176,8 +176,10 @@ void lwan_strbuf_free(struct lwan_strbuf *s) { if (UNLIKELY(!s)) return; - if (s->flags & BUFFER_MALLOCD) + if (s->flags & BUFFER_MALLOCD) { + assert(!(s->flags & BUFFER_FIXED)); free(s->buffer); + } if (s->flags & STRBUF_MALLOCD) free(s); } From 9d71ac058fdc376076f93435218260a2b3fbc15c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 11 Jun 2021 08:39:31 -0700 Subject: [PATCH 1781/2505] Send 503 response if EPOLL_CTL_ADD failed --- src/lib/lwan-thread.c | 65 ++++++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 466af17ca..c58cf13f7 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -334,7 +334,7 @@ static void update_date_cache(struct lwan_thread *thread) thread->date.expires); } -static bool send_buffer_without_coro(int fd, const char *buf, size_t buf_len) +static bool send_buffer_without_coro(int fd, const char *buf, size_t buf_len, int flags) { size_t total_sent = 0; @@ -343,7 +343,7 @@ static bool send_buffer_without_coro(int fd, const char *buf, size_t buf_len) if (!to_send) return true; - ssize_t sent = write(fd, buf + total_sent, to_send); + ssize_t sent = send(fd, buf + total_sent, to_send, flags); if (sent <= 0) { if (errno == EINTR) continue; @@ -356,9 +356,41 @@ static bool send_buffer_without_coro(int fd, const char *buf, size_t buf_len) return false; } -static bool send_string_without_coro(int fd, const char *str) +static bool send_string_without_coro(int fd, const char *str, int flags) { - return send_buffer_without_coro(fd, str, strlen(str)); + return send_buffer_without_coro(fd, str, strlen(str), flags); +} + +static void send_response_without_coro(const struct lwan *l, + int fd, + enum lwan_http_status status) +{ + if (!send_string_without_coro(fd, "HTTP/1.0 ", MSG_MORE)) + return; + + if (!send_string_without_coro( + fd, lwan_http_status_as_string_with_code(status), MSG_MORE)) + return; + + if (!send_string_without_coro(fd, "\r\nConnection: close", MSG_MORE)) + return; + + if (!send_string_without_coro(fd, "\r\nContent-Type: text/html", MSG_MORE)) + return; + + if (send_buffer_without_coro(fd, lwan_strbuf_get_buffer(&l->headers), + lwan_strbuf_get_length(&l->headers), + MSG_MORE)) { + struct lwan_strbuf buffer; + + lwan_strbuf_init(&buffer); + lwan_fill_default_response(&buffer, status); + + send_buffer_without_coro(fd, lwan_strbuf_get_buffer(&buffer), + lwan_strbuf_get_length(&buffer), 0); + + lwan_strbuf_free(&buffer); + } } static ALWAYS_INLINE bool spawn_coro(struct lwan_connection *conn, @@ -391,26 +423,7 @@ static ALWAYS_INLINE bool spawn_coro(struct lwan_connection *conn, lwan_status_error("Couldn't spawn coroutine for file descriptor %d", fd); - if (!send_string_without_coro(fd, "HTTP/1.0 503 Unavailable")) - goto out; - if (!send_string_without_coro(fd, "\r\nConnection: close")) - goto out; - if (!send_string_without_coro(fd, "\r\nContent-Type: text/html")) - goto out; - if (send_buffer_without_coro(fd, lwan_strbuf_get_buffer(&tq->lwan->headers), - lwan_strbuf_get_length(&tq->lwan->headers))) { - struct lwan_strbuf buffer; - - lwan_strbuf_init(&buffer); - lwan_fill_default_response(&buffer, HTTP_UNAVAILABLE); - - send_buffer_without_coro(fd, lwan_strbuf_get_buffer(&buffer), - lwan_strbuf_get_length(&buffer)); - - lwan_strbuf_free(&buffer); - } - -out: + send_response_without_coro(tq->lwan, fd, HTTP_UNAVAILABLE); shutdown(fd, SHUT_RDWR); close(fd); return false; @@ -502,11 +515,11 @@ static bool accept_waiting_clients(const struct lwan_thread *t) int r = epoll_ctl(conn->thread->epoll_fd, EPOLL_CTL_ADD, fd, &ev); if (UNLIKELY(r < 0)) { - /* FIXME: send a "busy" response here? No coroutine has been - * created at this point to use the usual stuff, though. */ lwan_status_perror("Could not add file descriptor %d to epoll " "set %d. Dropping connection", fd, conn->thread->epoll_fd); + + send_response_without_coro(t->lwan, fd, HTTP_UNAVAILABLE); shutdown(fd, SHUT_RDWR); close(fd); } From b65e4c9dd901cf03dbc95bece8fd96d7991ba413 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 11 Jun 2021 08:40:14 -0700 Subject: [PATCH 1782/2505] Process EAGAIN too in send_buffer_without_coro() --- src/lib/lwan-thread.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index c58cf13f7..18aaf35fd 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -347,6 +347,8 @@ static bool send_buffer_without_coro(int fd, const char *buf, size_t buf_len, in if (sent <= 0) { if (errno == EINTR) continue; + if (errno == EAGAIN) + continue; break; } From e758902550d92c1c1ea6758ad2b22a8e73a6039e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 11 Jun 2021 08:46:16 -0700 Subject: [PATCH 1783/2505] Move some function prototypes to lwan-private.h --- src/lib/liblwan.sym | 5 ----- src/lib/lwan-mod-redirect.c | 2 +- src/lib/lwan-mod-response.c | 2 +- src/lib/lwan-private.h | 15 +++++++++++++++ src/lib/lwan.h | 15 --------------- 5 files changed, 17 insertions(+), 22 deletions(-) diff --git a/src/lib/liblwan.sym b/src/lib/liblwan.sym index ba820b083..039230042 100644 --- a/src/lib/liblwan.sym +++ b/src/lib/liblwan.sym @@ -9,8 +9,6 @@ global: lwan_get_config_path; lwan_get_default_config; - lwan_http_status_as_*; - lwan_init; lwan_init_with_config; lwan_shutdown; @@ -39,9 +37,6 @@ global: lwan_status_perror; lwan_status_warning; - lwan_straitjacket_enforce; - lwan_straitjacket_enforce_from_config; - lwan_tpl_apply; lwan_tpl_apply_with_buffer; lwan_tpl_compile_file; diff --git a/src/lib/lwan-mod-redirect.c b/src/lib/lwan-mod-redirect.c index eb9edd142..8ac6d20b6 100644 --- a/src/lib/lwan-mod-redirect.c +++ b/src/lib/lwan-mod-redirect.c @@ -21,7 +21,7 @@ #include #include -#include "lwan.h" +#include "lwan-private.h" #include "lwan-mod-redirect.h" struct redirect_priv { diff --git a/src/lib/lwan-mod-response.c b/src/lib/lwan-mod-response.c index 7dcf89167..88136b117 100644 --- a/src/lib/lwan-mod-response.c +++ b/src/lib/lwan-mod-response.c @@ -21,7 +21,7 @@ #include #include -#include "lwan.h" +#include "lwan-private.h" #include "lwan-mod-response.h" static enum lwan_http_status diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 0bcaf6b10..fc9ed7f90 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -226,3 +226,18 @@ static ALWAYS_INLINE int lwan_calculate_n_packets(size_t total) long int lwan_getentropy(void *buffer, size_t buffer_len, int flags); uint64_t lwan_random_uint64(); +const char *lwan_http_status_as_string(enum lwan_http_status status) + __attribute__((const)) __attribute__((warn_unused_result)); +const char *lwan_http_status_as_string_with_code(enum lwan_http_status status) + __attribute__((const)) __attribute__((warn_unused_result)); +const char *lwan_http_status_as_descriptive_string(enum lwan_http_status status) + __attribute__((const)) __attribute__((warn_unused_result)); + +int lwan_connection_get_fd(const struct lwan *lwan, + const struct lwan_connection *conn) + __attribute__((pure)) __attribute__((warn_unused_result)); + +int lwan_format_rfc_time(const time_t in, char out LWAN_ARRAY_PARAM(30)); +int lwan_parse_rfc_time(const char in LWAN_ARRAY_PARAM(30), time_t *out); + +void lwan_straitjacket_enforce(const struct lwan_straitjacket *sj); diff --git a/src/lib/lwan.h b/src/lib/lwan.h index e50d2785a..531449b5d 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -521,13 +521,6 @@ bool lwan_response_set_event_stream(struct lwan_request *request, enum lwan_http_status status); void lwan_response_send_event(struct lwan_request *request, const char *event); - -const char *lwan_http_status_as_string(enum lwan_http_status status) - __attribute__((const)) __attribute__((warn_unused_result)); -const char *lwan_http_status_as_string_with_code(enum lwan_http_status status) - __attribute__((const)) __attribute__((warn_unused_result)); -const char *lwan_http_status_as_descriptive_string(enum lwan_http_status status) - __attribute__((const)) __attribute__((warn_unused_result)); const char *lwan_determine_mime_type_for_file_name(const char *file_name) __attribute__((pure)) __attribute__((warn_unused_result)); @@ -535,21 +528,13 @@ void lwan_init(struct lwan *l); void lwan_init_with_config(struct lwan *l, const struct lwan_config *config); void lwan_shutdown(struct lwan *l); -void lwan_straitjacket_enforce(const struct lwan_straitjacket *sj); - const struct lwan_config *lwan_get_default_config(void); -int lwan_connection_get_fd(const struct lwan *lwan, - const struct lwan_connection *conn) - __attribute__((pure)) __attribute__((warn_unused_result)); - const char * lwan_request_get_remote_address(struct lwan_request *request, char buffer LWAN_ARRAY_PARAM(INET6_ADDRSTRLEN)) __attribute__((warn_unused_result)); -int lwan_format_rfc_time(const time_t in, char out LWAN_ARRAY_PARAM(30)); -int lwan_parse_rfc_time(const char in LWAN_ARRAY_PARAM(30), time_t *out); static inline enum lwan_request_flags lwan_request_get_method(const struct lwan_request *request) From 378db055c206d826772a0280c497edb2a7afd848 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 11 Jun 2021 21:05:48 -0700 Subject: [PATCH 1784/2505] Try to fix build on oss-fuzz infra https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=34641 --- src/lib/lwan-thread.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 18aaf35fd..4a9cbadb2 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -94,6 +94,19 @@ static void graceful_close(struct lwan *l, /* close(2) will be called when the coroutine yields with CONN_CORO_ABORT */ } +#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) +static void lwan_random_seed_prng_for_thread(const struct lwan_thread *t) +{ + (void)t; +} + +uint64_t lwan_random_uint64() +{ + static uint64_t value; + + return ATOMIC_INC(value); +} +#else static __thread __uint128_t lehmer64_state; static void lwan_random_seed_prng_for_thread(const struct lwan_thread *t) @@ -112,6 +125,7 @@ uint64_t lwan_random_uint64() lehmer64_state *= 0xda942042e4dd58b5ull; return (uint64_t)(lehmer64_state >> 64); } +#endif __attribute__((noreturn)) static int process_request_coro(struct coro *coro, void *data) From 20a669d33b3a2c26fdbedbc145af70479ea04a37 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 11 Jun 2021 21:47:49 -0700 Subject: [PATCH 1785/2505] If strbuf:set_static() is called, buffer doesn't have fixed size anymore BUFFER_FIXED should only be set if strbuf has been initialized with a fixed size; if the buffer is changed (e.g. by calling the set_static() method), then that flag doesn't make any sense -- the buffer is passed as a constant, and as such it makes no sense for strbuf to try to grow that buffer in any operation. --- src/lib/lwan-strbuf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-strbuf.c b/src/lib/lwan-strbuf.c index c014b3fc1..9b1007f43 100644 --- a/src/lib/lwan-strbuf.c +++ b/src/lib/lwan-strbuf.c @@ -214,7 +214,7 @@ bool lwan_strbuf_set_static(struct lwan_strbuf *s1, const char *s2, size_t sz) s1->buffer = (char *)s2; s1->used = s1->capacity = sz; - s1->flags &= ~BUFFER_MALLOCD; + s1->flags &= ~(BUFFER_MALLOCD | BUFFER_FIXED); return true; } From 1f11c71d47ebe7de3d65c8fecddf10de1a4c3d64 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 11 Jun 2021 21:50:30 -0700 Subject: [PATCH 1786/2505] tm.tm_mday ranges from 1-31, not 2-31 --- src/lib/lwan-time.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-time.c b/src/lib/lwan-time.c index 8830ba65d..debfafb18 100644 --- a/src/lib/lwan-time.c +++ b/src/lib/lwan-time.c @@ -70,7 +70,7 @@ int lwan_parse_rfc_time(const char in[static 30], time_t *out) str += 5; tm.tm_mday = parse_2_digit_num(str, ' ', 31); - if (UNLIKELY(tm.tm_mday <= 1)) + if (UNLIKELY(tm.tm_mday <= 0)) return -EINVAL; str += 3; From 4f5ecdeb75bd2dc1855997dcb52924fc53a71ac8 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Sat, 19 Jun 2021 23:11:40 +0800 Subject: [PATCH 1787/2505] Update TCMalloc repository --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e5c87b60a..7fa7644d4 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,7 @@ with, specify one of the following options to the CMake invocation line: - `-DSANITIZER=thread` selects the Thread Sanitizer. Alternative memory allocators can be selected as well. Lwan currently -supports [TCMalloc](https://github.com/gperftools/gperftools), +supports [TCMalloc](https://github.com/google/tcmalloc), [mimalloc](https://github.com/microsoft/mimalloc), and [jemalloc](http://jemalloc.net/) out of the box. To use either one of them, pass `-DALTERNATIVE_MALLOC=name` to the CMake invocation line, using the From d28f15d304986122fcbf280946bc1436b007a727 Mon Sep 17 00:00:00 2001 From: "Leandro A. F. Pereira" Date: Wed, 1 Sep 2021 08:58:35 -0700 Subject: [PATCH 1788/2505] Fix `make install` after getting rid of `queue.h` Fixes #306. --- src/lib/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index 518ea4807..bfc9321e1 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -158,7 +158,6 @@ install(FILES lwan-template.h lwan-trie.h lwan-strbuf.h - queue.h timeout.h list.h DESTINATION "${CMAKE_INSTALL_FULL_INCLUDEDIR}/lwan") From a341c03b3ef8cca1557c4612f2337c816e01b252 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 2 Sep 2021 08:25:08 -0700 Subject: [PATCH 1789/2505] In debug builds, log time to read request from client socket --- src/lib/lwan-request.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 69604b840..5cdd6a17f 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1451,16 +1451,19 @@ static const char *get_request_method(struct lwan_request *request) static void log_request(struct lwan_request *request, enum lwan_http_status status, - double duration) + double time_to_read_request, + double time_to_process_request) { char ip_buffer[INET6_ADDRSTRLEN]; - lwan_status_debug("%s [%s] %016lx \"%s %s HTTP/%s\" %d %s %.3f ms", + lwan_status_debug("%s [%s] %016lx \"%s %s HTTP/%s\" %d %s (r:%.3fms p:%.3fms)", lwan_request_get_remote_address(request, ip_buffer), request->conn->thread->date.date, request->request_id, get_request_method(request), request->original_url.value, request->flags & REQUEST_IS_HTTP_1_0 ? "1.0" : "1.1", - status, request->response.mime_type, duration); + status, request->response.mime_type, + time_to_read_request, + time_to_process_request); } #else #define log_request(...) @@ -1501,9 +1504,14 @@ void lwan_process_request(struct lwan *l, struct lwan_request *request) enum lwan_http_status status; struct lwan_url_map *url_map; +#ifndef NDEBUG + struct timespec request_read_begin_time = current_precise_monotonic_timespec(); +#endif status = read_request(request); #ifndef NDEBUG + double time_to_read_request = elapsed_time_ms(request_read_begin_time); + struct timespec request_begin_time = current_precise_monotonic_timespec(); #endif if (UNLIKELY(status != HTTP_OK)) { @@ -1545,7 +1553,7 @@ void lwan_process_request(struct lwan *l, struct lwan_request *request) log_and_return: lwan_response(request, status); - log_request(request, status, elapsed_time_ms(request_begin_time)); + log_request(request, status, time_to_read_request, elapsed_time_ms(request_begin_time)); } static inline void * From 2b7dd5baff244f051cf688c754ca73cfd64fd0b5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 2 Sep 2021 08:25:48 -0700 Subject: [PATCH 1790/2505] Get number of CPUs before setting up from config Move the call to get_number_of_cpus() before try_setup_from_config() because straightjacket might be specified in the configuration file and this will block access to /proc and /sys, which will cause get_number_of_cpus() to get incorrect fallback values. --- src/lib/lwan.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 49792902f..bfe15baae 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -713,6 +713,11 @@ void lwan_init_with_config(struct lwan *l, const struct lwan_config *config) lwan_job_thread_init(); lwan_tables_init(); + /* Get the number of CPUs here because straightjacket might be active + * and this will block access to /proc and /sys, which will cause + * get_number_of_cpus() to get incorrect fallback values. */ + get_number_of_cpus(l); + try_setup_from_config(l, config); if (!lwan_strbuf_get_length(&l->headers)) @@ -723,7 +728,6 @@ void lwan_init_with_config(struct lwan *l, const struct lwan_config *config) /* Continue initialization as normal. */ lwan_status_debug("Initializing lwan web server"); - get_number_of_cpus(l); if (!l->config.n_threads) { l->thread.count = l->online_cpus; if (l->thread.count == 1) From 1d663201de8943c46c6c6ce5967960e0cb206aac Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 2 Sep 2021 08:27:11 -0700 Subject: [PATCH 1791/2505] Only use CPU topology to pre-schedule clients if n_cpus > 1 Otherwise, fall back to using a round-robing prescheduler. --- src/lib/lwan-thread.c | 79 +++++++++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 36 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 4a9cbadb2..0d694f1ee 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -863,45 +863,52 @@ void lwan_thread_init(struct lwan *l) if (!l->thread.threads) lwan_status_critical("Could not allocate memory for threads"); + uint32_t *schedtbl; + uint32_t n_threads; + bool adj_affinity; + #ifdef __x86_64__ - static_assert(sizeof(struct lwan_connection) == 32, - "Two connections per cache line"); + if (l->online_cpus > 1) { + static_assert(sizeof(struct lwan_connection) == 32, + "Two connections per cache line"); #ifdef _SC_LEVEL1_DCACHE_LINESIZE - assert(sysconf(_SC_LEVEL1_DCACHE_LINESIZE) == 64); -#endif - - lwan_status_debug("%d CPUs of %d are online. " - "Reading topology to pre-schedule clients", - l->online_cpus, l->available_cpus); - - /* - * Pre-schedule each file descriptor, to reduce some operations in the - * fast path. - * - * Since struct lwan_connection is guaranteed to be 32-byte long, two of - * them can fill up a cache line. Assume siblings share cache lines and - * use the CPU topology to group two connections per cache line in such - * a way that false sharing is avoided. - */ - uint32_t n_threads = - (uint32_t)lwan_nextpow2((size_t)((l->thread.count - 1) * 2)); - uint32_t *schedtbl = alloca(n_threads * sizeof(uint32_t)); - - bool adj_affinity = topology_to_schedtbl(l, schedtbl, n_threads); - - n_threads--; /* Transform count into mask for AND below */ - - for (unsigned int i = 0; i < total_conns; i++) - l->conns[i].thread = &l->thread.threads[schedtbl[i & n_threads]]; -#else - for (unsigned int i = 0; i < l->thread.count; i++) - l->thread.threads[i].cpu = i % l->online_cpus; - for (unsigned int i = 0; i < total_conns; i++) - l->conns[i].thread = &l->thread.threads[i % l->thread.count]; - - uint32_t *schedtbl = NULL; - const bool adj_affinity = false; + assert(sysconf(_SC_LEVEL1_DCACHE_LINESIZE) == 64); #endif + lwan_status_debug("%d CPUs of %d are online. " + "Reading topology to pre-schedule clients", + l->online_cpus, l->available_cpus); + /* + * Pre-schedule each file descriptor, to reduce some operations in the + * fast path. + * + * Since struct lwan_connection is guaranteed to be 32-byte long, two of + * them can fill up a cache line. Assume siblings share cache lines and + * use the CPU topology to group two connections per cache line in such + * a way that false sharing is avoided. + */ + n_threads = (uint32_t)lwan_nextpow2((size_t)((l->thread.count - 1) * 2)); + schedtbl = alloca(n_threads * sizeof(uint32_t)); + + adj_affinity = topology_to_schedtbl(l, schedtbl, n_threads); + + n_threads--; /* Transform count into mask for AND below */ + + for (unsigned int i = 0; i < total_conns; i++) + l->conns[i].thread = &l->thread.threads[schedtbl[i & n_threads]]; + } else +#endif /* __x86_64__ */ + { + lwan_status_debug("Using round-robin to preschedule clients"); + + for (unsigned int i = 0; i < l->thread.count; i++) + l->thread.threads[i].cpu = i % l->online_cpus; + for (unsigned int i = 0; i < total_conns; i++) + l->conns[i].thread = &l->thread.threads[i % l->thread.count]; + + schedtbl = NULL; + adj_affinity = false; + n_threads = l->thread.count; + } for (unsigned int i = 0; i < l->thread.count; i++) { struct lwan_thread *thread = NULL; From b1a93624e1efa0dda39e09b094d4014ad90df6e1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 2 Sep 2021 08:28:13 -0700 Subject: [PATCH 1792/2505] Send a call to alert() if using freegeoip with JSONP --- src/samples/freegeoip/freegeoip.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/samples/freegeoip/freegeoip.c b/src/samples/freegeoip/freegeoip.c index 0879002c7..f361d326e 100644 --- a/src/samples/freegeoip/freegeoip.c +++ b/src/samples/freegeoip/freegeoip.c @@ -71,7 +71,7 @@ static const struct lwan_var_descriptor template_descriptor[] = { }; static const char json_template_str[] = - "{{callback?}}{{callback}}({{/callback?}}" + "{{callback?}}alert('Using JSONP, especially with a third-party service, is insecure. Stop using this ASAP.');{{callback}}({{/callback?}}" "{" "\"country_code\":\"{{country.code}}\"," "\"country_name\":\"{{country.name}}\"," From ccb824c49621683edaa957dfcc29d940a93f39b1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 12 Sep 2021 07:24:58 -0700 Subject: [PATCH 1793/2505] Hoist call to conn_flags_to_epoll_events() outside the loop --- src/lib/lwan-thread.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 0d694f1ee..186b7894c 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -516,6 +516,7 @@ turn_timer_wheel(struct timeout_queue *tq, struct lwan_thread *t, int epoll_fd) static bool accept_waiting_clients(const struct lwan_thread *t) { + const uint32_t read_events = conn_flags_to_epoll_events(CONN_EVENTS_READ); const struct lwan_connection *conns = t->lwan->conns; while (true) { @@ -524,10 +525,7 @@ static bool accept_waiting_clients(const struct lwan_thread *t) if (LIKELY(fd >= 0)) { const struct lwan_connection *conn = &conns[fd]; - struct epoll_event ev = { - .data.ptr = (void *)conn, - .events = conn_flags_to_epoll_events(CONN_EVENTS_READ), - }; + struct epoll_event ev = {.data.ptr = (void *)conn, .events = read_events}; int r = epoll_ctl(conn->thread->epoll_fd, EPOLL_CTL_ADD, fd, &ev); if (UNLIKELY(r < 0)) { From 7c65a6d03ba468697d655b0b83f88133791e170b Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Sun, 12 Sep 2021 13:32:18 +0800 Subject: [PATCH 1794/2505] Suppress warnings about ignored returned value Both readahead and madvise are hints. The failling to write is considered as non-error. Use gcc pragma to suppress the warnings. --- src/lib/lwan-readahead.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/lib/lwan-readahead.c b/src/lib/lwan-readahead.c index a9414c43a..98cb76ae7 100644 --- a/src/lib/lwan-readahead.c +++ b/src/lib/lwan-readahead.c @@ -81,7 +81,10 @@ void lwan_readahead_shutdown(void) lwan_status_debug("Shutting down readahead thread"); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-result" write(readahead_pipe_fd[1], &cmd, sizeof(cmd)); +#pragma GCC diagnostic pop pthread_join(readahead_self, NULL); close(readahead_pipe_fd[0]); @@ -100,7 +103,10 @@ void lwan_readahead_queue(int fd, off_t off, size_t size) }; /* Readahead is just a hint. Failing to write is not an error. */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-result" write(readahead_pipe_fd[1], &cmd, sizeof(cmd)); +#pragma GCC diagnostic pop } void lwan_madvise_queue(void *addr, size_t length) @@ -114,7 +120,10 @@ void lwan_madvise_queue(void *addr, size_t length) }; /* Madvise is just a hint. Failing to write is not an error. */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-result" write(readahead_pipe_fd[1], &cmd, sizeof(cmd)); +#pragma GCC diagnostic pop } static void *lwan_readahead_loop(void *data __attribute__((unused))) From 6c63fe6d15c15fcd62bce46aa201c2ed95341d36 Mon Sep 17 00:00:00 2001 From: Jacky_Yin Date: Mon, 13 Sep 2021 10:16:30 +0800 Subject: [PATCH 1795/2505] fix: check alignment of posix_memalign According to documentation of posix_memalign: Alignment must be a power of two and a multiple of sizeof(void *). --- src/lib/lwan-private.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index fc9ed7f90..a42448290 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -208,6 +208,7 @@ lwan_aligned_alloc(size_t n, size_t alignment) void *ret; assert((alignment & (alignment - 1)) == 0); + assert((alignment % (sizeof(void *)) == 0); n = (n + alignment - 1) & ~(alignment - 1); if (UNLIKELY(posix_memalign(&ret, alignment, n))) From 97b139e0e7d4f9d531f5aae5fc44faa2e1d86217 Mon Sep 17 00:00:00 2001 From: Jacky_Yin Date: Mon, 13 Sep 2021 11:29:50 +0800 Subject: [PATCH 1796/2505] fix: missing right parenthesis --- src/lib/lwan-private.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index a42448290..a727d8bb8 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -208,7 +208,7 @@ lwan_aligned_alloc(size_t n, size_t alignment) void *ret; assert((alignment & (alignment - 1)) == 0); - assert((alignment % (sizeof(void *)) == 0); + assert((alignment % (sizeof(void *))) == 0); n = (n + alignment - 1) & ~(alignment - 1); if (UNLIKELY(posix_memalign(&ret, alignment, n))) From 0f977ee5b873de57f65cf1a0deb1687f33df11db Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 15 Sep 2021 20:01:14 -0700 Subject: [PATCH 1797/2505] Add conditional rewriting supports to mod_rewrite This adds a new "condition" section to patterns, where one can specify conditions for a match to be expanded. Currently, one can only match on cookies, but more can be added in the future. --- README.md | 23 ++++++++ src/bin/testrunner/testrunner.conf | 10 ++++ src/lib/lwan-mod-rewrite.c | 86 +++++++++++++++++++++++++++++- 3 files changed, 118 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7fa7644d4..7b2775875 100644 --- a/README.md +++ b/README.md @@ -515,6 +515,29 @@ every pattern. `redirect_to` and `rewrite_as` options are mutually exclusive, and one of them must be specified at least. +It's also possible to specify conditions to trigger a rewrite. To specify one, +open a `condition` block, specify the condition type, and then the parameters +for that condition to be evaluated: + +|Condition|Parameters| +|---------|----------| +|`cookie` | A single `key` = `value`| + +For example, if one wants to send `site-dark-mode.css` if there is a `style` cookie +with the value `dark`, and send `site-light-mode.css` otherwise, one can write: + +``` +rewrite ... { + pattern site.css { + rewrite as = /site-dark-mode.css + condition cookie { style = dark } + } + pattern site.css { + rewrite as = /site-light-mode.css + } +} +``` + #### Redirect The `redirect` module will, as it says in the tin, generate a `301 diff --git a/src/bin/testrunner/testrunner.conf b/src/bin/testrunner/testrunner.conf index d77b0fc73..b6b1336fc 100644 --- a/src/bin/testrunner/testrunner.conf +++ b/src/bin/testrunner/testrunner.conf @@ -58,6 +58,16 @@ listener *:8080 { pattern user { rewrite as = /hello?name=${USER} } } + rewrite /css/ { + pattern test.css { + condition cookie { style = dark } + rewrite as = /hello?name=dark + } + pattern test.css { + rewrite as = /hello?name=light + } + } + response /brew-coffee { code = 418 } &hello_world /admin { diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index b9aca70e9..1c4f2fed8 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -46,11 +46,20 @@ enum pattern_flag { PATTERN_EXPAND_LWAN = 1<<2, PATTERN_EXPAND_LUA = 1<<3, PATTERN_EXPAND_MASK = PATTERN_EXPAND_LWAN | PATTERN_EXPAND_LUA, + + PATTERN_COND_COOKIE = 1<<4, + PATTERN_COND_MASK = PATTERN_COND_COOKIE, }; struct pattern { char *pattern; char *expand_pattern; + struct { + struct { + char *key; + char *value; + } cookie; + } condition; enum pattern_flag flags; }; @@ -257,6 +266,20 @@ rewrite_handle_request(struct lwan_request *request, if (captures <= 0) continue; + if (p->flags & PATTERN_COND_COOKIE) { + assert(p->condition.cookie.key); + assert(p->condition.cookie.value); + + const char *cookie_val = + lwan_request_get_cookie(request, p->condition.cookie.key); + + if (!cookie_val) + continue; + + if (!streq(cookie_val, p->condition.cookie.value)) + continue; + } + switch (p->flags & PATTERN_EXPAND_MASK) { #ifdef HAVE_LUA case PATTERN_EXPAND_LUA: @@ -304,6 +327,10 @@ static void rewrite_destroy(void *instance) LWAN_ARRAY_FOREACH(&pd->patterns, iter) { free(iter->pattern); free(iter->expand_pattern); + if (iter->flags & PATTERN_COND_COOKIE) { + free(iter->condition.cookie.key); + free(iter->condition.cookie.value); + } } pattern_array_reset(&pd->patterns); @@ -317,6 +344,59 @@ static void *rewrite_create_from_hash(const char *prefix, return rewrite_create(prefix, NULL); } +static void parse_condition(struct pattern *pattern, + struct config *config, + const struct config_line *line) +{ + char *cookie_key = NULL, *cookie_value = NULL; + + if (!streq(line->value, "cookie")) { + config_error(config, "Condition `%s' not supported", line->value); + return; + } + + while ((line = config_read_line(config))) { + switch (line->type) { + case CONFIG_LINE_TYPE_SECTION: + config_error(config, "Unexpected section: %s", line->key); + return; + + case CONFIG_LINE_TYPE_SECTION_END: + if (!cookie_key || !cookie_value) { + config_error(config, "Cookie key/value has not been specified"); + return; + } + + pattern->flags |= PATTERN_COND_COOKIE; + pattern->condition.cookie.key = cookie_key; + pattern->condition.cookie.value = cookie_value; + return; + + case CONFIG_LINE_TYPE_LINE: + if (cookie_key || cookie_value) { + config_error(config, "Can only condition on a single cookie. Currently has: %s=%s", cookie_key, cookie_value); + free(cookie_key); + free(cookie_value); + return; + } + + cookie_key = strdup(line->key); + if (!cookie_key) { + config_error(config, "Could not copy cookie key while parsing condition"); + return; + } + + cookie_value = strdup(line->value); + if (!cookie_value) { + free(cookie_key); + config_error(config, "Could not copy cookie value while parsing condition"); + return; + } + break; + } + } +} + static bool rewrite_parse_conf_pattern(struct private_data *pd, struct config *config, const struct config_line *line) @@ -356,7 +436,11 @@ static bool rewrite_parse_conf_pattern(struct private_data *pd, } break; case CONFIG_LINE_TYPE_SECTION: - config_error(config, "Unexpected section: %s", line->key); + if (streq(line->key, "condition")) { + parse_condition(pattern, config, line); + } else { + config_error(config, "Unexpected section: %s", line->key); + } break; case CONFIG_LINE_TYPE_SECTION_END: if (redirect_to && rewrite_as) { From b2dc3cafda9d71a6843f999ec52907b9cda4765b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 15 Sep 2021 20:02:53 -0700 Subject: [PATCH 1798/2505] Do not send Expires headers if request has query string Should avoid responses being cached by reverse-proxies, for instance. --- src/lib/lwan-response.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index e29f0eded..d54937001 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -234,6 +234,14 @@ has_uncommon_response_headers(enum lwan_request_flags v) return v & (RESPONSE_INCLUDE_REQUEST_ID | REQUEST_ALLOW_CORS | RESPONSE_CHUNKED_ENCODING); } +static bool +has_query_string(const struct lwan_request *request) +{ + /* FIXME: new flag to make this check cheaper? */ + const struct lwan_value *query_string = &request->helper->query_string; + return query_string->value && query_string->len; +} + size_t lwan_prepare_response_header_full( struct lwan_request *request, enum lwan_http_status status, @@ -328,7 +336,7 @@ size_t lwan_prepare_response_header_full( APPEND_STRING(request->response.mime_type); } - if (!expires_override) { + if (!expires_override || !has_query_string(request)) { APPEND_CONSTANT("\r\nExpires: "); APPEND_STRING_LEN(request->conn->thread->date.expires, 29); } From 65105674d9c041277231524d9b166629cd7145c9 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 15 Sep 2021 20:03:39 -0700 Subject: [PATCH 1799/2505] Remove unused HAVE_RAWMEMCHR from lwan-build-config.h --- src/cmake/lwan-build-config.h.cmake | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cmake/lwan-build-config.h.cmake b/src/cmake/lwan-build-config.h.cmake index 1af127ffa..50494cbe3 100644 --- a/src/cmake/lwan-build-config.h.cmake +++ b/src/cmake/lwan-build-config.h.cmake @@ -30,7 +30,6 @@ #cmakedefine HAVE_MKOSTEMP #cmakedefine HAVE_PIPE2 #cmakedefine HAVE_PTHREADBARRIER -#cmakedefine HAVE_RAWMEMCHR #cmakedefine HAVE_READAHEAD #cmakedefine HAVE_REALLOCARRAY #cmakedefine HAVE_EPOLL From d380d57ef95c6b8d9ed68c270ebdbd83326563b8 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 15 Sep 2021 21:03:54 -0700 Subject: [PATCH 1800/2505] Improve check for "this request has query string" --- src/lib/lwan-request.c | 1 + src/lib/lwan-response.c | 9 +++------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 5cdd6a17f..1819bd87e 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -431,6 +431,7 @@ static void parse_fragment_and_query(struct lwan_request *request, helper->query_string.value = query_string + 1; helper->query_string.len = (size_t)(space - query_string - 1); request->url.len -= helper->query_string.len + 1; + request->flags |= REQUEST_HAS_QUERY_STRING; } } diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index d54937001..c2c0e03f2 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -234,12 +234,9 @@ has_uncommon_response_headers(enum lwan_request_flags v) return v & (RESPONSE_INCLUDE_REQUEST_ID | REQUEST_ALLOW_CORS | RESPONSE_CHUNKED_ENCODING); } -static bool -has_query_string(const struct lwan_request *request) +static ALWAYS_INLINE bool has_query_string(enum lwan_request_flags v) { - /* FIXME: new flag to make this check cheaper? */ - const struct lwan_value *query_string = &request->helper->query_string; - return query_string->value && query_string->len; + return v & REQUEST_HAS_QUERY_STRING; } size_t lwan_prepare_response_header_full( @@ -336,7 +333,7 @@ size_t lwan_prepare_response_header_full( APPEND_STRING(request->response.mime_type); } - if (!expires_override || !has_query_string(request)) { + if (!expires_override || !has_query_string(request_flags)) { APPEND_CONSTANT("\r\nExpires: "); APPEND_STRING_LEN(request->conn->thread->date.expires, 29); } From 00aa37a349c74f8a2d8ff5b316caa91cbc3fabe2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 15 Sep 2021 21:06:53 -0700 Subject: [PATCH 1801/2505] Future-proof read_body_data() and test all supported methods --- src/lib/lwan-request.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 1819bd87e..545808d20 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1198,12 +1198,17 @@ static int read_body_data(struct lwan_request *request) bool allow_temp_file; char *new_buffer; - if (lwan_request_get_method(request) == REQUEST_METHOD_POST) { + switch (lwan_request_get_method(request)) { + case REQUEST_METHOD_POST: allow_temp_file = config->allow_post_temp_file; max_data_size = config->max_post_data_size; - } else { + break; + case REQUEST_METHOD_PUT: allow_temp_file = config->allow_put_temp_file; max_data_size = config->max_put_data_size; + break; + default: + return -HTTP_NOT_ALLOWED; } status = From d1b0bdda655f586444de48f287c70174b406a938 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 15 Sep 2021 21:14:45 -0700 Subject: [PATCH 1802/2505] Fix build after improving check for "has query string" --- src/lib/lwan.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 531449b5d..b642ee444 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -254,6 +254,8 @@ enum lwan_request_flags { REQUEST_PARSED_ACCEPT_ENCODING = 1 << 23, RESPONSE_INCLUDE_REQUEST_ID = 1 << 24, + + REQUEST_HAS_QUERY_STRING = 1 << 25, }; #undef SELECT_MASK From e62c9960e5bd21ad56d4ec12115fe815d083192e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 15 Sep 2021 21:15:21 -0700 Subject: [PATCH 1803/2505] Add conditional rewriting tests --- src/scripts/testsuite.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index 490ad2744..7bfa6daa7 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -449,6 +449,24 @@ def test_redirect_307(self): self.assertEqual(r.headers['location'], 'http://lwan.ws/') class TestRewrite(LwanTest): + def test_conditional_rewrite_with_cookie(self): + for key in ('style', 'something-else'): + for value in ('dark', 'dork', '', None): + r = requests.get('http://localhost:8080/css/test.css', cookies={key: value}) + + self.assertResponsePlain(r, 200) + + if (key, value) == ('style', 'dark'): + self.assertEqual(r.text, 'Hello, %s!' % value) + else: + self.assertNotEqual(r.text, 'Hello, %s!' % value) + + def test_conditional_rewrite_without_cookie(self): + r = requests.get('http://localhost:8080/css/test.css') + + self.assertResponsePlain(r, 200) + self.assertEqual(r.text, 'Hello, light!') + def test_pattern_redirect_to(self): r = requests.get('http://127.0.0.1:8080/pattern/foo/1234x5678', allow_redirects=False) From 4827a66e3257d3b5dcb726057a2cf7f50384dd46 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 15 Sep 2021 22:42:46 -0700 Subject: [PATCH 1804/2505] Simplify even further the test to elide the Expires header --- src/lib/lwan-response.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index c2c0e03f2..6c9d9117f 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -234,11 +234,6 @@ has_uncommon_response_headers(enum lwan_request_flags v) return v & (RESPONSE_INCLUDE_REQUEST_ID | REQUEST_ALLOW_CORS | RESPONSE_CHUNKED_ENCODING); } -static ALWAYS_INLINE bool has_query_string(enum lwan_request_flags v) -{ - return v & REQUEST_HAS_QUERY_STRING; -} - size_t lwan_prepare_response_header_full( struct lwan_request *request, enum lwan_http_status status, @@ -254,7 +249,7 @@ size_t lwan_prepare_response_header_full( char buffer[INT_TO_STR_BUFFER_SIZE]; const enum lwan_request_flags request_flags = request->flags; const enum lwan_connection_flags conn_flags = request->conn->flags; - bool expires_override = !!(request->flags & RESPONSE_NO_EXPIRES); + bool expires_override = !!(request->flags & (RESPONSE_NO_EXPIRES | REQUEST_HAS_QUERY_STRING)); assert(request->global_response_headers); @@ -333,7 +328,7 @@ size_t lwan_prepare_response_header_full( APPEND_STRING(request->response.mime_type); } - if (!expires_override || !has_query_string(request_flags)) { + if (!expires_override) { APPEND_CONSTANT("\r\nExpires: "); APPEND_STRING_LEN(request->conn->thread->date.expires, 29); } From 8be7c2571628fddd7fa910247d9c2929181c2019 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 16 Sep 2021 07:28:41 -0700 Subject: [PATCH 1805/2505] Ensure conditional rewrite tests check for expected output --- src/scripts/testsuite.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index 7bfa6daa7..01d5e02b9 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -457,9 +457,9 @@ def test_conditional_rewrite_with_cookie(self): self.assertResponsePlain(r, 200) if (key, value) == ('style', 'dark'): - self.assertEqual(r.text, 'Hello, %s!' % value) + self.assertEqual(r.text, 'Hello, dark!') else: - self.assertNotEqual(r.text, 'Hello, %s!' % value) + self.assertEqual(r.text, 'Hello, light!') def test_conditional_rewrite_without_cookie(self): r = requests.get('http://localhost:8080/css/test.css') From 673c4e1068704d24e81ab546bb8ffe8401723731 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 16 Sep 2021 07:29:09 -0700 Subject: [PATCH 1806/2505] Check for empty cookie keys when running conditional rewrite tests --- src/scripts/testsuite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index 01d5e02b9..0f6b1f2c6 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -450,7 +450,7 @@ def test_redirect_307(self): class TestRewrite(LwanTest): def test_conditional_rewrite_with_cookie(self): - for key in ('style', 'something-else'): + for key in ('style', 'something-else', ''): for value in ('dark', 'dork', '', None): r = requests.get('http://localhost:8080/css/test.css', cookies={key: value}) From c10e3192512ccc714a2298161ad87772b11531d5 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 16 Sep 2021 21:00:51 -0700 Subject: [PATCH 1807/2505] Add more condition types to mod_rewrite --- README.md | 12 +- src/bin/testrunner/testrunner.conf | 14 ++ src/lib/lwan-mod-rewrite.c | 368 +++++++++++++++++++++++++---- 3 files changed, 341 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index 7b2775875..50d79baea 100644 --- a/README.md +++ b/README.md @@ -519,9 +519,15 @@ It's also possible to specify conditions to trigger a rewrite. To specify one, open a `condition` block, specify the condition type, and then the parameters for that condition to be evaluated: -|Condition|Parameters| -|---------|----------| -|`cookie` | A single `key` = `value`| +|Condition|Parameters|Description| +|---------|----------|-----------| +|`cookie` | A single `key` = `value`| Checks if request has cookie `key` has value `value` | +|`query` | A single `key` = `value`| Checks if request has query variable `key` has value `value` | +|`post` | A single `key` = `value`| Checks if request has post data `key` has value `value` | +|`header` | A single `key` = `value`| Checks if request header `key` has value `value` | +|`environment` | A single `key` = `value`| Checks if environment variable `key` has value `value` | +|`stat` | `path`, `is_dir`, `is_file` | Checks if `path` exists in the filesystem, and optionally checks if `is_dir` or `is_file` | +|`lua` | `script` | Runs Lua function `matches(req)` inside `script` and checks if it returns `true` or `false` | For example, if one wants to send `site-dark-mode.css` if there is a `style` cookie with the value `dark`, and send `site-light-mode.css` otherwise, one can write: diff --git a/src/bin/testrunner/testrunner.conf b/src/bin/testrunner/testrunner.conf index b6b1336fc..52973adb8 100644 --- a/src/bin/testrunner/testrunner.conf +++ b/src/bin/testrunner/testrunner.conf @@ -63,6 +63,20 @@ listener *:8080 { condition cookie { style = dark } rewrite as = /hello?name=dark } + pattern test.css { + condition environment { USER = leandro} + condition stat { + path = /tmp/maoe.txt + is_file = true + } + condition lua { + script = '''function matches(req) + return false + end + ''' + } + rewrite as = /hello?name=maoe + } pattern test.css { rewrite as = /hello?name=light } diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index 1c4f2fed8..021fa6fb8 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -23,6 +23,7 @@ #include #include #include +#include #include "lwan-private.h" @@ -39,26 +40,42 @@ #endif enum pattern_flag { - PATTERN_HANDLE_REWRITE = 1<<0, - PATTERN_HANDLE_REDIRECT = 1<<1, + PATTERN_HANDLE_REWRITE = 1 << 0, + PATTERN_HANDLE_REDIRECT = 1 << 1, PATTERN_HANDLE_MASK = PATTERN_HANDLE_REWRITE | PATTERN_HANDLE_REDIRECT, - PATTERN_EXPAND_LWAN = 1<<2, - PATTERN_EXPAND_LUA = 1<<3, + PATTERN_EXPAND_LWAN = 1 << 2, + PATTERN_EXPAND_LUA = 1 << 3, PATTERN_EXPAND_MASK = PATTERN_EXPAND_LWAN | PATTERN_EXPAND_LUA, - PATTERN_COND_COOKIE = 1<<4, - PATTERN_COND_MASK = PATTERN_COND_COOKIE, + PATTERN_COND_COOKIE = 1 << 4, + PATTERN_COND_ENV_VAR = 1 << 5, + PATTERN_COND_STAT = 1 << 6, + PATTERN_COND_QUERY_VAR = 1 << 7, + PATTERN_COND_POST_VAR = 1 << 8, + PATTERN_COND_HEADER = 1 << 9, + PATTERN_COND_LUA = 1 << 10, + PATTERN_COND_MASK = PATTERN_COND_COOKIE | PATTERN_COND_ENV_VAR | + PATTERN_COND_STAT | PATTERN_COND_QUERY_VAR | + PATTERN_COND_POST_VAR | PATTERN_COND_HEADER | + PATTERN_COND_LUA, }; struct pattern { char *pattern; char *expand_pattern; struct { + struct lwan_key_value cookie, env_var, query_var, post_var, header; struct { - char *key; - char *value; - } cookie; + char *path; + unsigned int has_is_file : 1; + unsigned int has_is_dir : 1; + unsigned int is_file : 1; + unsigned int is_dir : 1; + } stat; + struct { + char *script; + } lua; } condition; enum pattern_flag flags; }; @@ -246,6 +263,102 @@ static const char *expand_lua(struct lwan_request *request, } #endif +static bool condition_matches(struct lwan_request *request, + const struct pattern *p) +{ + if (LIKELY(!(p->flags & PATTERN_COND_MASK))) + return true; + + if (p->flags & PATTERN_COND_COOKIE) { + assert(p->condition.cookie.key); + assert(p->condition.cookie.value); + + const char *cookie = + lwan_request_get_cookie(request, p->condition.cookie.key); + if (!cookie || !streq(cookie, p->condition.cookie.value)) + return false; + } + + if (p->flags & PATTERN_COND_ENV_VAR) { + assert(p->condition.env_var.key); + assert(p->condition.env_var.value); + + const char *env_var = secure_getenv(p->condition.env_var.key); + if (!env_var || !streq(env_var, p->condition.env_var.value)) + return false; + } + + if (p->flags & PATTERN_COND_QUERY_VAR) { + assert(p->condition.query_var.key); + assert(p->condition.query_var.value); + + const char *query = + lwan_request_get_query_param(request, p->condition.query_var.key); + if (!query || !streq(query, p->condition.query_var.value)) + return false; + } + + if (p->flags & PATTERN_COND_QUERY_VAR) { + assert(p->condition.post_var.key); + assert(p->condition.post_var.value); + + const char *post = + lwan_request_get_post_param(request, p->condition.post_var.key); + if (!post || !streq(post, p->condition.post_var.value)) + return false; + } + + if (p->flags & PATTERN_COND_STAT) { + assert(p->condition.stat.path); + + struct stat st; + + if (stat(p->condition.stat.path, &st) < 0) + return false; + if (p->condition.stat.has_is_file && + p->condition.stat.is_file != !!S_ISREG(st.st_mode)) { + return false; + } + if (p->condition.stat.has_is_dir && + p->condition.stat.is_dir != !!S_ISDIR(st.st_mode)) { + return false; + } + } + +#ifdef HAVE_LUA + if (p->flags & PATTERN_COND_LUA) { + assert(p->condition.lua.script); + + lua_State *L = lwan_lua_create_state(NULL, p->condition.lua.script); + if (!L) + return false; + coro_defer(request->conn->coro, lua_close_defer, L); + + lua_getglobal(L, "matches"); + if (!lua_isfunction(L, -1)) { + lwan_status_error( + "Could not obtain reference to `matches()` function: %s", + lwan_lua_state_last_error(L)); + return false; + } + + lwan_lua_state_push_request(L, request); + + if (lua_pcall(L, 1, 1, 0) != 0) { + lwan_status_error("Could not execute `matches()` function: %s", + lwan_lua_state_last_error(L)); + return false; + } + + if (!lua_toboolean(L, -1)) + return false; + } +#else + assert(!(p->flags & PATTERN_COND_LUA)); +#endif + + return true; +} static enum lwan_http_status rewrite_handle_request(struct lwan_request *request, struct lwan_response *response __attribute__((unused)), @@ -266,19 +379,8 @@ rewrite_handle_request(struct lwan_request *request, if (captures <= 0) continue; - if (p->flags & PATTERN_COND_COOKIE) { - assert(p->condition.cookie.key); - assert(p->condition.cookie.value); - - const char *cookie_val = - lwan_request_get_cookie(request, p->condition.cookie.key); - - if (!cookie_val) - continue; - - if (!streq(cookie_val, p->condition.cookie.value)) - continue; - } + if (!condition_matches(request, p)) + continue; switch (p->flags & PATTERN_EXPAND_MASK) { #ifdef HAVE_LUA @@ -331,6 +433,30 @@ static void rewrite_destroy(void *instance) free(iter->condition.cookie.key); free(iter->condition.cookie.value); } + if (iter->flags & PATTERN_COND_ENV_VAR) { + free(iter->condition.env_var.key); + free(iter->condition.env_var.value); + } + if (iter->flags & PATTERN_COND_QUERY_VAR) { + free(iter->condition.query_var.key); + free(iter->condition.query_var.value); + } + if (iter->flags & PATTERN_COND_POST_VAR) { + free(iter->condition.post_var.key); + free(iter->condition.post_var.value); + } + if (iter->flags & PATTERN_COND_HEADER) { + free(iter->condition.header.key); + free(iter->condition.header.value); + } + if (iter->flags & PATTERN_COND_STAT) { + free(iter->condition.stat.path); + } +#ifdef HAVE_LUA + if (iter->flags & PATTERN_COND_LUA) { + free(iter->condition.lua.script); + } +#endif } pattern_array_reset(&pd->patterns); @@ -344,59 +470,201 @@ static void *rewrite_create_from_hash(const char *prefix, return rewrite_create(prefix, NULL); } -static void parse_condition(struct pattern *pattern, - struct config *config, - const struct config_line *line) +static void parse_condition_key_value(struct pattern *pattern, + struct lwan_key_value *key_value, + enum pattern_flag condition_type, + struct config *config, + const struct config_line *line) { - char *cookie_key = NULL, *cookie_value = NULL; + char *key = NULL, *value = NULL; + + while ((line = config_read_line(config))) { + switch (line->type) { + case CONFIG_LINE_TYPE_SECTION: + config_error(config, "Unexpected section: %s", line->key); + goto out; + + case CONFIG_LINE_TYPE_SECTION_END: + if (!key || !value) { + config_error(config, "Key/value has not been specified"); + goto out; + } + + *key_value = (struct lwan_key_value){key, value}; + pattern->flags |= condition_type & PATTERN_COND_MASK; + return; + + case CONFIG_LINE_TYPE_LINE: + if (key || value) { + config_error(config, + "Can only condition on a single key/value pair. " + "Currently has: %s=%s", + key, value); + goto out; + } - if (!streq(line->value, "cookie")) { - config_error(config, "Condition `%s' not supported", line->value); - return; + key = strdup(line->key); + if (!key) { + config_error(config, + "Could not copy key while parsing condition"); + goto out; + } + + value = strdup(line->value); + if (!value) { + free(key); + config_error(config, + "Could not copy value while parsing condition"); + goto out; + } + break; + } } +out: + free(key); + free(value); +} + +static void parse_condition_stat(struct pattern *pattern, + struct config *config, + const struct config_line *line) +{ + char *path = NULL, *is_dir = NULL, *is_file = NULL; + while ((line = config_read_line(config))) { switch (line->type) { case CONFIG_LINE_TYPE_SECTION: config_error(config, "Unexpected section: %s", line->key); - return; + goto out; case CONFIG_LINE_TYPE_SECTION_END: - if (!cookie_key || !cookie_value) { - config_error(config, "Cookie key/value has not been specified"); - return; + if (!path) { + config_error(config, "Path not specified"); + goto out; } - pattern->flags |= PATTERN_COND_COOKIE; - pattern->condition.cookie.key = cookie_key; - pattern->condition.cookie.value = cookie_value; + pattern->condition.stat.path = path; + pattern->condition.stat.is_dir = parse_bool(is_dir, false); + pattern->condition.stat.is_file = parse_bool(is_file, false); + pattern->condition.stat.has_is_dir = is_dir != NULL; + pattern->condition.stat.has_is_file = is_file != NULL; + pattern->flags |= PATTERN_COND_STAT; return; case CONFIG_LINE_TYPE_LINE: - if (cookie_key || cookie_value) { - config_error(config, "Can only condition on a single cookie. Currently has: %s=%s", cookie_key, cookie_value); - free(cookie_key); - free(cookie_value); - return; + if (streq(line->key, "path")) { + if (path) { + config_error(config, "Path `%s` already specified", path); + goto out; + } + path = strdup(line->value); + if (!path) { + config_error(config, "Could not copy path"); + goto out; + } + } else if (streq(line->key, "is_dir")) { + is_dir = line->value; + } else if (streq(line->key, "is_file")) { + is_file = line->value; + } else { + config_error(config, "Unexpected key: %s", line->key); + goto out; } - cookie_key = strdup(line->key); - if (!cookie_key) { - config_error(config, "Could not copy cookie key while parsing condition"); - return; + break; + } + } + +out: + free(path); +} + +#ifdef HAVE_LUA +static void parse_condition_lua(struct pattern *pattern, + struct config *config, + const struct config_line *line) +{ + char *script = NULL; + + while ((line = config_read_line(config))) { + switch (line->type) { + case CONFIG_LINE_TYPE_SECTION: + config_error(config, "Unexpected section: %s", line->key); + goto out; + + case CONFIG_LINE_TYPE_SECTION_END: + if (!script) { + config_error(config, "Script not specified"); + goto out; } - cookie_value = strdup(line->value); - if (!cookie_value) { - free(cookie_key); - config_error(config, "Could not copy cookie value while parsing condition"); - return; + pattern->condition.lua.script = script; + pattern->flags |= PATTERN_COND_LUA; + return; + + case CONFIG_LINE_TYPE_LINE: + if (streq(line->key, "script")) { + if (script) { + config_error(config, "Script already specified"); + goto out; + } + script = strdup(line->value); + if (!script) { + config_error(config, "Could not copy script"); + goto out; + } + } else { + config_error(config, "Unexpected key: %s", line->key); + goto out; } + break; } } + +out: + free(script); } +#endif +static void parse_condition(struct pattern *pattern, + struct config *config, + const struct config_line *line) +{ + if (streq(line->value, "cookie")) { + return parse_condition_key_value(pattern, &pattern->condition.cookie, + PATTERN_COND_COOKIE, config, line); + } + if (streq(line->value, "query")) { + return parse_condition_key_value(pattern, + &pattern->condition.query_var, + PATTERN_COND_QUERY_VAR, config, line); + } + if (streq(line->value, "post")) { + return parse_condition_key_value(pattern, &pattern->condition.post_var, + PATTERN_COND_POST_VAR, config, line); + } + if (streq(line->value, "environment")) { + return parse_condition_key_value(pattern, + &pattern->condition.env_var, + PATTERN_COND_ENV_VAR, config, line); + } + if (streq(line->value, "header")) { + return parse_condition_key_value(pattern, &pattern->condition.header, + PATTERN_COND_HEADER, config, line); + } + if (streq(line->value, "stat")) { + return parse_condition_stat(pattern, config, line); + } +#ifdef HAVE_LUA + if (streq(line->value, "lua")) { + return parse_condition_lua(pattern, config, line); + } +#endif + + config_error(config, "Condition `%s' not supported", line->value); +} static bool rewrite_parse_conf_pattern(struct private_data *pd, struct config *config, const struct config_line *line) From 96f10c8b7641d12dc070cc65ff68c5882f2dda60 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 16 Sep 2021 21:48:45 -0700 Subject: [PATCH 1808/2505] Use strbuf with a fixed buffer rather than an open coded builder --- src/lib/lwan-mod-rewrite.c | 55 +++++++++++++------------------------- 1 file changed, 19 insertions(+), 36 deletions(-) diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index 021fa6fb8..87711f0ff 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -27,9 +27,10 @@ #include "lwan-private.h" +#include "patterns.h" #include "lwan-array.h" #include "lwan-mod-rewrite.h" -#include "patterns.h" +#include "lwan-strbuf.h" #ifdef HAVE_LUA #include @@ -86,11 +87,6 @@ struct private_data { struct pattern_array patterns; }; -struct str_builder { - char *buffer; - size_t size, len; -}; - static enum lwan_http_status module_redirect_to(struct lwan_request *request, const char *url) { @@ -123,25 +119,6 @@ static enum lwan_http_status module_rewrite_as(struct lwan_request *request, return HTTP_OK; } -static bool -append_str(struct str_builder *builder, const char *src, size_t src_len) -{ - size_t total_size; - char *dest; - - if (UNLIKELY(__builtin_add_overflow(builder->len, src_len, &total_size))) - return false; - - if (UNLIKELY(total_size >= builder->size)) - return false; - - dest = mempcpy(builder->buffer + builder->len, src, src_len); - *dest = '\0'; - builder->len = total_size; - - return true; -} - #define MAX_INT_DIGITS (3 * sizeof(int)) static __attribute__((noinline)) int parse_int_len(const char *s, size_t len, @@ -153,25 +130,30 @@ static __attribute__((noinline)) int parse_int_len(const char *s, size_t len, return parse_int(strndupa(s, len), default_value); } -static const char *expand(struct pattern *pattern, const char *orig, +static const char *expand(struct pattern *pattern, + const char *orig, char buffer[static PATH_MAX], - const struct str_find *sf, int captures) + const struct str_find *sf, + int captures) { const char *expand_pattern = pattern->expand_pattern; - struct str_builder builder = {.buffer = buffer, .size = PATH_MAX}; + struct lwan_strbuf strbuf; const char *ptr; ptr = strchr(expand_pattern, '%'); if (!ptr) return expand_pattern; + if (!lwan_strbuf_init_with_fixed_buffer(&strbuf, buffer, PATH_MAX)) + return NULL; + do { size_t index_len = strspn(ptr + 1, "0123456789"); if (ptr > expand_pattern) { const size_t len = (size_t)(ptr - expand_pattern); - if (UNLIKELY(!append_str(&builder, expand_pattern, len))) + if (UNLIKELY(!lwan_strbuf_append_str(&strbuf, expand_pattern, len))) return NULL; expand_pattern += len; @@ -183,13 +165,13 @@ static const char *expand(struct pattern *pattern, const char *orig, if (UNLIKELY(index < 0 || index > captures)) return NULL; - if (UNLIKELY( - !append_str(&builder, orig + sf[index].sm_so, - (size_t)(sf[index].sm_eo - sf[index].sm_so)))) + if (UNLIKELY(!lwan_strbuf_append_str( + &strbuf, orig + sf[index].sm_so, + (size_t)(sf[index].sm_eo - sf[index].sm_so)))) return NULL; expand_pattern += index_len; - } else if (UNLIKELY(!append_str(&builder, "%", 1))) { + } else if (UNLIKELY(!lwan_strbuf_append_char(&strbuf, '%'))) { return NULL; } @@ -197,13 +179,14 @@ static const char *expand(struct pattern *pattern, const char *orig, } while ((ptr = strchr(expand_pattern, '%'))); const size_t remaining_len = strlen(expand_pattern); - if (remaining_len && !append_str(&builder, expand_pattern, remaining_len)) + if (remaining_len && + !lwan_strbuf_append_str(&strbuf, expand_pattern, remaining_len)) return NULL; - if (UNLIKELY(!builder.len)) + if (UNLIKELY(!lwan_strbuf_get_length(&strbuf))) return NULL; - return builder.buffer; + return lwan_strbuf_get_buffer(&strbuf); } #ifdef HAVE_LUA From 575938c865adccd2cce630f69dcfa42545a96b45 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 16 Sep 2021 22:42:32 -0700 Subject: [PATCH 1809/2505] Expand values when checking for conditions in rewrite module --- README.md | 12 +++++-- src/lib/lwan-mod-rewrite.c | 69 +++++++++++++++++++++++++++++++------- 2 files changed, 66 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 50d79baea..03b61a823 100644 --- a/README.md +++ b/README.md @@ -529,8 +529,16 @@ for that condition to be evaluated: |`stat` | `path`, `is_dir`, `is_file` | Checks if `path` exists in the filesystem, and optionally checks if `is_dir` or `is_file` | |`lua` | `script` | Runs Lua function `matches(req)` inside `script` and checks if it returns `true` or `false` | -For example, if one wants to send `site-dark-mode.css` if there is a `style` cookie -with the value `dark`, and send `site-light-mode.css` otherwise, one can write: +The `value` in all conditions, with the exception of `lua`, can +reference the matched pattern using the same substitution syntax used +for the `rewrite as` or `redirect to` actions. For instance, +`condition cookie { some-cookie-name = foo-%1-bar }` will substitute +`%1` with the first match from the pattern this condition is related +to. + +For example, if one wants to send `site-dark-mode.css` if there is a +`style` cookie with the value `dark`, and send `site-light-mode.css` +otherwise, one can write: ``` rewrite ... { diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index 87711f0ff..3d5ad8448 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -130,13 +130,12 @@ static __attribute__((noinline)) int parse_int_len(const char *s, size_t len, return parse_int(strndupa(s, len), default_value); } -static const char *expand(struct pattern *pattern, - const char *orig, - char buffer[static PATH_MAX], - const struct str_find *sf, - int captures) +static const char *expand_string(const char *expand_pattern, + const char *orig, + char buffer[static PATH_MAX], + const struct str_find *sf, + int captures) { - const char *expand_pattern = pattern->expand_pattern; struct lwan_strbuf strbuf; const char *ptr; @@ -189,6 +188,15 @@ static const char *expand(struct pattern *pattern, return lwan_strbuf_get_buffer(&strbuf); } +static ALWAYS_INLINE const char *expand(const struct pattern *pattern, + const char *orig, + char buffer[static PATH_MAX], + const struct str_find *sf, + int captures) +{ + return expand_string(pattern->expand_pattern, orig, buffer, sf, captures); +} + #ifdef HAVE_LUA static void lua_close_defer(void *data) @@ -247,18 +255,28 @@ static const char *expand_lua(struct lwan_request *request, #endif static bool condition_matches(struct lwan_request *request, - const struct pattern *p) + const struct pattern *p, + struct str_find *sf, + int captures) { if (LIKELY(!(p->flags & PATTERN_COND_MASK))) return true; + const char *url = request->url.value; + char expanded_buf[PATH_MAX]; + if (p->flags & PATTERN_COND_COOKIE) { assert(p->condition.cookie.key); assert(p->condition.cookie.value); const char *cookie = lwan_request_get_cookie(request, p->condition.cookie.key); - if (!cookie || !streq(cookie, p->condition.cookie.value)) + if (!cookie) + return false; + + const char *val = expand_string(p->condition.cookie.value, url, + expanded_buf, sf, captures); + if (!val || !streq(val, cookie)) return false; } @@ -267,7 +285,12 @@ static bool condition_matches(struct lwan_request *request, assert(p->condition.env_var.value); const char *env_var = secure_getenv(p->condition.env_var.key); - if (!env_var || !streq(env_var, p->condition.env_var.value)) + if (!env_var) + return false; + + const char *val = expand_string(p->condition.env_var.value, url, + expanded_buf, sf, captures); + if (!val || !streq(val, env_var)) return false; } @@ -277,7 +300,13 @@ static bool condition_matches(struct lwan_request *request, const char *query = lwan_request_get_query_param(request, p->condition.query_var.key); - if (!query || !streq(query, p->condition.query_var.value)) + + if (!query) + return false; + + const char *val = expand_string(p->condition.query_var.value, url, + expanded_buf, sf, captures); + if (!val || !streq(val, query)) return false; } @@ -287,7 +316,12 @@ static bool condition_matches(struct lwan_request *request, const char *post = lwan_request_get_post_param(request, p->condition.post_var.key); - if (!post || !streq(post, p->condition.post_var.value)) + if (!post) + return false; + + const char *val = expand_string(p->condition.post_var.value, url, + expanded_buf, sf, captures); + if (!val || !streq(val, post)) return false; } @@ -296,8 +330,16 @@ static bool condition_matches(struct lwan_request *request, struct stat st; - if (stat(p->condition.stat.path, &st) < 0) + /* FIXME: Expanding path from a user-controlled URL and use the + * resulting path to call stat(2) on could lead to some information + * disclosure vulnerability. Would require the server to be configured + * in a certain way, though. + */ + const char *path = expand_string(p->condition.stat.path, url, + expanded_buf, sf, captures); + if (!path || stat(path, &st) < 0) return false; + if (p->condition.stat.has_is_file && p->condition.stat.is_file != !!S_ISREG(st.st_mode)) { return false; @@ -342,6 +384,7 @@ static bool condition_matches(struct lwan_request *request, return true; } + static enum lwan_http_status rewrite_handle_request(struct lwan_request *request, struct lwan_response *response __attribute__((unused)), @@ -362,7 +405,7 @@ rewrite_handle_request(struct lwan_request *request, if (captures <= 0) continue; - if (!condition_matches(request, p)) + if (!condition_matches(request, p, sf, captures)) continue; switch (p->flags & PATTERN_EXPAND_MASK) { From 4b4c625c4e345af6af6ca66fb906425ddb39cf1f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 16 Sep 2021 22:49:52 -0700 Subject: [PATCH 1810/2505] Fix post variable condition in rewrite module --- src/lib/lwan-mod-rewrite.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index 3d5ad8448..dc5166f8c 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -310,7 +310,7 @@ static bool condition_matches(struct lwan_request *request, return false; } - if (p->flags & PATTERN_COND_QUERY_VAR) { + if (p->flags & PATTERN_COND_POST_VAR) { assert(p->condition.post_var.key); assert(p->condition.post_var.value); From 2e0fb06554b2bf88450434c1719acac830c502e2 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 17 Sep 2021 08:01:33 -0700 Subject: [PATCH 1811/2505] Pass a const str_find pointer to condition_matches() --- src/lib/lwan-mod-rewrite.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index dc5166f8c..50639b723 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -256,7 +256,7 @@ static const char *expand_lua(struct lwan_request *request, static bool condition_matches(struct lwan_request *request, const struct pattern *p, - struct str_find *sf, + const struct str_find *sf, int captures) { if (LIKELY(!(p->flags & PATTERN_COND_MASK))) From a926f0bf2fd0b2f132dc661e7d0c90ce21e505cb Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 17 Sep 2021 08:01:52 -0700 Subject: [PATCH 1812/2505] Ensure trie is destroyed when reading from config fails --- src/lib/lwan.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index bfe15baae..c44538af6 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -591,6 +591,7 @@ static bool setup_from_config(struct lwan *lwan, const char *path) if (config_last_error(conf)) { lwan_status_critical("Error on config file \"%s\", line %d: %s", path, config_cur_line(conf), config_last_error(conf)); + lwan_trie_destroy(&lwan->url_map_trie); } config_close(conf); From 8ace2594473a489fc1d2954c94c5380d14d19d67 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 17 Sep 2021 08:00:53 -0700 Subject: [PATCH 1813/2505] Add utility function to return HTTP method as string --- src/lib/lwan-request.c | 21 ++++++++++----------- src/lib/lwan.h | 1 + 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 545808d20..6d9d0510e 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1439,8 +1439,7 @@ static bool handle_rewrite(struct lwan_request *request) return true; } -#ifndef NDEBUG -static const char *get_request_method(struct lwan_request *request) +const char *lwan_request_get_method_str(const struct lwan_request *request) { #define GENERATE_CASE_STMT(upper, lower, mask, constant) \ case REQUEST_METHOD_##upper: \ @@ -1451,10 +1450,10 @@ static const char *get_request_method(struct lwan_request *request) default: return "UNKNOWN"; } - #undef GENERATE_CASE_STMT } +#ifndef NDEBUG static void log_request(struct lwan_request *request, enum lwan_http_status status, double time_to_read_request, @@ -1462,14 +1461,14 @@ static void log_request(struct lwan_request *request, { char ip_buffer[INET6_ADDRSTRLEN]; - lwan_status_debug("%s [%s] %016lx \"%s %s HTTP/%s\" %d %s (r:%.3fms p:%.3fms)", - lwan_request_get_remote_address(request, ip_buffer), - request->conn->thread->date.date, request->request_id, - get_request_method(request), request->original_url.value, - request->flags & REQUEST_IS_HTTP_1_0 ? "1.0" : "1.1", - status, request->response.mime_type, - time_to_read_request, - time_to_process_request); + lwan_status_debug( + "%s [%s] %016lx \"%s %s HTTP/%s\" %d %s (r:%.3fms p:%.3fms)", + lwan_request_get_remote_address(request, ip_buffer), + request->conn->thread->date.date, request->request_id, + lwan_request_get_method_str(request), request->original_url.value, + request->flags & REQUEST_IS_HTTP_1_0 ? "1.0" : "1.1", status, + request->response.mime_type, time_to_read_request, + time_to_process_request); } #else #define log_request(...) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index b642ee444..ab90f7228 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -543,6 +543,7 @@ lwan_request_get_method(const struct lwan_request *request) { return (enum lwan_request_flags)(request->flags & REQUEST_METHOD_MASK); } +const char *lwan_request_get_method_str(const struct lwan_request *request); int lwan_request_get_range(struct lwan_request *request, off_t *from, From 79f7100e95f06a4fcc0c8acff2b526ed86574e7d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 17 Sep 2021 08:01:20 -0700 Subject: [PATCH 1814/2505] Allow rewriting conditionally on a HTTP method --- README.md | 1 + src/lib/lwan-mod-rewrite.c | 34 ++++++++++++++++++++++++++++------ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 03b61a823..4e851393a 100644 --- a/README.md +++ b/README.md @@ -526,6 +526,7 @@ for that condition to be evaluated: |`post` | A single `key` = `value`| Checks if request has post data `key` has value `value` | |`header` | A single `key` = `value`| Checks if request header `key` has value `value` | |`environment` | A single `key` = `value`| Checks if environment variable `key` has value `value` | +|`method` | `key` = `value`| Checks if HTTP method has value `value`; `key` must be `name` | |`stat` | `path`, `is_dir`, `is_file` | Checks if `path` exists in the filesystem, and optionally checks if `is_dir` or `is_file` | |`lua` | `script` | Runs Lua function `matches(req)` inside `script` and checks if it returns `true` or `false` | diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index 50639b723..700ed385c 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -56,17 +56,18 @@ enum pattern_flag { PATTERN_COND_POST_VAR = 1 << 8, PATTERN_COND_HEADER = 1 << 9, PATTERN_COND_LUA = 1 << 10, + PATTERN_COND_METHOD = 1 << 11, PATTERN_COND_MASK = PATTERN_COND_COOKIE | PATTERN_COND_ENV_VAR | PATTERN_COND_STAT | PATTERN_COND_QUERY_VAR | PATTERN_COND_POST_VAR | PATTERN_COND_HEADER | - PATTERN_COND_LUA, + PATTERN_COND_LUA | PATTERN_COND_METHOD, }; struct pattern { char *pattern; char *expand_pattern; struct { - struct lwan_key_value cookie, env_var, query_var, post_var, header; + struct lwan_key_value cookie, env_var, query_var, post_var, header, method; struct { char *path; unsigned int has_is_file : 1; @@ -265,6 +266,17 @@ static bool condition_matches(struct lwan_request *request, const char *url = request->url.value; char expanded_buf[PATH_MAX]; + if (p->flags & PATTERN_COND_METHOD) { + assert(p->condition.method.key); + assert(p->condition.method.value); + + const char *method = lwan_request_get_method_str(request); + if (!method) + return false; + if (strcasecmp(method, p->condition.method.value) != 0) + return false; + } + if (p->flags & PATTERN_COND_COOKIE) { assert(p->condition.cookie.key); assert(p->condition.cookie.value); @@ -475,6 +487,10 @@ static void rewrite_destroy(void *instance) free(iter->condition.header.key); free(iter->condition.header.value); } + if (iter->flags & PATTERN_COND_METHOD) { + free(iter->condition.method.key); + free(iter->condition.method.value); + } if (iter->flags & PATTERN_COND_STAT) { free(iter->condition.stat.path); } @@ -663,8 +679,7 @@ static void parse_condition(struct pattern *pattern, PATTERN_COND_COOKIE, config, line); } if (streq(line->value, "query")) { - return parse_condition_key_value(pattern, - &pattern->condition.query_var, + return parse_condition_key_value(pattern, &pattern->condition.query_var, PATTERN_COND_QUERY_VAR, config, line); } if (streq(line->value, "post")) { @@ -672,14 +687,20 @@ static void parse_condition(struct pattern *pattern, PATTERN_COND_POST_VAR, config, line); } if (streq(line->value, "environment")) { - return parse_condition_key_value(pattern, - &pattern->condition.env_var, + return parse_condition_key_value(pattern, &pattern->condition.env_var, PATTERN_COND_ENV_VAR, config, line); } if (streq(line->value, "header")) { return parse_condition_key_value(pattern, &pattern->condition.header, PATTERN_COND_HEADER, config, line); } + if (streq(line->value, "method")) { + parse_condition_key_value(pattern, &pattern->condition.method, + PATTERN_COND_METHOD, config, line); + if (!streq(pattern->condition.method.key, "name")) + config_error(config, "Method condition requires `name`"); + return; + } if (streq(line->value, "stat")) { return parse_condition_stat(pattern, config, line); } @@ -691,6 +712,7 @@ static void parse_condition(struct pattern *pattern, config_error(config, "Condition `%s' not supported", line->value); } + static bool rewrite_parse_conf_pattern(struct private_data *pd, struct config *config, const struct config_line *line) From b3015d6b04c766497fbfea15935642f45c3f720d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 17 Sep 2021 08:30:50 -0700 Subject: [PATCH 1815/2505] Reduce size of struct pattern to 128 bytes from 144 bytes It now fits in two cache lines! :) --- src/lib/lwan-mod-rewrite.c | 64 ++++++++++++++++++++++++++------------ 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index 700ed385c..f7c2947f3 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -61,19 +61,32 @@ enum pattern_flag { PATTERN_COND_STAT | PATTERN_COND_QUERY_VAR | PATTERN_COND_POST_VAR | PATTERN_COND_HEADER | PATTERN_COND_LUA | PATTERN_COND_METHOD, + + PATTERN_COND_STAT__HAS_IS_FILE = 1 << 12, + PATTERN_COND_STAT__HAS_IS_DIR = 1 << 13, + PATTERN_COND_STAT__IS_FILE = 1 << 14, + PATTERN_COND_STAT__IS_DIR = 1 << 15, + + PATTERN_COND_STAT__FILE_CHECK = + PATTERN_COND_STAT__HAS_IS_FILE | PATTERN_COND_STAT__IS_FILE, + PATTERN_COND_STAT__DIR_CHECK = + PATTERN_COND_STAT__HAS_IS_DIR | PATTERN_COND_STAT__IS_DIR, }; struct pattern { char *pattern; char *expand_pattern; struct { - struct lwan_key_value cookie, env_var, query_var, post_var, header, method; + struct lwan_key_value cookie; + struct lwan_key_value env_var; + struct lwan_key_value query_var; + struct lwan_key_value post_var; + struct lwan_key_value header; + struct { + char *name; + } method; struct { char *path; - unsigned int has_is_file : 1; - unsigned int has_is_dir : 1; - unsigned int is_file : 1; - unsigned int is_dir : 1; } stat; struct { char *script; @@ -273,7 +286,7 @@ static bool condition_matches(struct lwan_request *request, const char *method = lwan_request_get_method_str(request); if (!method) return false; - if (strcasecmp(method, p->condition.method.value) != 0) + if (strcasecmp(method, p->condition.method.name) != 0) return false; } @@ -352,14 +365,14 @@ static bool condition_matches(struct lwan_request *request, if (!path || stat(path, &st) < 0) return false; - if (p->condition.stat.has_is_file && - p->condition.stat.is_file != !!S_ISREG(st.st_mode)) { + if ((p->flags & PATTERN_COND_STAT__FILE_CHECK) == + PATTERN_COND_STAT__FILE_CHECK && + !S_ISREG(st.st_mode)) return false; - } - if (p->condition.stat.has_is_dir && - p->condition.stat.is_dir != !!S_ISDIR(st.st_mode)) { + if ((p->flags & PATTERN_COND_STAT__DIR_CHECK) == + PATTERN_COND_STAT__DIR_CHECK && + !S_ISDIR(st.st_mode)) return false; - } } #ifdef HAVE_LUA @@ -488,8 +501,7 @@ static void rewrite_destroy(void *instance) free(iter->condition.header.value); } if (iter->flags & PATTERN_COND_METHOD) { - free(iter->condition.method.key); - free(iter->condition.method.value); + free(iter->condition.method.name); } if (iter->flags & PATTERN_COND_STAT) { free(iter->condition.stat.path); @@ -587,10 +599,16 @@ static void parse_condition_stat(struct pattern *pattern, } pattern->condition.stat.path = path; - pattern->condition.stat.is_dir = parse_bool(is_dir, false); - pattern->condition.stat.is_file = parse_bool(is_file, false); - pattern->condition.stat.has_is_dir = is_dir != NULL; - pattern->condition.stat.has_is_file = is_file != NULL; + if (is_dir) { + pattern->flags |= PATTERN_COND_STAT__HAS_IS_DIR; + if (parse_bool(is_dir, false)) + pattern->flags |= PATTERN_COND_STAT__IS_DIR; + } + if (is_file) { + pattern->flags |= PATTERN_COND_STAT__HAS_IS_FILE; + if (parse_bool(is_file, false)) + pattern->flags |= PATTERN_COND_STAT__IS_FILE; + } pattern->flags |= PATTERN_COND_STAT; return; @@ -695,10 +713,16 @@ static void parse_condition(struct pattern *pattern, PATTERN_COND_HEADER, config, line); } if (streq(line->value, "method")) { - parse_condition_key_value(pattern, &pattern->condition.method, + struct lwan_key_value method = {}; + + parse_condition_key_value(pattern, &method, PATTERN_COND_METHOD, config, line); - if (!streq(pattern->condition.method.key, "name")) + if (!streq(method.key, "name")) { config_error(config, "Method condition requires `name`"); + free(method.value); + } + free(method.key); + pattern->condition.method.name = method.value; return; } if (streq(line->value, "stat")) { From 03c65c6f37305d7febf4c737af292dfe1db40be1 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 17 Sep 2021 08:41:38 -0700 Subject: [PATCH 1816/2505] Plug memory leaks while parsing method rewrite condition --- src/lib/lwan-mod-rewrite.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index f7c2947f3..781438801 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -717,12 +717,17 @@ static void parse_condition(struct pattern *pattern, parse_condition_key_value(pattern, &method, PATTERN_COND_METHOD, config, line); + if (config_last_error(config)) + return; if (!streq(method.key, "name")) { config_error(config, "Method condition requires `name`"); free(method.value); + } else { + pattern->condition.method.name = method.value; } + free(method.key); - pattern->condition.method.name = method.value; + return; } if (streq(line->value, "stat")) { From 1896387c7282bf03806e2276b0a82d2c7095418e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 17 Sep 2021 22:39:41 -0700 Subject: [PATCH 1817/2505] Validate if method rewrite condition is valid on load As bonus, make runtime check more efficient by comparing integers rather than strings. --- src/lib/lwan-mod-rewrite.c | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index 781438801..63c125b7b 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -82,15 +82,14 @@ struct pattern { struct lwan_key_value query_var; struct lwan_key_value post_var; struct lwan_key_value header; - struct { - char *name; - } method; struct { char *path; } stat; struct { char *script; } lua; + enum lwan_request_flags method; + /* FIXME: Use pahole to find alignment holes? */ } condition; enum pattern_flag flags; }; @@ -280,13 +279,7 @@ static bool condition_matches(struct lwan_request *request, char expanded_buf[PATH_MAX]; if (p->flags & PATTERN_COND_METHOD) { - assert(p->condition.method.key); - assert(p->condition.method.value); - - const char *method = lwan_request_get_method_str(request); - if (!method) - return false; - if (strcasecmp(method, p->condition.method.name) != 0) + if (lwan_request_get_method(request) != p->condition.method) return false; } @@ -500,9 +493,6 @@ static void rewrite_destroy(void *instance) free(iter->condition.header.key); free(iter->condition.header.value); } - if (iter->flags & PATTERN_COND_METHOD) { - free(iter->condition.method.name); - } if (iter->flags & PATTERN_COND_STAT) { free(iter->condition.stat.path); } @@ -688,6 +678,21 @@ static void parse_condition_lua(struct pattern *pattern, } #endif +static bool get_method_from_string(struct pattern *pattern, const char *string) +{ +#define GENERATE_CMP(upper, lower, mask, constant) \ + if (!strcasecmp(string, #upper)) { \ + pattern->condition.method = (mask); \ + return true; \ + } + + FOR_EACH_REQUEST_METHOD(GENERATE_CMP) + +#undef GENERATE_CMP + + return false; +} + static void parse_condition(struct pattern *pattern, struct config *config, const struct config_line *line) @@ -719,14 +724,15 @@ static void parse_condition(struct pattern *pattern, PATTERN_COND_METHOD, config, line); if (config_last_error(config)) return; + if (!streq(method.key, "name")) { config_error(config, "Method condition requires `name`"); - free(method.value); - } else { - pattern->condition.method.name = method.value; + } else if (!get_method_from_string(pattern, method.value)) { + config_error(config, "Unknown HTTP method: %s", method.value); } free(method.key); + free(method.value); return; } From bbdafc7c2ab1539adbfc10a1f1d6a5fa98dd3755 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 18 Sep 2021 13:08:50 -0700 Subject: [PATCH 1818/2505] Conditional rewrites on accept-encoding headers, too --- README.md | 61 +++++++++++++++++++++++++--------- src/lib/lwan-mod-rewrite.c | 68 +++++++++++++++++++++++++++++++++----- 2 files changed, 105 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 4e851393a..f812c0f7d 100644 --- a/README.md +++ b/README.md @@ -526,33 +526,62 @@ for that condition to be evaluated: |`post` | A single `key` = `value`| Checks if request has post data `key` has value `value` | |`header` | A single `key` = `value`| Checks if request header `key` has value `value` | |`environment` | A single `key` = `value`| Checks if environment variable `key` has value `value` | -|`method` | `key` = `value`| Checks if HTTP method has value `value`; `key` must be `name` | +|`method`⋆ | `key` = `value`| Checks if HTTP method has value `value`; `key` must be `name` | |`stat` | `path`, `is_dir`, `is_file` | Checks if `path` exists in the filesystem, and optionally checks if `is_dir` or `is_file` | -|`lua` | `script` | Runs Lua function `matches(req)` inside `script` and checks if it returns `true` or `false` | +|`lua`⋆ | `script` | Runs Lua function `matches(req)` inside `script` and checks if it returns `true` or `false` | +|`encoding`⋆ | `deflate`, `gzip`, `brotli`, `zstd`, `none` | Checks if client accepts responses in a determined encoding (e.g. `deflate = yes` for Deflate encoding) | -The `value` in all conditions, with the exception of `lua`, can -reference the matched pattern using the same substitution syntax used -for the `rewrite as` or `redirect to` actions. For instance, -`condition cookie { some-cookie-name = foo-%1-bar }` will substitute -`%1` with the first match from the pattern this condition is related -to. +The `value` in all conditions, with the exception of those marked with an +star`⋆`, can reference the matched pattern using the same substitution +syntax used for the `rewrite as` or `redirect to` actions. For instance, +`condition cookie { some-cookie-name = foo-%1-bar }` will substitute `%1` +with the first match from the pattern this condition is related to. For example, if one wants to send `site-dark-mode.css` if there is a `style` cookie with the value `dark`, and send `site-light-mode.css` otherwise, one can write: ``` -rewrite ... { - pattern site.css { - rewrite as = /site-dark-mode.css - condition cookie { style = dark } - } - pattern site.css { - rewrite as = /site-light-mode.css - } +pattern site.css { + rewrite as = /site-dark-mode.css + condition cookie { style = dark } +} +pattern site.css { + rewrite as = /site-light-mode.css +} +``` + +Another example: if one wants to send pre-compressed files +if they do exist in the filesystem and the user requested them: + +``` +pattern (%g+) { + condition encoding { brotli = yes } + condition stat { path = %1.brotli } + rewrite as = %1.brotli +} +pattern (%g+) { + condition encoding { gzip = yes } + condition stat { path = %1.gzip } + rewrite as = %1.gzip +} +pattern (%g+) { + condition encoding { zstd = yes } + condition stat { path = %1.zstd } + rewrite as = %1.zstd +} +pattern (%g+) { + condition encoding { deflate = yes } + condition stat { path = %1.deflate } + rewrite as = %1.deflate } ``` +(In general, this is not necessary, as the file serving module will do this +automatically and pick the smallest file available for the requested +encoding, cache it for a while, but this shows it's possible to have a +similar feature by configuration alone.) + #### Redirect The `redirect` module will, as it says in the tin, generate a `301 diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index 63c125b7b..34b7f6af3 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -57,15 +57,17 @@ enum pattern_flag { PATTERN_COND_HEADER = 1 << 9, PATTERN_COND_LUA = 1 << 10, PATTERN_COND_METHOD = 1 << 11, + PATTERN_COND_ACCEPT_ENCODING = 1 << 12, PATTERN_COND_MASK = PATTERN_COND_COOKIE | PATTERN_COND_ENV_VAR | PATTERN_COND_STAT | PATTERN_COND_QUERY_VAR | PATTERN_COND_POST_VAR | PATTERN_COND_HEADER | - PATTERN_COND_LUA | PATTERN_COND_METHOD, + PATTERN_COND_LUA | PATTERN_COND_METHOD | + PATTERN_COND_ACCEPT_ENCODING, - PATTERN_COND_STAT__HAS_IS_FILE = 1 << 12, - PATTERN_COND_STAT__HAS_IS_DIR = 1 << 13, - PATTERN_COND_STAT__IS_FILE = 1 << 14, - PATTERN_COND_STAT__IS_DIR = 1 << 15, + PATTERN_COND_STAT__HAS_IS_FILE = 1 << 13, + PATTERN_COND_STAT__HAS_IS_DIR = 1 << 14, + PATTERN_COND_STAT__IS_FILE = 1 << 15, + PATTERN_COND_STAT__IS_DIR = 1 << 16, PATTERN_COND_STAT__FILE_CHECK = PATTERN_COND_STAT__HAS_IS_FILE | PATTERN_COND_STAT__IS_FILE, @@ -88,7 +90,7 @@ struct pattern { struct { char *script; } lua; - enum lwan_request_flags method; + enum lwan_request_flags request_flags; /* FIXME: Use pahole to find alignment holes? */ } condition; enum pattern_flag flags; @@ -279,7 +281,16 @@ static bool condition_matches(struct lwan_request *request, char expanded_buf[PATH_MAX]; if (p->flags & PATTERN_COND_METHOD) { - if (lwan_request_get_method(request) != p->condition.method) + const enum lwan_request_flags method = + p->condition.request_flags & REQUEST_METHOD_MASK; + if (lwan_request_get_method(request) != method) + return false; + } + + if (p->flags & PATTERN_COND_ACCEPT_ENCODING) { + const enum lwan_request_flags accept = + p->condition.request_flags & REQUEST_ACCEPT_MASK; + if (!(request->flags & accept)) return false; } @@ -630,6 +641,44 @@ static void parse_condition_stat(struct pattern *pattern, free(path); } +static void parse_condition_accept_encoding(struct pattern *pattern, + struct config *config) +{ + const struct config_line *line; + + while ((line = config_read_line(config))) { + switch (line->type) { + case CONFIG_LINE_TYPE_SECTION: + config_error(config, "Unexpected section: %s", line->key); + return; + + case CONFIG_LINE_TYPE_SECTION_END: + pattern->flags |= PATTERN_COND_ACCEPT_ENCODING; + return; + + case CONFIG_LINE_TYPE_LINE: + if (streq(line->key, "deflate")) { + if (parse_bool(line->value, false)) + pattern->condition.request_flags |= REQUEST_ACCEPT_DEFLATE; + } else if (streq(line->key, "gzip")) { + if (parse_bool(line->value, false)) + pattern->condition.request_flags |= REQUEST_ACCEPT_GZIP; + } else if (streq(line->key, "brotli")) { + if (parse_bool(line->value, false)) + pattern->condition.request_flags |= REQUEST_ACCEPT_BROTLI; + } else if (streq(line->key, "zstd")) { + if (parse_bool(line->value, false)) + pattern->condition.request_flags |= REQUEST_ACCEPT_ZSTD; + } else if (!streq(line->key, "none")) { + config_error(config, "Unsupported encoding for condition: %s", + line->key); + return; + } + break; + } + } +} + #ifdef HAVE_LUA static void parse_condition_lua(struct pattern *pattern, struct config *config, @@ -682,7 +731,7 @@ static bool get_method_from_string(struct pattern *pattern, const char *string) { #define GENERATE_CMP(upper, lower, mask, constant) \ if (!strcasecmp(string, #upper)) { \ - pattern->condition.method = (mask); \ + pattern->condition.request_flags = (mask); \ return true; \ } @@ -739,6 +788,9 @@ static void parse_condition(struct pattern *pattern, if (streq(line->value, "stat")) { return parse_condition_stat(pattern, config, line); } + if (streq(line->value, "encoding")) { + return parse_condition_accept_encoding(pattern, config); + } #ifdef HAVE_LUA if (streq(line->value, "lua")) { return parse_condition_lua(pattern, config, line); From f91442004481b2372965e1265c8dd6e8cfb457a0 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 18 Sep 2021 13:09:52 -0700 Subject: [PATCH 1819/2505] Fix typo in README --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f812c0f7d..ef5d16c64 100644 --- a/README.md +++ b/README.md @@ -531,11 +531,11 @@ for that condition to be evaluated: |`lua`⋆ | `script` | Runs Lua function `matches(req)` inside `script` and checks if it returns `true` or `false` | |`encoding`⋆ | `deflate`, `gzip`, `brotli`, `zstd`, `none` | Checks if client accepts responses in a determined encoding (e.g. `deflate = yes` for Deflate encoding) | -The `value` in all conditions, with the exception of those marked with an -star`⋆`, can reference the matched pattern using the same substitution -syntax used for the `rewrite as` or `redirect to` actions. For instance, -`condition cookie { some-cookie-name = foo-%1-bar }` will substitute `%1` -with the first match from the pattern this condition is related to. +The `value` in all conditions, with the exception of those marked with `⋆`, +can reference the matched pattern using the same substitution syntax used +for the `rewrite as` or `redirect to` actions. For instance, `condition +cookie { some-cookie-name = foo-%1-bar }` will substitute `%1` with the +first match from the pattern this condition is related to. For example, if one wants to send `site-dark-mode.css` if there is a `style` cookie with the value `dark`, and send `site-light-mode.css` From 4bb1274c8ab7c42d00536ab25e52927ccf9715cb Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 18 Sep 2021 17:12:31 -0700 Subject: [PATCH 1820/2505] Remove 4 bytes hole in struct lwan_thread --- src/lib/lwan.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index ab90f7228..44989d25c 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -436,8 +436,8 @@ struct lwan_thread { char date[30]; char expires[30]; } date; - struct timeouts *wheel; int epoll_fd; + struct timeouts *wheel; int listen_fd; unsigned int cpu; pthread_t self; From 0e203a5950a82e1cad195710f17c3afc3b30b95a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sun, 19 Sep 2021 07:38:25 -0700 Subject: [PATCH 1821/2505] Ensure accept encoding is parsed when conditionally rewriting --- src/lib/lwan-mod-rewrite.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index 34b7f6af3..6eac307a6 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -290,7 +290,7 @@ static bool condition_matches(struct lwan_request *request, if (p->flags & PATTERN_COND_ACCEPT_ENCODING) { const enum lwan_request_flags accept = p->condition.request_flags & REQUEST_ACCEPT_MASK; - if (!(request->flags & accept)) + if (!(lwan_request_get_accept_encoding(request) & accept)) return false; } From 3bdd1339b5bcdaa264c31da71f7a91a30324feba Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 27 Sep 2021 20:44:11 -0700 Subject: [PATCH 1822/2505] Reuse expanded_buf in condition_matches() No need to allocate another PATH_MAX-sized buffer while matching a condition if the expanded buffer will be overritten if the condition matches. --- src/lib/lwan-mod-rewrite.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index 6eac307a6..71d9cc5be 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -272,13 +272,13 @@ static const char *expand_lua(struct lwan_request *request, static bool condition_matches(struct lwan_request *request, const struct pattern *p, const struct str_find *sf, - int captures) + int captures, + char expanded_buf[static PATH_MAX]) { if (LIKELY(!(p->flags & PATTERN_COND_MASK))) return true; const char *url = request->url.value; - char expanded_buf[PATH_MAX]; if (p->flags & PATTERN_COND_METHOD) { const enum lwan_request_flags method = @@ -434,7 +434,7 @@ rewrite_handle_request(struct lwan_request *request, if (captures <= 0) continue; - if (!condition_matches(request, p, sf, captures)) + if (!condition_matches(request, p, sf, captures, final_url)) continue; switch (p->flags & PATTERN_EXPAND_MASK) { From a9719e9cf1bb90a8b1e48bee79edbae19b076f32 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 27 Sep 2021 20:46:15 -0700 Subject: [PATCH 1823/2505] Add another reference to Lwan --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index ef5d16c64..53de60904 100644 --- a/README.md +++ b/README.md @@ -882,3 +882,5 @@ in no particular order. Contributions are appreciated: > "Impressive all and all, even more for being written in (grokkable!) C. Nice work." > [tpaschalis](https://news.ycombinator.com/item?id=17550961) + +> "LWAN was a complete failure" [dermetfan](http://dermetfan.net/posts/zig-with-c-web-servers.html) From 9204264f0fc094544f41f09a38af7fa8a1a23ad4 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 27 Sep 2021 20:46:37 -0700 Subject: [PATCH 1824/2505] get_method_from_string() shouldn't reset request_flags --- src/lib/lwan-mod-rewrite.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index 71d9cc5be..b58e3357b 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -731,7 +731,7 @@ static bool get_method_from_string(struct pattern *pattern, const char *string) { #define GENERATE_CMP(upper, lower, mask, constant) \ if (!strcasecmp(string, #upper)) { \ - pattern->condition.request_flags = (mask); \ + pattern->condition.request_flags |= (mask); \ return true; \ } From ef6a89737318285e62d6e88e00dd68e9757a285f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 27 Sep 2021 20:49:48 -0700 Subject: [PATCH 1825/2505] Add more conditions to rewrite module --- README.md | 6 ++++++ src/lib/lwan-mod-rewrite.c | 42 +++++++++++++++++++++++++++++++++----- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 53de60904..0a2c3dda5 100644 --- a/README.md +++ b/README.md @@ -530,6 +530,9 @@ for that condition to be evaluated: |`stat` | `path`, `is_dir`, `is_file` | Checks if `path` exists in the filesystem, and optionally checks if `is_dir` or `is_file` | |`lua`⋆ | `script` | Runs Lua function `matches(req)` inside `script` and checks if it returns `true` or `false` | |`encoding`⋆ | `deflate`, `gzip`, `brotli`, `zstd`, `none` | Checks if client accepts responses in a determined encoding (e.g. `deflate = yes` for Deflate encoding) | +|`proxied`♠ | Boolean | Checks if request has been proxied through PROXY protocol | +|`http_1.0`♠ | Boolean | Checks if request is made with a HTTP/1.0 client | +|`has_query_string`♠ | Boolean | Checks if request has a query string (even if empty) | The `value` in all conditions, with the exception of those marked with `⋆`, can reference the matched pattern using the same substitution syntax used @@ -537,6 +540,9 @@ for the `rewrite as` or `redirect to` actions. For instance, `condition cookie { some-cookie-name = foo-%1-bar }` will substitute `%1` with the first match from the pattern this condition is related to. +Conditions marked with `♠` do not require a section, and can be written, for +instance, as `condition has_query_string = yes`. + For example, if one wants to send `site-dark-mode.css` if there is a `style` cookie with the value `dark`, and send `site-light-mode.css` otherwise, one can write: diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index b58e3357b..d74fae2c8 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -58,16 +58,21 @@ enum pattern_flag { PATTERN_COND_LUA = 1 << 10, PATTERN_COND_METHOD = 1 << 11, PATTERN_COND_ACCEPT_ENCODING = 1 << 12, + PATTERN_COND_PROXIED = 1 << 13, + PATTERN_COND_HTTP10 = 1 << 14, + PATTERN_COND_HAS_QUERY_STRING = 1 << 15, PATTERN_COND_MASK = PATTERN_COND_COOKIE | PATTERN_COND_ENV_VAR | PATTERN_COND_STAT | PATTERN_COND_QUERY_VAR | PATTERN_COND_POST_VAR | PATTERN_COND_HEADER | PATTERN_COND_LUA | PATTERN_COND_METHOD | - PATTERN_COND_ACCEPT_ENCODING, + PATTERN_COND_ACCEPT_ENCODING | + PATTERN_COND_PROXIED | PATTERN_COND_HTTP10 | + PATTERN_COND_HAS_QUERY_STRING, - PATTERN_COND_STAT__HAS_IS_FILE = 1 << 13, - PATTERN_COND_STAT__HAS_IS_DIR = 1 << 14, - PATTERN_COND_STAT__IS_FILE = 1 << 15, - PATTERN_COND_STAT__IS_DIR = 1 << 16, + PATTERN_COND_STAT__HAS_IS_FILE = 1 << 16, + PATTERN_COND_STAT__HAS_IS_DIR = 1 << 17, + PATTERN_COND_STAT__IS_FILE = 1 << 18, + PATTERN_COND_STAT__IS_DIR = 1 << 19, PATTERN_COND_STAT__FILE_CHECK = PATTERN_COND_STAT__HAS_IS_FILE | PATTERN_COND_STAT__IS_FILE, @@ -294,6 +299,21 @@ static bool condition_matches(struct lwan_request *request, return false; } + if (p->flags & PATTERN_COND_PROXIED) { + if (!(request->flags & p->condition.request_flags & REQUEST_PROXIED)) + return false; + } + + if (p->flags & PATTERN_COND_HTTP10) { + if (!(request->flags & p->condition.request_flags & REQUEST_IS_HTTP_1_0)) + return false; + } + + if (p->flags & PATTERN_COND_HAS_QUERY_STRING) { + if (!(request->flags & p->condition.request_flags & REQUEST_HAS_QUERY_STRING)) + return false; + } + if (p->flags & PATTERN_COND_COOKIE) { assert(p->condition.cookie.key); assert(p->condition.cookie.value); @@ -833,6 +853,18 @@ static bool rewrite_parse_conf_pattern(struct private_data *pd, goto out; } else if (streq(line->key, "expand_with_lua")) { expand_with_lua = parse_bool(line->value, false); + } else if (streq(line->key, "condition_proxied")) { + if (parse_bool(line->value, false)) { + pattern->flags |= PATTERN_COND_PROXIED; + } + } else if (streq(line->key, "condition_http_1.0")) { + if (parse_bool(line->value, false)) { + pattern->flags |= PATTERN_COND_HTTP10; + } + } else if (streq(line->key, "condition_has_query_string")) { + if (parse_bool(line->value, false)) { + pattern->flags |= PATTERN_COND_HAS_QUERY_STRING; + } } else { config_error(config, "Unexpected key: %s", line->key); goto out; From 214b3550b39d1af0ca69102ca1bf61448be97042 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 28 Sep 2021 08:30:33 -0700 Subject: [PATCH 1826/2505] Simplify how method conditions are specified in the config file --- README.md | 8 ++++---- src/lib/lwan-mod-rewrite.c | 25 ++++++------------------- 2 files changed, 10 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 0a2c3dda5..17dc2b44f 100644 --- a/README.md +++ b/README.md @@ -526,15 +526,15 @@ for that condition to be evaluated: |`post` | A single `key` = `value`| Checks if request has post data `key` has value `value` | |`header` | A single `key` = `value`| Checks if request header `key` has value `value` | |`environment` | A single `key` = `value`| Checks if environment variable `key` has value `value` | -|`method`⋆ | `key` = `value`| Checks if HTTP method has value `value`; `key` must be `name` | |`stat` | `path`, `is_dir`, `is_file` | Checks if `path` exists in the filesystem, and optionally checks if `is_dir` or `is_file` | -|`lua`⋆ | `script` | Runs Lua function `matches(req)` inside `script` and checks if it returns `true` or `false` | -|`encoding`⋆ | `deflate`, `gzip`, `brotli`, `zstd`, `none` | Checks if client accepts responses in a determined encoding (e.g. `deflate = yes` for Deflate encoding) | +|`lua`♦ | `script` | Runs Lua function `matches(req)` inside `script` and checks if it returns `true` or `false` | +|`encoding`♦ | `deflate`, `gzip`, `brotli`, `zstd`, `none` | Checks if client accepts responses in a determined encoding (e.g. `deflate = yes` for Deflate encoding) | |`proxied`♠ | Boolean | Checks if request has been proxied through PROXY protocol | |`http_1.0`♠ | Boolean | Checks if request is made with a HTTP/1.0 client | |`has_query_string`♠ | Boolean | Checks if request has a query string (even if empty) | +|`method`♠ | Method name | Checks if HTTP method is the one specified | -The `value` in all conditions, with the exception of those marked with `⋆`, +The `value` in all conditions, with the exception of those marked with `♦`, can reference the matched pattern using the same substitution syntax used for the `rewrite as` or `redirect to` actions. For instance, `condition cookie { some-cookie-name = foo-%1-bar }` will substitute `%1` with the diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index d74fae2c8..39a883c1b 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -786,25 +786,6 @@ static void parse_condition(struct pattern *pattern, return parse_condition_key_value(pattern, &pattern->condition.header, PATTERN_COND_HEADER, config, line); } - if (streq(line->value, "method")) { - struct lwan_key_value method = {}; - - parse_condition_key_value(pattern, &method, - PATTERN_COND_METHOD, config, line); - if (config_last_error(config)) - return; - - if (!streq(method.key, "name")) { - config_error(config, "Method condition requires `name`"); - } else if (!get_method_from_string(pattern, method.value)) { - config_error(config, "Unknown HTTP method: %s", method.value); - } - - free(method.key); - free(method.value); - - return; - } if (streq(line->value, "stat")) { return parse_condition_stat(pattern, config, line); } @@ -865,6 +846,12 @@ static bool rewrite_parse_conf_pattern(struct private_data *pd, if (parse_bool(line->value, false)) { pattern->flags |= PATTERN_COND_HAS_QUERY_STRING; } + } else if (streq(line->key, "condition_method")) { + if (!get_method_from_string(pattern, line->value)) { + config_error(config, "Unknown HTTP method: %s", line->value); + goto out; + } + pattern->flags |= PATTERN_COND_METHOD; } else { config_error(config, "Unexpected key: %s", line->key); goto out; From 9b89cdda45d5a8564ac40165834e60b3a99db1c3 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 28 Sep 2021 08:39:12 -0700 Subject: [PATCH 1827/2505] Simplify how Lua scripts can be specified in rewrite conditions --- README.md | 38 +++++++++++----------- src/lib/lwan-mod-rewrite.c | 64 ++++++-------------------------------- 2 files changed, 29 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index 17dc2b44f..e29c613a7 100644 --- a/README.md +++ b/README.md @@ -519,26 +519,26 @@ It's also possible to specify conditions to trigger a rewrite. To specify one, open a `condition` block, specify the condition type, and then the parameters for that condition to be evaluated: -|Condition|Parameters|Description| +|Condition|Can use subst. syntax|Parameters|Description| |---------|----------|-----------| -|`cookie` | A single `key` = `value`| Checks if request has cookie `key` has value `value` | -|`query` | A single `key` = `value`| Checks if request has query variable `key` has value `value` | -|`post` | A single `key` = `value`| Checks if request has post data `key` has value `value` | -|`header` | A single `key` = `value`| Checks if request header `key` has value `value` | -|`environment` | A single `key` = `value`| Checks if environment variable `key` has value `value` | -|`stat` | `path`, `is_dir`, `is_file` | Checks if `path` exists in the filesystem, and optionally checks if `is_dir` or `is_file` | -|`lua`♦ | `script` | Runs Lua function `matches(req)` inside `script` and checks if it returns `true` or `false` | -|`encoding`♦ | `deflate`, `gzip`, `brotli`, `zstd`, `none` | Checks if client accepts responses in a determined encoding (e.g. `deflate = yes` for Deflate encoding) | -|`proxied`♠ | Boolean | Checks if request has been proxied through PROXY protocol | -|`http_1.0`♠ | Boolean | Checks if request is made with a HTTP/1.0 client | -|`has_query_string`♠ | Boolean | Checks if request has a query string (even if empty) | -|`method`♠ | Method name | Checks if HTTP method is the one specified | - -The `value` in all conditions, with the exception of those marked with `♦`, -can reference the matched pattern using the same substitution syntax used -for the `rewrite as` or `redirect to` actions. For instance, `condition -cookie { some-cookie-name = foo-%1-bar }` will substitute `%1` with the -first match from the pattern this condition is related to. +|`cookie` | Yes | A single `key` = `value`| Checks if request has cookie `key` has value `value` | +|`query` | Yes | A single `key` = `value`| Checks if request has query variable `key` has value `value` | +|`post` | Yes | A single `key` = `value`| Checks if request has post data `key` has value `value` | +|`header` | Yes | A single `key` = `value`| Checks if request header `key` has value `value` | +|`environment` | Yes | A single `key` = `value`| Checks if environment variable `key` has value `value` | +|`stat` | Yes | `path`, `is_dir`, `is_file` | Checks if `path` exists in the filesystem, and optionally checks if `is_dir` or `is_file` | +|`encoding` | No | `deflate`, `gzip`, `brotli`, `zstd`, `none` | Checks if client accepts responses in a determined encoding (e.g. `deflate = yes` for Deflate encoding) | +|`proxied`♠ | No | Boolean | Checks if request has been proxied through PROXY protocol | +|`http_1.0`♠ | No | Boolean | Checks if request is made with a HTTP/1.0 client | +|`has_query_string`♠ | No | Boolean | Checks if request has a query string (even if empty) | +|`method`♠ |No | Method name | Checks if HTTP method is the one specified | +|`lua`♠ |No| String | Runs Lua function `matches(req)` inside String and checks if it returns `true` or `false` | + +*Can use subst. syntax* refers to the ability to reference the matched +pattern using the same substitution syntax used for the `rewrite as` or +`redirect to` actions. For instance, `condition cookie { some-cookie-name = +foo-%1-bar }` will substitute `%1` with the first match from the pattern +this condition is related to. Conditions marked with `♠` do not require a section, and can be written, for instance, as `condition has_query_string = yes`. diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index 39a883c1b..10f23b3ce 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -699,54 +699,6 @@ static void parse_condition_accept_encoding(struct pattern *pattern, } } -#ifdef HAVE_LUA -static void parse_condition_lua(struct pattern *pattern, - struct config *config, - const struct config_line *line) -{ - char *script = NULL; - - while ((line = config_read_line(config))) { - switch (line->type) { - case CONFIG_LINE_TYPE_SECTION: - config_error(config, "Unexpected section: %s", line->key); - goto out; - - case CONFIG_LINE_TYPE_SECTION_END: - if (!script) { - config_error(config, "Script not specified"); - goto out; - } - - pattern->condition.lua.script = script; - pattern->flags |= PATTERN_COND_LUA; - return; - - case CONFIG_LINE_TYPE_LINE: - if (streq(line->key, "script")) { - if (script) { - config_error(config, "Script already specified"); - goto out; - } - script = strdup(line->value); - if (!script) { - config_error(config, "Could not copy script"); - goto out; - } - } else { - config_error(config, "Unexpected key: %s", line->key); - goto out; - } - - break; - } - } - -out: - free(script); -} -#endif - static bool get_method_from_string(struct pattern *pattern, const char *string) { #define GENERATE_CMP(upper, lower, mask, constant) \ @@ -792,11 +744,6 @@ static void parse_condition(struct pattern *pattern, if (streq(line->value, "encoding")) { return parse_condition_accept_encoding(pattern, config); } -#ifdef HAVE_LUA - if (streq(line->value, "lua")) { - return parse_condition_lua(pattern, config, line); - } -#endif config_error(config, "Condition `%s' not supported", line->value); } @@ -852,7 +799,16 @@ static bool rewrite_parse_conf_pattern(struct private_data *pd, goto out; } pattern->flags |= PATTERN_COND_METHOD; - } else { + } else +#ifdef HAVE_LUA + if (streq(line->key, "condition_lua")) { + pattern->condition.lua.script = strdup(line->value); + if (!pattern->condition.lua.script) + lwan_status_critical("Couldn't copy Lua script"); + pattern->flags |= PATTERN_COND_LUA; + } else +#endif + { config_error(config, "Unexpected key: %s", line->key); goto out; } From 2d90fd97f5f4c38fce581b06e04ec77c33cb6f45 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 28 Sep 2021 08:40:11 -0700 Subject: [PATCH 1828/2505] Fix broken table in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e29c613a7..8cf13ff93 100644 --- a/README.md +++ b/README.md @@ -520,7 +520,7 @@ open a `condition` block, specify the condition type, and then the parameters for that condition to be evaluated: |Condition|Can use subst. syntax|Parameters|Description| -|---------|----------|-----------| +|---------|---------------------|----------|-----------| |`cookie` | Yes | A single `key` = `value`| Checks if request has cookie `key` has value `value` | |`query` | Yes | A single `key` = `value`| Checks if request has query variable `key` has value `value` | |`post` | Yes | A single `key` = `value`| Checks if request has post data `key` has value `value` | From d54fdeb5ae114461f589a593faafec39c42b1cb0 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 30 Sep 2021 21:17:09 -0700 Subject: [PATCH 1829/2505] Fix building with Glibc 2.34 Glibc 2.34 now defines SIGSTKSZ to a function call, so we can't use static_assert() anymore. Do the assertion only once now, during startup, in a constructor function. Fixes #318. --- src/lib/lwan-coro.c | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 081b42686..39af42c0f 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -62,22 +62,30 @@ void __asan_unpoison_memory_region(void const volatile *addr, size_t size); #define CORO_BUMP_PTR_ALLOC_SIZE 1024 -static_assert(DEFAULT_BUFFER_SIZE < CORO_STACK_SIZE, - "Request buffer fits inside coroutine stack"); - #if (!defined(NDEBUG) && defined(MAP_STACK)) || defined(__OpenBSD__) /* As an exploit mitigation, OpenBSD requires any stacks to be allocated via * mmap(... MAP_STACK ...). * * Also enable this on debug builds to catch stack overflows while testing * (MAP_STACK exists in Linux, but it's a no-op). */ - #define ALLOCATE_STACK_WITH_MMAP +#endif -static_assert((CORO_STACK_SIZE % PAGE_SIZE) == 0, - "Coroutine stack size is a multiple of page size"); -static_assert((CORO_STACK_SIZE >= PAGE_SIZE), - "Coroutine stack size is at least a page long"); +#ifndef NDEBUG +__attribute__((constructor)) static void assert_sizes_are_sane(void) +{ + /* This is done in runtime rather than during compilation time because + * in Glibc >= 2.34, SIGSTKSZ is defined as sysconf(_SC_MINSIGSTKSZ). */ + + /* Request buffer fits inside coroutine stack */ + assert(DEFAULT_BUFFER_SIZE < CORO_STACK_SIZE); +#ifdef ALLOCATE_STACK_WITH_MMAP + /* Coroutine stack size is a multiple of page size */ + assert((CORO_STACK_SIZE % PAGE_SIZE) == 0); + /* Coroutine stack size is at least a page long */ + assert((CORO_STACK_SIZE >= PAGE_SIZE)); +#endif +} #endif typedef void (*defer1_func)(void *data); From 5968917e43a70d55a57739d86c0d7146e3f4db16 Mon Sep 17 00:00:00 2001 From: Ananth Bhaskararaman Date: Sun, 3 Oct 2021 22:02:39 +0530 Subject: [PATCH 1830/2505] Build lwan container image and push to GitHub Container Registry. --- .github/workflows/container-images.yml | 39 ++++++++++++++++++++++++++ Containerfile | 15 ++++++++++ 2 files changed, 54 insertions(+) create mode 100644 .github/workflows/container-images.yml create mode 100644 Containerfile diff --git a/.github/workflows/container-images.yml b/.github/workflows/container-images.yml new file mode 100644 index 000000000..63b4f18a7 --- /dev/null +++ b/.github/workflows/container-images.yml @@ -0,0 +1,39 @@ +name: Build and publish lwan container images + +on: + release: + types: [published] + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push-image: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Build image + id: build-image + uses: redhat-actions/buildah-build@v2 + with: + image: ${{ env.IMAGE_NAME }} + tags: latest ${{ github.event.release.tag_name }} + dockerfiles: | + ./Containerfile + + - name: Push to ghcr.io + id: push-to-ghcr + uses: redhat-actions/push-to-registry@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + image: ${{ steps.build-image.outputs.image }} + tags: ${{ steps.build-image.outputs.tags }} diff --git a/Containerfile b/Containerfile new file mode 100644 index 000000000..077c7c119 --- /dev/null +++ b/Containerfile @@ -0,0 +1,15 @@ +FROM docker.io/library/alpine:3.14.2 AS build +RUN apk add --no-cache gcc make musl-dev cmake pkgconfig linux-headers \ + luajit-dev sqlite-dev zlib-dev brotli-dev zstd-dev +COPY . /lwan +WORKDIR /lwan/build +RUN cmake .. -DCMAKE_BUILD_TYPE=Release +RUN make -j + +FROM docker.io/library/alpine:3.14.2 +RUN apk add --no-cache luajit sqlite zlib brotli zstd-dev +COPY --from=build /lwan/build/src/bin/lwan/lwan . +COPY --from=build /lwan/lwan.conf . +EXPOSE 8080 +VOLUME /wwwroot +ENTRYPOINT ["/lwan"] \ No newline at end of file From fe4ce16f181ed37509b976b98ddf1d6745b5beb2 Mon Sep 17 00:00:00 2001 From: Ananth Bhaskararaman Date: Tue, 5 Oct 2021 09:35:27 +0530 Subject: [PATCH 1831/2505] Updated README.md. Added symlink to Dockerfile. --- Dockerfile | 1 + README.md | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 120000 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 120000 index 000000000..5240dc01e --- /dev/null +++ b/Dockerfile @@ -0,0 +1 @@ +Containerfile \ No newline at end of file diff --git a/README.md b/README.md index 8cf13ff93..3ca8209d9 100644 --- a/README.md +++ b/README.md @@ -635,6 +635,35 @@ section with a `basic` parameter, and set one of its options. | `realm` | `str` | `Lwan` | Realm for authorization. This is usually shown in the user/password UI in browsers | | `password_file` | `str` | `NULL` | Path for a file containing username and passwords (in clear text). The file format is the same as the configuration file format used by Lwan | +Container Images +---------------- +lwan container images are available at [ghcr.io/lperiera/lwan](https://ghcr.io/lperiera/lwan). +Container runtimes like [docker](https://docker.io) or [podman](https://podman.io) may be used to build and run lwan in a container. + + +### Pull lwan images from GHCR +Container images are tagged with release version numbers, so a specific version of lwan can be pulled. + + # latest version + docker pull ghcr.io/lperiera/lwan:latest + # pull a specific version + docker pull ghcr.io/lperiera/lwan:v0.3 + +### Build images locally +Clone the repository and use Containerfile (Dockerfile) to build lwan with all optional dependencies enabled. + + podman build -t lwan . + +### Run your image +The image expects to find static content at /wwwroot, so a volume containing your content can be mounted. + + docker run --rm -p 8080:8080 -v ./www:/wwwroot lwan + +To bring your own own lwan.conf, simply mount it at /lwan.conf. + + podman run --rm -p 8080:8080 -v ./lwan.conf:/lwan.conf lwan + + Hacking ------- From 73b50ad75104a0f9cb380432d60499ddceaf4086 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 5 Oct 2021 17:39:12 -0700 Subject: [PATCH 1832/2505] Tweak container registry information in README.md --- README.md | 58 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 3ca8209d9..61d2bd24a 100644 --- a/README.md +++ b/README.md @@ -635,33 +635,6 @@ section with a `basic` parameter, and set one of its options. | `realm` | `str` | `Lwan` | Realm for authorization. This is usually shown in the user/password UI in browsers | | `password_file` | `str` | `NULL` | Path for a file containing username and passwords (in clear text). The file format is the same as the configuration file format used by Lwan | -Container Images ----------------- -lwan container images are available at [ghcr.io/lperiera/lwan](https://ghcr.io/lperiera/lwan). -Container runtimes like [docker](https://docker.io) or [podman](https://podman.io) may be used to build and run lwan in a container. - - -### Pull lwan images from GHCR -Container images are tagged with release version numbers, so a specific version of lwan can be pulled. - - # latest version - docker pull ghcr.io/lperiera/lwan:latest - # pull a specific version - docker pull ghcr.io/lperiera/lwan:v0.3 - -### Build images locally -Clone the repository and use Containerfile (Dockerfile) to build lwan with all optional dependencies enabled. - - podman build -t lwan . - -### Run your image -The image expects to find static content at /wwwroot, so a volume containing your content can be mounted. - - docker run --rm -p 8080:8080 -v ./www:/wwwroot lwan - -To bring your own own lwan.conf, simply mount it at /lwan.conf. - - podman run --rm -p 8080:8080 -v ./lwan.conf:/lwan.conf lwan Hacking @@ -836,6 +809,7 @@ been seen in the wild. *Help build this list!* Some other distribution channels were made available as well: +* Container images are available from the [ghcr.io/lpereira/lwan](GitHub Container Registry). (More information below.) * A `Dockerfile` is maintained by [@jaxgeller](https://github.com/jaxgeller), and is [available from the Docker registry](https://hub.docker.com/r/jaxgeller/lwan/). * A buildpack for Heroku is maintained by [@bherrera](https://github.com/bherrera), and is [available from its repo](https://github.com/bherrera/heroku-buildpack-lwan). * Lwan is also available as a package in [Biicode](http://docs.biicode.com/c++/examples/lwan.html). @@ -872,6 +846,36 @@ Not really third-party, but alas: * The [author's blog](http://tia.mat.br). * The [project's webpage](http://lwan.ws). +Container Images +---------------- + +Lwan container images are available at +[ghcr.io/lpereira/lwan](https://ghcr.io/lpereira/lwan). Container runtimes +like [Docker](https://docker.io) or [Podman](https://podman.io) may be used +to build and run Lwan in a container. + +### Pull lwan images from GHCR +Container images are tagged with release version numbers, so a specific version of Lwan can be pulled. + + # latest version + docker pull ghcr.io/lperiera/lwan:latest + # pull a specific version + docker pull ghcr.io/lperiera/lwan:v0.3 + +### Build images locally +Clone the repository and use `Containerfile` (Dockerfile) to build Lwan with all optional dependencies enabled. + + podman build -t lwan . + +### Run your image +The image expects to find static content at `/wwwroot`, so a volume containing your content can be mounted. + + docker run --rm -p 8080:8080 -v ./www:/wwwroot lwan + +To bring your own `lwan.conf`, simply mount it at `/lwan.conf`. + + podman run --rm -p 8080:8080 -v ./lwan.conf:/lwan.conf lwan + Lwan quotes ----------- From be79edacd67f24b2a806da077f455f85b1741875 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 6 Oct 2021 08:45:42 -0700 Subject: [PATCH 1833/2505] Add workflow_dispatch event to the container-images workflow --- .github/workflows/container-images.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/container-images.yml b/.github/workflows/container-images.yml index 63b4f18a7..47ff11306 100644 --- a/.github/workflows/container-images.yml +++ b/.github/workflows/container-images.yml @@ -3,6 +3,7 @@ name: Build and publish lwan container images on: release: types: [published] + workflow_dispatch: env: REGISTRY: ghcr.io From 984a42727780e615c5b828fc0882fba183fc6800 Mon Sep 17 00:00:00 2001 From: sunzhenliang Date: Fri, 8 Oct 2021 10:58:44 +0800 Subject: [PATCH 1834/2505] Fix warning in implicit type conversion --- src/samples/clock/pong.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/samples/clock/pong.c b/src/samples/clock/pong.c index b44ebab69..02b54ad46 100644 --- a/src/samples/clock/pong.c +++ b/src/samples/clock/pong.c @@ -47,7 +47,7 @@ static void pong_time_update(struct pong_time *pong_time) strftime(digits, sizeof(digits), "%H%M", my_localtime(&cur_time)); for (int i = 0; i < 4; i++) - pong_time->time[i] = digits[i] - '0'; + pong_time->time[i] = (char)(digits[i] - '0'); pong_time->hour = pong_time->time[0] * 10 + pong_time->time[1]; pong_time->minute = pong_time->time[2] * 10 + pong_time->time[3]; From 5259d982ff1f798ad412fe71e9d6281ec644222d Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 11 Oct 2021 21:40:30 -0700 Subject: [PATCH 1835/2505] Add weighttp for benchmarking purposes --- README.md | 3 +- src/bin/tools/CMakeLists.txt | 5 +- src/bin/tools/COPYING.weighttp | 33 + src/bin/tools/weighttp.c | 2021 ++++++++++++++++++++++++++++++++ 4 files changed, 2060 insertions(+), 2 deletions(-) create mode 100644 src/bin/tools/COPYING.weighttp create mode 100644 src/bin/tools/weighttp.c diff --git a/README.md b/README.md index 61d2bd24a..8a6bd5198 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ The build system will look for these libraries and enable/link if available. - [Python](https://www.python.org/) (2.6+) with Requests - [Lua 5.1](http://www.lua.org) - To run benchmark: - - Special version of [Weighttp](https://github.com/lpereira/weighttp) + - [Weighttp](https://github.com/lpereira/weighttp) -- bundled and built alongside Lwan for convenience - [Matplotlib](https://github.com/matplotlib/matplotlib) - To build TechEmpower benchmark suite: - Client libraries for either [MySQL](https://dev.mysql.com) or [MariaDB](https://mariadb.org) @@ -110,6 +110,7 @@ This will generate a few binaries: - `src/bin/tools/mimegen`: Builds the extension-MIME type table. Used during build process. - `src/bin/tools/bin2hex`: Generates a C file from a binary file, suitable for use with #include. - `src/bin/tools/configdump`: Dumps a configuration file using the configuration reader API. + - `src/bin/tools/weighttp`: Rewrite of the `weighttp` HTTP benchmarking tool. #### Remarks diff --git a/src/bin/tools/CMakeLists.txt b/src/bin/tools/CMakeLists.txt index 275de1876..e5cc0b96b 100644 --- a/src/bin/tools/CMakeLists.txt +++ b/src/bin/tools/CMakeLists.txt @@ -40,5 +40,8 @@ else () ${CMAKE_SOURCE_DIR}/src/lib/missing.c ) - export(TARGETS configdump mimegen bin2hex FILE ${CMAKE_BINARY_DIR}/ImportExecutables.cmake) + add_executable(weighttp weighttp.c) + target_link_libraries(weighttp ${CMAKE_THREAD_LIBS_INIT}) + + export(TARGETS weighttp configdump mimegen bin2hex FILE ${CMAKE_BINARY_DIR}/ImportExecutables.cmake) endif () diff --git a/src/bin/tools/COPYING.weighttp b/src/bin/tools/COPYING.weighttp new file mode 100644 index 000000000..7328b4f41 --- /dev/null +++ b/src/bin/tools/COPYING.weighttp @@ -0,0 +1,33 @@ +Copyright (c) 2016, Glue Logic LLC. All rights reserved. code()gluelogic.com + +License: MIT License (see below) + +================ + +This rewrite is based on weighttp by Thomas Porzelt + git://git.lighttpd.net/weighttp + https://github.com/lighttpd/weighttp/ + +================ + +The MIT License + +Copyright (c) 2009-2011 Thomas Porzelt + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/bin/tools/weighttp.c b/src/bin/tools/weighttp.c new file mode 100644 index 000000000..4c32102f1 --- /dev/null +++ b/src/bin/tools/weighttp.c @@ -0,0 +1,2021 @@ +/* + * weighttp - a lightweight and simple webserver benchmarking tool + * + * Copyright (c) 2016, Glue Logic LLC. All rights reserved. code()gluelogic.com + * + * This rewrite is based on weighttp by Thomas Porzelt + * Copyright (c) 2009-2011 Thomas Porzelt + * git://git.lighttpd.net/weighttp + * https://github.com/lighttpd/weighttp/ + * + * License: + * MIT, see COPYING.weighttp file + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE 1 +#endif + +#pragma GCC diagnostic ignored "-Wsign-conversion" +#pragma GCC diagnostic ignored "-Wconversion" +#pragma GCC diagnostic ignored "-Wvla" + +#include +#include /* socket() connect() SOCK_NONBLOCK sockaddr_storage */ +#include /* fstat() */ +#include /* gettimeofday() */ +#include /* errno EINTR EAGAIN EWOULDBLOCK EINPROGRESS EALREADY */ +#include /* open() fcntl() pipe2() F_SETFL (O_* flags) */ +#include /* PRIu64 PRId64 */ +#include /* USHRT_MAX */ +#include /* setlocale() */ +#include /* getaddrinfo() freeaddrinfo() */ +#include /* poll() POLLIN POLLOUT POLLERR POLLHUP */ +#include /* pthread_create() pthread_join() */ +#include /* va_start() va_end() vfprintf() */ +#include +#include /* calloc() free() exit() strtoul() strtoull() */ +#include /* UINT32_MAX */ +#include /* signal() */ +#include +#include /* strcasecmp() strncasecmp() */ +#include /* read() write() close() getopt() optarg optind optopt*/ + +#include +#include +#include +#include + +#ifndef MSG_DONTWAIT +#define MSG_DONTWAIT 0 +#endif +#ifndef MSG_NOSIGNAL +#define MSG_NOSIGNAL 0 +#endif +#ifndef SOCK_NONBLOCK +#define SOCK_NONBLOCK 0 +#endif + +#ifndef PACKAGE_VERSION +#define PACKAGE_VERSION "" +#endif + + +/*(oversimplified; these attributes are supported by some other compilers)*/ +#if defined(__GNUC__) || defined(__clang__) +#ifndef __attribute_cold__ +#define __attribute_cold__ __attribute__((__cold__)) +#endif +#ifndef __attribute_hot__ +#define __attribute_hot__ __attribute__((__hot__)) +#endif +#ifndef __attribute_noinline__ +#define __attribute_noinline__ __attribute__((__noinline__)) +#endif +#ifndef __attribute_nonnull__ +#define __attribute_nonnull__ __attribute__((__nonnull__)) +#endif +#ifndef __attribute_noreturn__ +#define __attribute_noreturn__ __attribute__((__noreturn__)) +#endif +#ifndef __attribute_pure__ +#define __attribute_pure__ __attribute__((__pure__)) +#endif +#ifndef __attribute_format__ +#define __attribute_format__(x) __attribute__((__format__ x)) +#endif +#else +#ifndef __builtin_expect +#define __builtin_expect(x, y) (x) +#endif +#ifndef __attribute_cold__ +#define __attribute_cold__ +#endif +#ifndef __attribute_hot__ +#define __attribute_hot__ +#endif +#ifndef __attribute_noinline__ +#define __attribute_noinline__ +#endif +#ifndef __attribute_nonnull__ +#define __attribute_nonnull__ +#endif +#ifndef __attribute_noreturn__ +#define __attribute_noreturn__ +#endif +#ifndef __attribute_pure__ +#define __attribute_pure__ +#endif +#ifndef __attribute_format__ +#define __attribute_format__(x) +#endif +#endif + + +__attribute_cold__ +__attribute_noinline__ +static void +show_version (void) +{ + puts("\nweighttp " PACKAGE_VERSION + " - a lightweight and simple webserver benchmarking tool\n"); +} + + +__attribute_cold__ +__attribute_noinline__ +static void +show_help (void) +{ + puts( + "weighttp \n" + " -n num number of requests (mandatory)\n" + " -t num thread count (default: 1)\n" + " -c num concurrent clients (default: 1)\n" + " -k keep alive (default: no)\n" + " -K num num pipelined requests (default: 1)\n" + " -6 use ipv6 (default: no)\n" + " -i use HTTP HEAD method (default: GET)\n" + " -m method use custom HTTP method (default: GET)\n" + " -H str add header to request (\"label: value\"); repeatable\n" + " -b size socket buffer sizes (SO_SNDBUF, SO_RCVBUF)\n" + " -B addr local address to bind to when making outgoing connections\n" + " -C cookie add cookie to request (\"cookie-name=value\"); repeatable\n" + " -F use TCP Fast Open (RFC 7413)\n" + " -T type Content-Type header to use for POST/PUT data,\n" + " e.g. application/x-www-form-urlencoded\n" + " (default: text/plain)\n" + " -A string add Basic WWW Authorization (str is username:password)\n" + " -P string add Basic Proxy-Authorization (str is username:password)\n" + " -X proxy proxy:port or unix domain socket path beginning w/ '/'\n" + " -p file make HTTP POST request using file contents for body\n" + " -u file make HTTP PUT request using file contents for body\n" + " -d (ignored; compatibility with Apache Bench (ab))\n" + " -l (ignored; compatibility with Apache Bench (ab))\n" + " -r (ignored; compatibility with Apache Bench (ab))\n" + " -q quiet: do not show version header or progress\n" + " -h show help and exit\n" + " -V show version and exit\n\n" + "example: \n" + " weighttpd -n 500000 -c 100 -t 2 -K 64 http://localhost/index.html\n"); +} + +/* Notes regarding pipelining + * Enabling pipelining (-p x where x > 1) results in extra requests being sent + * beyond the precise number requested on the command line. Subsequently, + * extra bytes might be read and reported in stats at the end of the test run. + * Additionally, the extra requests are dropped once the req_todo amount is + * reached, and so the target web server(s) might report errors that client + * dropped connection (client disconnect) for those final requests. + * + * The benefits of pipelining include reduced latency between request/response, + * as well as potentially fewer socket read()s for data if multiple requests or + * multiple responses are available to be read by server or client, respectively + */ + +#define CLIENT_BUFFER_SIZE 32 * 1024 + + +struct Stats; +typedef struct Stats Stats; +struct Client; +typedef struct Client Client; +struct Worker; +typedef struct Worker Worker; +struct Config; +typedef struct Config Config; +struct Worker_Config; +typedef struct Worker_Config Worker_Config; + + +struct Stats { + uint64_t req_todo; /* total num of requests to do */ + uint64_t req_started; /* total num of requests started */ + uint64_t req_done; /* total num of requests done */ + uint64_t req_success; /* total num of successful requests */ + uint64_t req_failed; /* total num of failed requests */ + uint64_t req_error; /* total num of errored requests */ + uint64_t bytes_total; /* total num of bytes received (headers+body) */ + uint64_t bytes_headers; /* total num of bytes received (headers) */ + uint64_t req_2xx; + uint64_t req_3xx; + uint64_t req_4xx; + uint64_t req_5xx; +}; + +struct Client { + int revents; + enum { + PARSER_CONNECT, + PARSER_START, + PARSER_HEADER, + PARSER_BODY + } parser_state; + + uint32_t buffer_offset; /* pos in buffer (size of data in buffer) */ + uint32_t parser_offset; /* pos in parsing (behind buffer_offset) */ + uint32_t request_offset; /* pos in sending request */ + int chunked; + int64_t content_length; + int64_t chunk_size; + int64_t chunk_received; + int http_status_success; + int config_keepalive; + int keepalive; + int keptalive; + int pipelined; + int pipeline_max; + int tcp_fastopen; + int http_head; + int so_bufsz; + + uint32_t request_size; + const char *request; + struct pollfd *pfd; + Stats *stats; + const struct addrinfo *raddr; + const struct addrinfo *laddr; + char buffer[CLIENT_BUFFER_SIZE]; +}; + +struct Worker { + struct pollfd *pfds; + Client *clients; + Stats stats; + struct addrinfo raddr; + struct addrinfo laddr; + struct sockaddr_storage raddr_storage; + struct sockaddr_storage laddr_storage; +}; + +struct Worker_Config { + const Config *config; + int id; + int num_clients; + uint64_t num_requests; + Stats stats; + /* pad struct Worker_Config for cache line separation between threads. + * Round up to 256 to avoid chance of false sharing between threads. + * Alternatively, could memalign the allocation of struct Worker_Config + * list to cache line size (e.g. 128 bytes) */ + uint64_t padding[(256 - (1*sizeof(void *)) + - (2*sizeof(int)) + - (1*sizeof(uint64_t)) + - sizeof(Stats)) + / sizeof(uint64_t)]; +}; + +struct Config { + Worker_Config *wconfs; + char *proxy; + struct timeval ts_start; + struct timeval ts_end; + + uint64_t req_count; + int thread_count; + int keep_alive; + int concur_count; + int pipeline_max; + int tcp_fastopen; + int http_head; + int so_bufsz; + + int quiet; + uint32_t request_size; + char *request; + char buf[16384]; /*(used for simple 8k memaligned request buffer on stack)*/ + struct addrinfo raddr; + struct addrinfo laddr; + struct sockaddr_storage raddr_storage; + struct sockaddr_storage laddr_storage; + struct laddrs { + struct addrinfo **addrs; + int num; + } laddrs; +}; + + +__attribute_cold__ +__attribute_nonnull__ +static void +client_init (Worker * const restrict worker, + const Config * const restrict config, + const int i) +{ + Client * const restrict client = worker->clients+i; + client->pfd = worker->pfds+i; + client->pfd->fd = -1; + client->parser_state = PARSER_CONNECT; + + client->stats = &worker->stats; + client->raddr = &worker->raddr; + client->laddr = config->laddrs.num > 0 + ? config->laddrs.addrs[(i % config->laddrs.num)] + : (0 != worker->laddr.ai_addrlen) ? &worker->laddr : NULL; + client->config_keepalive = config->keep_alive; + client->pipeline_max = config->pipeline_max; + client->tcp_fastopen = config->tcp_fastopen; + client->http_head = config->http_head; + client->so_bufsz = config->so_bufsz; + client->request_size = config->request_size; + client->request = config->request; + /* future: might copy config->request to new allocation in Worker + * so that all memory accesses during benchmark execution are to + * independent, per-thread allocations */ +} + + +__attribute_cold__ +__attribute_nonnull__ +static void +client_delete (const Client * const restrict client) +{ + if (-1 != client->pfd->fd) + close(client->pfd->fd); +} + + +__attribute_cold__ +__attribute_nonnull__ +__attribute_noinline__ +static void +worker_init (Worker * const restrict worker, + Worker_Config * const restrict wconf) +{ + const Config * const restrict config = wconf->config; + memset(worker, 0, sizeof(Worker)); + memcpy(&worker->laddr, &config->laddr, sizeof(config->laddr)); + memcpy(&worker->raddr, &config->raddr, sizeof(config->raddr)); + if (config->laddr.ai_addrlen) + worker->laddr.ai_addr = (struct sockaddr *) + memcpy(&worker->laddr_storage, + &config->laddr_storage, config->laddr.ai_addrlen); + worker->raddr.ai_addr = (struct sockaddr *) + memcpy(&worker->raddr_storage, + &config->raddr_storage, config->raddr.ai_addrlen); + const int num_clients = wconf->num_clients; + worker->stats.req_todo = wconf->num_requests; + worker->pfds = (struct pollfd *)calloc(num_clients, sizeof(struct pollfd)); + worker->clients = (Client *)calloc(num_clients, sizeof(Client)); + for (int i = 0; i < num_clients; ++i) + client_init(worker, wconf->config, i); +} + + +__attribute_cold__ +__attribute_nonnull__ +__attribute_noinline__ +static void +worker_delete (Worker * const restrict worker, + Worker_Config * const restrict wconf) +{ + int i; + const int num_clients = wconf->num_clients; + + /* adjust bytes_total to discard count of excess responses + * (> worker->stats.req_todo) */ + if (worker->clients[0].pipeline_max > 1) { + for (i = 0; i < num_clients; ++i) { + worker->stats.bytes_total -= ( worker->clients[i].buffer_offset + - worker->clients[i].parser_offset ); + } + } + + memcpy(&wconf->stats, &worker->stats, sizeof(Stats)); + for (i = 0; i < num_clients; ++i) + client_delete(worker->clients+i); + free(worker->clients); + free(worker->pfds); +} + + +__attribute_cold__ +__attribute_noinline__ +__attribute_nonnull__ +static void +wconfs_init (Config * const restrict config) +{ + /* create Worker_Config data structures for each (future) thread */ + Worker_Config * const restrict wconfs = + (Worker_Config *)calloc(config->thread_count, sizeof(Worker_Config)); + + uint32_t rest_concur = config->concur_count % config->thread_count; + uint32_t rest_req = config->req_count % config->thread_count; + + for (int i = 0; i < config->thread_count; ++i) { + uint64_t reqs = config->req_count / config->thread_count; + int concur = config->concur_count / config->thread_count; + + if (rest_concur) { + concur += 1; + rest_concur -= 1; + } + + if (rest_req) { + reqs += 1; + rest_req -= 1; + } + + if (!config->quiet) + printf("spawning thread #%d: %d concurrent requests, " + "%"PRIu64" total requests\n", i+1, concur, reqs); + + wconfs[i].config = config; + wconfs[i].id = i; + wconfs[i].num_clients = concur; + wconfs[i].num_requests = reqs; + } + + config->wconfs = wconfs; +} + + +__attribute_cold__ +__attribute_noinline__ +__attribute_nonnull__ +static void +wconfs_delete (const Config * const restrict config) +{ + free(config->wconfs); + if (config->request < config->buf + || config->buf+sizeof(config->buf) <= config->request) + free(config->request); + + if (config->laddrs.num > 0) { + for (int i = 0; i < config->laddrs.num; ++i) + freeaddrinfo(config->laddrs.addrs[i]); + free(config->laddrs.addrs); + } +} + + +__attribute_hot__ +__attribute_nonnull__ +static void +client_reset (Client * const restrict client, const int success) +{ + /* update worker stats */ + Stats * const restrict stats = client->stats; + + ++stats->req_done; + if (__builtin_expect( (0 != success), 1)) + ++stats->req_success; + else + ++stats->req_failed; + + client->revents = (stats->req_started < stats->req_todo) ? POLLOUT : 0; + if (client->revents && client->keepalive) { + /*(assumes writable; will find out soon if not and register interest)*/ + ++stats->req_started; + client->parser_state = PARSER_START; + client->keptalive = 1; + if (client->parser_offset == client->buffer_offset) { + client->parser_offset = 0; + client->buffer_offset = 0; + } + #if 0 + else if (client->parser_offset > (CLIENT_BUFFER_SIZE/2)) { + memmove(client->buffer, client->buffer+client->parser_offset, + client->buffer_offset - client->parser_offset + 1); + client->buffer_offset -= client->parser_offset; + client->parser_offset = 0; + } + /* future: if we tracked size of headers for first successful response, + * we might use that size to determine whether or not to memmove() + * any remaining contents in client->buffer to the beginning of buffer, + * e.g. if parser_offset + expected_response_len exceeds buffer size + * On the size, if we expect to already have completed response fully + * received in buffer, then skip the memmove(). */ + #endif + if (--client->pipelined && client->buffer_offset) + client->revents |= POLLIN; + } + else { + close(client->pfd->fd); + client->pfd->fd = -1; + client->pfd->events = 0; + /*client->pfd->revents = 0;*/ + client->parser_state = PARSER_CONNECT; + } +} + + +__attribute_cold__ +__attribute_noinline__ +__attribute_nonnull__ +static void +client_error (Client * const restrict client) +{ + ++client->stats->req_error; + if (client->parser_state != PARSER_BODY) { + /*(might include subsequent responses to pipelined requests, but + * some sort of invalid response received if client_error() called)*/ + client->stats->bytes_headers += + (client->buffer_offset - client->parser_offset); + client->buffer_offset = 0; + client->parser_offset = 0; + } + client->keepalive = 0; + client_reset(client, 0); +} + + +__attribute_cold__ +__attribute_noinline__ +__attribute_nonnull__ +static void +client_perror (Client * const restrict client, const char * const restrict tag) +{ + const int errnum = errno; + client->buffer[0] = '\0'; + #if defined(_GNU_SOURCE) && defined(__GLIBC__) + const char * const errstr = + strerror_r(errnum, client->buffer, sizeof(client->buffer)); + #else /* XSI-compliant strerror_r() */ + const char * const errstr = client->buffer; + strerror_r(errnum, client->buffer, sizeof(client->buffer)); + #endif + fprintf(stderr, "error: %s failed: (%d) %s\n", tag, errnum, errstr); + client_error(client); +} + + +__attribute_nonnull__ +static void +client_connected (Client * const restrict client) +{ + client->request_offset = 0; + client->buffer_offset = 0; + client->parser_offset = 0; + client->parser_state = PARSER_START; + client->pipelined = 0; + client->keepalive = client->config_keepalive; + client->keptalive = 0; + /*client->success = 0;*/ +} + + +__attribute_noinline__ +__attribute_nonnull__ +static int +client_connect (Client * const restrict client) +{ + const struct addrinfo * const restrict raddr = client->raddr; + int fd = client->pfd->fd; + int opt; + + if (-1 == fd) { + ++client->stats->req_started; + + do { + fd = socket(raddr->ai_family,raddr->ai_socktype,raddr->ai_protocol); + } while (__builtin_expect( (-1 == fd), 0) && errno == EINTR); + + if (fd >= 0) { + #if !SOCK_NONBLOCK + fcntl(fd, F_SETFL, O_NONBLOCK | O_RDWR); /* set non-blocking */ + #endif + client->pfd->fd = fd; + } + else { + client_perror(client, "socket()"); + return 0; + } + + if (1 == client->pipeline_max && raddr->ai_family != AF_UNIX) { + /* disable Nagle if not pipelining requests and not AF_UNIX + * (pipelining enables keepalive, but if not pipelining but + * keepalive enabled, still want to disable Nagle to reduce latency + * when sending next keepalive request after receiving response) */ + opt = 1; + setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)); + } + + if (0 != client->so_bufsz) { + opt = client->so_bufsz; + if (0 != setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &opt, sizeof(opt))) + client_perror(client, "setsockopt() SO_SNDBUF"); + if (0 != setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt))) + client_perror(client, "setsockopt() SO_RCVBUF"); + } + + if (raddr->ai_family != AF_UNIX) { + /*(might not be correct for real clients, but ok for load test)*/ + struct linger l = { .l_onoff = 1, .l_linger = 0 }; + if (0 != setsockopt(fd, SOL_SOCKET, SO_LINGER, &l, sizeof(l))) + client_perror(client, "setsockopt() SO_LINGER"); + } + + if (NULL != client->laddr) { + if (0 != bind(fd,client->laddr->ai_addr,client->laddr->ai_addrlen)){ + client_perror(client, "bind() (local addr)"); + return 0; + } + } + + int rc; + #ifdef TCP_FASTOPEN + ssize_t wr = 0; + if (client->tcp_fastopen) {/*(disabled if config->proxy is AF_UNIX)*/ + wr = sendto(fd, client->request, client->request_size, + MSG_FASTOPEN | MSG_DONTWAIT | MSG_NOSIGNAL, + raddr->ai_addr, raddr->ai_addrlen); + if (wr > 0) { + client_connected(client); + if (client->request_size == (uint32_t)wr) { + client->pfd->events |= POLLIN; + if (++client->pipelined == client->pipeline_max) { + client->revents &= ~POLLOUT; + client->pfd->events &= ~POLLOUT; + } + } + else + client->request_offset = (uint32_t)wr; + return 1; + } + else if (-1 == wr && errno == EOPNOTSUPP) + wr = 0; + else { + /*(0 == wr with sendto() should not happen + * with MSG_FASTOPEN and non-zero request_size)*/ + wr = -1; + rc = -1; + } + } + if (0 == wr) + #endif + do { + rc = connect(fd, raddr->ai_addr, raddr->ai_addrlen); + } while (__builtin_expect( (-1 == rc), 0) && errno == EINTR); + + if (0 != rc) { + switch (errno) { + case EINPROGRESS: + case EALREADY: + /* async connect now in progress */ + client->revents &= ~POLLOUT; + client->pfd->events |= POLLOUT; + return 0; + default: + client_perror(client, "connect()"); + return 0; + } + } + } + else { + opt = 0; + socklen_t optlen = sizeof(opt); + if (0 != getsockopt(fd,SOL_SOCKET,SO_ERROR,&opt,&optlen) || 0 != opt) { + if (0 != opt) errno = opt; + client_perror(client, "connect() getsockopt()"); + return 0; /* error connecting */ + } + } + + /* successfully connected */ + client_connected(client); + return 1; +} + + +__attribute_nonnull__ +static int +client_parse_chunks (Client * const restrict client) +{ + do { + char *str = client->buffer+client->parser_offset; + + if (-1 == client->chunk_size) { + /* read chunk size */ + /*char *end = strchr(str, '\n');*/ + char *end = + memchr(str, '\n', client->buffer_offset - client->parser_offset); + if (!end) /* partial line */ + return 1; + ++end; + + /* assume server sends valid chunked header + * (not validating; (invalid) chunked header without any + * hex digits is treated as 0-chunk, ending input) */ + client->chunk_size = 0; + do { + int c = *str; + client->chunk_size <<= 4; + if (c >= '0' && c <= '9') + client->chunk_size |= (c - '0'); + else if ((c |= 0x20) >= 'a' && c <= 'f') + client->chunk_size |= (c - 'a' + 10); + else { + if (c=='\r' || c=='\n' || c==' ' || c=='\t' || c==';') + break; + client_error(client); + return 0; + } + } while (*++str != '\r' && *str != '\n'); + + if (0 == client->chunk_size) { + /* chunk of size 0 marks end of content body + * check for final "\r\n" ending response + * (not handling trailers if user supplied -H "TE: trailers") */ + if (end + 2 > client->buffer + client->buffer_offset) { + client->chunk_size = -1; + return 1; /* final "\r\n" not yet received */ + } + if (end[0] == '\r' && end[1] == '\n') + client->stats->bytes_headers += 2; + else + client->keepalive = 0; /*(just close con if trailers)*/ + client->parser_offset = end - client->buffer + 2; + client_reset(client, client->http_status_success); + return 0; /*(trigger loop continue in caller)*/ + } + + client->parser_offset = end - client->buffer; + client->chunk_received = 0; + client->chunk_size += 2; /*(for chunk "\r\n" end)*/ + } + + /* consume chunk until chunk_size is reached */ + const int rd = client->buffer_offset - client->parser_offset; + int chunk_remain = client->chunk_size - client->chunk_received; + if (rd >= chunk_remain) { + client->chunk_received += chunk_remain; + client->parser_offset += chunk_remain; + + if (client->buffer[client->parser_offset-1] != '\n') { + client_error(client); + return 0; + } + + /* got whole chunk, next! */ + client->chunk_size = -1; + client->chunk_received = 0; + } + else { + client->chunk_received += rd; + client->parser_offset += rd; + } + + } while (client->parser_offset != client->buffer_offset);/* more to parse */ + + client->parser_offset = 0; + client->buffer_offset = 0; + return 1; +} + + +__attribute_hot__ +__attribute_nonnull__ +__attribute_pure__ +static uint64_t +client_parse_uint64 (const char * const restrict str) +{ + /* quick-n-dirty conversion of numerical string to integral number + * Note: not validating field and not checking for valid number + * (weighttp not intended for use with requests > 2 GB, as transfer + * of body would take the majority of the time in that case)*/ + uint64_t x = 0; + for (int i = 0; (unsigned int)(str[i] - '0') < 10u; ++i) { + x *= 10; + x += (unsigned int)(str[i] - '0'); + } + return x; +} + + +__attribute_hot__ +__attribute_noinline__ +__attribute_nonnull__ +static int +client_parse (Client * const restrict client) +{ + char *end; + uint32_t len; + + /* future: might combine PARSER_START and PARSER_HEADER states by + * collecting entire set of headers (reading until "\r\n\r\n") + * prior to parsing */ + + switch (client->parser_state) { + + case PARSER_START: + /* look for HTTP/1.1 200 OK (though also accept HTTP/1.0 200) + * Note: does not support 1xx intermediate messages */ + /* Note: not validating response line; assume valid */ + /*end = strchr(client->buffer+client->parser_offset, '\n');*/ + end = memchr(client->buffer+client->parser_offset, '\n', + client->buffer_offset - client->parser_offset); + if (NULL != end) { + len = (uint32_t)(end - client->buffer - client->parser_offset + 1); + if (len < sizeof("HTTP/1.1 200\r\n")-1) { + client_error(client); + return 0; + } + } + else /*(partial response line; incomplete)*/ + return 1; + + client->content_length = -1; + client->chunked = 0; + client->http_status_success = 1; + switch (client->buffer[client->parser_offset + sizeof("HTTP/1.1 ")-1] + - '0') { + case 2: + ++client->stats->req_2xx; + break; + case 3: + ++client->stats->req_3xx; + break; + case 4: + client->http_status_success = 0; + ++client->stats->req_4xx; + break; + case 5: + client->http_status_success = 0; + ++client->stats->req_5xx; + break; + default: + /* invalid status code */ + client_error(client); + return 0; + } + client->stats->bytes_headers += len; + client->parser_offset += len; + client->parser_state = PARSER_HEADER; + /* fall through */ + + case PARSER_HEADER: + /* minimally peek at Content-Length, Connection, Transfer-Encoding */ + do { + const char *str = client->buffer+client->parser_offset; + /*end = strchr(str, '\n');*/ + end = + memchr(str, '\n', client->buffer_offset - client->parser_offset); + if (NULL == end) + return 1; + len = (uint32_t)(end - str + 1); + client->stats->bytes_headers += len; + client->parser_offset += len; + + /* minimum lengths for us to check for ':' in the following: + * "Content-Length:0\r\n" + * "Connection:close\r\n" + * "Transfer-Encoding:chunked\r\n"*/ + if (end - str < 17) + continue; + + if (str[14] == ':' + && (0 == memcmp(str, "Content-Length", + sizeof("Content-Length")-1) + || 0 == strncasecmp(str, "Content-Length", + sizeof("Content-Length")-1))) { + str += sizeof("Content-Length:")-1; + if (__builtin_expect( (*str == ' '), 1)) + ++str; + while (__builtin_expect( (*str == ' '), 0) + || __builtin_expect( (*str == '\t'), 0)) + ++str; + client->content_length = client_parse_uint64(str); + } + else if (str[10] == ':' + && (0 == memcmp(str, "Connection", + sizeof("Connection")-1) + || 0 == strncasecmp(str, "Connection", + sizeof("Connection")-1))) { + str += sizeof("Connection:")-1; + if (__builtin_expect( (*str == ' '), 1)) + ++str; + while (__builtin_expect( (*str == ' '), 0) + || __builtin_expect( (*str == '\t'), 0)) + ++str; + if ((*str | 0x20) == 'c') /*(assume "close")*/ + client->keepalive = 0; + } + else if (str[17] == ':' + && (0 == memcmp(str, "Transfer-Encoding", + sizeof("Transfer-Encoding")-1) + || 0 == strncasecmp(str, "Transfer-Encoding", + sizeof("Transfer-Encoding")-1))) { + client->chunked = 1; /*(assume "chunked")*/ + client->chunk_size = -1; + client->chunk_received = 0; + } + + } while (end[1] != '\r' || end[2] != '\n'); + + /* body reached */ + client->stats->bytes_headers += 2; + client->parser_offset += 2; + client->parser_state = PARSER_BODY; + if (client->http_head) + client->content_length = 0; + else if (!client->chunked && -1 == client->content_length) + client->keepalive = 0; + /* fall through */ + + case PARSER_BODY: + /* consume and discard response body */ + + if (client->chunked) + return client_parse_chunks(client); + else { + /* consume all data until content-length reached (or EOF) */ + if (-1 != client->content_length) { + uint32_t rd = client->buffer_offset - client->parser_offset; + if (client->content_length > rd) + client->content_length -= rd; + else { /* full response received */ + client->parser_offset += client->content_length; + client_reset(client, client->http_status_success); + return 0; /*(trigger loop continue in caller)*/ + } + } + + client->buffer_offset = 0; + client->parser_offset = 0; + return 1; + } + + case PARSER_CONNECT: /*(should not happen here)*/ + break; + } + + return 1; +} + + +__attribute_nonnull__ +static void +client_revents (Client * const restrict client) +{ + while (client->revents & POLLIN) { + /* parse pipelined responses */ + if (client->buffer_offset && !client_parse(client)) + continue; + + ssize_t r; + do { + r = recv(client->pfd->fd, client->buffer+client->buffer_offset, + sizeof(client->buffer) - client->buffer_offset - 1, + MSG_DONTWAIT); + } while (__builtin_expect( (-1 == r), 0) && errno == EINTR); + if (__builtin_expect( (r > 0), 1)) { + if (r < (ssize_t)(sizeof(client->buffer)-client->buffer_offset-1)) + client->revents &= ~POLLIN; + client->buffer[(client->buffer_offset += (uint32_t)r)] = '\0'; + client->stats->bytes_total += r; + + if (!client_parse(client)) + continue; + + /* PARSER_BODY handling consumes data, so buffer full might happen + * only when parsing response header line or chunked header line. + * If buffer is full, then line is *way* too long. However, if + * client->parser_offset is non-zero, then move data to beginning + * of buffer and attempt to read() more */ + if (__builtin_expect( + (client->buffer_offset == sizeof(client->buffer)-1), 0)) { + if (0 == client->parser_offset) { + client_error(client); /* response header too big */ + break; + } + else { + memmove(client->buffer,client->buffer+client->parser_offset, + client->buffer_offset - client->parser_offset + 1); + client->buffer_offset -= client->parser_offset; + client->parser_offset = 0; + } + } + } + else { + if (-1 == r) { /* error */ + if (errno == EAGAIN + #if EAGAIN != EWOULDBLOCK + || errno == EWOULDBLOCK + #endif + ) { + client->revents &= ~POLLIN; + client->pfd->events |= POLLIN; + break; + } + else + client_perror(client, "read()"); + } + else { /* disconnect; evaluate if end-of-response or error */ + if (client->http_status_success + && client->parser_state == PARSER_BODY + && !client->chunked && -1 == client->content_length) { + client->keepalive = 0; + client_reset(client, 1); + } + else { + if (client->keptalive + && client->parser_state == PARSER_START + && 0 == client->buffer_offset) { + /* (server might still read and discard request, + * but has initiated connection close) + * (decrement counters to redo request, including + * decrementing counters that will be incremented + * by call to client_error() directly below) */ + --client->stats->req_started; + --client->stats->req_failed; + --client->stats->req_error; + --client->stats->req_done; + } + client_error(client); + } + } + } + } + + if (__builtin_expect( (client->revents & (POLLERR|POLLHUP)), 0)) { + client->keepalive = 0; + client_reset(client, 0); + } + + while (client->revents & POLLOUT) { + ssize_t r; + if (client->parser_state == PARSER_CONNECT && !client_connect(client)) + continue; + + do { + r = send(client->pfd->fd, + client->request+client->request_offset, + client->request_size - client->request_offset, + MSG_DONTWAIT | MSG_NOSIGNAL); + } while (__builtin_expect( (-1 == r), 0) && errno == EINTR); + if (__builtin_expect( (r > 0), 1)) { + if (client->request_size == (uint32_t)r + || client->request_size==(client->request_offset+=(uint32_t)r)){ + /* request sent; register read interest for response */ + client->request_offset = 0; + client->pfd->events |= POLLIN; + if (++client->pipelined < client->pipeline_max) + continue; + else { + client->revents &= ~POLLOUT; /*(trigger write() loop exit)*/ + client->pfd->events &= ~POLLOUT; + } + } + else { + client->revents &= ~POLLOUT; /*(trigger write() loop exit)*/ + client->pfd->events |= POLLOUT; + } + } + else { + if (-1 == r) { /* error */ + if (errno == EAGAIN + #if EAGAIN != EWOULDBLOCK + || errno == EWOULDBLOCK + #endif + ) { + client->revents &= ~POLLOUT; + client->pfd->events |= POLLOUT; + break; + } + else + client_perror(client, "write()"); + } + else { /* (0 == r); not expected; not attempting to write 0 bytes */ + client->keepalive = 0; + client_reset(client, 0); + } + } + } +} + + +__attribute_nonnull__ +static void * +worker_thread (void * const arg) +{ + Worker worker; + int i, nready; + Worker_Config * const restrict wconf = (Worker_Config *)arg; + worker_init(&worker, wconf); + + const int num_clients = wconf->num_clients; + const int progress = + (0==wconf->id && !wconf->config->quiet); /* report only in first thread */ + const uint64_t progress_interval = /* print every 10% done */ + (worker.stats.req_todo > 10) ? worker.stats.req_todo / 10 : 1; + uint64_t progress_next = progress_interval; + + /* start all clients */ + for (i = 0; i < num_clients; ++i) { + if (worker.stats.req_started < worker.stats.req_todo) { + worker.clients[i].revents = POLLOUT; + client_revents(worker.clients+i); + } + } + + while (worker.stats.req_done < worker.stats.req_todo) { + do { /*(infinite wait)*/ + nready = poll(worker.pfds, (nfds_t)num_clients, -1); + } while (__builtin_expect( (-1 == nready), 0) && errno == EINTR); + if (__builtin_expect( (-1 == nready), 0)) { + /*(repurpose client_perror(); use client buffer for strerror_r())*/ + client_perror(worker.clients+0, "poll()"); /* fatal; ENOMEM */ + return NULL; + } + + i = 0; + do { + while (0 == worker.pfds[i].revents) + ++i; + worker.clients[i].revents |= worker.pfds[i].revents; + worker.pfds[i].revents = 0; + client_revents(worker.clients+i); + } while (--nready); + + if (progress) { + /*(assume progress of one thread approximates that of all threads)*/ + /*(RFE: main thread could poll and report progress of all workers)*/ + while (__builtin_expect( worker.stats.req_done >= progress_next,0)){ + printf("progress: %3d%% done\n", (int) + (worker.stats.req_done * 100 / worker.stats.req_todo)); + if (progress_next == worker.stats.req_todo) + break; + progress_next += progress_interval; + if (__builtin_expect( progress_next > worker.stats.req_todo, 0)) + progress_next = worker.stats.req_todo; + } + } + } + + worker_delete(&worker, wconf); + return NULL; +} + + +__attribute_cold__ +__attribute_noinline__ +__attribute_nonnull__ +static void +config_error_diagnostic (const char * const restrict errfmt, + const int perr, va_list ap) +{ + const int errnum = errno; + show_version(); + show_help(); + fflush(stdout); + + fprintf(stderr, "\nerror: "); + vfprintf(stderr, errfmt, ap); + + if (!perr) + fprintf(stderr, "\n\n"); + else { + char buf[1024]; + buf[0] = '\0'; + #if defined(_GNU_SOURCE) && defined(__GLIBC__) + const char * const errstr = strerror_r(errnum, buf, sizeof(buf)); + #else /* XSI-compliant strerror_r() */ + const char * const errstr = buf; + strerror_r(errnum, buf, sizeof(buf)); + #endif + + fprintf(stderr, ": (%d) %s\n\n", errnum, errstr); + } +} + + +__attribute_cold__ +__attribute_format__((__printf__, 1, 2)) +__attribute_noinline__ +__attribute_nonnull__ +__attribute_noreturn__ +static void +config_error (const char * const restrict errfmt, ...) +{ + va_list ap; + va_start(ap, errfmt); + config_error_diagnostic(errfmt, 0, ap); + va_end(ap); + exit(1); +} + + +__attribute_cold__ +__attribute_format__((__printf__, 1, 2)) +__attribute_noinline__ +__attribute_nonnull__ +__attribute_noreturn__ +static void +config_perror (const char * const restrict errfmt, ...) +{ + va_list ap; + va_start(ap, errfmt); + config_error_diagnostic(errfmt, 1, ap); + va_end(ap); + exit(1); +} + + +typedef struct config_params { + const char *method; + const char *uri; + char *laddrstr; + int use_ipv6; + int headers_num; + int cookies_num; + const char *headers[64]; + const char *cookies[64]; + const char *body_content_type; + const char *body_filename; + const char *authorization; + const char *proxy_authorization; +} config_params; + + +__attribute_cold__ +__attribute_nonnull__ +static int +config_laddr (Config * const restrict config, + const char * const restrict laddrstr) +{ + struct addrinfo hints, *res = NULL; + memset(&hints, 0, sizeof(hints)); + /*hints.ai_flags |= AI_NUMERICHOST;*/ + hints.ai_family = config->raddr.ai_family; + hints.ai_socktype = SOCK_STREAM; + + if (0 != getaddrinfo(laddrstr, NULL, &hints, &res) || NULL == res) + return 0; + + config->laddr.ai_family = res->ai_family; + config->laddr.ai_socktype = res->ai_socktype; + config->laddr.ai_protocol = res->ai_protocol; + config->laddr.ai_addrlen = res->ai_addrlen; + config->laddr.ai_addr = (struct sockaddr *) + memcpy(&config->laddr_storage, res->ai_addr, res->ai_addrlen); + + freeaddrinfo(res); + return 1; +} + + +__attribute_cold__ +__attribute_nonnull__ +static int +config_laddrs (Config * const restrict config, + char * const restrict laddrstr) +{ + char *s; + int num = 1; + for (s = laddrstr; NULL != (s = strchr(s, ',')); s = s+1) ++num; + if (1 == num) return config_laddr(config, laddrstr); + + struct addrinfo hints, **res; + memset(&hints, 0, sizeof(hints)); + /*hints.ai_flags |= AI_NUMERICHOST;*/ + hints.ai_family = config->raddr.ai_family; + hints.ai_socktype = SOCK_STREAM; + + config->laddrs.num = num; + config->laddrs.addrs = res = + (struct addrinfo **)calloc((size_t)num, sizeof(struct addrinfo *)); + + s = laddrstr; + for (int i = 0; i < num; ++i, ++res) { + char *e = strchr(s, ','); + if (NULL != e) *e = '\0'; + + *res = NULL; + if (0 != getaddrinfo(s, NULL, &hints, res) || NULL == *res) + return 0; /*(leave laddrstr modified so last addr is one w/ error)*/ + + if (NULL == e) break; + *e = ','; + s = e+1; + } + + return 1; +} + + +__attribute_cold__ +__attribute_nonnull__ +static void +config_raddr (Config * const restrict config, + const char * restrict hostname, uint16_t port, const int use_ipv6) +{ + if (config->proxy && config->proxy[0] == '/') { + #ifndef UNIX_PATH_MAX + #define UNIX_PATH_MAX 108 + #endif + const size_t len = strlen(config->proxy); + if (len >= UNIX_PATH_MAX) + config_error("socket path too long: %s", config->proxy); + + config->raddr.ai_family = AF_UNIX; + config->raddr.ai_socktype = SOCK_STREAM | SOCK_NONBLOCK; + config->raddr.ai_protocol = 0; + /* calculate effective SUN_LEN(); macro not always available)*/ + config->raddr.ai_addrlen = + (socklen_t)((size_t)(((struct sockaddr_un *) 0)->sun_path) + len); + config->raddr.ai_addr = (struct sockaddr *)&config->raddr_storage; + memset(&config->raddr_storage, 0, sizeof(config->raddr_storage)); + config->raddr_storage.ss_family = AF_UNIX; + memcpy(((struct sockaddr_un *)&config->raddr_storage)->sun_path, + config->proxy, len+1); + return; + } + + char host[1024]; /*(host should be < 256 chars)*/ + if (config->proxy) { /* (&& config->proxy[0] != '/') */ + char * const colon = strrchr(config->proxy, ':'); + if (colon) { + char *endptr; + unsigned long i = strtoul(colon+1, &endptr, 10); + if (*endptr == '\0' && 0 != i && i <= USHRT_MAX) + port = (unsigned short)i; + else /*(might mis-parse IPv6 addr which omitted port)*/ + config_error("could not parse -X proxy: %s", config->proxy); + + const size_t len = (size_t)(colon - config->proxy); + if (len >= sizeof(host)) + config_error("proxy host path too long: %s", config->proxy); + memcpy(host, config->proxy, len); + host[len] = '\0'; + hostname = host; + } + else { + hostname = config->proxy; + port = 80; /* default HTTP port */ + } + } + + struct addrinfo hints, *res, *res_first; + memset(&hints, 0, sizeof(hints)); + hints.ai_flags |= AI_NUMERICSERV; + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + char port_str[6]; + snprintf(port_str, sizeof(port_str), "%hu", port); + + if (0 != getaddrinfo(hostname, port_str, &hints, &res_first)) + config_error("could not resolve hostname: %s", hostname); + + for (res = res_first; res != NULL; res = res->ai_next) { + if (res->ai_family == (use_ipv6 ? AF_INET6 : AF_INET)) { + config->raddr.ai_family = res->ai_family; + config->raddr.ai_socktype = res->ai_socktype | SOCK_NONBLOCK; + config->raddr.ai_protocol = res->ai_protocol; + config->raddr.ai_addrlen = res->ai_addrlen; + config->raddr.ai_addr = (struct sockaddr *) + memcpy(&config->raddr_storage, res->ai_addr, res->ai_addrlen); + break; + } + } + + freeaddrinfo(res_first); + if (NULL == res) + config_error("could not resolve hostname: %s", hostname); +} + + +__attribute_cold__ +__attribute_nonnull__ +static int +config_base64_encode_pad (char * const restrict dst, const size_t dstsz, + const char * const restrict ssrc) +{ + static const char base64_table[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + + const size_t srclen = strlen(ssrc); + const int rem = (int)(srclen % 3); + const int tuples = (int)(srclen / 3); + const int tuplen = (int)(srclen - (size_t)rem); + if (srclen > INT_MAX/2) /*(ridiculous size; prevent integer overflow)*/ + return -1; + if (dstsz < (size_t)(4*tuples + (rem ? 4 : 0) + 1)) + return -1; + + int s = 0, d = 0; + unsigned int v; + const unsigned char * const src = (const unsigned char *)ssrc; + for (; s < tuplen; s += 3, d += 4) { + v = (src[s+0] << 16) | (src[s+1] << 8) | src[s+2]; + dst[d+0] = base64_table[(v >> 18) & 0x3f]; + dst[d+1] = base64_table[(v >> 12) & 0x3f]; + dst[d+2] = base64_table[(v >> 6) & 0x3f]; + dst[d+3] = base64_table[(v ) & 0x3f]; + } + + if (rem) { + if (1 == rem) { + v = (src[s+0] << 4); + dst[d+2] = base64_table[64]; /* pad */ + } + else { /*(2 == rem)*/ + v = (src[s+0] << 10) | (src[s+1] << 2); + dst[d+2] = base64_table[v & 0x3f]; v >>= 6; + } + dst[d+0] = base64_table[(v >> 6) & 0x3f]; + dst[d+1] = base64_table[(v ) & 0x3f]; + dst[d+3] = base64_table[64]; /* pad */ + d += 4; + } + + dst[d] = '\0'; + return d; /*(base64-encoded string length; might be 0)*/ +} + + +__attribute_cold__ +__attribute_nonnull__ +static void +config_request (Config * const restrict config, + const config_params * const restrict params) +{ + const char * restrict uri = params->uri; + uint16_t port = 80; + uint16_t default_port = 80; + char host[1024]; /*(host should be < 256 chars)*/ + + if (0 == strncmp(uri, "http://", sizeof("http://")-1)) + uri += 7; + else if (0 == strncmp(uri, "https://", sizeof("https://")-1)) { + uri += 8; + port = default_port = 443; + config_error("no ssl support yet"); + } + + /* XXX: note that this is not a fully proper URI parse */ + const char *c; + if ((c = strchr(uri, ':'))) { /* found ':' => host:port */ + if (c - uri + 1 > (int)sizeof(host)) + config_error("host name in URI is too long"); + memcpy(host, uri, c - uri); + host[c - uri] = '\0'; + + char *endptr; + unsigned long i = strtoul(c+1, &endptr, 10); + if (0 != i && i <= USHRT_MAX) { + port = (unsigned short)i; + uri = endptr; + } + else + config_error("could not parse URI"); + } + else { + if ((c = strchr(uri, '/'))) { + if (c - uri + 1 > (int)sizeof(host)) + config_error("host name in URI is too long"); + memcpy(host, uri, c - uri); + host[c - uri] = '\0'; + uri = c; + } + else { + size_t len = strlen(uri); + if (len + 1 > (int)sizeof(host)) + config_error("host name in URI is too long"); + memcpy(host, uri, len); + host[len] = '\0'; + uri += len; + } + } + + /* resolve hostname to sockaddr */ + config_raddr(config, host, port, params->use_ipv6); + + int idx_host = -1; + int idx_user_agent = -1; + int idx_content_type = -1; + int idx_content_length = -1; + int idx_transfer_encoding = -1; + const char * const * const restrict headers = params->headers; + for (int i = 0; i < params->headers_num; i++) { + if (0 == strncasecmp(headers[i],"Host:",sizeof("Host:")-1)) { + if (-1 != idx_host) + config_error("duplicate Host header"); + idx_host = i; + } + if (0 == strncasecmp(headers[i],"User-Agent:",sizeof("User-Agent:")-1)) + idx_user_agent = i; + if (0 == strncasecmp(headers[i],"Connection:",sizeof("Connection:")-1)) + config_error("Connection request header not allowed; " + "use -k param to enable keep-alive"); + if (0 == strncasecmp(headers[i],"Content-Type:", + sizeof("Content-Type:")-1)) + idx_content_type = i; + if (0 == strncasecmp(headers[i],"Content-Length:", + sizeof("Content-Length:")-1)) + idx_content_length = i; + if (0 == strncasecmp(headers[i],"Transfer-Encoding:", + sizeof("Transfer-Encoding:")-1)) + idx_transfer_encoding = i; + } + + /*(simple 8k memaligned request buffer (part of struct Config))*/ + config->request = + (char *)((uintptr_t)(config->buf + (8*1024-1)) & ~(uintptr_t)(8*1024-1)); + char * const restrict req = config->request; + const size_t sz = sizeof(config->buf) >> 1; + int offset = snprintf(req, sz, "%s %s HTTP/1.1\r\n", params->method, + config->proxy && config->proxy[0] != '/' + ? params->uri /*(provide full URI to proxy host)*/ + : *uri != '\0' ? uri : "/"); + if (offset >= (int)sz) + config_error("request too large"); + + int len = (-1 != idx_host) + ? snprintf(req+offset, sz-offset, "%s\r\n", headers[idx_host]) + : (port == default_port) + ? snprintf(req+offset, sz-offset, "Host: %s\r\n", host) + : snprintf(req+offset, sz-offset, "Host: %s:%hu\r\n", host, port); + if (len >= (int)sz - offset) + config_error("request too large"); + offset += len; + + if (!config->keep_alive) { + len = sizeof("Connection: close\r\n")-1; + if (len >= (int)sz - offset) + config_error("request too large"); + memcpy(req+offset, "Connection: close\r\n", len); + offset += len; + } + + int fd = -1; + off_t fsize = 0; + if (params->body_filename) { + #ifndef O_BINARY + #define O_BINARY 0 + #endif + #ifndef O_LARGEFILE + #define O_LARGEFILE 0 + #endif + #ifndef O_NOATIME + #define O_NOATIME 0 + #endif + fd = open(params->body_filename, + O_RDONLY|O_BINARY|O_LARGEFILE|O_NOATIME|O_NONBLOCK, 0); + if (-1 == fd) + config_perror("open(%s)", params->body_filename); + struct stat st; + if (0 != fstat(fd, &st)) + config_perror("fstat(%s)", params->body_filename); + fsize = st.st_size; + if (fsize > UINT32_MAX - (8*1024)) + config_error("file size too large (not supported > ~4GB) (%s)", + params->body_filename); + + /* If user specified Transfer-Encoding, trust that it is proper, + * e.g. chunked, and that body_filename contains already-chunked data */ + if (-1 == idx_transfer_encoding) { + if (-1 == idx_content_length) { + len = snprintf(req+offset, sz-offset, + "Content-Length: %"PRId64"\r\n", (int64_t)fsize); + if (len >= (int)sz - offset) + config_error("request too large"); + offset += len; + } /*(else trust user specified length matching body_filename size)*/ + } + else if (-1 != idx_content_length) + config_error("Content-Length must be omitted " + "if Transfer-Encoding provided"); + + if (params->body_content_type) { + if (-1 == idx_content_type) + config_error("Content-Type duplicated in -H and -T params"); + len = snprintf(req+offset, sz-offset, + "Content-Type: %s\r\n", params->body_content_type); + if (len >= (int)sz - offset) + config_error("request too large"); + offset += len; + } + else if (-1 == idx_content_type) { + len = sizeof("Content-Type: text/plain\r\n")-1; + if (len >= (int)sz - offset) + config_error("request too large"); + memcpy(req+offset, "Content-Type: text/plain\r\n", len); + offset += len; + } + } + + for (int i = 0; i < params->headers_num; ++i) { + if (i == idx_host) + continue; + len = snprintf(req+offset, sz-offset, "%s\r\n", headers[i]); + if (len >= (int)sz - offset) + config_error("request too large"); + offset += len; + } + + if (params->authorization) { + len = snprintf(req+offset, sz-offset, "Authorization: Basic "); + if (len >= (int)sz - offset) + config_error("request too large"); + offset += len; + + len = config_base64_encode_pad(req+offset, sz-offset, + params->authorization); + if (len < 0) + config_error("request too large"); + offset += len; + + if (2 >= (int)sz - offset) + config_error("request too large"); + memcpy(req+offset, "\r\n", 3); + offset += 2; + } + + if (params->proxy_authorization) { + len = snprintf(req+offset, sz-offset, "Proxy-Authorization: Basic "); + if (len >= (int)sz - offset) + config_error("request too large"); + offset += len; + + len = config_base64_encode_pad(req+offset, sz-offset, + params->proxy_authorization); + if (len < 0) + config_error("request too large"); + offset += len; + + if (2 >= (int)sz - offset) + config_error("request too large"); + memcpy(req+offset, "\r\n", 3); + offset += 2; + } + + if (-1 == idx_user_agent) { + len = sizeof("User-Agent: weighttp/" PACKAGE_VERSION "\r\n")-1; + if (len >= (int)sz - offset) + config_error("request too large"); + memcpy(req+offset, + "User-Agent: weighttp/" PACKAGE_VERSION "\r\n", len); + offset += len; + } + + const char * const * const restrict cookies = params->cookies; + for (int i = 0; i < params->cookies_num; ++i) { + len = snprintf(req+offset, sz-offset, "Cookie: %s\r\n",cookies[i]); + if (len >= (int)sz - offset) + config_error("request too large"); + offset += len; + } + + if (3 > (int)sz - offset) + config_error("request too large"); + memcpy(req+offset, "\r\n", 3); /*(including terminating '\0')*/ + offset += 2; /*(not including terminating '\0')*/ + + config->request_size = (uint32_t)(offset + fsize); + + if (-1 != fd && 0 != fsize) { + /*(not checking if file changed between fstat() and read())*/ + /*(not using mmap() since we expect benchmark test file to be smallish + * and able to fit in memory, or */ + config->request = malloc(config->request_size); + memcpy(config->request, req, (size_t)offset); + off_t reqsz = offset; + ssize_t rd; + do { + rd = read(fd, config->request+reqsz, config->request_size-reqsz); + } while (rd > 0 ? (reqsz += rd) < config->request_size + : (rd < 0 && errno == EINTR)); + if (reqsz != config->request_size) + config_perror("read(%s)", params->body_filename); + } +} + + +__attribute_cold__ +__attribute_noinline__ +__attribute_nonnull__ +static void +weighttp_setup (Config * const restrict config, const int argc, char *argv[]) +{ + int opt_show_help = 0; + int opt_show_version = 0; + config_params params; + memset(¶ms, 0, sizeof(params)); + + /* default settings */ + config->thread_count = 1; + config->concur_count = 1; + config->req_count = 0; + config->keep_alive = 0; + config->proxy = NULL; + config->pipeline_max = 0; + config->tcp_fastopen = 0; + config->http_head = 0; + config->so_bufsz = 0; + config->quiet = 0; + + setlocale(LC_ALL, "C"); + signal(SIGPIPE, SIG_IGN); + + const char * const optstr = ":hVikqdlr6Fm:n:t:c:b:p:u:A:B:C:H:K:P:T:X:"; + int opt; + while (-1 != (opt = getopt(argc, argv, optstr))) { + switch (opt) { + case '6': + params.use_ipv6 = 1; + break; + case 'A': + params.authorization = optarg; + break; + case 'B': + params.laddrstr = optarg; + break; + case 'C': + if (params.cookies_num == sizeof(params.cookies)/sizeof(char *)) + config_error("too many cookies"); + params.cookies[params.cookies_num++] = optarg; + break; + case 'F': + config->tcp_fastopen = 1; + break; + case 'H': + if (params.headers_num == sizeof(params.headers)/sizeof(char *)) + config_error("too many headers"); + params.headers[params.headers_num++] = optarg; + break; + case 'K': + config->pipeline_max = (int)strtoul(optarg, NULL, 10); + if (config->pipeline_max >= 2) + config->keep_alive = 1; + break; + case 'P': + params.proxy_authorization = optarg; + break; + case 'T': + params.body_content_type = optarg; + break; + case 'X': + config->proxy = optarg; + break; + case 'b': + config->so_bufsz = (int)strtoul(optarg, NULL, 10); + break; + case 'c': + config->concur_count = (int)strtoul(optarg, NULL, 10); + break; + case 'i': + config->http_head = 1; + break; + case 'k': + config->keep_alive = 1; + break; + case 'm': + params.method = optarg; + config->http_head = (0 == strcasecmp(optarg, "HEAD")); + break; + case 'n': + config->req_count = strtoull(optarg, NULL, 10); + break; + case 'p': + params.body_filename = optarg; + params.method = "POST"; + config->http_head = 0; + break; + case 'q': + config->quiet = 1; + break; + case 'd': + case 'l': + case 'r': + /*(ignored; compatibility with Apache Bench (ab))*/ + break; + case 't': + config->thread_count = (int)strtoul(optarg, NULL, 10); + break; + case 'u': + params.body_filename = optarg; + params.method = "PUT"; + config->http_head = 0; + break; + case ':': + config_error("option requires an argument: -%c", optopt); + case '?': + if ('?' != optopt) + config_error("unknown option: -%c", optopt); + /* fall through */ + case 'h': + opt_show_help = 1; + /* fall through */ + case 'V': + opt_show_version = 1; + break; + } + } + + if (opt_show_version || !config->quiet) + show_version(); + + if (opt_show_help) + show_help(); + + if (opt_show_version) + exit(0); + + if ((argc - optind) < 1) + config_error("missing URI argument"); + else if ((argc - optind) > 1) + config_error("too many arguments"); + params.uri = argv[optind]; + + /* check for sane arguments */ + if (!config->req_count) + config_error("num of requests has to be > 0"); + if (config->req_count == UINT64_MAX) + config_error("invalid req_count"); + if (!config->thread_count) + config_error("thread count has to be > 0"); + if ((uint64_t)config->thread_count > config->req_count) + config_error("thread_count > req_count"); + if (!config->concur_count) + config_error("num of concurrent clients has to be > 0"); + if ((uint64_t)config->concur_count > config->req_count) + config_error("concur_count > req_count"); + if (config->thread_count > config->concur_count) + config_error("thread_count > concur_count"); + if (config->pipeline_max < 1) + config->pipeline_max = 1; + if (NULL == params.method) + params.method = config->http_head ? "HEAD" : "GET"; + + config_request(config, ¶ms); + + config->laddr.ai_addrlen = 0; + config->laddrs.addrs = NULL; + config->laddrs.num = 0; + if (params.laddrstr && !config_laddrs(config, params.laddrstr)) + config_error("could not resolve local bind address: %s", + params.laddrstr); + + if (config->concur_count > 32768 && config->raddr.ai_family != AF_UNIX) { + int need = config->concur_count; + int avail = 32768; + int fd = open("/proc/sys/net/ipv4/ip_local_port_range", + O_RDONLY|O_BINARY|O_LARGEFILE|O_NONBLOCK, 0); + if (fd >= 0) { + char buf[32]; + ssize_t rd = read(fd, buf, sizeof(buf)); + if (rd >= 3 && rd < (ssize_t)sizeof(buf)) { + long lb, ub; + char *e; + buf[rd] = '\0'; + lb = strtoul(buf, &e, 10); + if (lb > 0 && lb < USHRT_MAX && *e) { + ub = strtoul(e, &e, 10); + if (ub > 0 && ub <= USHRT_MAX && (*e=='\0' || *e=='\n')) { + if (lb <= ub) + avail = ub - lb + 1; + } + } + } + close(fd); + } + if (config->laddrs.num) + need = (need + config->laddrs.num - 1) / config->laddrs.num; + if (need > avail) + config_error("not enough local ports for concurrency\n" + "Reduce concur or provide -B addr,addr,addr " + "to specify multiple local bind addrs"); + } + + /* (see [RFC7413] 4.1.3. Client Cookie Handling) */ + if ((config->proxy && config->proxy[0] == '/') + || config->request_size > (params.use_ipv6 ? 1440 : 1460)) + config->tcp_fastopen = 0; +} + + +__attribute_cold__ +__attribute_noinline__ +__attribute_nonnull__ +static void +weighttp_report (const Config * const restrict config) +{ + /* collect worker stats and releaes resources */ + Stats stats; + memset(&stats, 0, sizeof(stats)); + for (int i = 0; i < config->thread_count; ++i) { + const Stats * const restrict wstats = &config->wconfs[i].stats; + + stats.req_started += wstats->req_started; + stats.req_done += wstats->req_done; + stats.req_success += wstats->req_success; + stats.req_failed += wstats->req_failed; + stats.bytes_total += wstats->bytes_total; + stats.bytes_headers += wstats->bytes_headers; + stats.req_2xx += wstats->req_2xx; + stats.req_3xx += wstats->req_3xx; + stats.req_4xx += wstats->req_4xx; + stats.req_5xx += wstats->req_5xx; + } + + /* report cumulative stats */ + struct timeval tdiff; + tdiff.tv_sec = config->ts_end.tv_sec - config->ts_start.tv_sec; + tdiff.tv_usec = config->ts_end.tv_usec - config->ts_start.tv_usec; + if (tdiff.tv_usec < 0) { + --tdiff.tv_sec; + tdiff.tv_usec += 1000000; + } + const uint64_t total_usecs = tdiff.tv_sec * 1000000 + tdiff.tv_usec; + const uint64_t rps = stats.req_done * 1000000 / total_usecs; + const uint64_t kbps= stats.bytes_total / 1024 * 1000000 / total_usecs; + #if 1 /* JSON-style formatted output */ + printf("{\n" + " \"reqs_per_sec\": %"PRIu64",\n" + " \"kBps_per_sec\": %"PRIu64",\n" + " \"secs_elapsed\": %01d.%06ld,\n", + rps, kbps, (int)tdiff.tv_sec, (long)tdiff.tv_usec); + printf(" \"request_counts\": {\n" + " \"started\": %"PRIu64",\n" + " \"retired\": %"PRIu64",\n" + " \"total\": %"PRIu64"\n" + " },\n", + stats.req_started, stats.req_done, config->req_count); + printf(" \"response_counts\": {\n" + " \"pass\": %"PRIu64",\n" + " \"fail\": %"PRIu64",\n" + " \"errs\": %"PRIu64"\n" + " },\n", + stats.req_success, stats.req_failed, stats.req_error); + printf(" \"status_codes\": {\n" + " \"2xx\": %"PRIu64",\n" + " \"3xx\": %"PRIu64",\n" + " \"4xx\": %"PRIu64",\n" + " \"5xx\": %"PRIu64"\n" + " },\n", + stats.req_2xx, stats.req_3xx, stats.req_4xx, stats.req_5xx); + printf(" \"traffic\": {\n" + " \"bytes_total\": %12."PRIu64",\n" + " \"bytes_headers\": %12."PRIu64",\n" + " \"bytes_body\": %12."PRIu64"\n" + " }\n" + "}\n", + stats.bytes_total, stats.bytes_headers, + stats.bytes_total - stats.bytes_headers); + #else + printf("\nfinished in %01d.%06ld sec, %"PRIu64" req/s, %"PRIu64" kbyte/s\n", + (int)tdiff.tv_sec, (long)tdiff.tv_usec, rps, kbps); + + printf("requests: %"PRIu64" started, %"PRIu64" done, %"PRIu64" total\n" + "responses: %"PRIu64" success, %"PRIu64" fail, %"PRIu64" error\n", + stats.req_started, stats.req_done, config->req_count, + stats.req_success, stats.req_failed, stats.req_error); + + printf("status codes: " + "%"PRIu64" 2xx, %"PRIu64" 3xx, %"PRIu64" 4xx, %"PRIu64" 5xx\n", + stats.req_2xx, stats.req_3xx, stats.req_4xx, stats.req_5xx); + + printf("traffic: %"PRIu64" bytes total, %"PRIu64" bytes headers, " + "%"PRIu64" bytes body\n", stats.bytes_total, + stats.bytes_headers, stats.bytes_total - stats.bytes_headers); + #endif +} + + +int main (int argc, char *argv[]) +{ + Config config; + weighttp_setup(&config, argc, argv); /* exits if error (or done) */ + wconfs_init(&config); + #if defined(__STDC_VERSION__) && __STDC_VERSION__-0 >= 199901L /* C99 */ + pthread_t threads[config.thread_count]; /*(C99 dynamic array)*/ + #else + pthread_t * const restrict threads = + (pthread_t *)calloc(config.thread_count, sizeof(pthread_t)); + #endif + + if (!config.quiet) + puts("starting benchmark..."); + gettimeofday(&config.ts_start, NULL); + + for (int i = 0; i < config.thread_count; ++i) { + int err = pthread_create(threads+i, NULL, + worker_thread, (void*)(config.wconfs+i)); + if (__builtin_expect( (0 != err), 0)) { + fprintf(stderr, "error: failed spawning thread (%d)\n", err); + /*(XXX: leaks resources and does not attempt pthread_cancel())*/ + return 2; /* (unexpected) fatal error */ + } + } + + for (int i = 0; i < config.thread_count; ++i) { + int err = pthread_join(threads[i], NULL); + if (__builtin_expect( (0 != err), 0)) { + fprintf(stderr, "error: failed joining thread (%d)\n", err); + /*(XXX: leaks resources and does not attempt pthread_cancel())*/ + return 3; /* (unexpected) fatal error */ + } + } + + gettimeofday(&config.ts_end, NULL); + + #if !(defined(__STDC_VERSION__) && __STDC_VERSION__-0 >= 199901L) /* !C99 */ + free(threads); + #endif + weighttp_report(&config); + wconfs_delete(&config); + + return 0; +} From e1fe4cd6ff3733c71e45278e7ba310e7314af774 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Fri, 22 Oct 2021 19:20:53 -0700 Subject: [PATCH 1836/2505] Tentatively fix execution on x86_64 macOS --- src/lib/lwan-thread.c | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 186b7894c..bf0bd858e 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -834,20 +834,6 @@ adjust_thread_affinity(const struct lwan_thread *thread) if (pthread_setaffinity_np(thread->self, sizeof(set), &set)) lwan_status_warning("Could not set thread affinity"); } -#elif defined(__x86_64__) -static bool -topology_to_schedtbl(struct lwan *l, uint32_t schedtbl[], uint32_t n_threads) -{ - for (uint32_t i = 0; i < n_threads; i++) - schedtbl[i] = (i / 2) % l->thread.count; - return false; -} - -static void -adjust_thread_affinity(const struct lwan_thread *thread) -{ - (void)thread; -} #endif void lwan_thread_init(struct lwan *l) @@ -865,7 +851,7 @@ void lwan_thread_init(struct lwan *l) uint32_t n_threads; bool adj_affinity; -#ifdef __x86_64__ +#if defined(__x86_64__) && defined(__linux__) if (l->online_cpus > 1) { static_assert(sizeof(struct lwan_connection) == 32, "Two connections per cache line"); @@ -894,7 +880,7 @@ void lwan_thread_init(struct lwan *l) for (unsigned int i = 0; i < total_conns; i++) l->conns[i].thread = &l->thread.threads[schedtbl[i & n_threads]]; } else -#endif /* __x86_64__ */ +#endif /* __x86_64__ && __linux__ */ { lwan_status_debug("Using round-robin to preschedule clients"); From 9d411db8398eb4a583e5bc6047789f883ad9a909 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Fri, 22 Oct 2021 19:24:00 -0700 Subject: [PATCH 1837/2505] Update copyright information --- src/bin/lwan/main.c | 2 +- src/bin/testrunner/main.c | 2 +- src/bin/tools/bin2hex.c | 2 +- src/bin/tools/configdump.c | 2 +- src/bin/tools/mimegen.c | 2 +- src/cmake/lwan-build-config.h.cmake | 2 +- src/lib/hash.c | 2 +- src/lib/int-to-str.c | 2 +- src/lib/int-to-str.h | 2 +- src/lib/lwan-array.c | 2 +- src/lib/lwan-array.h | 2 +- src/lib/lwan-cache.c | 2 +- src/lib/lwan-cache.h | 2 +- src/lib/lwan-config.c | 2 +- src/lib/lwan-config.h | 2 +- src/lib/lwan-coro.c | 2 +- src/lib/lwan-coro.h | 2 +- src/lib/lwan-http-authorize.c | 2 +- src/lib/lwan-http-authorize.h | 2 +- src/lib/lwan-io-wrappers.c | 2 +- src/lib/lwan-io-wrappers.h | 2 +- src/lib/lwan-job.c | 2 +- src/lib/lwan-lua.c | 2 +- src/lib/lwan-lua.h | 2 +- src/lib/lwan-mod-lua.c | 2 +- src/lib/lwan-mod-lua.h | 2 +- src/lib/lwan-mod-redirect.c | 2 +- src/lib/lwan-mod-redirect.h | 2 +- src/lib/lwan-mod-response.c | 2 +- src/lib/lwan-mod-response.h | 2 +- src/lib/lwan-mod-rewrite.c | 2 +- src/lib/lwan-mod-rewrite.h | 2 +- src/lib/lwan-mod-serve-files.c | 2 +- src/lib/lwan-mod-serve-files.h | 2 +- src/lib/lwan-private.h | 2 +- src/lib/lwan-pubsub.c | 2 +- src/lib/lwan-pubsub.h | 2 +- src/lib/lwan-readahead.c | 2 +- src/lib/lwan-request.c | 2 +- src/lib/lwan-response.c | 2 +- src/lib/lwan-socket.c | 2 +- src/lib/lwan-status.c | 2 +- src/lib/lwan-status.h | 2 +- src/lib/lwan-straitjacket.c | 2 +- src/lib/lwan-strbuf.c | 2 +- src/lib/lwan-strbuf.h | 2 +- src/lib/lwan-tables.c | 2 +- src/lib/lwan-template.c | 2 +- src/lib/lwan-template.h | 2 +- src/lib/lwan-thread.c | 2 +- src/lib/lwan-time.c | 2 +- src/lib/lwan-tq.c | 2 +- src/lib/lwan-tq.h | 2 +- src/lib/lwan-trie.c | 2 +- src/lib/lwan-trie.h | 2 +- src/lib/lwan-websocket.c | 2 +- src/lib/lwan.c | 2 +- src/lib/lwan.h | 2 +- src/lib/missing-pthread.c | 2 +- src/lib/missing.c | 2 +- src/lib/missing/assert.h | 2 +- src/lib/missing/fcntl.h | 2 +- src/lib/missing/ioprio.h | 2 +- src/lib/missing/libproc.h | 2 +- src/lib/missing/limits.h | 2 +- src/lib/missing/linux/capability.h | 2 +- src/lib/missing/pthread.h | 2 +- src/lib/missing/pwd.h | 2 +- src/lib/missing/stdio.h | 2 +- src/lib/missing/stdlib.h | 2 +- src/lib/missing/string.h | 2 +- src/lib/missing/sys/epoll.h | 2 +- src/lib/missing/sys/ioctl.h | 2 +- src/lib/missing/sys/mman.h | 2 +- src/lib/missing/sys/sendfile.h | 2 +- src/lib/missing/sys/socket.h | 2 +- src/lib/missing/sys/syscall.h | 2 +- src/lib/missing/sys/types.h | 2 +- src/lib/missing/sys/vfs.h | 2 +- src/lib/missing/time.h | 2 +- src/lib/missing/unistd.h | 2 +- src/lib/realpathat.c | 2 +- src/lib/realpathat.h | 2 +- src/lib/ringbuffer.h | 2 +- src/samples/asyncawait/main.c | 2 +- src/samples/chatr/main.c | 2 +- src/samples/clock/blocks.c | 2 +- src/samples/clock/main.c | 2 +- src/samples/clock/pong.c | 2 +- src/samples/clock/pong.h | 2 +- src/samples/clock/xdaliclock.c | 2 +- src/samples/freegeoip/freegeoip.c | 2 +- src/samples/hello-no-meta/main.c | 2 +- src/samples/hello/main.c | 2 +- src/samples/techempower/database.c | 2 +- src/samples/techempower/database.h | 2 +- src/samples/techempower/json.c | 2 +- src/samples/techempower/techempower.c | 2 +- src/samples/websocket/main.c | 2 +- 99 files changed, 99 insertions(+), 99 deletions(-) diff --git a/src/bin/lwan/main.c b/src/bin/lwan/main.c index 62f607bb5..aebc9941b 100644 --- a/src/bin/lwan/main.c +++ b/src/bin/lwan/main.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012 Leandro A. F. Pereira + * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/bin/testrunner/main.c b/src/bin/testrunner/main.c index 833a56552..c4ca0c014 100644 --- a/src/bin/testrunner/main.c +++ b/src/bin/testrunner/main.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012 Leandro A. F. Pereira + * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/bin/tools/bin2hex.c b/src/bin/tools/bin2hex.c index 3b4795c9d..9676846c9 100644 --- a/src/bin/tools/bin2hex.c +++ b/src/bin/tools/bin2hex.c @@ -1,6 +1,6 @@ /* * bin2hex - convert binary files to hexdump - * Copyright (c) 2017 Leandro A. F. Pereira + * Copyright (c) 2017 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/bin/tools/configdump.c b/src/bin/tools/configdump.c index fd86e9e81..ba0a3c2b7 100644 --- a/src/bin/tools/configdump.c +++ b/src/bin/tools/configdump.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2019 Leandro A. F. Pereira + * Copyright (c) 2019 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/bin/tools/mimegen.c b/src/bin/tools/mimegen.c index 2ed941e23..f1edacd53 100644 --- a/src/bin/tools/mimegen.c +++ b/src/bin/tools/mimegen.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2016 Leandro A. F. Pereira + * Copyright (c) 2016 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/cmake/lwan-build-config.h.cmake b/src/cmake/lwan-build-config.h.cmake index 50494cbe3..d08bd7d30 100644 --- a/src/cmake/lwan-build-config.h.cmake +++ b/src/cmake/lwan-build-config.h.cmake @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2016 Leandro A. F. Pereira + * Copyright (c) 2016 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/hash.c b/src/lib/hash.c index f9b1509ff..d2048213e 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -1,7 +1,7 @@ /* * Based on libkmod-hash.c from libkmod - interface to kernel module operations * Copyright (C) 2011-2012 ProFUSION embedded systems - * Copyright (C) 2013 Leandro Pereira + * Copyright (C) 2013 L.Pereira * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public diff --git a/src/lib/int-to-str.c b/src/lib/int-to-str.c index 7425b6f29..168505b67 100644 --- a/src/lib/int-to-str.c +++ b/src/lib/int-to-str.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012 Leandro A. F. Pereira + * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/int-to-str.h b/src/lib/int-to-str.h index 2bafc358b..4c35fb407 100644 --- a/src/lib/int-to-str.h +++ b/src/lib/int-to-str.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012 Leandro A. F. Pereira + * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-array.c b/src/lib/lwan-array.c index 0dd2e1898..5a5fee8bf 100644 --- a/src/lib/lwan-array.c +++ b/src/lib/lwan-array.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2017 Leandro A. F. Pereira + * Copyright (c) 2017 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-array.h b/src/lib/lwan-array.h index 85c85c39d..66df734ef 100644 --- a/src/lib/lwan-array.h +++ b/src/lib/lwan-array.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2017 Leandro A. F. Pereira + * Copyright (c) 2017 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-cache.c b/src/lib/lwan-cache.c index dfbf922c9..fa8179f4f 100644 --- a/src/lib/lwan-cache.c +++ b/src/lib/lwan-cache.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2013 Leandro A. F. Pereira + * Copyright (c) 2013 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-cache.h b/src/lib/lwan-cache.h index d390f93fb..dc0369d22 100644 --- a/src/lib/lwan-cache.h +++ b/src/lib/lwan-cache.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2013 Leandro A. F. Pereira + * Copyright (c) 2013 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index f9a22b260..2cfd71adc 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2017 Leandro A. F. Pereira + * Copyright (c) 2017 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-config.h b/src/lib/lwan-config.h index 378ea49ed..35065027e 100644 --- a/src/lib/lwan-config.h +++ b/src/lib/lwan-config.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2013 Leandro A. F. Pereira + * Copyright (c) 2013 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 39af42c0f..76dcbbc91 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012 Leandro A. F. Pereira + * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-coro.h b/src/lib/lwan-coro.h index ee39a6acc..73afb7d7a 100644 --- a/src/lib/lwan-coro.h +++ b/src/lib/lwan-coro.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012 Leandro A. F. Pereira + * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-http-authorize.c b/src/lib/lwan-http-authorize.c index c077b1431..61034a794 100644 --- a/src/lib/lwan-http-authorize.c +++ b/src/lib/lwan-http-authorize.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2014 Leandro A. F. Pereira + * Copyright (c) 2014 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-http-authorize.h b/src/lib/lwan-http-authorize.h index b3a5f815c..7c6ad9279 100644 --- a/src/lib/lwan-http-authorize.h +++ b/src/lib/lwan-http-authorize.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2014 Leandro A. F. Pereira + * Copyright (c) 2014 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index b689ff83a..d87e43bc6 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2013 Leandro A. F. Pereira + * Copyright (c) 2013 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-io-wrappers.h b/src/lib/lwan-io-wrappers.h index de60ab025..e2bd316d8 100644 --- a/src/lib/lwan-io-wrappers.h +++ b/src/lib/lwan-io-wrappers.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2013 Leandro A. F. Pereira + * Copyright (c) 2013 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-job.c b/src/lib/lwan-job.c index 0c754dcb1..695a4b27e 100644 --- a/src/lib/lwan-job.c +++ b/src/lib/lwan-job.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012, 2013 Leandro A. F. Pereira + * Copyright (c) 2012, 2013 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index 61945333f..6de67fd95 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2014 Leandro A. F. Pereira + * Copyright (c) 2014 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-lua.h b/src/lib/lwan-lua.h index 041afeeb1..b1f1879fb 100644 --- a/src/lib/lwan-lua.h +++ b/src/lib/lwan-lua.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2014 Leandro A. F. Pereira + * Copyright (c) 2014 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-mod-lua.c b/src/lib/lwan-mod-lua.c index b20bc9355..f0d761371 100644 --- a/src/lib/lwan-mod-lua.c +++ b/src/lib/lwan-mod-lua.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2017 Leandro A. F. Pereira + * Copyright (c) 2017 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-mod-lua.h b/src/lib/lwan-mod-lua.h index 43e48106a..139b456d0 100644 --- a/src/lib/lwan-mod-lua.h +++ b/src/lib/lwan-mod-lua.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2017 Leandro A. F. Pereira + * Copyright (c) 2017 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-mod-redirect.c b/src/lib/lwan-mod-redirect.c index 8ac6d20b6..db8a64ad6 100644 --- a/src/lib/lwan-mod-redirect.c +++ b/src/lib/lwan-mod-redirect.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2014 Leandro A. F. Pereira + * Copyright (c) 2014 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-mod-redirect.h b/src/lib/lwan-mod-redirect.h index 031d0c5cd..3b670495c 100644 --- a/src/lib/lwan-mod-redirect.h +++ b/src/lib/lwan-mod-redirect.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2014 Leandro A. F. Pereira + * Copyright (c) 2014 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-mod-response.c b/src/lib/lwan-mod-response.c index 88136b117..2d35bf3f5 100644 --- a/src/lib/lwan-mod-response.c +++ b/src/lib/lwan-mod-response.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2017 Leandro A. F. Pereira + * Copyright (c) 2017 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-mod-response.h b/src/lib/lwan-mod-response.h index 37f2196de..a65352a7c 100644 --- a/src/lib/lwan-mod-response.h +++ b/src/lib/lwan-mod-response.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2017 Leandro A. F. Pereira + * Copyright (c) 2017 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index 10f23b3ce..1dfd4a908 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2015 Leandro A. F. Pereira + * Copyright (c) 2015 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-mod-rewrite.h b/src/lib/lwan-mod-rewrite.h index 3593f699b..82318b761 100644 --- a/src/lib/lwan-mod-rewrite.h +++ b/src/lib/lwan-mod-rewrite.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2014 Leandro A. F. Pereira + * Copyright (c) 2014 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 44fe2f020..bd3f16426 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012 Leandro A. F. Pereira + * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-mod-serve-files.h b/src/lib/lwan-mod-serve-files.h index c3347e157..879fc7ab7 100644 --- a/src/lib/lwan-mod-serve-files.h +++ b/src/lib/lwan-mod-serve-files.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012 Leandro A. F. Pereira + * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index a727d8bb8..2804cfa74 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012 Leandro A. F. Pereira + * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-pubsub.c b/src/lib/lwan-pubsub.c index 0fe1e6a97..bd82764ec 100644 --- a/src/lib/lwan-pubsub.c +++ b/src/lib/lwan-pubsub.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2020 Leandro A. F. Pereira + * Copyright (c) 2020 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-pubsub.h b/src/lib/lwan-pubsub.h index 5f8c1e658..21acbdfb8 100644 --- a/src/lib/lwan-pubsub.h +++ b/src/lib/lwan-pubsub.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2020 Leandro A. F. Pereira + * Copyright (c) 2020 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-readahead.c b/src/lib/lwan-readahead.c index 98cb76ae7..b18d31bd3 100644 --- a/src/lib/lwan-readahead.c +++ b/src/lib/lwan-readahead.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2018 Leandro A. F. Pereira + * Copyright (c) 2018 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 6d9d0510e..15691e3f5 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012-2014 Leandro A. F. Pereira + * Copyright (c) 2012-2014 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 6c9d9117f..b30f2e7c1 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012 Leandro A. F. Pereira + * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-socket.c b/src/lib/lwan-socket.c index 15f399a3c..86f2cb2aa 100644 --- a/src/lib/lwan-socket.c +++ b/src/lib/lwan-socket.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012, 2013 Leandro A. F. Pereira + * Copyright (c) 2012, 2013 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-status.c b/src/lib/lwan-status.c index 2c2c2efde..389df97aa 100644 --- a/src/lib/lwan-status.c +++ b/src/lib/lwan-status.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2021 Leandro A. F. Pereira + * Copyright (c) 2021 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-status.h b/src/lib/lwan-status.h index c6681eeaf..534286072 100644 --- a/src/lib/lwan-status.h +++ b/src/lib/lwan-status.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2013 Leandro A. F. Pereira + * Copyright (c) 2013 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-straitjacket.c b/src/lib/lwan-straitjacket.c index 957116558..1bec15b6b 100644 --- a/src/lib/lwan-straitjacket.c +++ b/src/lib/lwan-straitjacket.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2015 Leandro A. F. Pereira + * Copyright (c) 2015 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-strbuf.c b/src/lib/lwan-strbuf.c index 9b1007f43..a71c2ac34 100644 --- a/src/lib/lwan-strbuf.c +++ b/src/lib/lwan-strbuf.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012 Leandro A. F. Pereira + * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-strbuf.h b/src/lib/lwan-strbuf.h index d3cef9145..9c43cf68a 100644 --- a/src/lib/lwan-strbuf.h +++ b/src/lib/lwan-strbuf.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012 Leandro A. F. Pereira + * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index 8dabb2f12..821341185 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012, 2013 Leandro A. F. Pereira + * Copyright (c) 2012, 2013 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index b77ba5199..c5f308041 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012 Leandro A. F. Pereira + * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-template.h b/src/lib/lwan-template.h index 3fdd081fb..ae26c2367 100644 --- a/src/lib/lwan-template.h +++ b/src/lib/lwan-template.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012 Leandro A. F. Pereira + * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index bf0bd858e..982c49f13 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012, 2013 Leandro A. F. Pereira + * Copyright (c) 2012, 2013 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-time.c b/src/lib/lwan-time.c index debfafb18..ab77de60b 100644 --- a/src/lib/lwan-time.c +++ b/src/lib/lwan-time.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2017 Leandro A. F. Pereira + * Copyright (c) 2017 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-tq.c b/src/lib/lwan-tq.c index b6f65f23e..096e56b19 100644 --- a/src/lib/lwan-tq.c +++ b/src/lib/lwan-tq.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2019 Leandro A. F. Pereira + * Copyright (c) 2019 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-tq.h b/src/lib/lwan-tq.h index 5abaf9441..4c25971cd 100644 --- a/src/lib/lwan-tq.h +++ b/src/lib/lwan-tq.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2019 Leandro A. F. Pereira + * Copyright (c) 2019 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-trie.c b/src/lib/lwan-trie.c index 360bc1e78..2fccf7ae8 100644 --- a/src/lib/lwan-trie.c +++ b/src/lib/lwan-trie.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012 Leandro A. F. Pereira + * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-trie.h b/src/lib/lwan-trie.h index 76d3737a4..e9ea1e792 100644 --- a/src/lib/lwan-trie.h +++ b/src/lib/lwan-trie.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012 Leandro A. F. Pereira + * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index 6de54055a..8ec310854 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2019 Leandro A. F. Pereira + * Copyright (c) 2019 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan.c b/src/lib/lwan.c index c44538af6..31b841100 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012, 2013 Leandro A. F. Pereira + * Copyright (c) 2012, 2013 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 44989d25c..d4fbb9960 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012 Leandro A. F. Pereira + * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/missing-pthread.c b/src/lib/missing-pthread.c index 5cd00aa93..a9ff10a1a 100644 --- a/src/lib/missing-pthread.c +++ b/src/lib/missing-pthread.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2018 Leandro A. F. Pereira + * Copyright (c) 2018 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/missing.c b/src/lib/missing.c index 702482bbf..e3d42de38 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012 Leandro A. F. Pereira + * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/missing/assert.h b/src/lib/missing/assert.h index 0d7851440..49b8be9f3 100644 --- a/src/lib/missing/assert.h +++ b/src/lib/missing/assert.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012 Leandro A. F. Pereira + * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/missing/fcntl.h b/src/lib/missing/fcntl.h index 59f296628..0bc14c705 100644 --- a/src/lib/missing/fcntl.h +++ b/src/lib/missing/fcntl.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012 Leandro A. F. Pereira + * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/missing/ioprio.h b/src/lib/missing/ioprio.h index 6e2095de3..78fdb11ed 100644 --- a/src/lib/missing/ioprio.h +++ b/src/lib/missing/ioprio.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2018 Leandro A. F. Pereira + * Copyright (c) 2018 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/missing/libproc.h b/src/lib/missing/libproc.h index f280b6ed3..beaed31e5 100644 --- a/src/lib/missing/libproc.h +++ b/src/lib/missing/libproc.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012 Leandro A. F. Pereira + * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/missing/limits.h b/src/lib/missing/limits.h index 3024ffa9e..555a0d3c5 100644 --- a/src/lib/missing/limits.h +++ b/src/lib/missing/limits.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012 Leandro A. F. Pereira + * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/missing/linux/capability.h b/src/lib/missing/linux/capability.h index 0645e467d..1699249e5 100644 --- a/src/lib/missing/linux/capability.h +++ b/src/lib/missing/linux/capability.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2018 Leandro A. F. Pereira + * Copyright (c) 2018 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/missing/pthread.h b/src/lib/missing/pthread.h index 2a9551568..d6e86fa7c 100644 --- a/src/lib/missing/pthread.h +++ b/src/lib/missing/pthread.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012 Leandro A. F. Pereira + * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/missing/pwd.h b/src/lib/missing/pwd.h index d1955b33a..436e4c360 100644 --- a/src/lib/missing/pwd.h +++ b/src/lib/missing/pwd.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2019 Leandro A. F. Pereira + * Copyright (c) 2019 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/missing/stdio.h b/src/lib/missing/stdio.h index 6657163af..7a5abaf2e 100644 --- a/src/lib/missing/stdio.h +++ b/src/lib/missing/stdio.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2019 Leandro A. F. Pereira + * Copyright (c) 2019 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/missing/stdlib.h b/src/lib/missing/stdlib.h index 65d778ce5..bc54fbc62 100644 --- a/src/lib/missing/stdlib.h +++ b/src/lib/missing/stdlib.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2017 Leandro A. F. Pereira + * Copyright (c) 2017 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/missing/string.h b/src/lib/missing/string.h index 8134882f8..b0f24572e 100644 --- a/src/lib/missing/string.h +++ b/src/lib/missing/string.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012 Leandro A. F. Pereira + * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/missing/sys/epoll.h b/src/lib/missing/sys/epoll.h index 2175879e8..585f3553b 100644 --- a/src/lib/missing/sys/epoll.h +++ b/src/lib/missing/sys/epoll.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012 Leandro A. F. Pereira + * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/missing/sys/ioctl.h b/src/lib/missing/sys/ioctl.h index c7d61f50f..3f6dc4676 100644 --- a/src/lib/missing/sys/ioctl.h +++ b/src/lib/missing/sys/ioctl.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2019 Leandro A. F. Pereira + * Copyright (c) 2019 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/missing/sys/mman.h b/src/lib/missing/sys/mman.h index 6572327ea..798d72516 100644 --- a/src/lib/missing/sys/mman.h +++ b/src/lib/missing/sys/mman.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012 Leandro A. F. Pereira + * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/missing/sys/sendfile.h b/src/lib/missing/sys/sendfile.h index 50fa3c57d..5c564c590 100644 --- a/src/lib/missing/sys/sendfile.h +++ b/src/lib/missing/sys/sendfile.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012 Leandro A. F. Pereira + * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/missing/sys/socket.h b/src/lib/missing/sys/socket.h index cc3812b57..6fcaae7fe 100644 --- a/src/lib/missing/sys/socket.h +++ b/src/lib/missing/sys/socket.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012 Leandro A. F. Pereira + * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/missing/sys/syscall.h b/src/lib/missing/sys/syscall.h index ae435acd2..5d6d0ec05 100644 --- a/src/lib/missing/sys/syscall.h +++ b/src/lib/missing/sys/syscall.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012 Leandro A. F. Pereira + * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/missing/sys/types.h b/src/lib/missing/sys/types.h index c2a22db29..a749b4548 100644 --- a/src/lib/missing/sys/types.h +++ b/src/lib/missing/sys/types.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012 Leandro A. F. Pereira + * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/missing/sys/vfs.h b/src/lib/missing/sys/vfs.h index a55dfb2d5..c6de417eb 100644 --- a/src/lib/missing/sys/vfs.h +++ b/src/lib/missing/sys/vfs.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2020 Leandro A. F. Pereira + * Copyright (c) 2020 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/missing/time.h b/src/lib/missing/time.h index 06721a15e..9e71e5480 100644 --- a/src/lib/missing/time.h +++ b/src/lib/missing/time.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012 Leandro A. F. Pereira + * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/missing/unistd.h b/src/lib/missing/unistd.h index c74ca29d3..eaca0dac1 100644 --- a/src/lib/missing/unistd.h +++ b/src/lib/missing/unistd.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012 Leandro A. F. Pereira + * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/lib/realpathat.c b/src/lib/realpathat.c index 8736f1ea9..d3a2a5358 100644 --- a/src/lib/realpathat.c +++ b/src/lib/realpathat.c @@ -1,5 +1,5 @@ /* -at() version of realpath() - Copyright (C) 2012 Leandro A. F. Pereira + Copyright (C) 2012 L. A. F. Pereira Based on: return the canonical absolute name of a given file. Copyright (C) 1996-2002,2004,2005,2006,2008 Free Software Foundation, Inc. diff --git a/src/lib/realpathat.h b/src/lib/realpathat.h index 4fc1dd947..871c79896 100644 --- a/src/lib/realpathat.h +++ b/src/lib/realpathat.h @@ -1,5 +1,5 @@ /* -at() version of realpath() - Copyright (C) 2012 Leandro A. F. Pereira + Copyright (C) 2012 L. A. F. Pereira Based on: return the canonical absolute name of a given file. Copyright (C) 1996-2002,2004,2005,2006,2008 Free Software Foundation, Inc. diff --git a/src/lib/ringbuffer.h b/src/lib/ringbuffer.h index ffbb8fdc0..978d59603 100644 --- a/src/lib/ringbuffer.h +++ b/src/lib/ringbuffer.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2018 Leandro A. F. Pereira + * Copyright (c) 2018 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/samples/asyncawait/main.c b/src/samples/asyncawait/main.c index 41605dce7..f7ab0941f 100644 --- a/src/samples/asyncawait/main.c +++ b/src/samples/asyncawait/main.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2020 Leandro A. F. Pereira + * Copyright (c) 2020 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/samples/chatr/main.c b/src/samples/chatr/main.c index dd5e425ef..45f7cb448 100644 --- a/src/samples/chatr/main.c +++ b/src/samples/chatr/main.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2020 Leandro A. F. Pereira + * Copyright (c) 2020 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/samples/clock/blocks.c b/src/samples/clock/blocks.c index 6c81529ba..156dfd2b1 100644 --- a/src/samples/clock/blocks.c +++ b/src/samples/clock/blocks.c @@ -1,6 +1,6 @@ /* * Falling block clock - * Copyright (c) 2018 Leandro A. F. Pereira + * Copyright (c) 2018 L. A. F. Pereira * * Inspired by code written by Tobias Blum * https://github.com/toblum/esp_p10_tetris_clock diff --git a/src/samples/clock/main.c b/src/samples/clock/main.c index 314b31cda..81f8f359f 100644 --- a/src/samples/clock/main.c +++ b/src/samples/clock/main.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2018 Leandro A. F. Pereira + * Copyright (c) 2018 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/samples/clock/pong.c b/src/samples/clock/pong.c index 02b54ad46..33f8b88cf 100644 --- a/src/samples/clock/pong.c +++ b/src/samples/clock/pong.c @@ -1,7 +1,7 @@ /* * C port of Daniel Esteban's Pong Clock for Lwan * Copyright (C) 2019 Daniel Esteban - * Copyright (C) 2020 Leandro A. F. Pereira + * Copyright (C) 2020 L. A. F. Pereira * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/samples/clock/pong.h b/src/samples/clock/pong.h index ee8ce7f2a..43c367939 100644 --- a/src/samples/clock/pong.h +++ b/src/samples/clock/pong.h @@ -1,7 +1,7 @@ /* * C port of Daniel Esteban's Pong Clock for Lwan * Copyright (C) 2019 Daniel Esteban - * Copyright (C) 2020 Leandro A. F. Pereira + * Copyright (C) 2020 L. A. F. Pereira * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/samples/clock/xdaliclock.c b/src/samples/clock/xdaliclock.c index d0a976e1a..7f44b8999 100644 --- a/src/samples/clock/xdaliclock.c +++ b/src/samples/clock/xdaliclock.c @@ -1,6 +1,6 @@ /* * Lwan port of Dali Clock - * Copyright (c) 2018 Leandro A. F. Pereira + * Copyright (c) 2018 L. A. F. Pereira * * Based on: * Dali Clock - a melting digital clock for Pebble. diff --git a/src/samples/freegeoip/freegeoip.c b/src/samples/freegeoip/freegeoip.c index f361d326e..b5f4c10c0 100644 --- a/src/samples/freegeoip/freegeoip.c +++ b/src/samples/freegeoip/freegeoip.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2012 Leandro A. F. Pereira + * Copyright (c) 2012 L. A. F. Pereira * * SQL query is copied from freegeoip.go * Copyright (c) 2013 Alexandre Fiori diff --git a/src/samples/hello-no-meta/main.c b/src/samples/hello-no-meta/main.c index 98d96da8b..92ec86227 100644 --- a/src/samples/hello-no-meta/main.c +++ b/src/samples/hello-no-meta/main.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2018 Leandro A. F. Pereira + * Copyright (c) 2018 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/samples/hello/main.c b/src/samples/hello/main.c index c87bd7ef6..ea37df0d5 100644 --- a/src/samples/hello/main.c +++ b/src/samples/hello/main.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2018 Leandro A. F. Pereira + * Copyright (c) 2018 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/samples/techempower/database.c b/src/samples/techempower/database.c index e1ff94042..bab768abf 100644 --- a/src/samples/techempower/database.c +++ b/src/samples/techempower/database.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2014 Leandro A. F. Pereira + * Copyright (c) 2014 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/samples/techempower/database.h b/src/samples/techempower/database.h index 0af4f79a9..f3506c6e7 100644 --- a/src/samples/techempower/database.h +++ b/src/samples/techempower/database.h @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2014 Leandro A. F. Pereira + * Copyright (c) 2014 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/samples/techempower/json.c b/src/samples/techempower/json.c index 125f1281d..bf87beebe 100644 --- a/src/samples/techempower/json.c +++ b/src/samples/techempower/json.c @@ -1,6 +1,6 @@ /* * Copyright (c) 2017 Intel Corporation - * Copyright (c) 2020 Leandro A. F. Pereira + * Copyright (c) 2020 L. A. F. Pereira * * SPDX-License-Identifier: Apache-2.0 */ diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index fc713d132..3e9c699c5 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2014 Leandro A. F. Pereira + * Copyright (c) 2014 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/samples/websocket/main.c b/src/samples/websocket/main.c index e276a04d3..fef28d383 100644 --- a/src/samples/websocket/main.c +++ b/src/samples/websocket/main.c @@ -1,6 +1,6 @@ /* * lwan - simple web server - * Copyright (c) 2018 Leandro A. F. Pereira + * Copyright (c) 2018 L. A. F. Pereira * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License From 3d9d2d13b18f53666610fc9c4f9b1e45730357e3 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Sat, 6 Nov 2021 11:25:06 -0700 Subject: [PATCH 1838/2505] Don't generate a new request_id unless it's needed While generating one is cheap, if we can avoid it, let's avoid it. It's rarely needed. --- src/lib/lwan-lua.c | 2 +- src/lib/lwan-private.h | 5 +++++ src/lib/lwan-request.c | 2 +- src/lib/lwan-response.c | 8 +++----- src/lib/lwan-thread.c | 11 ++++++++++- src/lib/lwan.h | 1 - 6 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index 6de67fd95..d82349388 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -299,7 +299,7 @@ LWAN_LUA_METHOD(sleep) LWAN_LUA_METHOD(request_id) { struct lwan_request *request = lwan_lua_get_request_from_userdata(L); - lua_pushfstring(L, "%016lx", request->request_id); + lua_pushfstring(L, "%016lx", lwan_request_get_id(request)); return 1; } diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 2804cfa74..7798d438d 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -53,6 +53,8 @@ struct lwan_request_parser_helper { off_t from, to; } range; + uint64_t request_id; /* Request ID for debugging purposes */ + time_t error_when_time; /* Time to abort request read */ int error_when_n_packets; /* Max. number of packets */ int urls_rewritten; /* Times URLs have been rewritten */ @@ -242,3 +244,6 @@ int lwan_format_rfc_time(const time_t in, char out LWAN_ARRAY_PARAM(30)); int lwan_parse_rfc_time(const char in LWAN_ARRAY_PARAM(30), time_t *out); void lwan_straitjacket_enforce(const struct lwan_straitjacket *sj); + +uint64_t lwan_request_get_id(struct lwan_request *request); + diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 15691e3f5..e4ae6f4a7 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1464,7 +1464,7 @@ static void log_request(struct lwan_request *request, lwan_status_debug( "%s [%s] %016lx \"%s %s HTTP/%s\" %d %s (r:%.3fms p:%.3fms)", lwan_request_get_remote_address(request, ip_buffer), - request->conn->thread->date.date, request->request_id, + request->conn->thread->date.date, lwan_request_get_id(request), lwan_request_get_method_str(request), request->original_url.value, request->flags & REQUEST_IS_HTTP_1_0 ? "1.0" : "1.1", status, request->response.mime_type, time_to_read_request, diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index b30f2e7c1..2f380bcba 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -352,12 +352,10 @@ size_t lwan_prepare_response_header_full( APPEND_CONSTANT("\r\nTransfer-Encoding: chunked"); } if (request_flags & RESPONSE_INCLUDE_REQUEST_ID) { - char request_id[] = "fill-with-req-id"; APPEND_CONSTANT("\r\nX-Request-Id: "); - int r = snprintf(request_id, sizeof(request_id), "%016lx", request->request_id); - if (UNLIKELY(r < 0 || r >= (int)sizeof(request_id))) - return 0; - APPEND_STRING_LEN(request_id, (size_t)r); + RETURN_0_ON_OVERFLOW(16); + for (uint64_t id = lwan_request_get_id(request); id; id >>= 4) + APPEND_CHAR_NOCHECK("0123456789abcdef"[id & 15]); } } diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 982c49f13..25fe20b9e 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -127,6 +127,16 @@ uint64_t lwan_random_uint64() } #endif +uint64_t lwan_request_get_id(struct lwan_request *request) +{ + struct lwan_request_parser_helper *helper = request->helper; + + if (helper->request_id == 0) + helper->request_id = lwan_random_uint64(); + + return helper->request_id; +} + __attribute__((noreturn)) static int process_request_coro(struct coro *coro, void *data) { @@ -160,7 +170,6 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, struct lwan_request request = {.conn = conn, .global_response_headers = &lwan->headers, .fd = fd, - .request_id = lwan_random_uint64(), .response = {.buffer = &strbuf}, .flags = flags, .proxy = &proxy, diff --git a/src/lib/lwan.h b/src/lib/lwan.h index d4fbb9960..6749f2a3f 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -369,7 +369,6 @@ struct lwan_request { struct lwan_connection *conn; const struct lwan_strbuf *const global_response_headers; - uint64_t request_id; struct lwan_request_parser_helper *helper; struct lwan_value url; From 5e4d47e621e88e2d20996de3c9cc91cc2224fe41 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Sat, 6 Nov 2021 11:26:05 -0700 Subject: [PATCH 1839/2505] Use string switch to look for header separator --- src/lib/lwan-request.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index e4ae6f4a7..b3c82f713 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1611,12 +1611,12 @@ const char *lwan_request_get_header(struct lwan_request *request, if (UNLIKELY((size_t)(end - start) < header_len_with_separator)) continue; - if (strncmp(start + header_len, ": ", HEADER_VALUE_SEPARATOR_LEN)) - continue; - - if (!strncasecmp(start, header, header_len)) { - *end = '\0'; - return start + header_len_with_separator; + STRING_SWITCH_SMALL(start + header_len) { + case STR2_INT(':', ' '): + if (!strncasecmp(start, header, header_len)) { + *end = '\0'; + return start + header_len_with_separator; + } } } From 3944a63961f6279ab1faaa6f8621fc0dcb94c169 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Thu, 25 Nov 2021 10:28:53 -0800 Subject: [PATCH 1840/2505] Use LWAN_FOREACH_SECTION() in lwan.c No need to open-code the section-iteration loop when we have a nice macro to do that work for us. --- src/bin/lwan/main.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/bin/lwan/main.c b/src/bin/lwan/main.c index aebc9941b..4eccdc95e 100644 --- a/src/bin/lwan/main.c +++ b/src/bin/lwan/main.c @@ -34,12 +34,10 @@ enum args { static void print_module_info(void) { - extern const struct lwan_module_info SECTION_START(lwan_module); - extern const struct lwan_module_info SECTION_END(lwan_module); const struct lwan_module_info *module; printf("Available modules:\n"); - for (module = __start_lwan_module; module < __stop_lwan_module; module++) { + LWAN_SECTION_FOREACH(lwan_module, module) { printf(" * %s\n", module->name); } } @@ -47,12 +45,10 @@ static void print_module_info(void) static void print_handler_info(void) { - extern const struct lwan_handler_info SECTION_START(lwan_handler); - extern const struct lwan_handler_info SECTION_END(lwan_handler); const struct lwan_handler_info *handler; printf("Available handlers:\n"); - for (handler = __start_lwan_handler; handler < __stop_lwan_handler; handler++) { + LWAN_SECTION_FOREACH(lwan_handler, handler) { printf(" * %s\n", handler->name); } } From d48c1cf051e70228bc0adece1b7abda9afa57d64 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Thu, 25 Nov 2021 10:29:41 -0800 Subject: [PATCH 1841/2505] Avoid potential use-after-free when parsing pattern matching rules --- src/lib/lwan-mod-rewrite.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index 1dfd4a908..068362f49 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -605,7 +605,9 @@ static void parse_condition_stat(struct pattern *pattern, struct config *config, const struct config_line *line) { - char *path = NULL, *is_dir = NULL, *is_file = NULL; + char *path = NULL; + bool has_is_dir = false, is_dir; + bool has_is_file = false, is_file; while ((line = config_read_line(config))) { switch (line->type) { @@ -620,14 +622,14 @@ static void parse_condition_stat(struct pattern *pattern, } pattern->condition.stat.path = path; - if (is_dir) { + if (has_is_dir) { pattern->flags |= PATTERN_COND_STAT__HAS_IS_DIR; - if (parse_bool(is_dir, false)) + if (is_dir) pattern->flags |= PATTERN_COND_STAT__IS_DIR; } - if (is_file) { + if (has_is_file) { pattern->flags |= PATTERN_COND_STAT__HAS_IS_FILE; - if (parse_bool(is_file, false)) + if (is_file) pattern->flags |= PATTERN_COND_STAT__IS_FILE; } pattern->flags |= PATTERN_COND_STAT; @@ -645,9 +647,11 @@ static void parse_condition_stat(struct pattern *pattern, goto out; } } else if (streq(line->key, "is_dir")) { - is_dir = line->value; + is_dir = parse_bool(line->value, false); + has_is_dir = true; } else if (streq(line->key, "is_file")) { - is_file = line->value; + is_file = parse_bool(line->value, false); + has_is_file = true; } else { config_error(config, "Unexpected key: %s", line->key); goto out; From f6bc8a38f27fa9d6292530a15209987782758eaf Mon Sep 17 00:00:00 2001 From: L Pereira Date: Thu, 25 Nov 2021 10:30:42 -0800 Subject: [PATCH 1842/2505] Avoid waking up I/O threads that much in chat sample --- src/samples/websocket/main.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/samples/websocket/main.c b/src/samples/websocket/main.c index fef28d383..969b2e0d5 100644 --- a/src/samples/websocket/main.c +++ b/src/samples/websocket/main.c @@ -128,6 +128,7 @@ LWAN_HANDLER(ws_chat) enum lwan_http_status status; static int total_user_count; int user_id; + uint64_t sleep_time = 1000; sub = lwan_pubsub_subscribe(chat); if (!sub) @@ -166,9 +167,19 @@ LWAN_HANDLER(ws_chat) lwan_pubsub_msg_done(msg); lwan_response_websocket_write(request); + sleep_time = 500; } - lwan_request_sleep(request, 1000); + lwan_request_sleep(request, sleep_time); + + /* We're receiving a lot of messages, wait up to 1s (500ms in the loop + * above, and 500ms in the increment below). Otherwise, wait 500ms every + * time we return from lwan_request_sleep() until we reach 8s. This way, + * if a chat is pretty busy, we'll have a lag of at least 1s -- which is + * probably fine; if it's not busy, we can sleep a bit more and conserve + * some resources. */ + if (sleep_time <= 8000) + sleep_time += 500; break; case 0: /* We got something! Copy it to echo it back */ From c7da6ce648da85bf7ba794ca164d4980769c44cc Mon Sep 17 00:00:00 2001 From: L Pereira Date: Thu, 25 Nov 2021 10:31:24 -0800 Subject: [PATCH 1843/2505] Defer 32-bit x86 coroutine context swap routines to libucontext I don't have a 32-bit x86 computer anymore, so it's better to get rid of code that I rarely use to decrease maintenance burden. Should not affect performance. --- CMakeLists.txt | 2 +- src/lib/lwan-coro.c | 44 -------------------------------------------- src/lib/lwan-coro.h | 2 -- 3 files changed, 1 insertion(+), 47 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 38052eace..93f4b5608 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -299,7 +299,7 @@ else () set(LWAN_COMMON_LIBS -Wl,-whole-archive lwan-static -Wl,-no-whole-archive) endif () -if (NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^i[3456]86|x86_64") +if (NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^x86_64") set(HAVE_LIBUCONTEXT 1) endif () diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 76dcbbc91..5404e5529 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -181,32 +181,6 @@ asm(".text\n\t" "movq 64(%rsi),%rcx\n\t" "movq 56(%rsi),%rsi\n\t" "jmpq *%rcx\n\t"); -#elif defined(__i386__) -void __attribute__((noinline, visibility("internal"))) -coro_swapcontext(coro_context *current, coro_context *other); -asm(".text\n\t" - ".p2align 5\n\t" - ASM_ROUTINE(coro_swapcontext) - "movl 0x4(%esp),%eax\n\t" - "movl %ecx,0x1c(%eax)\n\t" /* ECX */ - "movl %ebx,0x0(%eax)\n\t" /* EBX */ - "movl %esi,0x4(%eax)\n\t" /* ESI */ - "movl %edi,0x8(%eax)\n\t" /* EDI */ - "movl %ebp,0xc(%eax)\n\t" /* EBP */ - "movl (%esp),%ecx\n\t" - "movl %ecx,0x14(%eax)\n\t" /* EIP */ - "leal 0x4(%esp),%ecx\n\t" - "movl %ecx,0x18(%eax)\n\t" /* ESP */ - "movl 8(%esp),%eax\n\t" - "movl 0x14(%eax),%ecx\n\t" /* EIP (1) */ - "movl 0x18(%eax),%esp\n\t" /* ESP */ - "pushl %ecx\n\t" /* EIP (2) */ - "movl 0x0(%eax),%ebx\n\t" /* EBX */ - "movl 0x4(%eax),%esi\n\t" /* ESI */ - "movl 0x8(%eax),%edi\n\t" /* EDI */ - "movl 0xc(%eax),%ebp\n\t" /* EBP */ - "movl 0x1c(%eax),%ecx\n\t" /* ECX */ - "ret\n\t"); #elif defined(HAVE_LIBUCONTEXT) #define coro_swapcontext(cur, oth) libucontext_swapcontext(cur, oth) #else @@ -282,24 +256,6 @@ void coro_reset(struct coro *coro, coro_function_t func, void *data) #define STACK_PTR 9 coro->context[STACK_PTR] = (rsp & ~0xful) - 0x8ul; -#elif defined(__i386__) - stack = (unsigned char *)(uintptr_t)(stack + CORO_STACK_SIZE); - - /* Make room for 3 args */ - stack -= sizeof(uintptr_t) * 3; - /* Ensure 4-byte alignment */ - stack = (unsigned char *)((uintptr_t)stack & (uintptr_t)~0x3); - - uintptr_t *argp = (uintptr_t *)stack; - *argp++ = 0; - *argp++ = (uintptr_t)coro; - *argp++ = (uintptr_t)func; - *argp++ = (uintptr_t)data; - - coro->context[5 /* EIP */] = (uintptr_t)coro_entry_point; - -#define STACK_PTR 6 - coro->context[STACK_PTR] = (uintptr_t)stack; #elif defined(HAVE_LIBUCONTEXT) libucontext_getcontext(&coro->context); diff --git a/src/lib/lwan-coro.h b/src/lib/lwan-coro.h index 73afb7d7a..530928958 100644 --- a/src/lib/lwan-coro.h +++ b/src/lib/lwan-coro.h @@ -25,8 +25,6 @@ #if defined(__x86_64__) typedef uintptr_t coro_context[10]; -#elif defined(__i386__) -typedef uintptr_t coro_context[7]; #elif defined(HAVE_LIBUCONTEXT) #include typedef libucontext_ucontext_t coro_context; From 863630d4f4c95edd283e6210b884a027fda304b9 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Fri, 26 Nov 2021 10:02:44 -0800 Subject: [PATCH 1844/2505] Fail websocket connections if PONG frames are received unexpectedly This should never happen with RFC-compliant clients, anyway. We can then get rid of discard_frame(), which is only used for that. --- src/lib/lwan-websocket.c | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index 8ec310854..c04e91aff 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -157,25 +157,6 @@ static size_t get_frame_length(struct lwan_request *request, uint16_t header) } } -static void discard_frame(struct lwan_request *request, uint16_t header) -{ - size_t len = get_frame_length(request, header); -#if defined(__linux__) - /* MSG_TRUNC for TCP sockets is only supported the way we need here - * on Linux. */ - int flags = MSG_TRUNC; -#else - /* On other OSes, we need to actually read into the buffer in order - * to discard the data. */ - int flags = 0; -#endif - - for (char buffer[1024]; len;) { - const size_t to_read = LWAN_MIN(len, sizeof(buffer)); - len -= (size_t)lwan_recv(request, buffer, to_read, flags); - } -} - static void unmask(char *msg, size_t msg_len, char mask[static 4]) { const uint32_t mask32 = string_as_uint32(mask); @@ -278,10 +259,6 @@ int lwan_response_websocket_read_hint(struct lwan_request *request, size_t size_ goto next_frame; case WS_OPCODE_PONG: - lwan_status_debug("Received unsolicited PONG frame, discarding frame"); - discard_frame(request, header); - goto next_frame; - case WS_OPCODE_RSVD_1 ... WS_OPCODE_RSVD_5: case WS_OPCODE_RSVD_CONTROL_1 ... WS_OPCODE_RSVD_CONTROL_5: case WS_OPCODE_INVALID: From 57489d5ea861abe8ae4f8de324b7ea31389b9cd6 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Tue, 14 Dec 2021 20:25:00 -0800 Subject: [PATCH 1845/2505] Update names --- src/bin/testrunner/testrunner.conf | 2 +- src/lib/lwan-coro.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bin/testrunner/testrunner.conf b/src/bin/testrunner/testrunner.conf index 52973adb8..a3bde5755 100644 --- a/src/bin/testrunner/testrunner.conf +++ b/src/bin/testrunner/testrunner.conf @@ -64,7 +64,7 @@ listener *:8080 { rewrite as = /hello?name=dark } pattern test.css { - condition environment { USER = leandro} + condition environment { COLUMNS = 80 } condition stat { path = /tmp/maoe.txt is_file = true diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 5404e5529..170eacc57 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -150,7 +150,7 @@ struct coro { * (or later). I'm not sure if I can distribute them inside a GPL program; * they're straightforward so I'm assuming there won't be any problem; if * there is, I'll just roll my own. - * -- Leandro + * -- L. */ #if defined(__x86_64__) void __attribute__((noinline, visibility("internal"))) From ef5d33dbc69363924a3c51913e65714d85599ed2 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Tue, 14 Dec 2021 20:24:09 -0800 Subject: [PATCH 1846/2505] Add another mention of Lwan in an academic journal to the README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8a6bd5198..61e1b822a 100644 --- a/README.md +++ b/README.md @@ -831,7 +831,8 @@ Lwan has been also used as a benchmark: Mentions in academic journals: -* [A dynamic predictive race detector for C/C++ programs](https://link.springer.com/article/10.1007/s11227-017-1996-8) uses Lwan as a "real world example". +* [A dynamic predictive race detector for C/C++ programs (in English, published 2017)](https://link.springer.com/article/10.1007/s11227-017-1996-8) uses Lwan as a "real world example". +* [High-precision Data Race Detection Method for Large Scale Programs (in Chinese, published 2021)](http://www.jos.org.cn/jos/article/abstract/6260) also uses Lwan as one of the case studies. Some talks mentioning Lwan: From 22681fe8bf7567a8c56f03f0dd014ff69ca58d06 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Mon, 13 Dec 2021 20:23:02 -0800 Subject: [PATCH 1847/2505] Try making a tail call to lwan_strbuf_free() from the _defer() function --- src/lib/lwan-thread.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 25fe20b9e..bdbd6c12d 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -42,7 +42,7 @@ static void lwan_strbuf_free_defer(void *data) { - lwan_strbuf_free((struct lwan_strbuf *)data); + return lwan_strbuf_free((struct lwan_strbuf *)data); } static void graceful_close(struct lwan *l, From dd3b77ccaaf8494e90127b4c124aeef46b90e048 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Sun, 12 Dec 2021 10:44:36 -0800 Subject: [PATCH 1848/2505] Port sd_listen_fds_with_names() from current version systemd This isn't a 1:1 port, because some of the required functions from libsystemd weren't also ported; these functions were rewritten to be a lot simpler -- although they now work slightly differently. --- src/lib/sd-daemon.c | 128 ++++++++++++++++++++++++++++++++++++++++++-- src/lib/sd-daemon.h | 9 ++++ 2 files changed, 134 insertions(+), 3 deletions(-) diff --git a/src/lib/sd-daemon.c b/src/lib/sd-daemon.c index b6d1c3fbe..9b318ad0a 100644 --- a/src/lib/sd-daemon.c +++ b/src/lib/sd-daemon.c @@ -36,6 +36,12 @@ #include "sd-daemon.h" #include "lwan-config.h" +static void unsetenv_listen_vars(void) { + unsetenv("LISTEN_PID"); + unsetenv("LISTEN_FDS"); + unsetenv("LISTEN_FDNAMES"); +} + int sd_listen_fds(int unset_environment) { int n, l, r, fd; const char *e; @@ -97,11 +103,127 @@ int sd_listen_fds(int unset_environment) { r = n; finish: - if (unset_environment) { - unsetenv("LISTEN_PID"); - unsetenv("LISTEN_FDS"); + if (unset_environment) + unsetenv_listen_vars(); + + return r; +} + +/* Both strv_extend_n() and strv_split() aren't simple copies of the + * same functions from systemd. These are simplified versions of those + * functions, used only in the sd_listen_fds_with_names() ported from + * newer versions of systemd. + */ +static int strv_extend_n(char ***p, const char *s, int n) { + if (!p) + return -EINVAL; + if (!s) + return -EINVAL; + + *p = calloc((size_t)n, sizeof(char *)); + if (!p) + return -ENOMEM; + + size_t s_size = strlen(s) + 1; + char *copies = calloc((size_t)(n + 1), s_size); + if (!copies) { + free(*p); + return -ENOMEM; + } + for (int i = 0; i < n; i++) { + char *copy = &copies[(size_t)i * s_size]; + *p[i] = memcpy(copy, s, s_size); + } + + return 0; +} + +static int strv_split(char ***p, const char *value, const char separator) { + char *copy = strdup(value); + int n_split = 0; + + if (!copy) + return -ENOMEM; + + for (char *c = copy; *c; ) { + char *sep_pos = strchr(c, separator); + if (!sep_pos) + break; + + n_split++; + c = sep_pos + 1; + } + + if (!n_split) + return 0; + + *p = calloc((size_t)(n_split + 1), sizeof(char *)); + if (!*p) { + free(copy); + return -ENOMEM; } + int i = 0; + for (char *c = copy; *c; ) { + char *sep_pos = strchr(c, separator); + if (!sep_pos) + break; + + *sep_pos = '\0'; + *p[i++] = c; + c = sep_pos + 1; + } + + return n_split; +} + +int sd_listen_fds_with_names(int unset_environment, char ***names) { + char **l = NULL; + bool have_names; + int n_names = 0, n_fds; + const char *e; + int r; + + if (!names) + return sd_listen_fds(unset_environment); + + e = getenv("LISTEN_FDNAMES"); + if (e) { + n_names = strv_split(&l, e, ':'); + if (n_names < 0) { + if (unset_environment) + unsetenv_listen_vars(); + return n_names; + } + + have_names = true; + } else { + have_names = false; + } + + n_fds = sd_listen_fds(unset_environment); + if (n_fds <= 0) { + r = n_fds; + goto fail; + } + + if (have_names) { + if (n_names != n_fds) { + r = -EINVAL; + goto fail; + } + } else { + r = strv_extend_n(&l, "unknown", n_fds); + if (r < 0) + goto fail; + } + + *names = l; + + return n_fds; + +fail: + free(l); return r; } diff --git a/src/lib/sd-daemon.h b/src/lib/sd-daemon.h index 31170e6b1..7a2277ff4 100644 --- a/src/lib/sd-daemon.h +++ b/src/lib/sd-daemon.h @@ -70,6 +70,7 @@ See sd_listen_fds(3) for more information. */ int sd_listen_fds(int unset_environment); +int sd_listen_fds_with_names(int unset_environment, char ***names); /* Helper call for identifying a passed file descriptor. Returns 1 if @@ -255,3 +256,11 @@ int sd_booted(void); */ int sd_watchdog_enabled(int unset_environment, uint64_t *usec); + +static inline void strv_free(char **p) +{ + if (p) { + free(p[0]); + free(p); + } +} From ca940f00a65044f2117ccd521f7623751b0d33fa Mon Sep 17 00:00:00 2001 From: L Pereira Date: Fri, 10 Dec 2021 23:25:45 -0800 Subject: [PATCH 1849/2505] (Finally) Implement HTTPS This is just a draft at the moment. Uses kTLS and mbedTLS to implement TLS 1.2 on Linux. (It's very unlikely other platforms will be supported, unless they also support something similar to kTLS. I don't want to make a lot more changes elsewhere and virtualize every send/receive operation.) This is working fine with testing on localhost, using both cURL and OpenSSL s_client. No tests over the internet, or even the local network, have been performed yet. Haven't measured performance yet, but I suspect it'll be decent enough to not really care much about making this more efficient. Handshake is just a handful of read/writes, some setsockopts(), and then the kernel takes over encryption/decryption. This is nice because individual keys for each connection isn't in userland anymore before that handler has even the chance of processing a single request. (Server private key and certificates are still in core, though, although I don't really know yet -- short of mprotecting() them to PROT_NONE when not in use -- how to make them really secret; maybe I just don't.) I used mbedTLS rather than something more traditional like OpenSSL because not only I'm more familiar with mbedTLS (having used it on another project), I don't really like the OpenSSL API. It's also harder to do the kTLS handshake by yourself with OpenSSL these days. I might try BearSSL later, as that has the nice property of not calling malloc() by itself, which could be interesting in this case. The major thing missing here is the ability to specify multiple listeners, one for the legacy HTTP, and another for HTTPS. I have played a bit with the configuration file and haven't found anything that I like yet. For now, if you build with this patch, the server will operate in a HTTPS-only mode. --- CMakeLists.txt | 23 ++ README.md | 81 ++++- lwan.conf | 9 +- src/bin/testrunner/testrunner.conf | 5 +- src/cmake/lwan-build-config.h.cmake | 1 + src/lib/lwan-io-wrappers.c | 12 +- src/lib/lwan-lua.c | 7 + src/lib/lwan-mod-rewrite.c | 37 +- src/lib/lwan-private.h | 23 +- src/lib/lwan-socket.c | 124 +++++-- src/lib/lwan-thread.c | 418 +++++++++++++++++++++-- src/lib/lwan.c | 136 ++++++-- src/lib/lwan.h | 25 +- src/samples/techempower/techempower.conf | 4 +- 14 files changed, 778 insertions(+), 127 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 93f4b5608..29dbd1908 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,6 +76,29 @@ if (ZSTD_FOUND) set(HAVE_ZSTD 1) endif () +option(ENABLE_TLS "Enable support for TLS (Linux-only)" "ON") +if (ENABLE_TLS) + check_include_file(linux/tls.h HAVE_LINUX_TLS_H) + if (HAVE_LINUX_TLS_H) + # TLS support requires Linux, as Lwan uses the kTLS flavor + # only supported there. + # TODO: Try using BearSSL instead of mbedTLS and only link + # against things that are absolutely necessary to perform + # a TLS 1.2 handshake to inform the kernel about the keys. + find_library(MBEDTLS NAMES mbedtls) + if (NOT ${MBEDTLS} STREQUAL "MBEDTLS-NOTFOUND") + find_library(MBEDTLS_CRYPTO NAMES mbedcrypto REQUIRED) + find_library(MBEDTLS_X509 NAMES mbedx509 REQUIRED) + + message(STATUS "Building with Linux kTLS + mbedTLS at ${MBEDTLS}") + set(HAVE_MBEDTLS 1) + list(APPEND ADDITIONAL_LIBRARIES ${MBEDTLS} ${MBEDTLS_CRYPTO} ${MBEDTLS_X509}) + else () + message(STATUS "mbedTLS not found: not building with TLS support") + endif () + endif () +endif () + option(USE_ALTERNATIVE_MALLOC "Use alternative malloc implementations" "OFF") if (USE_ALTERNATIVE_MALLOC) unset(ALTMALLOC_LIBS CACHE) diff --git a/README.md b/README.md index 61e1b822a..3e4bb09e1 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,8 @@ The build system will look for these libraries and enable/link if available. - [Valgrind](http://valgrind.org) - [Brotli](https://github.com/google/brotli) - [ZSTD](https://github.com/facebook/zstd) + - On Linux builds, if `-DENABLE_TLS=ON` (default) is passed: + - [mbedTLS](https://github.com/ARMmbed/mbedtls) - Alternative memory allocators can be used by passing `-DUSE_ALTERNATIVE_MALLOC` to CMake with the following values: - ["mimalloc"](https://github.com/microsoft/mimalloc) - ["jemalloc"](http://jemalloc.net/) @@ -63,10 +65,10 @@ will be downloaded and built alongside Lwan. - Ubuntu 14+: `apt-get update && apt-get install git cmake zlib1g-dev pkg-config` - macOS: `brew install cmake` -#### Build all examples - - ArchLinux: `pacman -S cmake zlib sqlite luajit libmariadbclient gperftools valgrind` +#### Build with all optional features + - ArchLinux: `pacman -S cmake zlib sqlite luajit libmariadbclient gperftools valgrind mbedtls` - FreeBSD: `pkg install cmake pkgconf sqlite3 lua51` - - Ubuntu 14+: `apt-get update && apt-get install git cmake zlib1g-dev pkg-config lua5.1-dev libsqlite3-dev libmysqlclient-dev` + - Ubuntu 14+: `apt-get update && apt-get install git cmake zlib1g-dev pkg-config lua5.1-dev libsqlite3-dev libmysqlclient-dev libmbedtls-dev` - macOS: `brew install cmake mysql-connector-c sqlite lua@5.1 pkg-config` ### Build commands @@ -340,17 +342,70 @@ written in the configuration file. ### Listeners -In order to specify which interfaces Lwan should listen on, a `listener` section -must be specified. Only one listener per Lwan process is accepted at the moment. -The only parameter to a listener block is the interface address and the port to -listen on; anything inside a listener section are instances of modules. +Only two listeners are supported per Lwan process: the HTTP listener (`listener` +section), and the HTTPS listener (`tls_listener` section). Only one listener +of each type is allowed. -The syntax for the listener parameter is `${ADDRESS}:${PORT}`, where `${ADDRESS}` -can either be `*` (binding to all interfaces), an IPv6 address (if surrounded by -square brackets), an IPv4 address, or a hostname. If systemd's socket activation -is used, `systemd` can be specified as a parameter. +> :warning: **Warning:** TLS support is experimental. Although it is stable +> during initial testing, your mileage may vary. Only TLSv1.2 is supported +> at this point, but TLSv1.3 is planned. -### Routing URLs Using Modules or Handlers +> :bulb: **Note:** TLS support requires :penguin: Linux with the `tls.ko` +> module built-in or loaded. Support for other operating systems may be +> added in the future. FreeBSD seems possible, other operating systems +> do not seem to offer similar feature. For unsupported operating systems, +> using a TLS terminator proxy such as [Hitch](https://hitch-tls.org/) is a good +> option. + +For both `listener` and `tls_listener` sections, the only parameter is the +the interface address and port to listen on. The listener syntax is +`${ADDRESS}:${PORT}`, where `${ADDRESS}` can either be `*` (binding to all +interfaces), an IPv6 address (if surrounded by square brackets), an IPv4 +address, or a hostname. For instance, `listener localhost:9876` would +listen only in the `lo` interface, port `9876`. + +While a `listener` section takes no keys, a `tls_listener` section +requires two: `cert` and `key`. Each point, respectively, to the location +on disk where the TLS certificate and private key files are located. + +> :magic_wand: **Tip:** To generate these keys for testing purposes, the +> OpenSSL command-line tool can be used like the following: +> `openssl req -nodes -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 7` + +> :bulb: **Note:** It's recommended that a [Straitjacket](#Straitjacket) with a `chroot` option is declared +> right after a `tls_listener` section, in such a way that the paths to the +> certificate and key are out of reach from that point on. + +If systemd socket activation is used, `systemd` can be specified as a +parameter. (If multiple listeners from systemd are specified, +`systemd:FileDescriptorName` can be specified, where `FileDescriptorName` +follows the [conventions set in the `systemd.socket` documentation](https://www.freedesktop.org/software/systemd/man/systemd.socket.html).) + +Examples: + +``` +listener *:8080 # Listen on all interfaces, port 8080, HTTP + +tls_listener *:8081 { # Listen on all interfaces, port 8081, HTTPS + cert = /path/to/cert.pem + key = /path/to/key.pem +} + +# Use named systemd socket activation for HTTP listener +listener systemd:my-service-http.socket + +# Use named systemd socket activation for HTTPS listener +tls_listener systemd:my-service-https.socket { + ... +} +``` + +### Site + +A `site` section groups instances of modules and handlers that will respond to +requests to a given URL prefix. + +#### Routing URLs Using Modules or Handlers In order to route URLs, Lwan matches the largest common prefix from the request URI with a set of prefixes specified in the listener section. How a request to @@ -438,6 +493,7 @@ information from the request, or to set the response, as seen below: - `req:body()` returns the request body (POST/PUT requests). - `req:request_id()` returns a string containing the request ID. - `req:request_date()` returns the date as it'll be written in the `Date` response header. + - `req:is_https()` returns `true` if this request is serviced through HTTPS, `false` otherwise. Handler functions may return either `nil` (in which case, a `200 OK` response is generated), or a number matching an HTTP status code. Attempting to return @@ -531,6 +587,7 @@ for that condition to be evaluated: |`encoding` | No | `deflate`, `gzip`, `brotli`, `zstd`, `none` | Checks if client accepts responses in a determined encoding (e.g. `deflate = yes` for Deflate encoding) | |`proxied`♠ | No | Boolean | Checks if request has been proxied through PROXY protocol | |`http_1.0`♠ | No | Boolean | Checks if request is made with a HTTP/1.0 client | +|`is_https`♠ | No | Boolean | Checks if request is made through HTTPS | |`has_query_string`♠ | No | Boolean | Checks if request has a query string (even if empty) | |`method`♠ |No | Method name | Checks if HTTP method is the one specified | |`lua`♠ |No| String | Runs Lua function `matches(req)` inside String and checks if it returns `true` or `false` | diff --git a/lwan.conf b/lwan.conf index 1af606547..85a4e3236 100644 --- a/lwan.conf +++ b/lwan.conf @@ -18,7 +18,14 @@ proxy_protocol = false # by default. Other options may require more privileges. straitjacket -listener *:8080 { +listener *:8080 + +#tls_listener *:8081 { +# cert = /path/to/cert.pem +# key = /path/to/key.pem +#} + +site { serve_files / { path = ./wwwroot diff --git a/src/bin/testrunner/testrunner.conf b/src/bin/testrunner/testrunner.conf index a3bde5755..1721c22aa 100644 --- a/src/bin/testrunner/testrunner.conf +++ b/src/bin/testrunner/testrunner.conf @@ -24,7 +24,9 @@ max_post_data_size = 1000000 # by default. Other options may require more privileges. straitjacket -listener *:8080 { +listener *:8080 + +site { &custom_header /customhdr &sleep /sleep @@ -146,6 +148,7 @@ listener *:8080 { cache for = ${CACHE_FOR:5} } } + headers { server = lwan/testrunner x-global-header = present diff --git a/src/cmake/lwan-build-config.h.cmake b/src/cmake/lwan-build-config.h.cmake index d08bd7d30..acb8b51a9 100644 --- a/src/cmake/lwan-build-config.h.cmake +++ b/src/cmake/lwan-build-config.h.cmake @@ -63,6 +63,7 @@ #cmakedefine HAVE_BROTLI #cmakedefine HAVE_ZSTD #cmakedefine HAVE_LIBUCONTEXT +#cmakedefine HAVE_MBEDTLS /* Valgrind support for coroutines */ #cmakedefine HAVE_VALGRIND diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index d87e43bc6..4d609b049 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -222,9 +222,17 @@ void lwan_sendfile(struct lwan_request *request, const char *header, size_t header_len) { - size_t chunk_size = LWAN_MIN(count, 1ul << 17); + /* Clamp each chunk to 2^14 bytes as that's the maximum TLS record size[1]. + * First chunk is clamped to 2^14 - header_len, because the header is sent + * using MSG_MORE. Subsequent chunks are sized 2^14 bytes. + * (Do this regardless of this connection being TLS or not for simplicity.) + * [1] https://www.kernel.org/doc/html/v5.12/networking/tls.html#sending-tls-application-data + */ + size_t chunk_size = LWAN_MIN(count, (1ul << 14) - header_len); size_t to_be_written = count; + assert(header_len < (1ul << 14)); + lwan_send(request, header, header_len, MSG_MORE); while (true) { @@ -244,7 +252,7 @@ void lwan_sendfile(struct lwan_request *request, if (!to_be_written) break; - chunk_size = LWAN_MIN(to_be_written, 1ul << 19); + chunk_size = LWAN_MIN(to_be_written, 1ul << 14); lwan_readahead_queue(in_fd, offset, chunk_size); try_again: diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index d82349388..343fa5275 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -107,6 +107,13 @@ LWAN_LUA_METHOD(header) return request_param_getter(L, lwan_request_get_header); } +LWAN_LUA_METHOD(is_https) +{ + struct lwan_request *request = lwan_lua_get_request_from_userdata(L); + lua_pushboolean(L, !!(request->conn->flags & CONN_TLS)); + return 1; +} + LWAN_LUA_METHOD(path) { struct lwan_request *request = lwan_lua_get_request_from_userdata(L); diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index 068362f49..67cf8668a 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -61,23 +61,27 @@ enum pattern_flag { PATTERN_COND_PROXIED = 1 << 13, PATTERN_COND_HTTP10 = 1 << 14, PATTERN_COND_HAS_QUERY_STRING = 1 << 15, + PATTERN_COND_HTTPS = 1 << 16, PATTERN_COND_MASK = PATTERN_COND_COOKIE | PATTERN_COND_ENV_VAR | PATTERN_COND_STAT | PATTERN_COND_QUERY_VAR | PATTERN_COND_POST_VAR | PATTERN_COND_HEADER | PATTERN_COND_LUA | PATTERN_COND_METHOD | PATTERN_COND_ACCEPT_ENCODING | PATTERN_COND_PROXIED | PATTERN_COND_HTTP10 | - PATTERN_COND_HAS_QUERY_STRING, + PATTERN_COND_HAS_QUERY_STRING | + PATTERN_COND_HTTPS, - PATTERN_COND_STAT__HAS_IS_FILE = 1 << 16, - PATTERN_COND_STAT__HAS_IS_DIR = 1 << 17, - PATTERN_COND_STAT__IS_FILE = 1 << 18, - PATTERN_COND_STAT__IS_DIR = 1 << 19, + PATTERN_COND_STAT__HAS_IS_FILE = 1 << 17, + PATTERN_COND_STAT__HAS_IS_DIR = 1 << 18, + PATTERN_COND_STAT__IS_FILE = 1 << 19, + PATTERN_COND_STAT__IS_DIR = 1 << 20, PATTERN_COND_STAT__FILE_CHECK = PATTERN_COND_STAT__HAS_IS_FILE | PATTERN_COND_STAT__IS_FILE, PATTERN_COND_STAT__DIR_CHECK = PATTERN_COND_STAT__HAS_IS_DIR | PATTERN_COND_STAT__IS_DIR, + + PATTERN_COND_HTTPS__IS_HTTPS = 1 << 21, }; struct pattern { @@ -292,6 +296,16 @@ static bool condition_matches(struct lwan_request *request, return false; } + if (p->flags & PATTERN_COND_HTTPS) { + bool is_tls = request->conn->flags & CONN_TLS; + if (p->flags & PATTERN_COND_HTTPS__IS_HTTPS) { + if (!is_tls) + return false; + } else if (is_tls) { + return false; + } + } + if (p->flags & PATTERN_COND_ACCEPT_ENCODING) { const enum lwan_request_flags accept = p->condition.request_flags & REQUEST_ACCEPT_MASK; @@ -786,17 +800,18 @@ static bool rewrite_parse_conf_pattern(struct private_data *pd, } else if (streq(line->key, "expand_with_lua")) { expand_with_lua = parse_bool(line->value, false); } else if (streq(line->key, "condition_proxied")) { - if (parse_bool(line->value, false)) { + if (parse_bool(line->value, false)) pattern->flags |= PATTERN_COND_PROXIED; - } } else if (streq(line->key, "condition_http_1.0")) { - if (parse_bool(line->value, false)) { + if (parse_bool(line->value, false)) pattern->flags |= PATTERN_COND_HTTP10; - } } else if (streq(line->key, "condition_has_query_string")) { - if (parse_bool(line->value, false)) { + if (parse_bool(line->value, false)) pattern->flags |= PATTERN_COND_HAS_QUERY_STRING; - } + } else if (streq(line->key, "condition_is_https")) { + if (parse_bool(line->value, false)) + pattern->flags |= PATTERN_COND_HTTPS__IS_HTTPS; + pattern->flags |= PATTERN_COND_HTTPS; } else if (streq(line->key, "condition_method")) { if (!get_method_from_string(pattern, line->value)) { config_error(config, "Unknown HTTP method: %s", line->value); diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 7798d438d..0cd1eedea 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -87,7 +87,9 @@ void lwan_set_thread_name(const char *name); void lwan_response_init(struct lwan *l); void lwan_response_shutdown(struct lwan *l); -int lwan_create_listen_socket(struct lwan *l, bool print_listening_msg); +int lwan_create_listen_socket(const struct lwan *l, + bool print_listening_msg, + bool is_https); void lwan_thread_init(struct lwan *l); void lwan_thread_shutdown(struct lwan *l); @@ -159,6 +161,21 @@ static ALWAYS_INLINE __attribute__((pure)) size_t lwan_nextpow2(size_t number) return number + 1; } +#if defined(HAVE_MBEDTLS) +#include +#include +#include + +struct lwan_tls_context { + mbedtls_ssl_config config; + mbedtls_x509_crt server_cert; + mbedtls_pk_context server_key; + + mbedtls_entropy_context entropy; + + mbedtls_ctr_drbg_context ctr_drbg; +}; +#endif #ifdef HAVE_LUA #include @@ -177,6 +194,10 @@ const char *lwan_lua_state_last_error(lua_State *L); __asm__ __volatile__("" ::"g"(no_discard_) : "memory"); \ } while (0) +static inline void lwan_always_bzero(void *ptr, size_t len) +{ + LWAN_NO_DISCARD(memset(ptr, 0, len)); +} #ifdef __APPLE__ #define SECTION_START(name_) __start_##name_[] __asm("section$start$__DATA$" #name_) diff --git a/src/lib/lwan-socket.c b/src/lib/lwan-socket.c index 86f2cb2aa..4d58d5e87 100644 --- a/src/lib/lwan-socket.c +++ b/src/lib/lwan-socket.c @@ -20,6 +20,7 @@ #define _GNU_SOURCE #include +#include #include #include #include @@ -108,17 +109,6 @@ static int set_socket_flags(int fd) return fd; } -static int setup_socket_from_systemd(void) -{ - int fd = SD_LISTEN_FDS_START; - - if (!sd_is_socket_inet(fd, AF_UNSPEC, SOCK_STREAM, 1, 0)) - lwan_status_critical("Passed file descriptor is not a " - "listening TCP socket"); - - return set_socket_flags(fd); -} - static sa_family_t parse_listener_ipv4(char *listener, char **node, char **port) { char *colon = strrchr(listener, ':'); @@ -187,13 +177,16 @@ static sa_family_t parse_listener(char *listener, char **node, char **port) return parse_listener_ipv4(listener, node, port); } -static int -listen_addrinfo(int fd, const struct addrinfo *addr, bool print_listening_msg) +static int listen_addrinfo(int fd, + const struct addrinfo *addr, + bool print_listening_msg, + bool is_https) { if (listen(fd, lwan_socket_get_backlog_size()) < 0) lwan_status_critical_perror("listen"); if (print_listening_msg) { + const char *s_if_https = is_https ? "s" : ""; char host_buf[NI_MAXHOST], serv_buf[NI_MAXSERV]; int ret = getnameinfo(addr->ai_addr, addr->ai_addrlen, host_buf, sizeof(host_buf), serv_buf, sizeof(serv_buf), @@ -202,9 +195,11 @@ listen_addrinfo(int fd, const struct addrinfo *addr, bool print_listening_msg) lwan_status_critical("getnameinfo: %s", gai_strerror(ret)); if (addr->ai_family == AF_INET6) - lwan_status_info("Listening on http://[%s]:%s", host_buf, serv_buf); + lwan_status_info("Listening on http%s://[%s]:%s", s_if_https, + host_buf, serv_buf); else - lwan_status_info("Listening on http://%s:%s", host_buf, serv_buf); + lwan_status_info("Listening on http%s://%s:%s", s_if_https, + host_buf, serv_buf); } return set_socket_flags(fd); @@ -224,7 +219,9 @@ listen_addrinfo(int fd, const struct addrinfo *addr, bool print_listening_msg) lwan_status_perror("%s not supported by the kernel", #_option); \ } while (0) -static int bind_and_listen_addrinfos(const struct addrinfo *addrs, bool print_listening_msg) +static int bind_and_listen_addrinfos(const struct addrinfo *addrs, + bool print_listening_msg, + bool is_https) { const struct addrinfo *addr; @@ -244,7 +241,7 @@ static int bind_and_listen_addrinfos(const struct addrinfo *addrs, bool print_li #endif if (!bind(fd, addr->ai_addr, addr->ai_addrlen)) - return listen_addrinfo(fd, addr, print_listening_msg); + return listen_addrinfo(fd, addr, print_listening_msg, is_https); close(fd); } @@ -252,10 +249,13 @@ static int bind_and_listen_addrinfos(const struct addrinfo *addrs, bool print_li lwan_status_critical("Could not bind socket"); } -static int setup_socket_normally(struct lwan *l, bool print_listening_msg) +static int setup_socket_normally(const struct lwan *l, + bool print_listening_msg, + bool is_https, + const char *listener_from_config) { char *node, *port; - char *listener = strdupa(l->config.listener); + char *listener = strdupa(listener_from_config); sa_family_t family = parse_listener(listener, &node, &port); if (family == AF_MAX) { @@ -272,24 +272,13 @@ static int setup_socket_normally(struct lwan *l, bool print_listening_msg) if (ret) lwan_status_critical("getaddrinfo: %s", gai_strerror(ret)); - int fd = bind_and_listen_addrinfos(addrs, print_listening_msg); + int fd = bind_and_listen_addrinfos(addrs, print_listening_msg, is_https); freeaddrinfo(addrs); return fd; } -int lwan_create_listen_socket(struct lwan *l, bool print_listening_msg) +static int set_socket_options(const struct lwan *l, int fd) { - int fd, n; - - n = sd_listen_fds(1); - if (n > 1) { - lwan_status_critical("Too many file descriptors received"); - } else if (n == 1) { - fd = setup_socket_from_systemd(); - } else { - fd = setup_socket_normally(l, print_listening_msg); - } - SET_SOCKET_OPTION(SOL_SOCKET, SO_LINGER, (&(struct linger){.l_onoff = 1, .l_linger = 1})); @@ -312,5 +301,76 @@ int lwan_create_listen_socket(struct lwan *l, bool print_listening_msg) return fd; } +static int from_systemd_socket(const struct lwan *l, int fd) +{ + if (!sd_is_socket_inet(fd, AF_UNSPEC, SOCK_STREAM, 1, 0)) { + lwan_status_critical("Passed file descriptor is not a " + "listening TCP socket"); + } + + return set_socket_options(l, set_socket_flags(fd)); +} + +int lwan_create_listen_socket(const struct lwan *l, + bool print_listening_msg, + bool is_https) +{ + const char *listener = is_https ? l->config.tls_listener + : l->config.listener; + + if (!strncmp(listener, "systemd:", sizeof("systemd:") - 1)) { + char **names; + int n = sd_listen_fds_with_names(false, &names); + int fd = -1; + + if (n < 0) { + errno = -n; + lwan_status_perror( + "Could not parse socket activation data from systemd"); + return n; + } + + listener += sizeof("systemd:") - 1; + + for (int i = 0; i < n; i++) { + if (!strcmp(names[i], listener)) { + fd = SD_LISTEN_FDS_START + i; + break; + } + } + + strv_free(names); + + if (fd < 0) { + lwan_status_critical( + "No socket named `%s' has been passed from systemd", listener); + } + + return from_systemd_socket(l, fd); + } + + if (streq(listener, "systemd")) { + int n = sd_listen_fds(false); + + if (n < 0) { + errno = -n; + lwan_status_perror("Could not obtain sockets passed from systemd"); + return n; + } + + if (n != 1) { + lwan_status_critical( + "%d listeners passed from systemd. Must specify listeners with " + "systemd:listener-name syntax", + n); + } + + return from_systemd_socket(l, SD_LISTEN_FDS_START); + } + + int fd = setup_socket_normally(l, print_listening_msg, is_https, listener); + return set_socket_options(l, fd); +} + #undef SET_SOCKET_OPTION #undef SET_SOCKET_OPTION_MAY_FAIL diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index bdbd6c12d..0f700c585 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -35,6 +35,17 @@ #include #endif +#if defined(HAVE_MBEDTLS) +#include +#include +#include +#include +#include + +#include +#include +#endif + #include "list.h" #include "murmur3.h" #include "lwan-private.h" @@ -137,6 +148,146 @@ uint64_t lwan_request_get_id(struct lwan_request *request) return helper->request_id; } +#if defined(HAVE_MBEDTLS) +static bool +lwan_setup_tls_keys(int fd, const mbedtls_ssl_context *ssl, int rx_or_tx) +{ + struct tls12_crypto_info_aes_gcm_128 info = { + .info = {.version = TLS_1_2_VERSION, + .cipher_type = TLS_CIPHER_AES_GCM_128}, + }; + const unsigned char *salt, *iv, *rec_seq; + mbedtls_gcm_context *gcm_ctx; + mbedtls_aes_context *aes_ctx; + + switch (rx_or_tx) { + case TLS_RX: + salt = ssl->transform->iv_dec; + rec_seq = ssl->in_ctr; + gcm_ctx = ssl->transform->cipher_ctx_dec.cipher_ctx; + break; + case TLS_TX: + salt = ssl->transform->iv_enc; + rec_seq = ssl->cur_out_ctr; + gcm_ctx = ssl->transform->cipher_ctx_enc.cipher_ctx; + break; + default: + __builtin_unreachable(); + } + + iv = salt + 4; + aes_ctx = gcm_ctx->cipher_ctx.cipher_ctx; + + memcpy(info.iv, iv, TLS_CIPHER_AES_GCM_128_IV_SIZE); + memcpy(info.rec_seq, rec_seq, TLS_CIPHER_AES_GCM_128_REC_SEQ_SIZE); + memcpy(info.key, aes_ctx->rk, TLS_CIPHER_AES_GCM_128_KEY_SIZE); + memcpy(info.salt, salt, TLS_CIPHER_AES_GCM_128_SALT_SIZE); + + if (UNLIKELY(setsockopt(fd, SOL_TLS, rx_or_tx, &info, sizeof(info)) < 0)) { + lwan_status_perror("Could not set kTLS keys for fd %d", fd); + lwan_always_bzero(&info, sizeof(info)); + return false; + } + + lwan_always_bzero(&info, sizeof(info)); + return true; +} + +__attribute__((format(printf, 2, 3))) +__attribute__((noinline, cold)) +static void lwan_status_mbedtls_error(int error_code, const char *fmt, ...) +{ + char *formatted; + va_list ap; + int r; + + va_start(ap, fmt); + r = vasprintf(&formatted, fmt, ap); + if (r >= 0) { + char mbedtls_errbuf[128]; + + mbedtls_strerror(error_code, mbedtls_errbuf, sizeof(mbedtls_errbuf)); + lwan_status_error("%s: %s", formatted, mbedtls_errbuf); + free(formatted); + } + va_end(ap); +} + +static void lwan_setup_tls_free_ssl_context(void *data1, void *data2) +{ + struct lwan_connection *conn = data1; + + if (UNLIKELY(conn->flags & CONN_NEEDS_TLS_SETUP)) { + mbedtls_ssl_context *ssl = data2; + + mbedtls_ssl_free(ssl); + conn->flags &= ~CONN_NEEDS_TLS_SETUP; + } +} + +static bool lwan_setup_tls(const struct lwan *l, struct lwan_connection *conn) +{ + mbedtls_ssl_context ssl; + bool retval = false; + int r; + + mbedtls_ssl_init(&ssl); + + r = mbedtls_ssl_setup(&ssl, &l->tls->config); + if (UNLIKELY(r != 0)) { + lwan_status_mbedtls_error(r, "Could not setup TLS context"); + return false; + } + + /* Yielding the coroutine during the handshake enables the I/O loop to + * destroy this coro (e.g. on connection hangup) before we have the + * opportunity to free the SSL context. Defer this call for these + * cases. */ + coro_defer2(conn->coro, lwan_setup_tls_free_ssl_context, conn, &ssl); + + int fd = lwan_connection_get_fd(l, conn); + /* FIXME: This is only required for the handshake; this uses read() and + * write() under the hood but maybe we can use something like recv() and + * send() instead to force MSG_MORE et al? (strace shows a few + * consecutive calls to write(); this might be sent in separate TCP + * fragments.) */ + mbedtls_ssl_set_bio(&ssl, &fd, mbedtls_net_send, mbedtls_net_recv, NULL); + + while (true) { + switch (mbedtls_ssl_handshake(&ssl)) { + case 0: + goto enable_tls_ulp; + case MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS: + case MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS: + case MBEDTLS_ERR_SSL_WANT_READ: + coro_yield(conn->coro, CONN_CORO_WANT_READ); + break; + case MBEDTLS_ERR_SSL_WANT_WRITE: + coro_yield(conn->coro, CONN_CORO_WANT_WRITE); + break; + default: + goto fail; + } + } + +enable_tls_ulp: + if (UNLIKELY(setsockopt(fd, SOL_TCP, TCP_ULP, "tls", sizeof("tls")) < 0)) + goto fail; + if (UNLIKELY(!lwan_setup_tls_keys(fd, &ssl, TLS_RX))) + goto fail; + if (UNLIKELY(!lwan_setup_tls_keys(fd, &ssl, TLS_TX))) + goto fail; + + retval = true; + +fail: + mbedtls_ssl_free(&ssl); + + conn->flags &= ~CONN_NEEDS_TLS_SETUP; + return retval; +} +#endif + __attribute__((noreturn)) static int process_request_coro(struct coro *coro, void *data) { @@ -160,6 +311,22 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, const size_t init_gen = 1; /* 1 call to coro_defer() */ assert(init_gen == coro_deferred_get_generation(coro)); +#if defined(HAVE_MBEDTLS) + if (conn->flags & CONN_NEEDS_TLS_SETUP) { + /* Sometimes this flag is unset when it *should* be set! Need to + * figure out why. This causes the TLS handshake to not happen, + * making the normal HTTP request reading code to try and read + * the handshake as if it were a HTTP request. */ + if (UNLIKELY(!lwan_setup_tls(lwan, conn))) { + coro_yield(coro, CONN_CORO_ABORT); + __builtin_unreachable(); + } + + conn->flags |= CONN_TLS; + } +#endif + assert(!(conn->flags & CONN_NEEDS_TLS_SETUP)); + while (true) { struct lwan_request_parser_helper helper = { .buffer = &buffer, @@ -265,6 +432,10 @@ static void update_epoll_flags(int fd, conn->flags |= or_mask[yield_result]; conn->flags &= and_mask[yield_result]; + assert(!(conn->flags & (CONN_LISTENER_HTTP | CONN_LISTENER_HTTPS))); + assert((conn->flags & (CONN_TLS | CONN_NEEDS_TLS_SETUP)) == + (prev_flags & (CONN_TLS | CONN_NEEDS_TLS_SETUP))); + if (conn->flags == prev_flags) return; @@ -357,6 +528,7 @@ static void update_date_cache(struct lwan_thread *thread) thread->date.expires); } +__attribute__((cold)) static bool send_buffer_without_coro(int fd, const char *buf, size_t buf_len, int flags) { size_t total_sent = 0; @@ -381,27 +553,41 @@ static bool send_buffer_without_coro(int fd, const char *buf, size_t buf_len, in return false; } +__attribute__((cold)) static bool send_string_without_coro(int fd, const char *str, int flags) { return send_buffer_without_coro(fd, str, strlen(str), flags); } -static void send_response_without_coro(const struct lwan *l, - int fd, - enum lwan_http_status status) +__attribute__((cold)) static void +send_last_response_without_coro(const struct lwan *l, + const struct lwan_connection *conn, + enum lwan_http_status status) { + int fd = lwan_connection_get_fd(l, conn); + + if (conn->flags & CONN_NEEDS_TLS_SETUP) { + /* There's nothing that can be done here if a client is expecting a + * TLS connection: the TLS handshake requires a coroutine as it + * might yield. (In addition, the TLS handshake might allocate + * memory, and if you couldn't create a coroutine at this point, + * it's unlikely you'd be able to allocate memory for the TLS + * context anyway.) */ + goto shutdown_and_close; + } + if (!send_string_without_coro(fd, "HTTP/1.0 ", MSG_MORE)) - return; + goto shutdown_and_close; if (!send_string_without_coro( fd, lwan_http_status_as_string_with_code(status), MSG_MORE)) - return; + goto shutdown_and_close; if (!send_string_without_coro(fd, "\r\nConnection: close", MSG_MORE)) - return; + goto shutdown_and_close; if (!send_string_without_coro(fd, "\r\nContent-Type: text/html", MSG_MORE)) - return; + goto shutdown_and_close; if (send_buffer_without_coro(fd, lwan_strbuf_get_buffer(&l->headers), lwan_strbuf_get_length(&l->headers), @@ -416,6 +602,10 @@ static void send_response_without_coro(const struct lwan *l, lwan_strbuf_free(&buffer); } + +shutdown_and_close: + shutdown(fd, SHUT_RDWR); + close(fd); } static ALWAYS_INLINE bool spawn_coro(struct lwan_connection *conn, @@ -423,6 +613,11 @@ static ALWAYS_INLINE bool spawn_coro(struct lwan_connection *conn, struct timeout_queue *tq) { struct lwan_thread *t = conn->thread; +#if defined(HAVE_MBEDTLS) + const enum lwan_connection_flags flags_to_keep = conn->flags & CONN_NEEDS_TLS_SETUP; +#else + const enum lwan_connection_flags flags_to_keep = 0; +#endif assert(!conn->coro); assert(!(conn->flags & CONN_ASYNC_AWAIT)); @@ -433,7 +628,7 @@ static ALWAYS_INLINE bool spawn_coro(struct lwan_connection *conn, *conn = (struct lwan_connection){ .coro = coro_new(switcher, process_request_coro, conn), - .flags = CONN_EVENTS_READ, + .flags = CONN_EVENTS_READ | flags_to_keep, .time_to_expire = tq->current_time + tq->move_to_last_bump, .thread = t, }; @@ -448,9 +643,7 @@ static ALWAYS_INLINE bool spawn_coro(struct lwan_connection *conn, lwan_status_error("Couldn't spawn coroutine for file descriptor %d", fd); - send_response_without_coro(tq->lwan, fd, HTTP_UNAVAILABLE); - shutdown(fd, SHUT_RDWR); - close(fd); + send_last_response_without_coro(tq->lwan, conn, HTTP_UNAVAILABLE); return false; } @@ -523,18 +716,19 @@ turn_timer_wheel(struct timeout_queue *tq, struct lwan_thread *t, int epoll_fd) return (int)timeouts_timeout(t->wheel); } -static bool accept_waiting_clients(const struct lwan_thread *t) +static bool accept_waiting_clients(const struct lwan_thread *t, + const struct lwan_connection *listen_socket) { const uint32_t read_events = conn_flags_to_epoll_events(CONN_EVENTS_READ); - const struct lwan_connection *conns = t->lwan->conns; + struct lwan_connection *conns = t->lwan->conns; + int listen_fd = (int)(intptr_t)(listen_socket - conns); while (true) { - int fd = - accept4(t->listen_fd, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC); + int fd = accept4(listen_fd, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC); if (LIKELY(fd >= 0)) { - const struct lwan_connection *conn = &conns[fd]; - struct epoll_event ev = {.data.ptr = (void *)conn, .events = read_events}; + struct lwan_connection *conn = &conns[fd]; + struct epoll_event ev = {.data.ptr = conn, .events = read_events}; int r = epoll_ctl(conn->thread->epoll_fd, EPOLL_CTL_ADD, fd, &ev); if (UNLIKELY(r < 0)) { @@ -542,11 +736,17 @@ static bool accept_waiting_clients(const struct lwan_thread *t) "set %d. Dropping connection", fd, conn->thread->epoll_fd); - send_response_without_coro(t->lwan, fd, HTTP_UNAVAILABLE); - shutdown(fd, SHUT_RDWR); - close(fd); + send_last_response_without_coro(t->lwan, conn, HTTP_UNAVAILABLE); +#if defined(HAVE_MBEDTLS) + } else if (listen_socket->flags & CONN_LISTENER_HTTPS) { + assert(listen_fd == t->tls_listen_fd); + assert(!(listen_socket->flags & CONN_LISTENER_HTTP)); + conn->flags |= CONN_NEEDS_TLS_SETUP; + } else { + assert(listen_fd == t->listen_fd); + assert(listen_socket->flags & CONN_LISTENER_HTTP); +#endif } - continue; } @@ -570,11 +770,13 @@ static bool accept_waiting_clients(const struct lwan_thread *t) } static int create_listen_socket(struct lwan_thread *t, - unsigned int num) + unsigned int num, + bool tls) { + const struct lwan *lwan = t->lwan; int listen_fd; - listen_fd = lwan_create_listen_socket(t->lwan, num == 0); + listen_fd = lwan_create_listen_socket(lwan, num == 0, tls); if (listen_fd < 0) lwan_status_critical("Could not create listen_fd"); @@ -606,7 +808,7 @@ static int create_listen_socket(struct lwan_thread *t, struct epoll_event event = { .events = EPOLLIN | EPOLLET | EPOLLERR, - .data.ptr = NULL, + .data.ptr = &t->lwan->conns[listen_fd], }; if (epoll_ctl(t->epoll_fd, EPOLL_CTL_ADD, listen_fd, &event) < 0) lwan_status_critical_perror("Could not add socket to epoll"); @@ -652,10 +854,15 @@ static void *thread_io_loop(void *data) } for (struct epoll_event *event = events; n_fds--; event++) { - struct lwan_connection *conn; + struct lwan_connection *conn = event->data.ptr; + + if (UNLIKELY(event->events & (EPOLLRDHUP | EPOLLHUP))) { + timeout_queue_expire(&tq, conn); + continue; + } - if (!event->data.ptr) { - if (LIKELY(accept_waiting_clients(t))) { + if (conn->flags & (CONN_LISTENER_HTTP | CONN_LISTENER_HTTPS)) { + if (LIKELY(accept_waiting_clients(t, conn))) { accepted_connections = true; continue; } @@ -664,16 +871,11 @@ static void *thread_io_loop(void *data) break; } - conn = event->data.ptr; - - if (UNLIKELY(event->events & (EPOLLRDHUP | EPOLLHUP))) { - timeout_queue_expire(&tq, conn); - continue; - } - if (!conn->coro) { - if (UNLIKELY(!spawn_coro(conn, &switcher, &tq))) + if (UNLIKELY(!spawn_coro(conn, &switcher, &tq))) { + send_last_response_without_coro(t->lwan, conn, HTTP_INTERNAL_ERROR); continue; + } } resume_coro(&tq, conn, epoll_fd); @@ -845,9 +1047,135 @@ adjust_thread_affinity(const struct lwan_thread *thread) } #endif +#if defined(HAVE_MBEDTLS) +static bool is_tls_ulp_supported(void) +{ + FILE *available_ulp = fopen("/proc/sys/net/ipv4/tcp_available_ulp", "re"); + char buffer[512]; + bool available = false; + + if (!available_ulp) + return false; + + if (fgets(buffer, 512, available_ulp)) { + if (strstr(buffer, "tls")) + available = true; + } + + fclose(available_ulp); + return available; +} + +static bool lwan_init_tls(struct lwan *l) +{ + static const int aes128_ciphers[] = { + /* Only allow Ephemeral Diffie-Hellman key exchange, so Perfect + * Forward Secrecy is possible. */ + MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + MBEDTLS_TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, + MBEDTLS_TLS_DHE_PSK_WITH_AES_128_GCM_SHA256, + + /* FIXME: Other ciphers are supported by kTLS, notably AES256 and + * ChaCha20-Poly1305. Add those here and patch + * lwan_setup_tls_keys() to match. */ + + /* FIXME: Maybe allow this to be user-tunable like other servers do? */ + 0, + }; + int r; + + if (!l->config.ssl.cert || !l->config.ssl.key) + return false; + + if (!is_tls_ulp_supported()) + lwan_status_critical( + "TLS ULP not loaded. Try running `modprobe tls` as root."); + + l->tls = calloc(1, sizeof(*l->tls)); + if (!l->tls) + lwan_status_critical("Could not allocate memory for SSL context"); + + lwan_status_debug("Initializing mbedTLS"); + + mbedtls_ssl_config_init(&l->tls->config); + mbedtls_x509_crt_init(&l->tls->server_cert); + mbedtls_pk_init(&l->tls->server_key); + mbedtls_entropy_init(&l->tls->entropy); + mbedtls_ctr_drbg_init(&l->tls->ctr_drbg); + + r = mbedtls_x509_crt_parse_file(&l->tls->server_cert, l->config.ssl.cert); + if (r) { + lwan_status_mbedtls_error(r, "Could not parse certificate at %s", + l->config.ssl.cert); + abort(); + } + + r = mbedtls_pk_parse_keyfile(&l->tls->server_key, l->config.ssl.key, NULL); + if (r) { + lwan_status_mbedtls_error(r, "Could not parse key file at %s", + l->config.ssl.key); + abort(); + } + + /* Even though this points to files that will probably be outside + * the reach of the server (if straightjackets are used), wipe this + * struct to get rid of the paths to these files. */ + lwan_always_bzero(l->config.ssl.cert, strlen(l->config.ssl.cert)); + free(l->config.ssl.cert); + lwan_always_bzero(l->config.ssl.key, strlen(l->config.ssl.key)); + free(l->config.ssl.key); + lwan_always_bzero(&l->config.ssl, sizeof(l->config.ssl)); + + mbedtls_ssl_conf_ca_chain(&l->tls->config, l->tls->server_cert.next, NULL); + r = mbedtls_ssl_conf_own_cert(&l->tls->config, &l->tls->server_cert, + &l->tls->server_key); + if (r) { + lwan_status_mbedtls_error(r, "Could not set cert/key"); + abort(); + } + + r = mbedtls_ctr_drbg_seed(&l->tls->ctr_drbg, mbedtls_entropy_func, + &l->tls->entropy, NULL, 0); + if (r) { + lwan_status_mbedtls_error(r, "Could not seed ctr_drbg"); + abort(); + } + + r = mbedtls_ssl_config_defaults(&l->tls->config, MBEDTLS_SSL_IS_SERVER, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT); + if (r) { + lwan_status_mbedtls_error(r, "Could not set mbedTLS default config"); + abort(); + } + + mbedtls_ssl_conf_rng(&l->tls->config, mbedtls_ctr_drbg_random, + &l->tls->ctr_drbg); + mbedtls_ssl_conf_ciphersuites(&l->tls->config, aes128_ciphers); + + mbedtls_ssl_conf_renegotiation(&l->tls->config, + MBEDTLS_SSL_RENEGOTIATION_DISABLED); + mbedtls_ssl_conf_legacy_renegotiation(&l->tls->config, + MBEDTLS_SSL_LEGACY_NO_RENEGOTIATION); + +#if defined(MBEDTLS_SSL_ALPN) + static const char *alpn_protos[] = {"http/1.1", NULL}; + mbedtls_ssl_conf_alpn_protocols(&l->tls->config, alpn_protos); +#endif + + return true; +} +#endif + void lwan_thread_init(struct lwan *l) { const unsigned int total_conns = l->thread.max_fd * l->thread.count; +#if defined(HAVE_MBEDTLS) + const bool tls_initialized = lwan_init_tls(l); +#else + const bool tls_initialized = false; +#endif lwan_status_debug("Initializing threads"); @@ -932,8 +1260,17 @@ void lwan_thread_init(struct lwan *l) create_thread(l, thread); - if ((thread->listen_fd = create_listen_socket(thread, i)) < 0) + if ((thread->listen_fd = create_listen_socket(thread, i, false)) < 0) lwan_status_critical_perror("Could not create listening socket"); + l->conns[thread->listen_fd].flags |= CONN_LISTENER_HTTP; + + if (tls_initialized) { + if ((thread->tls_listen_fd = create_listen_socket(thread, i, true)) < 0) + lwan_status_critical_perror("Could not create TLS listening socket"); + l->conns[thread->tls_listen_fd].flags |= CONN_LISTENER_HTTPS; + } else { + thread->tls_listen_fd = -1; + } if (adj_affinity) { l->thread.threads[i].cpu = schedtbl[i & n_threads]; @@ -972,4 +1309,13 @@ void lwan_thread_shutdown(struct lwan *l) } free(l->thread.threads); + + if (l->tls) { + mbedtls_ssl_config_free(&l->tls->config); + mbedtls_x509_crt_free(&l->tls->server_cert); + mbedtls_pk_free(&l->tls->server_key); + mbedtls_entropy_free(&l->tls->entropy); + mbedtls_ctr_drbg_free(&l->tls->ctr_drbg); + free(l->tls); + } } diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 31b841100..528a49df5 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -427,13 +427,95 @@ void lwan_set_url_map(struct lwan *l, const struct lwan_url_map *map) } } -static void parse_listener(struct config *c, - const struct config_line *l, - struct lwan *lwan) +const char *lwan_get_config_path(char *path_buf, size_t path_buf_len) +{ + char buffer[PATH_MAX]; + + if (proc_pidpath(getpid(), buffer, sizeof(buffer)) < 0) + goto out; + + char *path = strrchr(buffer, '/'); + if (!path) + goto out; + int ret = snprintf(path_buf, path_buf_len, "%s.conf", path + 1); + if (ret < 0 || ret >= (int)path_buf_len) + goto out; + + return path_buf; + +out: + return "lwan.conf"; +} + +static void parse_tls_listener(struct config *conf, const struct config_line *line, struct lwan *lwan) +{ +#if !defined(HAVE_MBEDTLS) + config_error(conf, "Lwan has been built without mbedTLS support"); + return; +#endif + + lwan->config.tls_listener = strdup(line->value); + if (!lwan->config.tls_listener) { + config_error(conf, "Could not allocate memory for tls_listener"); + return; + } + + lwan->config.ssl.cert = NULL; + lwan->config.ssl.key = NULL; + + while ((line = config_read_line(conf))) { + switch (line->type) { + case CONFIG_LINE_TYPE_SECTION_END: + return; + case CONFIG_LINE_TYPE_SECTION: + config_error(conf, "Unexpected section: %s", line->key); + return; + case CONFIG_LINE_TYPE_LINE: + if (streq(line->key, "cert")) { + free(lwan->config.ssl.cert); + lwan->config.ssl.cert = strdup(line->value); + if (!lwan->config.ssl.cert) + return lwan_status_critical("Could not copy string"); + } else if (streq(line->key, "key")) { + free(lwan->config.ssl.key); + lwan->config.ssl.key = strdup(line->value); + if (!lwan->config.ssl.key) + return lwan_status_critical("Could not copy string"); + } else { + config_error(conf, "Unexpected key: %s", line->key); + } + } + } + + config_error(conf, "Expecting section end while parsing SSL configuration"); +} + +static void +parse_listener(struct config *c, const struct config_line *l, struct lwan *lwan) { - free(lwan->config.listener); lwan->config.listener = strdup(l->value); + if (!lwan->config.listener) + config_error(c, "Could not allocate memory for listener"); + while ((l = config_read_line(c))) { + switch (l->type) { + case CONFIG_LINE_TYPE_LINE: + config_error(c, "Unexpected key %s", l->key); + return; + case CONFIG_LINE_TYPE_SECTION: + config_error(c, "Unexpected section %s", l->key); + return; + case CONFIG_LINE_TYPE_SECTION_END: + return; + } + } + + config_error(c, "Unexpected EOF while parsing listener"); +} + +static void +parse_site(struct config *c, const struct config_line *l, struct lwan *lwan) +{ while ((l = config_read_line(c))) { switch (l->type) { case CONFIG_LINE_TYPE_LINE: @@ -467,31 +549,13 @@ static void parse_listener(struct config *c, config_error(c, "Expecting section end while parsing listener"); } -const char *lwan_get_config_path(char *path_buf, size_t path_buf_len) -{ - char buffer[PATH_MAX]; - - if (proc_pidpath(getpid(), buffer, sizeof(buffer)) < 0) - goto out; - - char *path = strrchr(buffer, '/'); - if (!path) - goto out; - int ret = snprintf(path_buf, path_buf_len, "%s.conf", path + 1); - if (ret < 0 || ret >= (int)path_buf_len) - goto out; - - return path_buf; - -out: - return "lwan.conf"; -} - static bool setup_from_config(struct lwan *lwan, const char *path) { const struct config_line *line; struct config *conf; + bool has_site = false; bool has_listener = false; + bool has_tls_listener = false; char path_buf[PATH_MAX]; if (!path) @@ -568,17 +632,31 @@ static bool setup_from_config(struct lwan *lwan, const char *path) } break; case CONFIG_LINE_TYPE_SECTION: - if (streq(line->key, "listener")) { - if (!has_listener) { - parse_listener(conf, line, lwan); - has_listener = true; + if (streq(line->key, "site")) { + if (!has_site) { + parse_site(conf, line, lwan); + has_site = true; } else { - config_error(conf, "Only one listener supported"); + config_error(conf, "Only one site may be configured"); } } else if (streq(line->key, "straitjacket")) { lwan_straitjacket_enforce_from_config(conf); } else if (streq(line->key, "headers")) { parse_global_headers(conf, lwan); + } else if (streq(line->key, "listener")) { + if (has_listener) { + config_error(conf, "Listener already set up"); + } else { + parse_listener(conf, line, lwan); + has_listener = true; + } + } else if (streq(line->key, "tls_listener")) { + if (has_tls_listener) { + config_error(conf, "TLS Listener already set up"); + } else { + parse_tls_listener(conf, line, lwan); + has_tls_listener = true; + } } else { config_error(conf, "Unknown section type: %s", line->key); } diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 6749f2a3f..db0897eec 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -288,6 +288,19 @@ enum lwan_connection_flags { CONN_ASYNC_AWAIT = 1 << 8, CONN_SENT_CONNECTION_HEADER = 1 << 9, + + /* Both are used to know if an epoll event pertains to a listener rather + * than a client. */ + CONN_LISTENER_HTTP = 1 << 10, + CONN_LISTENER_HTTPS = 1 << 11, + + /* Set on file descriptors accepted by listeners with the + * CONN_LISTENER_HTTPS flag, and unset right after the handshake has been + * completed (when CONN_TLS is then set.) */ + CONN_NEEDS_TLS_SETUP = 1 << 12, + + /* Used mostly for the Lua and Rewrite modules */ + CONN_TLS = 1 << 14, }; enum lwan_connection_coro_yield { @@ -438,6 +451,7 @@ struct lwan_thread { int epoll_fd; struct timeouts *wheel; int listen_fd; + int tls_listen_fd; unsigned int cpu; pthread_t self; }; @@ -454,9 +468,15 @@ struct lwan_config { struct lwan_key_value *global_headers; char *listener; + char *tls_listener; char *error_template; char *config_file_path; + struct { + char *cert; + char *key; + } ssl; + size_t max_post_data_size; size_t max_put_data_size; @@ -483,8 +503,11 @@ struct lwan { unsigned int count; } thread; +#if defined(HAVE_MBEDTLS) + struct lwan_tls_context *tls; +#endif + struct lwan_config config; - struct coro_switcher switcher; unsigned int online_cpus; unsigned int available_cpus; diff --git a/src/samples/techempower/techempower.conf b/src/samples/techempower/techempower.conf index 28f3177c2..f912bf219 100644 --- a/src/samples/techempower/techempower.conf +++ b/src/samples/techempower/techempower.conf @@ -1,4 +1,6 @@ -listener *:8080 { +listener *:8080 + +site { # For main TWFB benchmarks &plaintext /plaintext &json /json From 879859b7554f27e10e387d86c19fbadb53b77362 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Tue, 14 Dec 2021 02:38:55 -0800 Subject: [PATCH 1850/2505] Lua methods now receive the request as second parameter This simplifies how Lua methods are declared in Lwan. While this breaks the API, this simplifies the usage a little bit by providing a reference to the request without requiring a call to lwan_lua_get_request_from_userdata(). --- src/bin/testrunner/testrunner.conf | 4 +--- src/lib/lwan-lua.c | 28 ++++++---------------------- src/lib/lwan-lua.h | 26 ++++++++++++++++++++++++-- src/lib/lwan.h | 13 ------------- 4 files changed, 31 insertions(+), 40 deletions(-) diff --git a/src/bin/testrunner/testrunner.conf b/src/bin/testrunner/testrunner.conf index 1721c22aa..57a254667 100644 --- a/src/bin/testrunner/testrunner.conf +++ b/src/bin/testrunner/testrunner.conf @@ -71,12 +71,10 @@ site { path = /tmp/maoe.txt is_file = true } - condition lua { - script = '''function matches(req) + condition lua = '''function matches(req) return false end ''' - } rewrite as = /hello?name=maoe } pattern test.css { diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index 343fa5275..a719f891b 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -42,7 +42,6 @@ ALWAYS_INLINE struct lwan_request *lwan_lua_get_request_from_userdata(lua_State LWAN_LUA_METHOD(say) { - struct lwan_request *request = lwan_lua_get_request_from_userdata(L); size_t response_str_len; const char *response_str = lua_tolstring(L, -1, &response_str_len); @@ -55,7 +54,6 @@ LWAN_LUA_METHOD(say) LWAN_LUA_METHOD(send_event) { - struct lwan_request *request = lwan_lua_get_request_from_userdata(L); size_t event_str_len; const char *event_str = lua_tolstring(L, -1, &event_str_len); const char *event_name = lua_tostring(L, -2); @@ -68,7 +66,6 @@ LWAN_LUA_METHOD(send_event) LWAN_LUA_METHOD(set_response) { - struct lwan_request *request = lwan_lua_get_request_from_userdata(L); size_t response_str_len; const char *response_str = lua_tolstring(L, -1, &response_str_len); @@ -78,13 +75,13 @@ LWAN_LUA_METHOD(set_response) } static int request_param_getter(lua_State *L, + struct lwan_request *request, const char *(*getter)(struct lwan_request *req, const char *key)) { - struct lwan_request *request = lwan_lua_get_request_from_userdata(L); const char *key_str = lua_tostring(L, -1); - const char *value = getter(request, key_str); + if (!value) lua_pushnil(L); else @@ -95,35 +92,30 @@ static int request_param_getter(lua_State *L, LWAN_LUA_METHOD(remote_address) { - struct lwan_request *request = lwan_lua_get_request_from_userdata(L); char ip_buffer[INET6_ADDRSTRLEN]; lua_pushstring(L, lwan_request_get_remote_address(request, ip_buffer)); return 1; } - LWAN_LUA_METHOD(header) { - return request_param_getter(L, lwan_request_get_header); + return request_param_getter(L, request, lwan_request_get_header); } LWAN_LUA_METHOD(is_https) { - struct lwan_request *request = lwan_lua_get_request_from_userdata(L); lua_pushboolean(L, !!(request->conn->flags & CONN_TLS)); return 1; } LWAN_LUA_METHOD(path) { - struct lwan_request *request = lwan_lua_get_request_from_userdata(L); lua_pushlstring(L, request->url.value, request->url.len); return 1; } LWAN_LUA_METHOD(query_string) { - struct lwan_request *request = lwan_lua_get_request_from_userdata(L); if (request->helper->query_string.len) { lua_pushlstring(L, request->helper->query_string.value, request->helper->query_string.len); } else { @@ -134,22 +126,21 @@ LWAN_LUA_METHOD(query_string) LWAN_LUA_METHOD(query_param) { - return request_param_getter(L, lwan_request_get_query_param); + return request_param_getter(L, request, lwan_request_get_query_param); } LWAN_LUA_METHOD(post_param) { - return request_param_getter(L, lwan_request_get_post_param); + return request_param_getter(L, request, lwan_request_get_post_param); } LWAN_LUA_METHOD(cookie) { - return request_param_getter(L, lwan_request_get_cookie); + return request_param_getter(L, request, lwan_request_get_cookie); } LWAN_LUA_METHOD(body) { - struct lwan_request *request = lwan_lua_get_request_from_userdata(L); if (request->helper->body_data.len) { lua_pushlstring(L, request->helper->body_data.value, request->helper->body_data.len); } else { @@ -160,7 +151,6 @@ LWAN_LUA_METHOD(body) LWAN_LUA_METHOD(ws_upgrade) { - struct lwan_request *request = lwan_lua_get_request_from_userdata(L); enum lwan_http_status status = lwan_request_websocket_upgrade(request); lua_pushinteger(L, status); @@ -170,7 +160,6 @@ LWAN_LUA_METHOD(ws_upgrade) LWAN_LUA_METHOD(ws_write) { - struct lwan_request *request = lwan_lua_get_request_from_userdata(L); size_t data_len; const char *data_str = lua_tolstring(L, -1, &data_len); @@ -182,7 +171,6 @@ LWAN_LUA_METHOD(ws_write) LWAN_LUA_METHOD(ws_read) { - struct lwan_request *request = lwan_lua_get_request_from_userdata(L); int r; /* FIXME: maybe return a table {status=r, content=buf}? */ @@ -238,7 +226,6 @@ LWAN_LUA_METHOD(set_headers) const int key_index = -2; const int value_index = -1; struct lwan_key_value_array *headers; - struct lwan_request *request = lwan_lua_get_request_from_userdata(L); struct coro *coro = request->conn->coro; struct lwan_key_value *kv; @@ -295,7 +282,6 @@ LWAN_LUA_METHOD(set_headers) LWAN_LUA_METHOD(sleep) { - struct lwan_request *request = lwan_lua_get_request_from_userdata(L); lua_Integer ms = lua_tointeger(L, -1); lwan_request_sleep(request, (uint64_t)ms); @@ -305,14 +291,12 @@ LWAN_LUA_METHOD(sleep) LWAN_LUA_METHOD(request_id) { - struct lwan_request *request = lwan_lua_get_request_from_userdata(L); lua_pushfstring(L, "%016lx", lwan_request_get_id(request)); return 1; } LWAN_LUA_METHOD(request_date) { - struct lwan_request *request = lwan_lua_get_request_from_userdata(L); lua_pushstring(L, request->conn->thread->date.date); return 1; } diff --git a/src/lib/lwan-lua.h b/src/lib/lwan-lua.h index b1f1879fb..75612c435 100644 --- a/src/lib/lwan-lua.h +++ b/src/lib/lwan-lua.h @@ -21,11 +21,33 @@ #include +struct lwan_request; + +struct lwan_lua_method_info { + const char *name; + int (*func)(); +}; + +#define LWAN_LUA_METHOD(name_) \ + static int lwan_lua_method_##name_##_wrapper(lua_State *L); \ + static int lwan_lua_method_##name_(lua_State *L, \ + struct lwan_request *request); \ + static const struct lwan_lua_method_info \ + __attribute__((used, section(LWAN_SECTION_NAME(lwan_lua_method)))) \ + lwan_lua_method_info_##name_ = { \ + .name = #name_, .func = lwan_lua_method_##name_##_wrapper}; \ + static int lwan_lua_method_##name_##_wrapper(lua_State *L) \ + { \ + struct lwan_request *request = lwan_lua_get_request_from_userdata(L); \ + return lwan_lua_method_##name_(L, request); \ + } \ + static ALWAYS_INLINE int lwan_lua_method_##name_( \ + lua_State *L, struct lwan_request *request) + + const char *lwan_lua_state_last_error(lua_State *L); lua_State *lwan_lua_create_state(const char *script_file, const char *script); void lwan_lua_state_push_request(lua_State *L, struct lwan_request *request); -struct lwan_request; struct lwan_request *lwan_lua_get_request_from_userdata(lua_State *L); - diff --git a/src/lib/lwan.h b/src/lib/lwan.h index db0897eec..e52b81048 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -82,14 +82,6 @@ extern "C" { struct lwan_response *response __attribute__((unused)), \ void *data __attribute__((unused))) -#define LWAN_LUA_METHOD(name_) \ - static int lwan_lua_method_##name_(lua_State *L); \ - static const struct lwan_lua_method_info \ - __attribute__((used, section(LWAN_SECTION_NAME(lwan_lua_method)))) \ - lwan_lua_method_info_##name_ = {.name = #name_, \ - .func = lwan_lua_method_##name_}; \ - static int lwan_lua_method_##name_(lua_State *L) - #define ALWAYS_INLINE inline __attribute__((always_inline)) #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ @@ -411,11 +403,6 @@ struct lwan_module_info { const struct lwan_module *module; }; -struct lwan_lua_method_info { - const char *name; - int (*func)(); -}; - struct lwan_handler_info { const char *name; enum lwan_http_status (*handler)(struct lwan_request *request, From 47d16a7ffed3c22a7aad75a58f0f4b63c549e48d Mon Sep 17 00:00:00 2001 From: L Pereira Date: Tue, 14 Dec 2021 20:23:46 -0800 Subject: [PATCH 1851/2505] Add admonitions to README.md --- README.md | 156 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 89 insertions(+), 67 deletions(-) diff --git a/README.md b/README.md index 3e4bb09e1..ae2acd4ee 100644 --- a/README.md +++ b/README.md @@ -54,8 +54,10 @@ The build system will look for these libraries and enable/link if available. - Client libraries for either [MySQL](https://dev.mysql.com) or [MariaDB](https://mariadb.org) - [SQLite 3](http://sqlite.org) -On non-x86 systems, [libucontext](https://github.com/kaniini/libucontext) -will be downloaded and built alongside Lwan. +> :bulb: **Note:** On non-x86_64 systems, +> [libucontext](https://github.com/kaniini/libucontext) will be downloaded +> and built alongside Lwan. This will require a network connection, so keep +> this in mind when packaging Lwan for non-x86_64 architectures. ### Common operating system package names @@ -118,10 +120,12 @@ This will generate a few binaries: Passing `-DCMAKE_BUILD_TYPE=Release` will enable some compiler optimizations (such as [LTO](http://gcc.gnu.org/wiki/LinkTimeOptimization)) -and tune the code for current architecture. *Please use this version -when benchmarking*, as the default is the Debug build, which not only -logs all requests to the standard output, but does so while holding a -mutex. +and tune the code for current architecture. + +> :exclamation: **Important:** *Please use the release build when benchmarking*, as +> the default is the Debug build, which not only logs all requests to the +> standard output, but does so while holding a lock, severely holding down +> the server. The default build (i.e. not passing `-DCMAKE_BUILD_TYPE=Release`) will build a version suitable for debugging purposes. This version can be used under @@ -174,10 +178,11 @@ Running ------- Set up the server by editing the provided `lwan.conf`; the format is -explained in details below. (Lwan will try to find a configuration file -based in the executable name in the current directory; `testrunner.conf` -will be used for the `testrunner` binary, `lwan.conf` for the `lwan` binary, -and so on.) +explained in details below. + +> :bulb: **Note:** Lwan will try to find a configuration file based in the +> executable name in the current directory; `testrunner.conf` will be used +> for the `testrunner` binary, `lwan.conf` for the `lwan` binary, and so on. Configuration files are loaded from the current directory. If no changes are made to this file, running Lwan will serve static files located in @@ -189,8 +194,9 @@ settings for the environment it's running on. Many of these settings can be tweaked in the configuration file, but it's usually a good idea to not mess with them. -Optionally, the `lwan` binary can be used for one-shot static file serving -without any configuration file. Run it with `--help` for help on that. +> :magic_wand: **Tip:** Optionally, the `lwan` binary can be used for one-shot +> static file serving without any configuration file. Run it with `--help` +> for help on that. Configuration File ---------------- @@ -206,9 +212,10 @@ can be empty; in this case, curly brackets are optional. an implementation detail, code reading configuration options will only be given the version with underscores). -Values can contain environment variables. Use the syntax `${VARIABLE_NAME}`. -Default values can be specified with a colon (e.g. `${VARIABLE_NAME:foo}`, -which evaluates to `${VARIABLE_NAME}` if it's set, or `foo` otherwise). +> :magic_wand: **Tip:** Values can contain environment variables. Use the +> syntax `${VARIABLE_NAME}`. Default values can be specified with a colon +> (e.g. `${VARIABLE_NAME:foo}`, which evaluates to `${VARIABLE_NAME}` if +> it's set, or `foo` otherwise). ``` sound volume = 11 # This one is 1 louder @@ -255,9 +262,9 @@ just added together; for instance, "1M 1w" specifies "1 month and 1 week" | `M` | 30-day Months | | `y` | 365-day Years | -A number with a multiplier not in this table is ignored; a warning is issued while -reading the configuration file. No spaces must exist between the number and its -multiplier. +> :bulb: **Note:** A number with a multiplier not in this table is ignored; a +> warning is issued while reading the configuration file. No spaces must +> exist between the number and its multiplier. #### Boolean Values @@ -300,6 +307,10 @@ e.g. instantiating the `serve_files` module, Lwan will refuse to start. (This check is only performed on Linux as a safeguard for malconfiguration.) +> :magic_wand: **Tip:** Declare a Straitjacket right before a `site` section +> in such a way that configuration files and private data (e.g. TLS keys) +> are out of reach of the server after initialization has taken place. + | Option | Type | Default | Description | |--------|------|---------|-------------| | `user` | `str` | `NULL` | Drop privileges to this user name | @@ -336,9 +347,9 @@ actual values while servicing requests. These include but is not limited to: - `Transfer-Encoding` - All `Access-Control-Allow-` headers -Header names are also case-insensitive (and case-preserving). Overriding -`SeRVeR` will override the `Server` header, but send it the way it was -written in the configuration file. +> :bulb: **Note:** Header names are also case-insensitive (and case-preserving). Overriding +> `SeRVeR` will override the `Server` header, but send it the way it was +> written in the configuration file. ### Listeners @@ -426,9 +437,10 @@ section can be present in the declaration of a module instance. Handlers do not take any configuration options, but may include the `authorization` section. -A list of built-in modules can be obtained by executing Lwan with the `-m` -command-line argument. The following is some basic documentation for the -modules shipped with Lwan. +> :magic_wand: **Tip:** A list of built-in modules can be obtained by +> executing Lwan with the `-m` command-line argument. + +The following is some basic documentation for the modules shipped with Lwan. #### File Serving @@ -457,22 +469,29 @@ frameworks such as [Sailor](https://github.com/lpereira/sailor-hello-lwan). Scripts can be served from files or embedded in the configuration file, and the results of loading them, the standard Lua modules, and (optionally, if -using LuaJIT) optimizing the code will be cached for a while. Each I/O -thread in Lwan will create an instance of a Lua VM (i.e. one `lua_State` -struct for every I/O thread), and each Lwan coroutine will spawn a Lua -thread (with `lua_newthread()`) per request. Because of this, Lua scripts -can't use global variables, as they may be not only serviced by different -threads, but the state will be available only for the amount of time -specified in the `cache_period` configuration option. +using LuaJIT) optimizing the code will be cached for a while. + +> :bulb: **Note:** Lua scripts can't use global variables, as they may be not +> only serviced by different threads, but the state will be available only +> for the amount of time specified in the `cache_period` configuration +> option. This is because each I/O thread in Lwan will create an instance +> of a Lua VM (i.e. one `lua_State` struct for every I/O thread), and each +> Lwan coroutine will spawn a Lua thread (with `lua_newthread()`) per +> request. There's no need to have one instance of the Lua module for each endpoint; a single script, embedded in the configuration file or otherwise, can service many different endpoints. Scripts are supposed to implement functions with the following signature: `handle_${METHOD}_${ENDPOINT}(req)`, where `${METHOD}` can be a HTTP method (i.e. `get`, `post`, `head`, etc.), and -`${ENDPOINT}` is the desired endpoint to be handled by that function. The -special `${ENDPOINT}` `root` can be specified to act as a catchall. The -`req` parameter points to a metatable that contains methods to obtain +`${ENDPOINT}` is the desired endpoint to be handled by that function. + +> :magic_wand: **Tip:** Use the `root` endpoint for a catchall. For example, +> the handler function `handle_get_root()` will be called if no other handler +> could be found for that request. If no catchall is specified, the server +> will return a `404 Not Found` error. + +The `req` parameter points to a metatable that contains methods to obtain information from the request, or to set the response, as seen below: - `req:query_param(param)` returns the query parameter (from the query string) with the key `param`, or `nil` if not found @@ -514,18 +533,19 @@ The `rewrite` module will match to either redirect to another URL, or rewrite the request in a way that Lwan will handle the request as if it were made in that way originally. -Forked from Lua 5.3.1, the regular expresion engine may not be as -feature-packed as most general-purpose engines, but has been chosen -specifically because it is a [deterministic finite -automaton](https://en.wikipedia.org/wiki/Deterministic_finite_automaton) in -an attempt to make some kinds of [denial of service -attacks](https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS) -not possible. +> :information_source: **Info:** Forked from Lua 5.3.1, the regular expresion +> engine may not be as feature-packed as most general-purpose engines, but +> has been chosen specifically because it is a [deterministic finite +> automaton](https://en.wikipedia.org/wiki/Deterministic_finite_automaton) +> in an attempt to make some kinds of [denial of service +> attacks](https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS) +> impossible. -The new URL can be specified using a -simple text substitution syntax, or use Lua scripts; Lua scripts will -contain the same metamethods available in the `req` metatable provided by -the Lua module, so it can be quite powerful. +The new URL can be specified using a simple text substitution syntax, or use Lua scripts. + +> :magic_wand: **Tip: * Lua scripts will contain the same metamethods +> available in the `req` metatable provided by the Lua module, so it can be +> quite powerful. Each instance of the rewrite module will require a `pattern` and the action to execute when such pattern is matched. Patterns are evaluated in the @@ -576,21 +596,21 @@ It's also possible to specify conditions to trigger a rewrite. To specify one, open a `condition` block, specify the condition type, and then the parameters for that condition to be evaluated: -|Condition|Can use subst. syntax|Parameters|Description| -|---------|---------------------|----------|-----------| -|`cookie` | Yes | A single `key` = `value`| Checks if request has cookie `key` has value `value` | -|`query` | Yes | A single `key` = `value`| Checks if request has query variable `key` has value `value` | -|`post` | Yes | A single `key` = `value`| Checks if request has post data `key` has value `value` | -|`header` | Yes | A single `key` = `value`| Checks if request header `key` has value `value` | -|`environment` | Yes | A single `key` = `value`| Checks if environment variable `key` has value `value` | -|`stat` | Yes | `path`, `is_dir`, `is_file` | Checks if `path` exists in the filesystem, and optionally checks if `is_dir` or `is_file` | -|`encoding` | No | `deflate`, `gzip`, `brotli`, `zstd`, `none` | Checks if client accepts responses in a determined encoding (e.g. `deflate = yes` for Deflate encoding) | -|`proxied`♠ | No | Boolean | Checks if request has been proxied through PROXY protocol | -|`http_1.0`♠ | No | Boolean | Checks if request is made with a HTTP/1.0 client | -|`is_https`♠ | No | Boolean | Checks if request is made through HTTPS | -|`has_query_string`♠ | No | Boolean | Checks if request has a query string (even if empty) | -|`method`♠ |No | Method name | Checks if HTTP method is the one specified | -|`lua`♠ |No| String | Runs Lua function `matches(req)` inside String and checks if it returns `true` or `false` | +|Condition |Can use subst. syntax|Section required|Parameters|Description| +|-------------------|---------------------|----------------|----------|-----------| +|`cookie` | Yes | Yes | A single `key` = `value`| Checks if request has cookie `key` has value `value` | +|`query` | Yes | Yes | A single `key` = `value`| Checks if request has query variable `key` has value `value` | +|`post` | Yes | Yes | A single `key` = `value`| Checks if request has post data `key` has value `value` | +|`header` | Yes | Yes | A single `key` = `value`| Checks if request header `key` has value `value` | +|`environment` | Yes | Yes | A single `key` = `value`| Checks if environment variable `key` has value `value` | +|`stat` | Yes | Yes | `path`, `is_dir`, `is_file` | Checks if `path` exists in the filesystem, and optionally checks if `is_dir` or `is_file` | +|`encoding` | No | Yes | `deflate`, `gzip`, `brotli`, `zstd`, `none` | Checks if client accepts responses in a determined encoding (e.g. `deflate = yes` for Deflate encoding) | +|`proxied` | No | No | Boolean | Checks if request has been proxied through PROXY protocol | +|`http_1.0` | No | No | Boolean | Checks if request is made with a HTTP/1.0 client | +|`is_https` | No | No | Boolean | Checks if request is made through HTTPS | +|`has_query_string` | No | No | Boolean | Checks if request has a query string (even if empty) | +|`method` | No | No | Method name | Checks if HTTP method is the one specified | +|`lua` | No | No | String | Runs Lua function `matches(req)` inside String and checks if it returns `true` or `false` | *Can use subst. syntax* refers to the ability to reference the matched pattern using the same substitution syntax used for the `rewrite as` or @@ -598,8 +618,8 @@ pattern using the same substitution syntax used for the `rewrite as` or foo-%1-bar }` will substitute `%1` with the first match from the pattern this condition is related to. -Conditions marked with `♠` do not require a section, and can be written, for -instance, as `condition has_query_string = yes`. +> :bulb: **Note:** Conditions that do not require a section have to be written +> as a key; for instance, `condition has_query_string = yes`. For example, if one wants to send `site-dark-mode.css` if there is a `style` cookie with the value `dark`, and send `site-light-mode.css` @@ -641,10 +661,10 @@ pattern (%g+) { } ``` -(In general, this is not necessary, as the file serving module will do this -automatically and pick the smallest file available for the requested -encoding, cache it for a while, but this shows it's possible to have a -similar feature by configuration alone.) +> :bulb: **Note:** In general, this is not necessary, as the file serving +> module will do this automatically and pick the smallest file available for +> the requested encoding, cache it for a while, but this shows it's possible +> to have a similar feature by configuration alone. #### Redirect @@ -693,7 +713,9 @@ section with a `basic` parameter, and set one of its options. | `realm` | `str` | `Lwan` | Realm for authorization. This is usually shown in the user/password UI in browsers | | `password_file` | `str` | `NULL` | Path for a file containing username and passwords (in clear text). The file format is the same as the configuration file format used by Lwan | - +> :warning: **Warning:** Not only passwords are stored in clear text in a file +> that should be accessible by the server, they'll be kept in memory for a few +> seconds. Avoid using this feature if possible. Hacking ------- From 2bad1417b3fb25de834032947fd745e5497f4b0f Mon Sep 17 00:00:00 2001 From: L Pereira Date: Tue, 14 Dec 2021 20:47:53 -0800 Subject: [PATCH 1852/2505] Fix generation of request IDs They were being built reversed. --- src/lib/lwan-response.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 2f380bcba..b1ceeb875 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -354,8 +354,9 @@ size_t lwan_prepare_response_header_full( if (request_flags & RESPONSE_INCLUDE_REQUEST_ID) { APPEND_CONSTANT("\r\nX-Request-Id: "); RETURN_0_ON_OVERFLOW(16); - for (uint64_t id = lwan_request_get_id(request); id; id >>= 4) - APPEND_CHAR_NOCHECK("0123456789abcdef"[id & 15]); + uint64_t id = lwan_request_get_id(request); + for (int i = 60; i >= 0; i -= 4) + APPEND_CHAR_NOCHECK("0123456789abcdef"[(id >> i) & 0xf]); } } From bc1eb26958ab6e1174d1b34aa924fe5b35e70367 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Wed, 15 Dec 2021 18:13:14 -0800 Subject: [PATCH 1853/2505] Tweaks to README.md --- README.md | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ae2acd4ee..974e19424 100644 --- a/README.md +++ b/README.md @@ -543,7 +543,7 @@ will handle the request as if it were made in that way originally. The new URL can be specified using a simple text substitution syntax, or use Lua scripts. -> :magic_wand: **Tip: * Lua scripts will contain the same metamethods +> :magic_wand: **Tip:** Lua scripts will contain the same metamethods > available in the `req` metatable provided by the Lua module, so it can be > quite powerful. @@ -763,9 +763,22 @@ Lwan is automatically fuzz-tested by though, one can [follow the instructions to test locally](https://github.com/google/oss-fuzz/blob/master/docs/new_project_guide.md#testing-locally). -This fuzzes only the request parsing code. There are plans to add fuzzing -drivers for other parts of the code, including the rewriting engine, -configuration file reader, template parser, and URL routing. +Currently, there are fuzzing drivers for the request parsing code, the +configuration file parser, the template parser, and the Lua string pattern +matching library used in the rewrite module. + +Adding new fuzzers is trivial: + +- Fuzzers are implemented in C++ and the sources are placed in + `src/bin/fuzz`. +- Fuzzers should be named `${FUZZER_NAME}_fuzzer.cc`. Look at the OSS-Fuzz + documentation and other fuzzers on information about how to write these. +- These files are not compiled by the Lwan build system, but rather by the + build scripts used by OSS-Fuzz. To test your fuzzer, please follow the + instructions to test locally, which will build the fuzzer in the + environment they'll be executed in. +- A fuzzing corpus has to be provided in `src/fuzz/corpus`. Files have to + be named `corpus-${FUZZER_NAME}-${UNIQUE_ID}`. ### Exporting APIs @@ -889,7 +902,7 @@ been seen in the wild. *Help build this list!* Some other distribution channels were made available as well: -* Container images are available from the [ghcr.io/lpereira/lwan](GitHub Container Registry). (More information below.) +* Container images are available from the [ghcr.io/lpereira/lwan](GitHub Container Registry). [More information below](#container-images). * A `Dockerfile` is maintained by [@jaxgeller](https://github.com/jaxgeller), and is [available from the Docker registry](https://hub.docker.com/r/jaxgeller/lwan/). * A buildpack for Heroku is maintained by [@bherrera](https://github.com/bherrera), and is [available from its repo](https://github.com/bherrera/heroku-buildpack-lwan). * Lwan is also available as a package in [Biicode](http://docs.biicode.com/c++/examples/lwan.html). @@ -919,7 +932,7 @@ Some talks mentioning Lwan: * This [talk about Iron](https://michaelsproul.github.io/iron-talk/), a framework for Rust, mentions Lwan as an *insane C thing*. * [University seminar presentation](https://github.com/cu-data-engineering-s15/syllabus/blob/master/student_lectures/LWAN.pdf) about Lwan. * This [presentation about Sailor web framework](http://www.slideshare.net/EtieneDalcol/web-development-with-lua-bulgaria-web-summit) mentions Lwan. -* [Performance and Scale @ Istio Service Mesh](https://www.youtube.com/watch?v=G4F5aRFEXnU), at around 7:30min, presented at KubeCon Europe 2018, mentions that Lwan is used on the server side for testing due to its performance and robustness. +* [Performance and Scale @ Istio Service Mesh](https://www.youtube.com/watch?v=G4F5aRFEXnU), presented at KubeCon Europe 2018, mentions (at the 7:30 mark) that Lwan is used on the server side for testing due to its performance and robustness. * [A multi-core Python HTTP server (much) faster than Go (spoiler: Cython)](https://www.youtube.com/watch?v=mZ9cXOH6NYk) presented at PyConFR 2018 by J.-P. Smets mentions [Nexedi's work](https://www.nexedi.com/NXD-Blog.Multicore.Python.HTTP.Server) on using Lwan as a backend for Python services with Cython. Not really third-party, but alas: From 436629d35dcac460150a92c0fc2729d98f658615 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Wed, 15 Dec 2021 18:13:31 -0800 Subject: [PATCH 1854/2505] Ensure request IDs are serialized in response as they're logged --- src/bin/testrunner/main.c | 21 +++++++++++++-------- src/scripts/testsuite.py | 3 ++- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/bin/testrunner/main.c b/src/bin/testrunner/main.c index c4ca0c014..9aa60f0bd 100644 --- a/src/bin/testrunner/main.c +++ b/src/bin/testrunner/main.c @@ -21,7 +21,7 @@ #include #include -#include "lwan.h" +#include "lwan-private.h" LWAN_HANDLER(quit_lwan) { @@ -193,15 +193,20 @@ LWAN_HANDLER(hello_world) "Key = \"%s\"; Value = \"%s\"\n", iter->key, iter->value); } - if (lwan_request_get_method(request) != REQUEST_METHOD_POST) - goto end; + if (lwan_request_get_method(request) == REQUEST_METHOD_POST) { + lwan_strbuf_append_strz(response->buffer, "\n\nPOST data\n"); + lwan_strbuf_append_strz(response->buffer, "---------\n\n"); - lwan_strbuf_append_strz(response->buffer, "\n\nPOST data\n"); - lwan_strbuf_append_strz(response->buffer, "---------\n\n"); + LWAN_ARRAY_FOREACH(lwan_request_get_post_params(request), iter) { + lwan_strbuf_append_printf(response->buffer, + "Key = \"%s\"; Value = \"%s\"\n", iter->key, iter->value); + } + } - LWAN_ARRAY_FOREACH(lwan_request_get_post_params(request), iter) { - lwan_strbuf_append_printf(response->buffer, - "Key = \"%s\"; Value = \"%s\"\n", iter->key, iter->value); + const char *dump_request_id = lwan_request_get_query_param(request, "dump_request_id"); + if (dump_request_id && streq(dump_request_id, "1")) { + lwan_strbuf_append_printf(response->buffer, "\nRequest ID: <<%0lx>>", + lwan_request_get_id(request)); } end: diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index 0f6b1f2c6..b06cd9e5f 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -703,11 +703,12 @@ class TestHelloWorld(LwanTest): def test_request_id(self): all_request_ids = set() for i in range(20): - r = requests.get('http://127.0.0.1:8080/hello') + r = requests.get('http://127.0.0.1:8080/hello?dump_request_id=1&dump_vars=1') self.assertResponsePlain(r) request_id = r.headers['x-request-id'] self.assertFalse(request_id in all_request_ids) self.assertTrue(re.match(r'^[a-f0-9]{16}$', request_id)) + self.assertTrue('Request ID: <<%s>>' % request_id in r.text) all_request_ids.add(request_id) def test_cookies(self): From 8e714848a40de51240227facbd4ab006529144bb Mon Sep 17 00:00:00 2001 From: L Pereira Date: Wed, 15 Dec 2021 18:14:17 -0800 Subject: [PATCH 1855/2505] Ensure only CONN_NEEDS_TLS_SETUP flag is set when accepting a TLS client This is just to ensure that flags used previously by another connection with the same file descriptor won't affect the I/O loop logic to look at the flags when determining what to do. --- src/lib/lwan-thread.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 0f700c585..abad90d3f 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -741,7 +741,7 @@ static bool accept_waiting_clients(const struct lwan_thread *t, } else if (listen_socket->flags & CONN_LISTENER_HTTPS) { assert(listen_fd == t->tls_listen_fd); assert(!(listen_socket->flags & CONN_LISTENER_HTTP)); - conn->flags |= CONN_NEEDS_TLS_SETUP; + conn->flags = CONN_NEEDS_TLS_SETUP; } else { assert(listen_fd == t->listen_fd); assert(listen_socket->flags & CONN_LISTENER_HTTP); From e3cfaa71a32f04a521cbca5b239adb084735f41f Mon Sep 17 00:00:00 2001 From: L Pereira Date: Wed, 15 Dec 2021 18:15:49 -0800 Subject: [PATCH 1856/2505] Guard some code against compilation if mbedSSL isn't available --- src/lib/lwan-thread.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index abad90d3f..ba9075f08 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -1310,6 +1310,7 @@ void lwan_thread_shutdown(struct lwan *l) free(l->thread.threads); +#if defined(HAVE_MBEDTLS) if (l->tls) { mbedtls_ssl_config_free(&l->tls->config); mbedtls_x509_crt_free(&l->tls->server_cert); @@ -1318,4 +1319,5 @@ void lwan_thread_shutdown(struct lwan *l) mbedtls_ctr_drbg_free(&l->tls->ctr_drbg); free(l->tls); } +#endif } From 464d30a84baa714d8c824f9c7d707f9f717dfcaa Mon Sep 17 00:00:00 2001 From: L Pereira Date: Wed, 15 Dec 2021 20:38:51 -0800 Subject: [PATCH 1857/2505] Reorder members of `struct lwan` to remove alignment holes This also packs members more likely to be used to the first cache line of that struct. --- src/lib/lwan-response.c | 4 ++-- src/lib/lwan-thread.c | 3 +-- src/lib/lwan.c | 16 ++++++++++------ src/lib/lwan.h | 15 ++++++++------- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index b1ceeb875..86412eea6 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -360,8 +360,8 @@ size_t lwan_prepare_response_header_full( } } - APPEND_STRING_LEN(lwan_strbuf_get_buffer(request->global_response_headers), - lwan_strbuf_get_length(request->global_response_headers)); + APPEND_STRING_LEN(request->global_response_headers->value, + request->global_response_headers->len); return (size_t)(p_headers - headers); } diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index ba9075f08..9f1a36fda 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -589,8 +589,7 @@ send_last_response_without_coro(const struct lwan *l, if (!send_string_without_coro(fd, "\r\nContent-Type: text/html", MSG_MORE)) goto shutdown_and_close; - if (send_buffer_without_coro(fd, lwan_strbuf_get_buffer(&l->headers), - lwan_strbuf_get_length(&l->headers), + if (send_buffer_without_coro(fd, l->headers.value, l->headers.len, MSG_MORE)) { struct lwan_strbuf buffer; diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 528a49df5..077e630c4 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -166,11 +166,12 @@ static bool can_override_header(const char *name) static void build_response_headers(struct lwan *l, const struct lwan_key_value *kv) { + struct lwan_strbuf strbuf; bool set_server = false; assert(l); - lwan_strbuf_init(&l->headers); + lwan_strbuf_init(&strbuf); for (; kv && kv->key; kv++) { if (!can_override_header(kv->key)) { @@ -179,15 +180,18 @@ static void build_response_headers(struct lwan *l, if (!strcasecmp(kv->key, "Server")) set_server = true; - lwan_strbuf_append_printf(&l->headers, "\r\n%s: %s", kv->key, + lwan_strbuf_append_printf(&strbuf, "\r\n%s: %s", kv->key, kv->value); } } if (!set_server) - lwan_strbuf_append_strz(&l->headers, "\r\nServer: lwan"); + lwan_strbuf_append_strz(&strbuf, "\r\nServer: lwan"); - lwan_strbuf_append_strz(&l->headers, "\r\n\r\n"); + lwan_strbuf_append_strz(&strbuf, "\r\n\r\n"); + + l->headers = (struct lwan_value){.value = lwan_strbuf_get_buffer(&strbuf), + .len = lwan_strbuf_get_length(&strbuf)}; } static void parse_global_headers(struct config *c, @@ -799,7 +803,7 @@ void lwan_init_with_config(struct lwan *l, const struct lwan_config *config) try_setup_from_config(l, config); - if (!lwan_strbuf_get_length(&l->headers)) + if (!l->headers.len) build_response_headers(l, config->global_headers); lwan_response_init(l); @@ -855,7 +859,7 @@ void lwan_shutdown(struct lwan *l) lwan_status_debug("Shutting down URL handlers"); lwan_trie_destroy(&l->url_map_trie); - lwan_strbuf_free(&l->headers); + free(l->headers.value); free(l->conns); lwan_response_shutdown(l); diff --git a/src/lib/lwan.h b/src/lib/lwan.h index e52b81048..f0920edf5 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -372,7 +372,7 @@ struct lwan_request { enum lwan_request_flags flags; int fd; struct lwan_connection *conn; - const struct lwan_strbuf *const global_response_headers; + const struct lwan_value *const global_response_headers; struct lwan_request_parser_helper *helper; @@ -481,19 +481,20 @@ struct lwan_config { struct lwan { struct lwan_trie url_map_trie; struct lwan_connection *conns; - struct lwan_strbuf headers; + struct lwan_value headers; + +#if defined(HAVE_MBEDTLS) + struct lwan_tls_context *tls; +#endif struct { - pthread_barrier_t barrier; struct lwan_thread *threads; + unsigned int max_fd; unsigned int count; + pthread_barrier_t barrier; } thread; -#if defined(HAVE_MBEDTLS) - struct lwan_tls_context *tls; -#endif - struct lwan_config config; unsigned int online_cpus; From 401ec528a5633bea2d1f56fbbc99f8bb4d4d3467 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Wed, 15 Dec 2021 20:39:37 -0800 Subject: [PATCH 1858/2505] Send a "Connection: close" header in some error case before closing connection --- src/lib/lwan-request.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index b3c82f713..3a2ef20eb 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1526,6 +1526,7 @@ void lwan_process_request(struct lwan *l, struct lwan_request *request) * information to even log the request because it has not been * parsed yet at this stage. Even if there are other requests waiting * in the pipeline, this seems like the safer thing to do. */ + request->conn->flags &= ~CONN_IS_KEEP_ALIVE; lwan_default_response(request, status); coro_yield(request->conn->coro, CONN_CORO_ABORT); __builtin_unreachable(); From 2f34d6ff3fa4cbb4a78e274e09cdf029ebb7c77d Mon Sep 17 00:00:00 2001 From: L Pereira Date: Wed, 15 Dec 2021 20:48:21 -0800 Subject: [PATCH 1859/2505] Get rid of alignment holes in serve_files_priv This uses only one cache line now! --- src/lib/lwan-mod-serve-files.c | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index bd3f16426..1a55a4c69 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -71,6 +71,12 @@ static const int open_mode = O_RDONLY | O_NONBLOCK | O_CLOEXEC; struct file_cache_entry; +enum serve_files_priv_flags { + SERVE_FILES_SERVE_PRECOMPRESSED = 1 << 0, + SERVE_FILES_AUTO_INDEX = 1 << 1, + SERVE_FILES_AUTO_INDEX_README = 1 << 2, +}; + struct serve_files_priv { struct cache *cache; @@ -78,16 +84,14 @@ struct serve_files_priv { size_t root_path_len; int root_fd; + enum serve_files_priv_flags flags; + const char *index_html; char *prefix; struct lwan_tpl *directory_list_tpl; size_t read_ahead; - - bool serve_precompressed_files; - bool auto_index; - bool auto_index_readme; }; struct cache_funcs { @@ -568,7 +572,7 @@ static bool mmap_init(struct file_cache_entry *ce, return false; lwan_madvise_queue(md->uncompressed.value, md->uncompressed.len); - if (LIKELY(priv->serve_precompressed_files)) { + if (LIKELY(priv->flags & SERVE_FILES_SERVE_PRECOMPRESSED)) { size_t compressed_size; file_fd = try_open_compressed(path, priv, st, &compressed_size); @@ -620,7 +624,7 @@ static bool sendfile_init(struct file_cache_entry *ce, } /* If precompressed files can be served, try opening it */ - if (LIKELY(priv->serve_precompressed_files)) { + if (LIKELY(priv->flags & SERVE_FILES_SERVE_PRECOMPRESSED)) { size_t compressed_sz; int fd = try_open_compressed(relpath, priv, st, &compressed_sz); @@ -662,7 +666,7 @@ static const char *dirlist_find_readme(struct lwan_strbuf *readme, "README.TXT", "README"}; int fd = -1; - if (!priv->auto_index_readme) + if (!(priv->flags & SERVE_FILES_AUTO_INDEX_README)) return NULL; for (size_t i = 0; i < N_ELEMENTS(candidates); i++) { @@ -796,7 +800,7 @@ static const struct cache_funcs *get_funcs(struct serve_files_priv *priv, if (UNLIKELY(errno != ENOENT)) return NULL; - if (LIKELY(priv->auto_index)) { + if (LIKELY(priv->flags & SERVE_FILES_AUTO_INDEX)) { /* If it doesn't, we want to generate a directory list. */ return &dirlist_funcs; } @@ -1028,11 +1032,16 @@ static void *serve_files_create(const char *prefix, void *args) priv->root_fd = root_fd; priv->index_html = settings->index_html ? settings->index_html : "index.html"; - priv->serve_precompressed_files = settings->serve_precompressed_files; - priv->auto_index = settings->auto_index; - priv->auto_index_readme = settings->auto_index_readme; + priv->read_ahead = settings->read_ahead; + if (settings->serve_precompressed_files) + priv->flags |= SERVE_FILES_SERVE_PRECOMPRESSED; + if (settings->auto_index) + priv->flags |= SERVE_FILES_AUTO_INDEX; + if (settings->auto_index_readme) + priv->flags |= SERVE_FILES_AUTO_INDEX_README; + return priv; out_tpl_prefix_copy: From db771c857cd5302066d0f4373ff94a359dd7d505 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Wed, 15 Dec 2021 20:59:14 -0800 Subject: [PATCH 1860/2505] Fix TLS connection handshake issue This was a race condition that sometimes happened when a thread set the connection flags to request a TLS setup after adding the file descriptor to another thread's epoll set. If the request were accepted by the other thread's I/O thread, the CONN_NEEDS_TLS_SETUP flag wasn't yet set, so Lwan never initiated the TLS handshake. Fix this by setting the flag before adding the file descriptor to the epoll set. --- src/lib/lwan-thread.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 9f1a36fda..c03d150db 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -313,10 +313,6 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, #if defined(HAVE_MBEDTLS) if (conn->flags & CONN_NEEDS_TLS_SETUP) { - /* Sometimes this flag is unset when it *should* be set! Need to - * figure out why. This causes the TLS handshake to not happen, - * making the normal HTTP request reading code to try and read - * the handshake as if it were a HTTP request. */ if (UNLIKELY(!lwan_setup_tls(lwan, conn))) { coro_yield(coro, CONN_CORO_ABORT); __builtin_unreachable(); @@ -728,24 +724,28 @@ static bool accept_waiting_clients(const struct lwan_thread *t, if (LIKELY(fd >= 0)) { struct lwan_connection *conn = &conns[fd]; struct epoll_event ev = {.data.ptr = conn, .events = read_events}; - int r = epoll_ctl(conn->thread->epoll_fd, EPOLL_CTL_ADD, fd, &ev); + int r; - if (UNLIKELY(r < 0)) { - lwan_status_perror("Could not add file descriptor %d to epoll " - "set %d. Dropping connection", - fd, conn->thread->epoll_fd); - - send_last_response_without_coro(t->lwan, conn, HTTP_UNAVAILABLE); #if defined(HAVE_MBEDTLS) - } else if (listen_socket->flags & CONN_LISTENER_HTTPS) { + if (listen_socket->flags & CONN_LISTENER_HTTPS) { assert(listen_fd == t->tls_listen_fd); assert(!(listen_socket->flags & CONN_LISTENER_HTTP)); conn->flags = CONN_NEEDS_TLS_SETUP; } else { assert(listen_fd == t->listen_fd); assert(listen_socket->flags & CONN_LISTENER_HTTP); + } #endif + + r = epoll_ctl(conn->thread->epoll_fd, EPOLL_CTL_ADD, fd, &ev); + if (UNLIKELY(r < 0)) { + lwan_status_perror("Could not add file descriptor %d to epoll " + "set %d. Dropping connection", + fd, conn->thread->epoll_fd); + send_last_response_without_coro(t->lwan, conn, HTTP_UNAVAILABLE); + conn->flags = 0; } + continue; } From 834d9f7fd1a19e07f55085641e626d538ec75e39 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Wed, 15 Dec 2021 21:17:14 -0800 Subject: [PATCH 1861/2505] Add an `Installing` section to the README --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 974e19424..902f4ff1a 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,13 @@ Build status | macOS | x86_64 | ![osx-release](https://shield.lwan.ws/img/gycKbr/release-sierra "Release macOS") | ![osx-debug](https://shield.lwan.ws/img/gycKbr/debug-sierra "Debug macOS") | | | | OpenBSD 6.6 | x86_64 | ![openbsd-release](https://shield.lwan.ws/img/gycKbr/release-openbsd "Release OpenBSD") | ![openbsd-debug](https://shield.lwan.ws/img/gycKbr/debug-openbsd "Debug OpenBSD") | | ![openbsd-tests](https://shield.lwan.ws/img/gycKbr/openbsd-unit-tests "OpenBSD Tests") | +Installing +---------- + +You can either [build Lwan yourself](#Building), use a [container +image](#container-image), or grab a package from [your favorite +distribution](#lwan-in-the-wild). + Building -------- From 8bb01afc3143f3ed8bbeaccffd9a190e7c4be77b Mon Sep 17 00:00:00 2001 From: L Pereira Date: Wed, 15 Dec 2021 21:55:22 -0800 Subject: [PATCH 1862/2505] Do not search for fragment in URL Fragments are supposed to be a client-only thing and never sent to the server, so don't bother looking for it server-side. Removing the call to memrchr() shaved off ~240 cycles for every request served on average on my i7-10510U CPU. --- src/lib/lwan-request.c | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 3a2ef20eb..39c67c0bd 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -416,15 +416,6 @@ static void parse_fragment_and_query(struct lwan_request *request, { struct lwan_request_parser_helper *helper = request->helper; - /* Fragments shouldn't be received by the server, but look for them anyway - * just in case. */ - char *fragment = memrchr(request->url.value, '#', request->url.len); - if (UNLIKELY(fragment != NULL)) { - *fragment = '\0'; - request->url.len = (size_t)(fragment - request->url.value); - space = fragment; - } - char *query_string = memchr(request->url.value, '?', request->url.len); if (query_string) { *query_string = '\0'; From 0c2f2a5af9d09167134c98838eca5041fe8e985e Mon Sep 17 00:00:00 2001 From: L Pereira Date: Wed, 15 Dec 2021 22:22:26 -0800 Subject: [PATCH 1863/2505] s/parse_fragment_and_query/find_query_string/g This function doesn't parse the query string or look for the fragment anymore. --- src/lib/lwan-request.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 39c67c0bd..185543dc4 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -411,8 +411,7 @@ static void parse_form_data(struct lwan_request *request) url_decode, '&'); } -static void parse_fragment_and_query(struct lwan_request *request, - const char *space) +static void find_query_string(struct lwan_request *request, const char *space) { struct lwan_request_parser_helper *helper = request->helper; @@ -452,7 +451,7 @@ identify_http_path(struct lwan_request *request, char *buffer) request->url.value = buffer; request->url.len = (size_t)(space - buffer); - parse_fragment_and_query(request, space); + find_query_string(request, space); request->original_url = request->url; *space++ = '\0'; @@ -1419,7 +1418,7 @@ static bool handle_rewrite(struct lwan_request *request) request->flags &= ~RESPONSE_URL_REWRITTEN; - parse_fragment_and_query(request, request->url.value + request->url.len); + find_query_string(request, request->url.value + request->url.len); helper->urls_rewritten++; if (UNLIKELY(helper->urls_rewritten > 4)) { From 9a8153da95a033c108cc7348adfd6bc1ee226fb0 Mon Sep 17 00:00:00 2001 From: ryancaicse <73822648+ryancaicse@users.noreply.github.com> Date: Tue, 21 Dec 2021 11:53:21 +0800 Subject: [PATCH 1864/2505] Prevent resource leaks on error cases Prevent resource leaks on error cases in mimegen.c --- src/bin/tools/mimegen.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/bin/tools/mimegen.c b/src/bin/tools/mimegen.c index f1edacd53..dcefe61f5 100644 --- a/src/bin/tools/mimegen.c +++ b/src/bin/tools/mimegen.c @@ -220,6 +220,7 @@ int main(int argc, char *argv[]) ext_mime = hash_str_new(free, free); if (!ext_mime) { fprintf(stderr, "Could not allocate hash table\n"); + fclose(fp); return 1; } @@ -263,6 +264,7 @@ int main(int argc, char *argv[]) if (!k || !v) { fprintf(stderr, "Could not allocate memory\n"); + fclose(fp); return 1; } @@ -273,6 +275,7 @@ int main(int argc, char *argv[]) if (r != -EEXIST) { fprintf(stderr, "Could not add extension to hash table\n"); + fclose(fp); return 1; } } @@ -283,6 +286,7 @@ int main(int argc, char *argv[]) exts = calloc(hash_get_count(ext_mime), sizeof(char *)); if (!exts) { fprintf(stderr, "Could not allocate extension array\n"); + fclose(fp); return 1; } hash_iter_init(ext_mime, &iter); @@ -294,6 +298,7 @@ int main(int argc, char *argv[]) output.ptr = malloc(output.capacity); if (!output.ptr) { fprintf(stderr, "Could not allocate temporary memory\n"); + fclose(fp); return 1; } for (i = 0; i < hash_get_count(ext_mime); i++) { @@ -306,12 +311,14 @@ int main(int argc, char *argv[]) if (output_append_padded(&output, ext_lower) < 0) { fprintf(stderr, "Could not append to output\n"); + fclose(fp); return 1; } } for (i = 0; i < hash_get_count(ext_mime); i++) { if (output_append(&output, hash_find(ext_mime, exts[i])) < 0) { fprintf(stderr, "Could not append to output\n"); + fclose(fp); return 1; } } @@ -320,6 +327,7 @@ int main(int argc, char *argv[]) compressed = compress_output(&output, &compressed_size); if (!compressed) { fprintf(stderr, "Could not compress data\n"); + fclose(fp); return 1; } From e5954138c245c9578070693c2eff47ecea2f7182 Mon Sep 17 00:00:00 2001 From: Jens Date: Wed, 22 Dec 2021 11:30:11 +0100 Subject: [PATCH 1865/2505] Update README.md Fix typos in docker repository and link to the container images section --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 902f4ff1a..df2bf2238 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Installing ---------- You can either [build Lwan yourself](#Building), use a [container -image](#container-image), or grab a package from [your favorite +image](#container-images), or grab a package from [your favorite distribution](#lwan-in-the-wild). Building @@ -959,9 +959,9 @@ to build and run Lwan in a container. Container images are tagged with release version numbers, so a specific version of Lwan can be pulled. # latest version - docker pull ghcr.io/lperiera/lwan:latest + docker pull ghcr.io/lpereira/lwan:latest # pull a specific version - docker pull ghcr.io/lperiera/lwan:v0.3 + docker pull ghcr.io/lpereira/lwan:v0.3 ### Build images locally Clone the repository and use `Containerfile` (Dockerfile) to build Lwan with all optional dependencies enabled. From c17099dde7ca9de8d75cafad04ac0a97acd4ef92 Mon Sep 17 00:00:00 2001 From: beliefsky <36815107+beliefsky@users.noreply.github.com> Date: Fri, 5 Nov 2021 15:17:02 +0800 Subject: [PATCH 1866/2505] Consider "amd64" when checking for ${CMAKE_SYSTEM_PROCESSOR} --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 29dbd1908..0ab365e8f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -322,7 +322,7 @@ else () set(LWAN_COMMON_LIBS -Wl,-whole-archive lwan-static -Wl,-no-whole-archive) endif () -if (NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^x86_64") +if (NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^x86_64|amd64") set(HAVE_LIBUCONTEXT 1) endif () From 4a474f878a34a23f86a2804b22967aa08e6d757a Mon Sep 17 00:00:00 2001 From: L Pereira Date: Wed, 22 Dec 2021 08:32:23 -0800 Subject: [PATCH 1867/2505] When reading a `tls_listener` section, check if cert/key is set --- src/lib/lwan.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 077e630c4..96244e9c9 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -470,6 +470,10 @@ static void parse_tls_listener(struct config *conf, const struct config_line *li while ((line = config_read_line(conf))) { switch (line->type) { case CONFIG_LINE_TYPE_SECTION_END: + if (!lwan->config.ssl.cert) + config_error(conf, "Missing path to certificate"); + if (!lwan->config.ssl.key) + config_error(conf, "Missing path to private key"); return; case CONFIG_LINE_TYPE_SECTION: config_error(conf, "Unexpected section: %s", line->key); From cf9103082382814e7fe4c50e6a88e87f5dd0ad77 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Wed, 22 Dec 2021 08:42:21 -0800 Subject: [PATCH 1868/2505] Fix compilation warnings (bogus -Wmaybe-uinitialized) These aren't really issues because another value gates the usage of these potentially uninitialized variables; however, the fix is easy enough that it's better to just do it. --- src/lib/lwan-mod-rewrite.c | 4 ++-- src/lib/lwan-socket.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index 67cf8668a..3149c7e65 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -620,8 +620,8 @@ static void parse_condition_stat(struct pattern *pattern, const struct config_line *line) { char *path = NULL; - bool has_is_dir = false, is_dir; - bool has_is_file = false, is_file; + bool has_is_dir = false, is_dir = false; + bool has_is_file = false, is_file = false; while ((line = config_read_line(config))) { switch (line->type) { diff --git a/src/lib/lwan-socket.c b/src/lib/lwan-socket.c index 4d58d5e87..50daa523a 100644 --- a/src/lib/lwan-socket.c +++ b/src/lib/lwan-socket.c @@ -319,7 +319,7 @@ int lwan_create_listen_socket(const struct lwan *l, : l->config.listener; if (!strncmp(listener, "systemd:", sizeof("systemd:") - 1)) { - char **names; + char **names = NULL; int n = sd_listen_fds_with_names(false, &names); int fd = -1; From f2ea63f79261875554ca7b2e8ba5f64d83994cef Mon Sep 17 00:00:00 2001 From: L Pereira Date: Thu, 23 Dec 2021 10:14:24 -0800 Subject: [PATCH 1869/2505] Update mime.types to latest version --- src/bin/tools/mime.types | 303 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 285 insertions(+), 18 deletions(-) diff --git a/src/bin/tools/mime.types b/src/bin/tools/mime.types index 7722199a8..15b09b11c 100644 --- a/src/bin/tools/mime.types +++ b/src/bin/tools/mime.types @@ -14,43 +14,76 @@ # MIME type (lowercased) Extensions # ============================================ ========== # application/1d-interleaved-parityfec +# application/3gpdash-qoe-report+xml # application/3gpp-ims+xml +# application/a2l # application/activemessage +# application/alto-costmap+json +# application/alto-costmapfilter+json +# application/alto-directory+json +# application/alto-endpointcost+json +# application/alto-endpointcostparams+json +# application/alto-endpointprop+json +# application/alto-endpointpropparams+json +# application/alto-error+json +# application/alto-networkmap+json +# application/alto-networkmapfilter+json +# application/aml application/andrew-inset ez # application/applefile application/applixware aw +# application/atf +# application/atfx application/atom+xml atom application/atomcat+xml atomcat +# application/atomdeleted+xml # application/atomicmail application/atomsvc+xml atomsvc +# application/atxml # application/auth-policy+xml +# application/bacnet-xdd+zip # application/batch-smtp # application/beep+xml +# application/calendar+json # application/calendar+xml +# application/call-completion # application/cals-1840 +# application/cbor # application/ccmp+xml application/ccxml+xml ccxml +# application/cdfx+xml application/cdmi-capability cdmia application/cdmi-container cdmic application/cdmi-domain cdmid application/cdmi-object cdmio application/cdmi-queue cdmiq +# application/cdni +# application/cea # application/cea-2018+xml # application/cellml+xml # application/cfw +# application/cms # application/cnrp+xml +# application/coap-group+json # application/commonground # application/conference-info+xml # application/cpl+xml +# application/csrattrs # application/csta+xml # application/cstadata+xml +# application/csvm+json application/cu-seeme cu # application/cybercash +# application/dash+xml +# application/dashdelta application/davmount+xml davmount # application/dca-rft +# application/dcd # application/dec-dx # application/dialog-info+xml # application/dicom +# application/dii +# application/dit # application/dns application/docbook+xml dbk # application/dskpp+xml @@ -61,7 +94,15 @@ application/ecmascript ecma # application/edi-consent # application/edi-x12 # application/edifact +# application/efi +# application/emergencycalldata.comment+xml +# application/emergencycalldata.deviceinfo+xml +# application/emergencycalldata.providerinfo+xml +# application/emergencycalldata.serviceinfo+xml +# application/emergencycalldata.subscriberinfo+xml application/emma+xml emma +# application/emotionml+xml +# application/encaprtp # application/epp+xml application/epub+zip epub # application/eshop @@ -69,12 +110,15 @@ application/epub+zip epub application/exi exi # application/fastinfoset # application/fastsoap +# application/fdt+xml # application/fits application/font-tdpfr pfr # application/framework-attributes+xml +# application/geo+json application/gml+xml gml application/gpx+xml gpx application/gxf gxf +# application/gzip # application/h224 # application/held+xml # application/http @@ -94,15 +138,30 @@ application/inkml+xml ink inkml application/ipfix ipfix # application/ipp # application/isup +# application/its+xml application/java-archive jar application/java-serialized-object ser application/java-vm class application/javascript js +# application/jose +# application/jose+json +# application/jrd+json application/json json +# application/json-patch+json +# application/json-seq application/jsonml+json jsonml +# application/jwk+json +# application/jwk-set+json +# application/jwt # application/kpml-request+xml # application/kpml-response+xml +# application/ld+json +# application/lgr+xml +# application/link-format +# application/load-control+xml application/lost+xml lostxml +# application/lostsync+xml +# application/lxf application/mac-binhex40 hqx application/mac-compactpro cpt # application/macwriteii @@ -110,9 +169,9 @@ application/mads+xml mads application/marc mrc application/marcxml+xml mrcx application/mathematica ma nb mb +application/mathml+xml mathml # application/mathml-content+xml # application/mathml-presentation+xml -application/mathml+xml mathml # application/mbms-associated-procedure-description+xml # application/mbms-deregister+xml # application/mbms-envelope+xml @@ -122,13 +181,17 @@ application/mathml+xml mathml # application/mbms-reception-report+xml # application/mbms-register+xml # application/mbms-register-response+xml +# application/mbms-schedule+xml # application/mbms-user-service-description+xml application/mbox mbox +# application/media-policy-dataset+xml # application/media_control+xml application/mediaservercontrol+xml mscml +# application/merge-patch+json application/metalink+xml metalink application/metalink4+xml meta4 application/mets+xml mets +# application/mf4 # application/mikey application/mods+xml mods # application/moss-keys @@ -140,6 +203,8 @@ application/mp4 mp4s # application/mpeg4-generic # application/mpeg4-iod # application/mpeg4-iod-xmt +# application/mrb-consumer+xml +# application/mrb-publish+xml # application/msc-ivr+xml # application/msc-mixer+xml application/msword doc dot @@ -148,19 +213,23 @@ application/mxf mxf # application/news-checkgroups # application/news-groupinfo # application/news-transmission +# application/nlsml+xml # application/nss # application/ocsp-request # application/ocsp-response application/octet-stream bin dms lrf mar so dist distz pkg bpk dump elc deploy application/oda oda +# application/odx application/oebps-package+xml opf application/ogg ogx application/omdoc+xml omdoc application/onenote onetoc onetoc2 onetmp onepkg application/oxps oxps +# application/p2p-overlay+xml # application/parityfec application/patch-ops-error+xml xer application/pdf pdf +# application/pdx application/pgp-encrypted pgp # application/pgp-keys application/pgp-signature asc sig @@ -168,6 +237,7 @@ application/pics-rules prf # application/pidf+xml # application/pidf-diff+xml application/pkcs10 p10 +# application/pkcs12 application/pkcs7-mime p7m p7c application/pkcs7-signature p7s application/pkcs8 p8 @@ -179,20 +249,29 @@ application/pkixcmp pki application/pls+xml pls # application/poc-settings+xml application/postscript ai eps ps +# application/ppsp-tracker+json +# application/problem+json +# application/problem+xml +# application/provenance+xml # application/prs.alvestrand.titrax-sheet application/prs.cww cww +# application/prs.hpub+zip # application/prs.nprend # application/prs.plucker # application/prs.rdf-xml-crypt # application/prs.xsf+xml application/pskc+xml pskcxml # application/qsig +# application/raptorfec +# application/rdap+json application/rdf+xml rdf application/reginfo+xml rif application/relax-ng-compact-syntax rnc # application/remote-printing +# application/reputon+json application/resource-lists+xml rl application/resource-lists-diff+xml rld +# application/rfc+xml # application/riscos # application/rlmi+xml application/rls-services+xml rs @@ -203,15 +282,21 @@ application/rpki-roa roa application/rsd+xml rsd application/rss+xml rss application/rtf rtf +# application/rtploopback # application/rtx # application/samlassertion+xml # application/samlmetadata+xml application/sbml+xml sbml +# application/scaip+xml +# application/scim+json application/scvp-cv-request scq application/scvp-cv-response scs application/scvp-vp-request spq application/scvp-vp-response spp application/sdp sdp +# application/sep+xml +# application/sep-exi +# application/session-info # application/set-payment application/set-payment-initiation setpay # application/set-registration @@ -226,11 +311,13 @@ application/shf+xml shf # application/slate # application/smil application/smil+xml smi smil +# application/smpte336m # application/soap+fastinfoset # application/soap+xml application/sparql-query rq application/sparql-results+xml srx # application/spirits-event+xml +# application/sql application/srgs gram application/srgs+xml grxml application/sru+xml sru @@ -252,25 +339,42 @@ application/thraud+xml tfi # application/timestamp-query # application/timestamp-reply application/timestamped-data tsd +# application/ttml+xml # application/tve-trigger # application/ulpfec +# application/urc-grpsheet+xml +# application/urc-ressheet+xml +# application/urc-targetdesc+xml +# application/urc-uisocketdesc+xml +# application/vcard+json # application/vcard+xml # application/vemmi # application/vividence.scriptfile +# application/vnd.3gpp-prose+xml +# application/vnd.3gpp-prose-pc3ch+xml +# application/vnd.3gpp.access-transfer-events+xml # application/vnd.3gpp.bsf+xml +# application/vnd.3gpp.mid-call+xml application/vnd.3gpp.pic-bw-large plb application/vnd.3gpp.pic-bw-small psb application/vnd.3gpp.pic-bw-var pvb # application/vnd.3gpp.sms +# application/vnd.3gpp.sms+xml +# application/vnd.3gpp.srvcc-ext+xml +# application/vnd.3gpp.srvcc-info+xml +# application/vnd.3gpp.state-and-event-info+xml +# application/vnd.3gpp.ussd+xml # application/vnd.3gpp2.bcmcsinfo+xml # application/vnd.3gpp2.sms application/vnd.3gpp2.tcap tcap +# application/vnd.3lightssoftware.imagescal application/vnd.3m.post-it-notes pwn application/vnd.accpac.simply.aso aso application/vnd.accpac.simply.imp imp application/vnd.acucobol acu application/vnd.acucorp atc acutc application/vnd.adobe.air-application-installer-package+zip air +# application/vnd.adobe.flash.movie application/vnd.adobe.formscentral.fcdt fcdt application/vnd.adobe.fxp fxp fxpl # application/vnd.adobe.partial-upload @@ -282,42 +386,62 @@ application/vnd.ahead.space ahead application/vnd.airzip.filesecure.azf azf application/vnd.airzip.filesecure.azs azs application/vnd.amazon.ebook azw +# application/vnd.amazon.mobi8-ebook application/vnd.americandynamics.acc acc application/vnd.amiga.ami ami # application/vnd.amundsen.maze+xml application/vnd.android.package-archive apk +# application/vnd.anki application/vnd.anser-web-certificate-issue-initiation cii application/vnd.anser-web-funds-transfer-initiation fti application/vnd.antix.game-component atx +# application/vnd.apache.thrift.binary +# application/vnd.apache.thrift.compact +# application/vnd.apache.thrift.json +# application/vnd.api+json application/vnd.apple.installer+xml mpkg application/vnd.apple.mpegurl m3u8 # application/vnd.arastra.swi application/vnd.aristanetworks.swi swi +# application/vnd.artsquare application/vnd.astraea-software.iota iota application/vnd.audiograph aep # application/vnd.autopackage # application/vnd.avistar+xml +# application/vnd.balsamiq.bmml+xml +# application/vnd.balsamiq.bmpr +# application/vnd.bekitzur-stech+json +# application/vnd.biopax.rdf+xml application/vnd.blueice.multipass mpm # application/vnd.bluetooth.ep.oob +# application/vnd.bluetooth.le.oob application/vnd.bmi bmi application/vnd.businessobjects rep # application/vnd.cab-jscript # application/vnd.canon-cpdl # application/vnd.canon-lips # application/vnd.cendio.thinlinc.clientconf +# application/vnd.century-systems.tcp_stream application/vnd.chemdraw+xml cdxml +# application/vnd.chess-pgn application/vnd.chipnuts.karaoke-mmd mmd application/vnd.cinderella cdy # application/vnd.cirpack.isdn-ext +# application/vnd.citationstyles.style+xml application/vnd.claymore cla application/vnd.cloanto.rp9 rp9 application/vnd.clonk.c4group c4g c4d c4f c4p c4u application/vnd.cluetrust.cartomobile-config c11amc application/vnd.cluetrust.cartomobile-config-pkg c11amz +# application/vnd.coffeescript # application/vnd.collection+json +# application/vnd.collection.doc+json +# application/vnd.collection.next+json +# application/vnd.comicbook+zip # application/vnd.commerce-battelle application/vnd.commonspace csp application/vnd.contact.cmsg cdbcmsg +# application/vnd.coreos.ignition+json application/vnd.cosmocaller cmc application/vnd.crick.clicker clkx application/vnd.crick.clicker.keyboard clkk @@ -335,22 +459,32 @@ application/vnd.cups-ppd ppd # application/vnd.curl application/vnd.curl.car car application/vnd.curl.pcurl pcurl +# application/vnd.cyan.dean.root+xml # application/vnd.cybank application/vnd.dart dart application/vnd.data-vision.rdz rdz +# application/vnd.debian.binary-package application/vnd.dece.data uvf uvvf uvd uvvd application/vnd.dece.ttml+xml uvt uvvt application/vnd.dece.unspecified uvx uvvx application/vnd.dece.zip uvz uvvz application/vnd.denovo.fcselayout-link fe_launch +# application/vnd.desmume.movie # application/vnd.dir-bi.plate-dl-nosuffix +# application/vnd.dm.delegation+xml application/vnd.dna dna +# application/vnd.document+json application/vnd.dolby.mlp mlp # application/vnd.dolby.mobile.1 # application/vnd.dolby.mobile.2 +# application/vnd.doremir.scorecloud-binary-document application/vnd.dpgraph dpg application/vnd.dreamfactory dfac +# application/vnd.drive+json application/vnd.ds-keypoint kpxx +# application/vnd.dtg.local +# application/vnd.dtg.local.flash +# application/vnd.dtg.local.html application/vnd.dvb.ait ait # application/vnd.dvb.dvbj # application/vnd.dvb.esgcontainer @@ -372,6 +506,7 @@ application/vnd.dvb.ait ait application/vnd.dvb.service svc # application/vnd.dxr application/vnd.dynageo geo +# application/vnd.dzr # application/vnd.easykaraoke.cdgdownload # application/vnd.ecdis-update application/vnd.ecowin.chart mag @@ -382,6 +517,7 @@ application/vnd.ecowin.chart mag # application/vnd.ecowin.seriesupdate # application/vnd.emclient.accessrequest+xml application/vnd.enliven nml +# application/vnd.enphase.envoy # application/vnd.eprints.data+xml application/vnd.epson.esf esf application/vnd.epson.msf msf @@ -391,6 +527,8 @@ application/vnd.epson.ssf ssf # application/vnd.ericsson.quickcall application/vnd.eszigno3+xml es3 et3 # application/vnd.etsi.aoc+xml +# application/vnd.etsi.asic-e+zip +# application/vnd.etsi.asic-s+zip # application/vnd.etsi.cug+xml # application/vnd.etsi.iptvcommand+xml # application/vnd.etsi.iptvdiscovery+xml @@ -402,20 +540,26 @@ application/vnd.eszigno3+xml es3 et3 # application/vnd.etsi.iptvsync+xml # application/vnd.etsi.iptvueprofile+xml # application/vnd.etsi.mcid+xml +# application/vnd.etsi.mheg5 # application/vnd.etsi.overload-control-policy-dataset+xml +# application/vnd.etsi.pstn+xml # application/vnd.etsi.sci+xml # application/vnd.etsi.simservs+xml +# application/vnd.etsi.timestamp-token # application/vnd.etsi.tsl+xml # application/vnd.etsi.tsl.der # application/vnd.eudora.data application/vnd.ezpix-album ez2 application/vnd.ezpix-package ez3 # application/vnd.f-secure.mobile +# application/vnd.fastcopy-disk-image application/vnd.fdf fdf application/vnd.fdsn.mseed mseed application/vnd.fdsn.seed seed dataless # application/vnd.ffsns +# application/vnd.filmit.zfc # application/vnd.fints +# application/vnd.firemonkeys.cloudcell application/vnd.flographit gph application/vnd.fluxtime.clip ftc # application/vnd.font-fontforge-sfd @@ -430,13 +574,15 @@ application/vnd.fujitsu.oasysgp fg5 application/vnd.fujitsu.oasysprs bh2 # application/vnd.fujixerox.art-ex # application/vnd.fujixerox.art4 -# application/vnd.fujixerox.hbpl application/vnd.fujixerox.ddd ddd application/vnd.fujixerox.docuworks xdw application/vnd.fujixerox.docuworks.binder xbd +# application/vnd.fujixerox.docuworks.container +# application/vnd.fujixerox.hbpl # application/vnd.fut-misnet application/vnd.fuzzysheet fzs application/vnd.genomatix.tuxedo txd +# application/vnd.geo+json # application/vnd.geocube+xml application/vnd.geogebra.file ggb application/vnd.geogebra.tool ggt @@ -444,11 +590,15 @@ application/vnd.geometry-explorer gex gre application/vnd.geonext gxt application/vnd.geoplan g2w application/vnd.geospace g3w +# application/vnd.gerber # application/vnd.globalplatform.card-content-mgt # application/vnd.globalplatform.card-content-mgt-response application/vnd.gmx gmx application/vnd.google-earth.kml+xml kml application/vnd.google-earth.kmz kmz +# application/vnd.gov.sk.e-form+xml +# application/vnd.gov.sk.e-form+zip +# application/vnd.gov.sk.xmldatacontainer+xml application/vnd.grafeq gqf gqs # application/vnd.gridmp application/vnd.groove-account gac @@ -463,6 +613,8 @@ application/vnd.hal+xml hal application/vnd.handheld-entertainment+xml zmm application/vnd.hbci hbci # application/vnd.hcl-bireports +# application/vnd.hdt +# application/vnd.heroku+json application/vnd.hhe.lesson-player les application/vnd.hp-hpgl hpgl application/vnd.hp-hpid hpid @@ -472,6 +624,7 @@ application/vnd.hp-pcl pcl application/vnd.hp-pclxl pclxl # application/vnd.httphone application/vnd.hydrostatix.sof-data sfd-hdstx +# application/vnd.hyperdrive+json # application/vnd.hzn-3d-crossword # application/vnd.ibm.afplinedata # application/vnd.ibm.electronic-media @@ -480,9 +633,19 @@ application/vnd.ibm.modcap afp listafp list3820 application/vnd.ibm.rights-management irm application/vnd.ibm.secure-container sc application/vnd.iccprofile icc icm +# application/vnd.ieee.1905 application/vnd.igloader igl application/vnd.immervision-ivp ivp application/vnd.immervision-ivu ivu +# application/vnd.ims.imsccv1p1 +# application/vnd.ims.imsccv1p2 +# application/vnd.ims.imsccv1p3 +# application/vnd.ims.lis.v2.result+json +# application/vnd.ims.lti.v2.toolconsumerprofile+json +# application/vnd.ims.lti.v2.toolproxy+json +# application/vnd.ims.lti.v2.toolproxy.id+json +# application/vnd.ims.lti.v2.toolsettings+json +# application/vnd.ims.lti.v2.toolsettings.simple+json # application/vnd.informedcontrol.rms+xml # application/vnd.informix-visionary # application/vnd.infotech.project @@ -495,6 +658,7 @@ application/vnd.intergeo i2g # application/vnd.intertrust.nncp application/vnd.intu.qbo qbo application/vnd.intu.qfx qfx +# application/vnd.iptc.g2.catalogitem+xml # application/vnd.iptc.g2.conceptitem+xml # application/vnd.iptc.g2.knowledgeitem+xml # application/vnd.iptc.g2.newsitem+xml @@ -517,6 +681,7 @@ application/vnd.jam jam application/vnd.jcp.javame.midlet-rms rms application/vnd.jisp jisp application/vnd.joost.joda-archive joda +# application/vnd.jsk.isdn-ngn application/vnd.kahootz ktz ktr application/vnd.kde.karbon karbon application/vnd.kde.kchart chrt @@ -543,18 +708,24 @@ application/vnd.lotus-organizer org application/vnd.lotus-screencam scm application/vnd.lotus-wordpro lwp application/vnd.macports.portpkg portpkg +# application/vnd.mapbox-vector-tile # application/vnd.marlin.drm.actiontoken+xml # application/vnd.marlin.drm.conftoken+xml # application/vnd.marlin.drm.license+xml # application/vnd.marlin.drm.mdcf +# application/vnd.mason+json +# application/vnd.maxmind.maxmind-db application/vnd.mcd mcd application/vnd.medcalcdata mc1 application/vnd.mediastation.cdkey cdkey # application/vnd.meridian-slingshot application/vnd.mfer mwf application/vnd.mfmp mfm +# application/vnd.micro+json application/vnd.micrografx.flo flo application/vnd.micrografx.igx igx +# application/vnd.microsoft.portable-executable +# application/vnd.miele+json application/vnd.mif mif # application/vnd.minisoft-hp3000-save # application/vnd.mitsubishi.misty-guard.trustweb @@ -576,6 +747,7 @@ application/vnd.mophun.certificate mpc # application/vnd.motorola.flexsuite.wem # application/vnd.motorola.iprm application/vnd.mozilla.xul+xml xul +# application/vnd.ms-3mfdocument application/vnd.ms-artgalry cil # application/vnd.ms-asf application/vnd.ms-cab-compressed cab @@ -602,9 +774,15 @@ application/vnd.ms-powerpoint.presentation.macroenabled.12 pptm application/vnd.ms-powerpoint.slide.macroenabled.12 sldm application/vnd.ms-powerpoint.slideshow.macroenabled.12 ppsm application/vnd.ms-powerpoint.template.macroenabled.12 potm +# application/vnd.ms-printdevicecapabilities+xml # application/vnd.ms-printing.printticket+xml +# application/vnd.ms-printschematicket+xml application/vnd.ms-project mpp mpt # application/vnd.ms-tnef +# application/vnd.ms-windows.devicepairing +# application/vnd.ms-windows.nwprinting.oob +# application/vnd.ms-windows.printerpairing +# application/vnd.ms-windows.wsd.oob # application/vnd.ms-wmdrm.lic-chlg-req # application/vnd.ms-wmdrm.lic-resp # application/vnd.ms-wmdrm.meter-chlg-req @@ -614,6 +792,7 @@ application/vnd.ms-word.template.macroenabled.12 dotm application/vnd.ms-works wps wks wcm wdb application/vnd.ms-wpl wpl application/vnd.ms-xpsdocument xps +# application/vnd.msa-disk-image application/vnd.mseq mseq # application/vnd.msign # application/vnd.multiad.creator @@ -627,6 +806,8 @@ application/vnd.mynfc taglet # application/vnd.nervana # application/vnd.netfpx application/vnd.neurolanguage.nlu nlu +# application/vnd.nintendo.nitro.rom +# application/vnd.nintendo.snes.rom application/vnd.nitf ntf nitf application/vnd.noblenet-directory nnd application/vnd.noblenet-sealer nns @@ -634,8 +815,8 @@ application/vnd.noblenet-web nnw # application/vnd.nokia.catalogs # application/vnd.nokia.conml+wbxml # application/vnd.nokia.conml+xml -# application/vnd.nokia.isds-radio-presets # application/vnd.nokia.iptv.config+xml +# application/vnd.nokia.isds-radio-presets # application/vnd.nokia.landmark+wbxml # application/vnd.nokia.landmark+xml # application/vnd.nokia.landmarkcollection+xml @@ -650,7 +831,9 @@ application/vnd.nokia.radio-presets rpss application/vnd.novadigm.edm edm application/vnd.novadigm.edx edx application/vnd.novadigm.ext ext +# application/vnd.ntt-local.content-share # application/vnd.ntt-local.file-transfer +# application/vnd.ntt-local.ogw_remote-access # application/vnd.ntt-local.sip-ta_remote # application/vnd.ntt-local.sip-ta_tcp_stream application/vnd.oasis.opendocument.chart odc @@ -703,12 +886,15 @@ application/vnd.olpc-sugar xo # application/vnd.oma.cab-address-book+xml # application/vnd.oma.cab-feature-handler+xml # application/vnd.oma.cab-pcc+xml +# application/vnd.oma.cab-subs-invite+xml # application/vnd.oma.cab-user-prefs+xml # application/vnd.oma.dcd # application/vnd.oma.dcdc application/vnd.oma.dd2+xml dd2 # application/vnd.oma.drm.risd+xml # application/vnd.oma.group-usage-list+xml +# application/vnd.oma.lwm2m+json +# application/vnd.oma.lwm2m+tlv # application/vnd.oma.pal+xml # application/vnd.oma.poc.detailed-progress-report+xml # application/vnd.oma.poc.final-report+xml @@ -722,6 +908,10 @@ application/vnd.oma.dd2+xml dd2 # application/vnd.omads-file+xml # application/vnd.omads-folder+xml # application/vnd.omaloc-supl-init +# application/vnd.onepager +# application/vnd.openblox.game+xml +# application/vnd.openblox.game-binary +# application/vnd.openeye.oeb application/vnd.openofficeorg.extension oxt # application/vnd.openxmlformats-officedocument.custom-properties+xml # application/vnd.openxmlformats-officedocument.customxmlproperties+xml @@ -797,16 +987,21 @@ application/vnd.openxmlformats-officedocument.wordprocessingml.template dotx # application/vnd.openxmlformats-package.core-properties+xml # application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml # application/vnd.openxmlformats-package.relationships+xml -# application/vnd.quobject-quoxdocument +# application/vnd.oracle.resource+json +# application/vnd.orange.indata # application/vnd.osa.netdeploy application/vnd.osgeo.mapguide.package mgp # application/vnd.osgi.bundle application/vnd.osgi.dp dp application/vnd.osgi.subsystem esa # application/vnd.otps.ct-kip+xml +# application/vnd.oxli.countgraph +# application/vnd.pagerduty+json application/vnd.palm pdb pqa oprc +# application/vnd.panoply # application/vnd.paos.xml application/vnd.pawaafile paw +# application/vnd.pcos application/vnd.pg.format str application/vnd.pg.osasli ei6 # application/vnd.piaccess.application-licence @@ -828,7 +1023,9 @@ application/vnd.pvi.ptid1 ptid # application/vnd.pwg-multiplexed # application/vnd.pwg-xhtml-print+xml # application/vnd.qualcomm.brew-app-res +# application/vnd.quarantainenet application/vnd.quark.quarkxpress qxd qxt qwd qwt qxl qxb +# application/vnd.quobject-quoxdocument # application/vnd.radisys.moml+xml # application/vnd.radisys.msml+xml # application/vnd.radisys.msml-audit+xml @@ -846,6 +1043,7 @@ application/vnd.quark.quarkxpress qxd qxt qwd qwt qxl qxb # application/vnd.radisys.msml-dialog-transform+xml # application/vnd.rainstor.data # application/vnd.rapid +# application/vnd.rar application/vnd.realvnc.bed bed application/vnd.recordare.musicxml mxl application/vnd.recordare.musicxml+xml musicxml @@ -882,6 +1080,7 @@ application/vnd.shana.informed.formtemplate itp application/vnd.shana.informed.interchange iif application/vnd.shana.informed.package ipk application/vnd.simtech-mindmapper twd twds +# application/vnd.siren+json application/vnd.smaf mmf # application/vnd.smart.notebook application/vnd.smart.teacher teacher @@ -902,6 +1101,7 @@ application/vnd.stardivision.writer-global sgl application/vnd.stepmania.package smzip application/vnd.stepmania.stepchart sm # application/vnd.street-stream +# application/vnd.sun.wadl+xml application/vnd.sun.xml.calc sxc application/vnd.sun.xml.calc.template stc application/vnd.sun.xml.draw sxd @@ -912,7 +1112,6 @@ application/vnd.sun.xml.math sxm application/vnd.sun.xml.writer sxw application/vnd.sun.xml.writer.global sxg application/vnd.sun.xml.writer.template stw -# application/vnd.sun.wadl+xml application/vnd.sus-calendar sus susp application/vnd.svd svd # application/vnd.swiftview-ics @@ -921,9 +1120,15 @@ application/vnd.syncml+xml xsm application/vnd.syncml.dm+wbxml bdm application/vnd.syncml.dm+xml xdm # application/vnd.syncml.dm.notification +# application/vnd.syncml.dmddf+wbxml +# application/vnd.syncml.dmddf+xml +# application/vnd.syncml.dmtnds+wbxml +# application/vnd.syncml.dmtnds+xml # application/vnd.syncml.ds.notification application/vnd.tao.intent-module-archive tao application/vnd.tcpdump.pcap pcap cap dmp +# application/vnd.tmd.mediaflex.api+xml +# application/vnd.tml application/vnd.tmobile-livetv tmo application/vnd.trid.tpt tpt application/vnd.triscape.mxs mxs @@ -948,9 +1153,12 @@ application/vnd.uoml+xml uoml # application/vnd.uplanet.listcmd # application/vnd.uplanet.listcmd-wbxml # application/vnd.uplanet.signal +# application/vnd.uri-map +# application/vnd.valve.source.material application/vnd.vcx vcx # application/vnd.vd-study # application/vnd.vectorworks +# application/vnd.vel+json # application/vnd.verimatrix.vcas # application/vnd.vidsoft.vidconference application/vnd.visio vsd vst vss vsw @@ -963,7 +1171,9 @@ application/vnd.wap.wbxml wbxml application/vnd.wap.wmlc wmlc application/vnd.wap.wmlscriptc wmlsc application/vnd.webturbo wtb +# application/vnd.wfa.p2p # application/vnd.wfa.wsc +# application/vnd.windows.devicepairing # application/vnd.wmc # application/vnd.wmf.bootstrap # application/vnd.wolfram.mathematica @@ -976,6 +1186,7 @@ application/vnd.wt.stf stf # application/vnd.wv.csp+wbxml # application/vnd.wv.csp+xml # application/vnd.wv.ssp+xml +# application/vnd.xacml+json application/vnd.xara xar application/vnd.xfdl xfdl # application/vnd.xfdl.webform @@ -995,6 +1206,7 @@ application/vnd.yamaha.smaf-audio saf application/vnd.yamaha.smaf-phrase spf # application/vnd.yamaha.through-ngn # application/vnd.yamaha.tunnel-udpencap +# application/vnd.yaoweme application/vnd.yellowriver-custom-menu cmp application/vnd.zul zir zirz application/vnd.zzazz.deck+xml zaz @@ -1028,8 +1240,8 @@ application/x-cdlink vcd application/x-cfs-compressed cfs application/x-chat chat application/x-chess-pgn pgn -application/x-conference nsc # application/x-compress +application/x-conference nsc application/x-cpio cpio application/x-csh csh application/x-debian-package deb udeb @@ -1048,14 +1260,11 @@ application/x-font-bdf bdf application/x-font-ghostscript gsf # application/x-font-libgrx application/x-font-linux-psf psf -application/x-font-otf otf application/x-font-pcf pcf application/x-font-snf snf # application/x-font-speedo # application/x-font-sunos-news -application/x-font-ttf ttf ttc application/x-font-type1 pfa pfb pfm afm -application/x-font-woff woff # application/x-font-vfont application/x-freearc arc application/x-futuresplash spl @@ -1117,6 +1326,7 @@ application/x-texinfo texinfo texi application/x-tgif obj application/x-ustar ustar application/x-wais-source src +# application/x-www-form-urlencoded application/x-x509-ca-cert der crt application/x-xfig fig application/x-xliff+xml xlf @@ -1124,6 +1334,7 @@ application/x-xpinstall xpi application/x-xz xz application/x-zmachine z1 z2 z3 z4 z5 z6 z7 z8 # application/x400-bp +# application/xacml+xml application/xaml+xml xaml # application/xcap-att+xml # application/xcap-caps+xml @@ -1131,14 +1342,15 @@ application/xcap-diff+xml xdf # application/xcap-el+xml # application/xcap-error+xml # application/xcap-ns+xml -# application/xcon-conference-info-diff+xml # application/xcon-conference-info+xml +# application/xcon-conference-info-diff+xml application/xenc+xml xenc application/xhtml+xml xhtml xht # application/xhtml-voice+xml application/xml xml xsl application/xml-dtd dtd # application/xml-external-parsed-entity +# application/xml-patch+xml # application/xmpp+xml application/xop+xml xop application/xproc+xml xpl @@ -1148,6 +1360,7 @@ application/xv+xml mxml xhvml xvml xvm application/yang yang application/yin+xml yin application/zip zip +# application/zlib # audio/1d-interleaved-parityfec # audio/32kadpcm # audio/3gpp @@ -1157,6 +1370,7 @@ audio/adpcm adp # audio/amr # audio/amr-wb # audio/amr-wb+ +# audio/aptx # audio/asc # audio/atrac-advanced-lossless # audio/atrac-x @@ -1175,6 +1389,7 @@ audio/basic au snd # audio/dv # audio/dvi4 # audio/eac3 +# audio/encaprtp # audio/evrc # audio/evrc-qcp # audio/evrc0 @@ -1182,11 +1397,16 @@ audio/basic au snd # audio/evrcb # audio/evrcb0 # audio/evrcb1 +# audio/evrcnw +# audio/evrcnw0 +# audio/evrcnw1 # audio/evrcwb # audio/evrcwb0 # audio/evrcwb1 +# audio/evs # audio/example # audio/fwdred +# audio/g711-0 # audio/g719 # audio/g722 # audio/g7221 @@ -1213,31 +1433,33 @@ audio/basic au snd # audio/lpc audio/midi mid midi kar rmi # audio/mobile-xmf -audio/mp4 mp4a +audio/mp4 m4a mp4a # audio/mp4a-latm # audio/mpa # audio/mpa-robust audio/mpeg mpga mp2 mp2a mp3 m2a m3a # audio/mpeg4-generic # audio/musepack -audio/ogg oga ogg spx +audio/ogg oga ogg spx opus # audio/opus # audio/parityfec # audio/pcma # audio/pcma-wb -# audio/pcmu-wb # audio/pcmu +# audio/pcmu-wb # audio/prs.sid # audio/qcelp +# audio/raptorfec # audio/red # audio/rtp-enc-aescm128 # audio/rtp-midi +# audio/rtploopback # audio/rtx audio/s3m s3m audio/silk sil # audio/smv -# audio/smv0 # audio/smv-qcp +# audio/smv0 # audio/sp-midi # audio/speex # audio/t140c @@ -1309,13 +1531,22 @@ chemical/x-cml cml chemical/x-csml csml # chemical/x-pdb chemical/x-xyz xyz +font/collection ttc +font/otf otf +# font/sfnt +font/ttf ttf +font/woff woff +font/woff2 woff2 image/bmp bmp image/cgm cgm +# image/dicom-rle +# image/emf # image/example # image/fits image/g3fax g3 image/gif gif image/ief ief +# image/jls # image/jp2 image/jpeg jpeg jpg jpe # image/jpm @@ -1325,16 +1556,18 @@ image/ktx ktx image/png png image/prs.btif btif # image/prs.pti +# image/pwg-raster image/sgi sgi image/svg+xml svg svgz # image/t38 image/tiff tiff tif # image/tiff-fx image/vnd.adobe.photoshop psd +# image/vnd.airzip.accelerator.azv # image/vnd.cns.inf2 image/vnd.dece.graphic uvi uvvi uvg uvvg -image/vnd.dvb.subtitle sub image/vnd.djvu djvu djv +image/vnd.dvb.subtitle sub image/vnd.dwg dwg image/vnd.dxf dxf image/vnd.fastbidsheet fbs @@ -1345,6 +1578,7 @@ image/vnd.fujixerox.edmics-rlc rlc # image/vnd.globalgraphics.pgb # image/vnd.microsoft.icon # image/vnd.mix +# image/vnd.mozilla.apng image/vnd.ms-modi mdi image/vnd.ms-photo wdp image/vnd.net-fpx npx @@ -1353,9 +1587,13 @@ image/vnd.net-fpx npx # image/vnd.sealedmedia.softseal.gif # image/vnd.sealedmedia.softseal.jpg # image/vnd.svf +# image/vnd.tencent.tap +# image/vnd.valve.source.texture image/vnd.wap.wbmp wbmp image/vnd.xiff xif +# image/vnd.zbrush.pcx image/webp webp +# image/wmf image/x-3ds 3ds image/x-cmu-raster ras image/x-cmx cmx @@ -1393,7 +1631,9 @@ message/rfc822 eml mime # message/sipfrag # message/tracking-status # message/vnd.si.simp +# message/vnd.wfa.wsc # model/example +# model/gltf+json model/iges igs iges model/mesh msh mesh silo model/vnd.collada+xml dae @@ -1405,13 +1645,18 @@ model/vnd.gdl gdl model/vnd.gtw gtw # model/vnd.moml+xml model/vnd.mts mts +# model/vnd.opengex # model/vnd.parasolid.transmit.binary # model/vnd.parasolid.transmit.text +# model/vnd.rosette.annotated-data-model +# model/vnd.valve.source.compiled-map model/vnd.vtu vtu model/vrml wrl vrml model/x3d+binary x3db x3dbz +# model/x3d+fastinfoset model/x3d+vrml x3dv x3dvz model/x3d+xml x3d x3dz +# model/x3d-vrml # multipart/alternative # multipart/appledouble # multipart/byteranges @@ -1426,30 +1671,41 @@ model/x3d+xml x3d x3dz # multipart/report # multipart/signed # multipart/voice-message +# multipart/x-mixed-replace # text/1d-interleaved-parityfec text/cache-manifest appcache text/calendar ics ifb text/css css text/csv csv +# text/csv-schema # text/directory # text/dns # text/ecmascript +# text/encaprtp # text/enriched # text/example # text/fwdred +# text/grammar-ref-list text/html html htm # text/javascript +# text/jcr-cnd +# text/markdown +# text/mizar text/n3 n3 +# text/parameters # text/parityfec text/plain txt text conf def list log in +# text/provenance-notation # text/prs.fallenstein.rst text/prs.lines.tag dsc -# text/vnd.radisys.msml-basic-layout +# text/prs.prop.logic +# text/raptorfec # text/red # text/rfc822-headers text/richtext rtx # text/rtf # text/rtp-enc-aescm128 +# text/rtploopback # text/rtx text/sgml sgml sgm # text/t140 @@ -1459,11 +1715,13 @@ text/turtle ttl # text/ulpfec text/uri-list uri uris urls text/vcard vcard +# text/vnd.a # text/vnd.abc text/vnd.curl curl text/vnd.curl.dcurl dcurl -text/vnd.curl.scurl scurl text/vnd.curl.mcurl mcurl +text/vnd.curl.scurl scurl +# text/vnd.debian.copyright # text/vnd.dmclientscript text/vnd.dvb.subtitle sub # text/vnd.esmertec.theme-descriptor @@ -1478,6 +1736,7 @@ text/vnd.in3d.spot spot # text/vnd.motorola.reflex # text/vnd.ms-mediapackage # text/vnd.net2phone.commcenter.command +# text/vnd.radisys.msml-basic-layout # text/vnd.si.uricatalogue text/vnd.sun.j2me.app-descriptor jad # text/vnd.trolltech.linguist @@ -1489,9 +1748,9 @@ text/x-asm s asm text/x-c c cc cxx cpp h hh dic text/x-fortran f for f77 f90 text/x-java-source java +text/x-nfo nfo text/x-opml opml text/x-pascal p pas -text/x-nfo nfo text/x-setext etx text/x-sfv sfv text/x-uuencode uu @@ -1507,6 +1766,7 @@ video/3gpp2 3g2 # video/bt656 # video/celb # video/dv +# video/encaprtp # video/example video/h261 h261 video/h263 h263 @@ -1515,6 +1775,8 @@ video/h263 h263 video/h264 h264 # video/h264-rcdo # video/h264-svc +# video/h265 +# video/iso.segment video/jpeg jpgv # video/jpeg2000 video/jpm jpm jpgm @@ -1532,8 +1794,10 @@ video/ogg ogv # video/parityfec # video/pointer video/quicktime qt mov +# video/raptorfec # video/raw # video/rtp-enc-aescm128 +# video/rtploopback # video/rtx # video/smpte292m # video/ulpfec @@ -1564,12 +1828,15 @@ video/vnd.ms-playready.media.pyv pyv # video/vnd.nokia.interleaved-multimedia # video/vnd.nokia.videovoip # video/vnd.objectvideo +# video/vnd.radgamettools.bink +# video/vnd.radgamettools.smacker # video/vnd.sealed.mpeg1 # video/vnd.sealed.mpeg4 # video/vnd.sealed.swf # video/vnd.sealedmedia.softseal.mov video/vnd.uvvu.mp4 uvu uvvu video/vnd.vivo viv +# video/vp8 video/webm webm video/x-f4v f4v video/x-fli fli From bddc06266404011c970ec07a200f5c9aad45049b Mon Sep 17 00:00:00 2001 From: L Pereira Date: Sun, 16 Jan 2022 14:38:33 -0800 Subject: [PATCH 1870/2505] Simplify lookup of MIME types Use a branchless version of "make the extension uppercase". --- src/bin/tools/mimegen.c | 2 +- src/lib/lwan-tables.c | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/bin/tools/mimegen.c b/src/bin/tools/mimegen.c index dcefe61f5..1ea5e96e9 100644 --- a/src/bin/tools/mimegen.c +++ b/src/bin/tools/mimegen.c @@ -307,7 +307,7 @@ int main(int argc, char *argv[]) strncpy(ext_lower, exts[i], 8); for (char *p = ext_lower; *p; p++) - *p |= 0x20; + *p &= ~0x20; if (output_append_padded(&output, ext_lower) < 0) { fprintf(stderr, "Could not append to output\n"); diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index 821341185..aa2dba4b8 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -135,15 +135,12 @@ lwan_determine_mime_type_for_file_name(const char *file_name) } if (LIKELY(*last_dot)) { - char key[9]; + uint64_t key = string_as_uint64(last_dot + 1); const unsigned char *extension; - strncpy(key, last_dot + 1, 8); - key[8] = '\0'; - for (char *p = key; *p; p++) - *p |= 0x20; + key &= ~0x2020202020202020ull; - extension = bsearch(key, uncompressed_mime_entries, MIME_ENTRIES, 8, + extension = bsearch(&key, uncompressed_mime_entries, MIME_ENTRIES, 8, compare_mime_entry); if (LIKELY(extension)) return mime_types[(extension - uncompressed_mime_entries) / 8]; From 516b71d28e02af4c3ebbfc60345efb89514fa732 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Sun, 16 Jan 2022 14:39:42 -0800 Subject: [PATCH 1871/2505] Only send 100-continue response if we could allocate memory --- src/lib/lwan-request.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 185543dc4..a67bb00ae 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1206,6 +1206,11 @@ static int read_body_data(struct lwan_request *request) if (status != HTTP_PARTIAL_CONTENT) return -(int)status; + new_buffer = + alloc_body_buffer(request->conn->coro, total + 1, allow_temp_file); + if (UNLIKELY(!new_buffer)) + return -HTTP_INTERNAL_ERROR; + if (!(request->flags & REQUEST_IS_HTTP_1_0)) { /* §8.2.3 https://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html */ const char *expect = lwan_request_get_header(request, "Expect"); @@ -1217,11 +1222,6 @@ static int read_body_data(struct lwan_request *request) } } - new_buffer = - alloc_body_buffer(request->conn->coro, total + 1, allow_temp_file); - if (UNLIKELY(!new_buffer)) - return -HTTP_INTERNAL_ERROR; - helper->body_data.value = new_buffer; helper->body_data.len = total; if (have) { From bf29dc1a5db34b47a94135254850401818c61d33 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Sun, 16 Jan 2022 14:40:16 -0800 Subject: [PATCH 1872/2505] Make AES and GCM contexts const while setting up TLS keys --- src/lib/lwan-thread.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index c03d150db..82d792872 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -157,8 +157,8 @@ lwan_setup_tls_keys(int fd, const mbedtls_ssl_context *ssl, int rx_or_tx) .cipher_type = TLS_CIPHER_AES_GCM_128}, }; const unsigned char *salt, *iv, *rec_seq; - mbedtls_gcm_context *gcm_ctx; - mbedtls_aes_context *aes_ctx; + const mbedtls_gcm_context *gcm_ctx; + const mbedtls_aes_context *aes_ctx; switch (rx_or_tx) { case TLS_RX: From 307d8003903ee3f4b106696161793f115c3efb3c Mon Sep 17 00:00:00 2001 From: L Pereira Date: Sun, 16 Jan 2022 14:41:20 -0800 Subject: [PATCH 1873/2505] Remove one branch per client being accepted --- src/lib/lwan-thread.c | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 82d792872..37ba2a885 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -717,6 +717,18 @@ static bool accept_waiting_clients(const struct lwan_thread *t, const uint32_t read_events = conn_flags_to_epoll_events(CONN_EVENTS_READ); struct lwan_connection *conns = t->lwan->conns; int listen_fd = (int)(intptr_t)(listen_socket - conns); + enum lwan_connection_flags new_conn_flags = 0; + +#if defined(HAVE_MBEDTLS) + if (listen_socket->flags & CONN_LISTENER_HTTPS) { + assert(listen_fd == t->tls_listen_fd); + assert(!(listen_socket->flags & CONN_LISTENER_HTTP)); + new_conn_flags = CONN_NEEDS_TLS_SETUP; + } else { + assert(listen_fd == t->listen_fd); + assert(listen_socket->flags & CONN_LISTENER_HTTP); + } +#endif while (true) { int fd = accept4(listen_fd, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC); @@ -726,16 +738,7 @@ static bool accept_waiting_clients(const struct lwan_thread *t, struct epoll_event ev = {.data.ptr = conn, .events = read_events}; int r; -#if defined(HAVE_MBEDTLS) - if (listen_socket->flags & CONN_LISTENER_HTTPS) { - assert(listen_fd == t->tls_listen_fd); - assert(!(listen_socket->flags & CONN_LISTENER_HTTP)); - conn->flags = CONN_NEEDS_TLS_SETUP; - } else { - assert(listen_fd == t->listen_fd); - assert(listen_socket->flags & CONN_LISTENER_HTTP); - } -#endif + conn->flags = new_conn_flags; r = epoll_ctl(conn->thread->epoll_fd, EPOLL_CTL_ADD, fd, &ev); if (UNLIKELY(r < 0)) { @@ -1087,9 +1090,10 @@ static bool lwan_init_tls(struct lwan *l) if (!l->config.ssl.cert || !l->config.ssl.key) return false; - if (!is_tls_ulp_supported()) + if (!is_tls_ulp_supported()) { lwan_status_critical( "TLS ULP not loaded. Try running `modprobe tls` as root."); + } l->tls = calloc(1, sizeof(*l->tls)); if (!l->tls) From f5f80a9fc000efd96bf9e32260f128078d180a09 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Sun, 16 Jan 2022 14:44:12 -0800 Subject: [PATCH 1874/2505] Add option to send Strict-Transport-Security headers automatically --- README.md | 8 +++++--- src/lib/lwan-response.c | 7 ++++++- src/lib/lwan-thread.c | 2 +- src/lib/lwan.c | 5 ++++- src/lib/lwan.h | 3 +++ 5 files changed, 19 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index df2bf2238..d5dfa1143 100644 --- a/README.md +++ b/README.md @@ -382,9 +382,11 @@ interfaces), an IPv6 address (if surrounded by square brackets), an IPv4 address, or a hostname. For instance, `listener localhost:9876` would listen only in the `lo` interface, port `9876`. -While a `listener` section takes no keys, a `tls_listener` section -requires two: `cert` and `key`. Each point, respectively, to the location -on disk where the TLS certificate and private key files are located. +While a `listener` section takes no keys, a `tls_listener` section requires +two: `cert` and `key` (each pointing, respectively, to the location on disk +where the TLS certificate and private key files are located) and takes an +optional boolean `hsts` key, which controls if `Strict-Transport-Security` +headers will be sent on HTTPS responses. > :magic_wand: **Tip:** To generate these keys for testing purposes, the > OpenSSL command-line tool can be used like the following: diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 86412eea6..21fdf896a 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -231,7 +231,8 @@ has_content_length(enum lwan_request_flags v) static ALWAYS_INLINE __attribute__((const)) bool has_uncommon_response_headers(enum lwan_request_flags v) { - return v & (RESPONSE_INCLUDE_REQUEST_ID | REQUEST_ALLOW_CORS | RESPONSE_CHUNKED_ENCODING); + return v & (RESPONSE_INCLUDE_REQUEST_ID | REQUEST_ALLOW_CORS | + RESPONSE_CHUNKED_ENCODING | REQUEST_WANTS_HSTS_HEADER); } size_t lwan_prepare_response_header_full( @@ -358,6 +359,10 @@ size_t lwan_prepare_response_header_full( for (int i = 60; i >= 0; i -= 4) APPEND_CHAR_NOCHECK("0123456789abcdef"[(id >> i) & 0xf]); } + if (request_flags & REQUEST_WANTS_HSTS_HEADER) { + APPEND_CONSTANT("\r\nStrict-Transport-Security: " + "max-age=31536000; includeSubdomains"); + } } APPEND_STRING_LEN(request->global_response_headers->value, diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 37ba2a885..ce75e1369 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -365,7 +365,7 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, lwan_strbuf_reset_trim(&strbuf, 2048); /* Only allow flags from config. */ - flags = request.flags & (REQUEST_PROXIED | REQUEST_ALLOW_CORS); + flags = request.flags & (REQUEST_PROXIED | REQUEST_ALLOW_CORS | REQUEST_WANTS_HSTS_HEADER); next_request = helper.next_request; } diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 96244e9c9..dc3f3ac43 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -489,6 +489,8 @@ static void parse_tls_listener(struct config *conf, const struct config_line *li lwan->config.ssl.key = strdup(line->value); if (!lwan->config.ssl.key) return lwan_status_critical("Could not copy string"); + } else if (streq(line->key, "hsts")) { + lwan->config.ssl.send_hsts_header = parse_bool(line->value, false); } else { config_error(conf, "Unexpected key: %s", line->key); } @@ -699,7 +701,8 @@ static void try_setup_from_config(struct lwan *l, l->config.request_flags = (l->config.proxy_protocol ? REQUEST_ALLOW_PROXY_REQS : 0) | - (l->config.allow_cors ? REQUEST_ALLOW_CORS : 0); + (l->config.allow_cors ? REQUEST_ALLOW_CORS : 0) | + (l->config.ssl.send_hsts_header ? REQUEST_WANTS_HSTS_HEADER : 0); } static rlim_t setup_open_file_count_limits(void) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index f0920edf5..6266c51ef 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -248,6 +248,8 @@ enum lwan_request_flags { RESPONSE_INCLUDE_REQUEST_ID = 1 << 24, REQUEST_HAS_QUERY_STRING = 1 << 25, + + REQUEST_WANTS_HSTS_HEADER = 1 << 26, }; #undef SELECT_MASK @@ -462,6 +464,7 @@ struct lwan_config { struct { char *cert; char *key; + bool send_hsts_header; } ssl; size_t max_post_data_size; From 2ad22af740f20679fadce2ec57b4e1f0387e5f9b Mon Sep 17 00:00:00 2001 From: L Pereira Date: Sun, 16 Jan 2022 14:45:23 -0800 Subject: [PATCH 1875/2505] Ensure TLS certificates and keys can be specified without configuration files --- src/lib/lwan.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index dc3f3ac43..842afa36a 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -792,6 +792,8 @@ void lwan_init_with_config(struct lwan *l, const struct lwan_config *config) memcpy(&l->config, config, sizeof(*config)); l->config.listener = dup_or_null(l->config.listener); l->config.config_file_path = dup_or_null(l->config.config_file_path); + l->config.ssl.key = dup_or_null(l->config.ssl.key); + l->config.ssl.cert = dup_or_null(l->config.ssl.cert); /* Initialize status first, as it is used by other things during * their initialization. */ @@ -860,6 +862,11 @@ void lwan_shutdown(struct lwan *l) free(l->config.error_template); free(l->config.config_file_path); + lwan_always_bzero(l->config.ssl.cert, strlen(l->config.ssl.cert)); + free(l->config.ssl.cert); + lwan_always_bzero(l->config.ssl.key, strlen(l->config.ssl.key)); + free(l->config.ssl.key); + lwan_job_thread_shutdown(); lwan_thread_shutdown(l); From b8440fbca22e0fbab1dde1e1dde49d20af3d54c0 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Sun, 16 Jan 2022 14:49:33 -0800 Subject: [PATCH 1876/2505] Store value of Host: header (if sent) This is in preparation to implementing SNI and Virtual Hosts. --- README.md | 1 + src/lib/lwan-lua.c | 12 ++++++++++++ src/lib/lwan-private.h | 2 ++ src/lib/lwan-request.c | 10 ++++++++++ src/lib/lwan.h | 2 ++ 5 files changed, 27 insertions(+) diff --git a/README.md b/README.md index d5dfa1143..cdde21119 100644 --- a/README.md +++ b/README.md @@ -522,6 +522,7 @@ information from the request, or to set the response, as seen below: - `req:request_id()` returns a string containing the request ID. - `req:request_date()` returns the date as it'll be written in the `Date` response header. - `req:is_https()` returns `true` if this request is serviced through HTTPS, `false` otherwise. + - `req:host()` returns the value of the `Host` header if present, otherwise `nil`. Handler functions may return either `nil` (in which case, a `200 OK` response is generated), or a number matching an HTTP status code. Attempting to return diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index a719f891b..eb0f453c6 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -114,6 +114,18 @@ LWAN_LUA_METHOD(path) return 1; } +LWAN_LUA_METHOD(host) +{ + const char *host = lwan_request_get_host(request); + + if (host) + lua_pushstring(L, host); + else + lua_pushnil(L); + + return 1; +} + LWAN_LUA_METHOD(query_string) { if (request->helper->query_string.len) { diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 0cd1eedea..d22ef0bb4 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -41,6 +41,8 @@ struct lwan_request_parser_helper { struct lwan_value connection; /* Connection: */ + struct lwan_value host; /* Host: */ + struct lwan_key_value_array cookies, query_params, post_params; struct { /* If-Modified-Since: */ diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index a67bb00ae..f08f6dcf7 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -570,6 +570,9 @@ static bool parse_headers(struct lwan_request_parser_helper *helper, case STR4_INT_L('I', 'f', '-', 'M'): SET_HEADER_VALUE(if_modified_since.raw, "If-Modified-Since"); break; + case STR4_INT_L('H', 'o', 's', 't'): + SET_HEADER_VALUE(host, "Host"); + break; case STR4_INT_L('R', 'a', 'n', 'g'): SET_HEADER_VALUE(range.raw, "Range"); break; @@ -1614,6 +1617,13 @@ const char *lwan_request_get_header(struct lwan_request *request, return NULL; } +const char *lwan_request_get_host(struct lwan_request *request) +{ + const struct lwan_request_parser_helper *helper = request->helper; + + return helper->host.len ? helper->host.value : NULL; +} + ALWAYS_INLINE int lwan_connection_get_fd(const struct lwan *lwan, const struct lwan_connection *conn) { diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 6266c51ef..c13313a06 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -545,6 +545,8 @@ void lwan_shutdown(struct lwan *l); const struct lwan_config *lwan_get_default_config(void); +const char *lwan_request_get_host(struct lwan_request *request); + const char * lwan_request_get_remote_address(struct lwan_request *request, char buffer LWAN_ARRAY_PARAM(INET6_ADDRSTRLEN)) From 8c712cc1013fa4de49e2e911939e7856f8b24fcc Mon Sep 17 00:00:00 2001 From: L Pereira Date: Sun, 16 Jan 2022 15:14:23 -0800 Subject: [PATCH 1877/2505] Print build-time options when running Lwan with --help --- src/bin/lwan/main.c | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/bin/lwan/main.c b/src/bin/lwan/main.c index 4eccdc95e..5c0b11d27 100644 --- a/src/bin/lwan/main.c +++ b/src/bin/lwan/main.c @@ -53,6 +53,46 @@ print_handler_info(void) } } +static void +print_build_time_configuration(void) +{ + printf("Build-time configuration:"); +#ifdef HAVE_LUA + printf(" Lua"); +#endif +#ifdef HAVE_BROTLI + printf(" Brotli"); +#endif +#ifdef HAVE_ZSTD + printf(" zstd"); +#endif +#ifdef HAVE_MBEDTLS + printf(" mbedTLS"); +#endif +#ifdef HAVE_LIBUCONTEXT + printf(" libucontext"); +#endif +#ifdef HAVE_EPOLL + printf(" epoll"); +#endif +#ifdef HAVE_KQUEUE + printf(" kqueue"); +#endif +#ifdef HAVE_SO_ATTACH_REUSEPORT_CBPF + printf(" sockopt-reuseport-CBPF"); +#endif +#ifdef HAVE_SO_INCOMING_CPU + printf(" sockopt-reuseport-incoming-cpu"); +#endif +#ifdef HAVE_VALGRIND + printf(" valgrind"); +#endif +#ifdef HAVE_SYSLOG + printf(" syslog"); +#endif + printf(".\n"); +} + static void print_help(const char *argv0, const struct lwan_config *config) { @@ -84,6 +124,8 @@ print_help(const char *argv0, const struct lwan_config *config) printf(" Use /etc/%s:\n", config_file); printf(" %s -c /etc/%s\n", argv0, config_file); printf("\n"); + print_build_time_configuration(); + printf("\n"); printf("Report bugs at .\n"); free(current_dir); From 6d9073243ea5b14d2ea3ad120018963957fedce5 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Thu, 3 Feb 2022 20:10:05 -0800 Subject: [PATCH 1878/2505] Allow deferred callbacks to be disarmed --- src/lib/lwan-coro.c | 35 +++++++++++++++++++++++++++++++---- src/lib/lwan-coro.h | 13 ++++++++----- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 170eacc57..5e3366a3f 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -360,22 +360,47 @@ void coro_free(struct coro *coro) free(coro); } -ALWAYS_INLINE void coro_defer(struct coro *coro, defer1_func func, void *data) +static void disarmed_defer(void *data __attribute__((unused))) +{ +} + +void coro_defer_disarm(struct coro *coro, struct coro_defer *defer) +{ + const size_t num_defers = coro_defer_array_len(&coro->defer); + + assert(num_defers != 0 && defer != NULL); + + if (defer == coro_defer_array_get_elem(&coro->defer, num_defers - 1)) { + /* If we're disarming the last defer we armed, there's no need to waste + * space of a deferred callback to an empty function like + * disarmed_defer(). */ + struct lwan_array *defer_base = (struct lwan_array *)&coro->defer; + defer_base->elements--; + } else { + defer->one.func = disarmed_defer; + defer->has_two_args = false; + } +} + +ALWAYS_INLINE struct coro_defer * +coro_defer(struct coro *coro, defer1_func func, void *data) { struct coro_defer *defer = coro_defer_array_append(&coro->defer); if (UNLIKELY(!defer)) { lwan_status_error("Could not add new deferred function for coro %p", coro); - return; + return NULL; } defer->one.func = func; defer->one.data = data; defer->has_two_args = false; + + return defer; } -ALWAYS_INLINE void +ALWAYS_INLINE struct coro_defer * coro_defer2(struct coro *coro, defer2_func func, void *data1, void *data2) { struct coro_defer *defer = coro_defer_array_append(&coro->defer); @@ -383,13 +408,15 @@ coro_defer2(struct coro *coro, defer2_func func, void *data1, void *data2) if (UNLIKELY(!defer)) { lwan_status_error("Could not add new deferred function for coro %p", coro); - return; + return NULL; } defer->two.func = func; defer->two.data1 = data1; defer->two.data2 = data2; defer->has_two_args = true; + + return defer; } void *coro_malloc_full(struct coro *coro, diff --git a/src/lib/lwan-coro.h b/src/lib/lwan-coro.h index 530928958..ecb2c7c51 100644 --- a/src/lib/lwan-coro.h +++ b/src/lib/lwan-coro.h @@ -33,6 +33,7 @@ typedef libucontext_ucontext_t coro_context; #endif struct coro; +struct coro_defer; typedef int (*coro_function_t)(struct coro *coro, void *data); @@ -50,11 +51,13 @@ int64_t coro_resume(struct coro *coro); int64_t coro_resume_value(struct coro *coro, int64_t value); int64_t coro_yield(struct coro *coro, int64_t value); -void coro_defer(struct coro *coro, void (*func)(void *data), void *data); -void coro_defer2(struct coro *coro, - void (*func)(void *data1, void *data2), - void *data1, - void *data2); +struct coro_defer * +coro_defer(struct coro *coro, void (*func)(void *data), void *data); +struct coro_defer *coro_defer2(struct coro *coro, + void (*func)(void *data1, void *data2), + void *data1, + void *data2); +void coro_defer_disarm(struct coro *coro, struct coro_defer *defer); void coro_deferred_run(struct coro *coro, size_t generation); size_t coro_deferred_get_generation(const struct coro *coro); From 27c3d4ba22b7cfe62d963fe4516cc92cef59bb7e Mon Sep 17 00:00:00 2001 From: L Pereira Date: Thu, 3 Feb 2022 20:10:39 -0800 Subject: [PATCH 1879/2505] Disarm deferred callback to remove timer after sleeping --- src/lib/lwan-request.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index f08f6dcf7..4076b3ff4 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1688,6 +1688,7 @@ void lwan_request_sleep(struct lwan_request *request, uint64_t ms) struct lwan_connection *conn = request->conn; struct timeouts *wheel = conn->thread->wheel; struct timespec now; + struct coro_defer *defer = NULL; /* We need to update the timer wheel right now because * a request might have requested to sleep a long time @@ -1701,11 +1702,16 @@ void lwan_request_sleep(struct lwan_request *request, uint64_t ms) timeouts_add(wheel, &request->timeout, ms); if (!(conn->flags & CONN_HAS_REMOVE_SLEEP_DEFER)) { - coro_defer2(conn->coro, remove_sleep, wheel, &request->timeout); + defer = coro_defer2(conn->coro, remove_sleep, wheel, &request->timeout); conn->flags |= CONN_HAS_REMOVE_SLEEP_DEFER; } coro_yield(conn->coro, CONN_CORO_SUSPEND); + + if (defer) { + coro_defer_disarm(conn->coro, defer); + remove_sleep(wheel, &request->timeout); + } } ALWAYS_INLINE int From f7721506b08756e38769c2e443e1af4a700d93fd Mon Sep 17 00:00:00 2001 From: L Pereira Date: Thu, 3 Feb 2022 20:11:37 -0800 Subject: [PATCH 1880/2505] Simplify TLS connection flags --- src/lib/lwan-thread.c | 40 +++++++++++++++++++--------------------- src/lib/lwan.h | 9 ++------- 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index ce75e1369..1ae6bedad 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -213,16 +213,11 @@ static void lwan_status_mbedtls_error(int error_code, const char *fmt, ...) va_end(ap); } -static void lwan_setup_tls_free_ssl_context(void *data1, void *data2) +static void lwan_setup_tls_free_ssl_context(void *data) { - struct lwan_connection *conn = data1; + mbedtls_ssl_context *ssl = data; - if (UNLIKELY(conn->flags & CONN_NEEDS_TLS_SETUP)) { - mbedtls_ssl_context *ssl = data2; - - mbedtls_ssl_free(ssl); - conn->flags &= ~CONN_NEEDS_TLS_SETUP; - } + mbedtls_ssl_free(ssl); } static bool lwan_setup_tls(const struct lwan *l, struct lwan_connection *conn) @@ -243,7 +238,13 @@ static bool lwan_setup_tls(const struct lwan *l, struct lwan_connection *conn) * destroy this coro (e.g. on connection hangup) before we have the * opportunity to free the SSL context. Defer this call for these * cases. */ - coro_defer2(conn->coro, lwan_setup_tls_free_ssl_context, conn, &ssl); + struct coro_defer *defer = + coro_defer(conn->coro, lwan_setup_tls_free_ssl_context, &ssl); + + if (UNLIKELY(!defer)) { + lwan_status_error("Could not defer cleanup of the TLS context"); + return false; + } int fd = lwan_connection_get_fd(l, conn); /* FIXME: This is only required for the handshake; this uses read() and @@ -281,9 +282,8 @@ static bool lwan_setup_tls(const struct lwan *l, struct lwan_connection *conn) retval = true; fail: + coro_defer_disarm(conn->coro, defer); mbedtls_ssl_free(&ssl); - - conn->flags &= ~CONN_NEEDS_TLS_SETUP; return retval; } #endif @@ -312,16 +312,15 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, assert(init_gen == coro_deferred_get_generation(coro)); #if defined(HAVE_MBEDTLS) - if (conn->flags & CONN_NEEDS_TLS_SETUP) { + if (conn->flags & CONN_TLS) { if (UNLIKELY(!lwan_setup_tls(lwan, conn))) { - coro_yield(coro, CONN_CORO_ABORT); + coro_yield(conn->coro, CONN_CORO_ABORT); __builtin_unreachable(); } - - conn->flags |= CONN_TLS; } +#else + assert(!(conn->flags & CONN_TLS)); #endif - assert(!(conn->flags & CONN_NEEDS_TLS_SETUP)); while (true) { struct lwan_request_parser_helper helper = { @@ -429,8 +428,7 @@ static void update_epoll_flags(int fd, conn->flags &= and_mask[yield_result]; assert(!(conn->flags & (CONN_LISTENER_HTTP | CONN_LISTENER_HTTPS))); - assert((conn->flags & (CONN_TLS | CONN_NEEDS_TLS_SETUP)) == - (prev_flags & (CONN_TLS | CONN_NEEDS_TLS_SETUP))); + assert((conn->flags & CONN_TLS) == (prev_flags & CONN_TLS)); if (conn->flags == prev_flags) return; @@ -562,7 +560,7 @@ send_last_response_without_coro(const struct lwan *l, { int fd = lwan_connection_get_fd(l, conn); - if (conn->flags & CONN_NEEDS_TLS_SETUP) { + if (conn->flags & CONN_TLS) { /* There's nothing that can be done here if a client is expecting a * TLS connection: the TLS handshake requires a coroutine as it * might yield. (In addition, the TLS handshake might allocate @@ -609,7 +607,7 @@ static ALWAYS_INLINE bool spawn_coro(struct lwan_connection *conn, { struct lwan_thread *t = conn->thread; #if defined(HAVE_MBEDTLS) - const enum lwan_connection_flags flags_to_keep = conn->flags & CONN_NEEDS_TLS_SETUP; + const enum lwan_connection_flags flags_to_keep = conn->flags & CONN_TLS; #else const enum lwan_connection_flags flags_to_keep = 0; #endif @@ -723,7 +721,7 @@ static bool accept_waiting_clients(const struct lwan_thread *t, if (listen_socket->flags & CONN_LISTENER_HTTPS) { assert(listen_fd == t->tls_listen_fd); assert(!(listen_socket->flags & CONN_LISTENER_HTTP)); - new_conn_flags = CONN_NEEDS_TLS_SETUP; + new_conn_flags = CONN_TLS; } else { assert(listen_fd == t->listen_fd); assert(listen_socket->flags & CONN_LISTENER_HTTP); diff --git a/src/lib/lwan.h b/src/lib/lwan.h index c13313a06..8b803aaef 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -288,13 +288,8 @@ enum lwan_connection_flags { CONN_LISTENER_HTTP = 1 << 10, CONN_LISTENER_HTTPS = 1 << 11, - /* Set on file descriptors accepted by listeners with the - * CONN_LISTENER_HTTPS flag, and unset right after the handshake has been - * completed (when CONN_TLS is then set.) */ - CONN_NEEDS_TLS_SETUP = 1 << 12, - - /* Used mostly for the Lua and Rewrite modules */ - CONN_TLS = 1 << 14, + /* Is this a TLS connection? */ + CONN_TLS = 1 << 12, }; enum lwan_connection_coro_yield { From ecadc9af35b18cf0d5ff046b897500f8131bd2c7 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Thu, 3 Feb 2022 20:15:16 -0800 Subject: [PATCH 1881/2505] Minor tweaks in file serving module --- src/lib/lwan-mod-serve-files.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 1a55a4c69..1f3c8c31e 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -475,14 +475,10 @@ static void zstd_value(const struct lwan_value *uncompressed, static void try_readahead(const struct serve_files_priv *priv, int fd, size_t size) { - if (size > priv->read_ahead) - size = priv->read_ahead; - - if (LIKELY(size)) - lwan_readahead_queue(fd, 0, size); + lwan_readahead_queue(fd, 0, LWAN_MIN(size, priv->read_ahead)); } -static bool is_world_readable(mode_t mode) +static inline bool is_world_readable(mode_t mode) { const mode_t world_readable = S_IRUSR | S_IRGRP | S_IROTH; From e653cd386d30f1cf8b964ae393443c0ccb046650 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Thu, 3 Feb 2022 20:15:34 -0800 Subject: [PATCH 1882/2505] Be more specific if setting kTLS keys fail --- src/lib/lwan-thread.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 1ae6bedad..9bfd1e42a 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -184,7 +184,9 @@ lwan_setup_tls_keys(int fd, const mbedtls_ssl_context *ssl, int rx_or_tx) memcpy(info.salt, salt, TLS_CIPHER_AES_GCM_128_SALT_SIZE); if (UNLIKELY(setsockopt(fd, SOL_TLS, rx_or_tx, &info, sizeof(info)) < 0)) { - lwan_status_perror("Could not set kTLS keys for fd %d", fd); + lwan_status_perror("Could not set %s kTLS keys for fd %d", + rx_or_tx == TLS_TX ? "transmission" : "reception", + fd); lwan_always_bzero(&info, sizeof(info)); return false; } From 4fd973aa4bd4ccbd389f3f2de47112ef7cf2a619 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Thu, 3 Feb 2022 20:17:41 -0800 Subject: [PATCH 1883/2505] Consider popularity of HTTP verbs when parsing them --- src/lib/lwan-mod-lua.c | 2 +- src/lib/lwan-mod-rewrite.c | 2 +- src/lib/lwan-request.c | 17 +++++++++-------- src/lib/lwan.h | 20 ++++++++++---------- 4 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/lib/lwan-mod-lua.c b/src/lib/lwan-mod-lua.c index f0d761371..b8503a36b 100644 --- a/src/lib/lwan-mod-lua.c +++ b/src/lib/lwan-mod-lua.c @@ -103,7 +103,7 @@ static ALWAYS_INLINE struct lwan_value get_handle_prefix(struct lwan_request *request) { #define ENTRY(s) {.value = s, .len = sizeof(s) - 1} -#define GEN_TABLE_ENTRY(upper, lower, mask, constant) \ +#define GEN_TABLE_ENTRY(upper, lower, mask, constant, probability) \ [REQUEST_METHOD_##upper] = ENTRY("handle_" #lower "_"), static const struct lwan_value method2name[REQUEST_METHOD_MASK] = { diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index 3149c7e65..ac3a7a2d1 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -719,7 +719,7 @@ static void parse_condition_accept_encoding(struct pattern *pattern, static bool get_method_from_string(struct pattern *pattern, const char *string) { -#define GENERATE_CMP(upper, lower, mask, constant) \ +#define GENERATE_CMP(upper, lower, mask, constant, probability) \ if (!strcasecmp(string, #upper)) { \ pattern->condition.request_flags |= (mask); \ return true; \ diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 4076b3ff4..e3897d0e7 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -245,16 +245,17 @@ static char *parse_proxy_protocol_v2(struct lwan_request *request, char *buffer) static ALWAYS_INLINE char *identify_http_method(struct lwan_request *request, char *buffer) { -#define GENERATE_CASE_STMT(upper, lower, mask, constant) \ - case constant: \ - request->flags |= (mask); \ - return buffer + sizeof(#upper); + const uint32_t first_four = string_as_uint32(buffer); - STRING_SWITCH (buffer) { - FOR_EACH_REQUEST_METHOD(GENERATE_CASE_STMT) +#define GENERATE_IF(upper, lower, mask, constant, probability) \ + if (__builtin_expect_with_probability(first_four, (constant), probability)) { \ + request->flags |= (mask); \ + return buffer + sizeof(#upper); \ } -#undef GENERATE_CASE_STMT + FOR_EACH_REQUEST_METHOD(GENERATE_IF) + +#undef GENERATE_IF return NULL; } @@ -1434,7 +1435,7 @@ static bool handle_rewrite(struct lwan_request *request) const char *lwan_request_get_method_str(const struct lwan_request *request) { -#define GENERATE_CASE_STMT(upper, lower, mask, constant) \ +#define GENERATE_CASE_STMT(upper, lower, mask, constant, probability) \ case REQUEST_METHOD_##upper: \ return #upper; diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 8b803aaef..e1c3fd2a5 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -202,16 +202,16 @@ enum lwan_handler_flags { /* 1<<0 set: response has body; see has_response_body() in lwan-response.c */ /* 1<<3 set: request has body; see request_has_body() in lwan-request.c */ -#define FOR_EACH_REQUEST_METHOD(X) \ - X(GET, get, (1 << 0), STR4_INT('G', 'E', 'T', ' ')) \ - X(POST, post, (1 << 3 | 1 << 1 | 1 << 0), STR4_INT('P', 'O', 'S', 'T')) \ - X(HEAD, head, (1 << 1), STR4_INT('H', 'E', 'A', 'D')) \ - X(OPTIONS, options, (1 << 2), STR4_INT('O', 'P', 'T', 'I')) \ - X(DELETE, delete, (1 << 1 | 1 << 2), STR4_INT('D', 'E', 'L', 'E')) \ - X(PUT, put, (1 << 3 | 1 << 2 | 1 << 0), STR4_INT('P', 'U', 'T', ' ')) - -#define SELECT_MASK(upper, lower, mask, constant) mask | -#define GENERATE_ENUM_ITEM(upper, lower, mask, constant) REQUEST_METHOD_##upper = mask, +#define FOR_EACH_REQUEST_METHOD(X) \ + X(GET, get, (1 << 0), (STR4_INT('G', 'E', 'T', ' ')), 0.6) \ + X(POST, post, (1 << 3 | 1 << 1 | 1 << 0), (STR4_INT('P', 'O', 'S', 'T')), 0.2)\ + X(HEAD, head, (1 << 1), (STR4_INT('H', 'E', 'A', 'D')), 0.2) \ + X(OPTIONS, options, (1 << 2), (STR4_INT('O', 'P', 'T', 'I')), 0.1) \ + X(DELETE, delete, (1 << 1 | 1 << 2), (STR4_INT('D', 'E', 'L', 'E')), 0.1) \ + X(PUT, put, (1 << 3 | 1 << 2 | 1 << 0), (STR4_INT('P', 'U', 'T', ' ')), 0.1) + +#define SELECT_MASK(upper, lower, mask, constant, probability) mask | +#define GENERATE_ENUM_ITEM(upper, lower, mask, constant, probability) REQUEST_METHOD_##upper = mask, enum lwan_request_flags { REQUEST_ALL_FLAGS = -1, From 0fc0e5211bea14cd214c632aa2affadfe4f9afd0 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Thu, 3 Feb 2022 20:18:41 -0800 Subject: [PATCH 1884/2505] Remove branches from parse_2_digit_num_no_end_check() --- src/lib/lwan-time.c | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/lib/lwan-time.c b/src/lib/lwan-time.c index ab77de60b..2a9b03f93 100644 --- a/src/lib/lwan-time.c +++ b/src/lib/lwan-time.c @@ -21,24 +21,20 @@ #include #include #include +#include #include "lwan-private.h" #include "int-to-str.h" static int parse_2_digit_num_no_end_check(const char *str, unsigned int max) { - static const unsigned int tens[] = {0, 10, 20, 30, 40, 50, 60, 70, 80, 90}; - - if (UNLIKELY(!lwan_char_isdigit(str[0]))) - return -EINVAL; - if (UNLIKELY(!lwan_char_isdigit(str[1]))) - return -EINVAL; - - unsigned int val = tens[str[0] - '0'] + (unsigned int)(str[1] - '0'); - if (UNLIKELY(val > max)) - return -EINVAL; - - return (int)val; + static const unsigned int tens[16] = { + 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, [11 ... 15] = UINT_MAX, + }; + const unsigned int n_tens = (unsigned int)(str[0] - '0'); + const unsigned int n_ones = (unsigned int)(str[1] - '0'); + const unsigned int val = tens[n_tens & 15] + n_ones; + return UNLIKELY(val > max) ? -EINVAL : (int)val; } static int From 19228589aaf8d2bfb414e04e6b65c2bd1bd1a6ae Mon Sep 17 00:00:00 2001 From: L Pereira Date: Thu, 3 Feb 2022 20:52:56 -0800 Subject: [PATCH 1885/2505] Use assertions as optimization hints --- src/lib/missing/assert.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/lib/missing/assert.h b/src/lib/missing/assert.h index 49b8be9f3..e99df3dd1 100644 --- a/src/lib/missing/assert.h +++ b/src/lib/missing/assert.h @@ -29,4 +29,18 @@ # define static_assert(expr, msg) #endif +/* Use assertions as optimization hints */ +#ifndef NDEBUG +#undef assert +#ifdef __clang__ +#define assert(expr) __builtin_assume(expr) +#else +#define assert(expr) \ + do { \ + if (!(expr)) \ + __builtin_unreachable(); \ + } while (0) +#endif +#endif + #endif /* MISSING_ASSERT_H */ From f17940a98388443249f81e67934e745a62427ebc Mon Sep 17 00:00:00 2001 From: L Pereira Date: Fri, 4 Feb 2022 18:21:25 -0800 Subject: [PATCH 1886/2505] Just use lwan_always_bzero() instead of fourty_two_and_free() --- src/lib/lwan-http-authorize.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/lib/lwan-http-authorize.c b/src/lib/lwan-http-authorize.c index 61034a794..6864640e8 100644 --- a/src/lib/lwan-http-authorize.c +++ b/src/lib/lwan-http-authorize.c @@ -36,15 +36,10 @@ struct realm_password_file_t { static struct cache *realm_password_cache = NULL; -static void fourty_two_and_free(void *str) +static void zero_and_free(void *str) { - if (LIKELY(str)) { - char *s = str; - while (*s) - *s++ = 42; - LWAN_NO_DISCARD(str); - free(str); - } + if (LIKELY(str)) + lwan_always_bzero(str, strlen(str)); } static struct cache_entry * @@ -57,7 +52,7 @@ create_realm_file(const char *key, void *context __attribute__((unused))) if (UNLIKELY(!rpf)) return NULL; - rpf->entries = hash_str_new(fourty_two_and_free, fourty_two_and_free); + rpf->entries = hash_str_new(zero_and_free, zero_and_free); if (UNLIKELY(!rpf->entries)) goto error_no_close; From 6847954207ab9d9932573baa0f4eac7e08e964b8 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Fri, 4 Feb 2022 18:21:51 -0800 Subject: [PATCH 1887/2505] No need to call writev() in lwan_writev(), just use sendmsg() --- src/lib/lwan-io-wrappers.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index 4d609b049..7ed955de5 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -47,15 +47,11 @@ lwan_writev(struct lwan_request *request, struct iovec *iov, int iov_count) return lwan_send(request, vec->iov_base, vec->iov_len, flags); } - if (flags) { - struct msghdr hdr = { - .msg_iov = iov + curr_iov, - .msg_iovlen = (size_t)remaining_len, - }; - written = sendmsg(request->fd, &hdr, flags); - } else { - written = writev(request->fd, iov + curr_iov, remaining_len); - } + struct msghdr hdr = { + .msg_iov = iov + curr_iov, + .msg_iovlen = (size_t)remaining_len, + }; + written = sendmsg(request->fd, &hdr, flags); if (UNLIKELY(written < 0)) { /* FIXME: Consider short writes as another try as well? */ From 43c35d4cdafe784538d27a8c514d568f6a141a3f Mon Sep 17 00:00:00 2001 From: L Pereira Date: Sun, 6 Feb 2022 12:24:03 -0800 Subject: [PATCH 1888/2505] Fix HTTP method identification --- src/lib/lwan-request.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index e3897d0e7..7211b3d17 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -247,10 +247,11 @@ static ALWAYS_INLINE char *identify_http_method(struct lwan_request *request, { const uint32_t first_four = string_as_uint32(buffer); -#define GENERATE_IF(upper, lower, mask, constant, probability) \ - if (__builtin_expect_with_probability(first_four, (constant), probability)) { \ - request->flags |= (mask); \ - return buffer + sizeof(#upper); \ +#define GENERATE_IF(upper, lower, mask, constant, probability) \ + if (__builtin_expect_with_probability(first_four == (constant), 1, \ + probability)) { \ + request->flags |= (mask); \ + return buffer + sizeof(#upper); \ } FOR_EACH_REQUEST_METHOD(GENERATE_IF) From ffcc855aace042e2c67ad9e1d2a52478291520b8 Mon Sep 17 00:00:00 2001 From: pontscho Date: Thu, 20 Jan 2022 17:58:50 +0100 Subject: [PATCH 1889/2505] making brotli/zstd optional (enabled by default) --- CMakeLists.txt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0ab365e8f..2a201674e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,7 +58,10 @@ else () set(HAVE_LUA 1) endif () -pkg_check_modules(BROTLI libbrotlienc libbrotlidec libbrotlicommon) +option(ENABLE_BROTLI "Enable support for brotli" "ON") +if (ENABLE_BROTLI) + pkg_check_modules(BROTLI libbrotlienc libbrotlidec libbrotlicommon) +endif () if (BROTLI_FOUND) list(APPEND ADDITIONAL_LIBRARIES "${BROTLI_LDFLAGS}") if (NOT BROTLI_INCLUDE_DIRS STREQUAL "") @@ -67,7 +70,10 @@ if (BROTLI_FOUND) set(HAVE_BROTLI 1) endif () -pkg_check_modules(ZSTD libzstd) +option(ENABLE_ZSTD "Enable support for zstd" "ON") +if (ENABLE_ZSTD) + pkg_check_modules(ZSTD libzstd) +endif () if (ZSTD_FOUND) list(APPEND ADDITIONAL_LIBRARIES "${ZSTD_LDFLAGS}") if (NOT ZSTD_INCLUDE_DIRS STREQUAL "") From 0282898c8a8a68cd992210f7a3d51dcc6ecf646e Mon Sep 17 00:00:00 2001 From: pontscho Date: Thu, 20 Jan 2022 17:46:06 +0100 Subject: [PATCH 1890/2505] enabling luajit 2.1 --- CMakeLists.txt | 4 +++- src/lib/lwan-lua.c | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a201674e..70bf6b4ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,9 +41,11 @@ endif () foreach (pc_file luajit lua lua51 lua5.1 lua-5.1) if (${pc_file} STREQUAL "luajit") - pkg_check_modules(LUA luajit>=2.0 luajit<=2.0.999) + pkg_check_modules(LUA luajit>=2.0 luajit<2.2) + add_definitions(-DLUAVMTYPE=luajit) else () pkg_check_modules(LUA ${pc_file}>=5.1.0 ${pc_file}<=5.1.999) + add_definitions(-DLUAVMTYPE=lua) endif () if (LUA_FOUND) list(APPEND ADDITIONAL_LIBRARIES "${LUA_LDFLAGS}") diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index eb0f453c6..fef463f27 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -31,6 +31,10 @@ #include "lwan-lua.h" +#if LUAVMTYPE == luajit +#define luaL_reg luaL_Reg +#endif + static const char *request_metatable_name = "Lwan.Request"; ALWAYS_INLINE struct lwan_request *lwan_lua_get_request_from_userdata(lua_State *L) From d2c797a2a2cd468aa2c9e4c1bc8b0f02e40f5980 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Sun, 6 Feb 2022 12:30:53 -0800 Subject: [PATCH 1891/2505] Document -DENABLE_BROTLI and -DENABLE_ZSTD --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index cdde21119..c3a6db8fd 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,9 @@ The build system will look for these libraries and enable/link if available. - [Lua 5.1](http://www.lua.org) or [LuaJIT 2.0](http://luajit.org) - [Valgrind](http://valgrind.org) - [Brotli](https://github.com/google/brotli) + - Can be disabled by passing `-DENABLE_BROTLI=NO` - [ZSTD](https://github.com/facebook/zstd) + - Can be disabled by passing `-DENABLE_ZSTD=NO` - On Linux builds, if `-DENABLE_TLS=ON` (default) is passed: - [mbedTLS](https://github.com/ARMmbed/mbedtls) - Alternative memory allocators can be used by passing `-DUSE_ALTERNATIVE_MALLOC` to CMake with the following values: From 82769a7b293827a1e326a625acb78ac7f4b622e6 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Sun, 6 Feb 2022 12:35:58 -0800 Subject: [PATCH 1892/2505] Pass LuaJIT presence through define in lwan-build-config.h --- CMakeLists.txt | 3 +-- src/cmake/lwan-build-config.h.cmake | 2 +- src/lib/lwan-lua.c | 3 ++- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 70bf6b4ff..ef108420e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,10 +42,9 @@ endif () foreach (pc_file luajit lua lua51 lua5.1 lua-5.1) if (${pc_file} STREQUAL "luajit") pkg_check_modules(LUA luajit>=2.0 luajit<2.2) - add_definitions(-DLUAVMTYPE=luajit) + set(HAVE_LUA_JIT 1) else () pkg_check_modules(LUA ${pc_file}>=5.1.0 ${pc_file}<=5.1.999) - add_definitions(-DLUAVMTYPE=lua) endif () if (LUA_FOUND) list(APPEND ADDITIONAL_LIBRARIES "${LUA_LDFLAGS}") diff --git a/src/cmake/lwan-build-config.h.cmake b/src/cmake/lwan-build-config.h.cmake index acb8b51a9..7baf895c5 100644 --- a/src/cmake/lwan-build-config.h.cmake +++ b/src/cmake/lwan-build-config.h.cmake @@ -60,6 +60,7 @@ /* Libraries */ #cmakedefine HAVE_LUA +#cmakedefine HAVE_LUA_JIT #cmakedefine HAVE_BROTLI #cmakedefine HAVE_ZSTD #cmakedefine HAVE_LIBUCONTEXT @@ -67,4 +68,3 @@ /* Valgrind support for coroutines */ #cmakedefine HAVE_VALGRIND - diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index fef463f27..03550f325 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -27,11 +27,12 @@ #include #include +#include "lwan-build-config.h" #include "lwan-private.h" #include "lwan-lua.h" -#if LUAVMTYPE == luajit +#if defined(HAVE_LUA_JIT) #define luaL_reg luaL_Reg #endif From 43d9c64199269a7b87ee9f234fa7770469c94235 Mon Sep 17 00:00:00 2001 From: pontscho Date: Mon, 7 Feb 2022 19:45:14 +0100 Subject: [PATCH 1893/2505] Remove typedef from epoll.h on macos --- src/lib/missing/sys/epoll.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/missing/sys/epoll.h b/src/lib/missing/sys/epoll.h index 585f3553b..7e0fded47 100644 --- a/src/lib/missing/sys/epoll.h +++ b/src/lib/missing/sys/epoll.h @@ -22,7 +22,7 @@ #pragma once #include -enum { +enum epoll_event_flag { EPOLLIN = 1 << 0, EPOLLOUT = 1 << 1, EPOLLONESHOT = 1 << 2, @@ -30,11 +30,11 @@ enum { EPOLLERR = 1 << 4, EPOLLET = 1 << 5, EPOLLHUP = EPOLLRDHUP -} epoll_event_flag; +}; -enum { EPOLL_CTL_ADD, EPOLL_CTL_MOD, EPOLL_CTL_DEL } epoll_op; +enum epoll_op { EPOLL_CTL_ADD, EPOLL_CTL_MOD, EPOLL_CTL_DEL }; -enum { EPOLL_CLOEXEC = 1 << 0 } epoll_create_flags; +enum epoll_create_flags { EPOLL_CLOEXEC = 1 << 0 }; struct epoll_event { uint32_t events; From 81b4b5494ae6528b7884f935c2822b94d358b74f Mon Sep 17 00:00:00 2001 From: pontscho Date: Mon, 7 Feb 2022 19:30:49 +0100 Subject: [PATCH 1894/2505] Increasing header buffer size because 512 bytes isn't enough for more complex http headers for example like cookies that coding something. --- src/lib/lwan-private.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index d22ef0bb4..53ff4a36c 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -63,7 +63,7 @@ struct lwan_request_parser_helper { }; #define DEFAULT_BUFFER_SIZE 4096 -#define DEFAULT_HEADERS_SIZE 512 +#define DEFAULT_HEADERS_SIZE 2048 #define N_HEADER_START 64 From cfc06b544d38f387956b6a80908f6a5ada14e608 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Fri, 18 Feb 2022 23:20:30 -0800 Subject: [PATCH 1895/2505] Be more explicit if isn't found when enabling kTLS --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index ef108420e..8ebac1f97 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -103,6 +103,8 @@ if (ENABLE_TLS) else () message(STATUS "mbedTLS not found: not building with TLS support") endif () + else () + message(STATUS " not found: not building with TLS support") endif () endif () From 2185b854a4217e1c81c058dbdef19a8c2903a072 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Fri, 18 Feb 2022 23:21:13 -0800 Subject: [PATCH 1896/2505] Add coro_defer_fire_and_disarm() --- src/lib/lwan-coro.c | 13 +++++++++++++ src/lib/lwan-coro.h | 1 + 2 files changed, 14 insertions(+) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 5e3366a3f..48b96d06c 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -382,6 +382,19 @@ void coro_defer_disarm(struct coro *coro, struct coro_defer *defer) } } +void coro_defer_fire_and_disarm(struct coro *coro, struct coro_defer *defer) +{ + assert(coro); + assert(defer); + + if (defer->has_two_args) + defer->two.func(defer->two.data1, defer->two.data2); + else + defer->one.func(defer->one.data); + + return coro_defer_disarm(coro, defer); +} + ALWAYS_INLINE struct coro_defer * coro_defer(struct coro *coro, defer1_func func, void *data) { diff --git a/src/lib/lwan-coro.h b/src/lib/lwan-coro.h index ecb2c7c51..bbe52698f 100644 --- a/src/lib/lwan-coro.h +++ b/src/lib/lwan-coro.h @@ -58,6 +58,7 @@ struct coro_defer *coro_defer2(struct coro *coro, void *data1, void *data2); void coro_defer_disarm(struct coro *coro, struct coro_defer *defer); +void coro_defer_fire_and_disarm(struct coro *coro, struct coro_defer *defer); void coro_deferred_run(struct coro *coro, size_t generation); size_t coro_deferred_get_generation(const struct coro *coro); From f6f28b020676260f3c214278447347dc07209c2a Mon Sep 17 00:00:00 2001 From: L Pereira Date: Fri, 18 Feb 2022 23:22:25 -0800 Subject: [PATCH 1897/2505] Simplify disarming of deferred statements in some places --- src/lib/lwan-request.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 7211b3d17..5720dbaa4 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -322,14 +322,13 @@ static void parse_key_values(struct lwan_request *request, struct lwan_key_value *kv; char *ptr = helper_value->value; const char *end = helper_value->value + helper_value->len; + struct coro_defer *reset_defer; if (!helper_value->len) return; lwan_key_value_array_init(array); - /* Calling lwan_key_value_array_reset() twice is fine, so even if 'goto - * error' is executed in this function, nothing bad should happen. */ - coro_defer(request->conn->coro, reset_key_value_array, array); + reset_defer = coro_defer(request->conn->coro, reset_key_value_array, array); do { char *key, *value; @@ -368,7 +367,7 @@ static void parse_key_values(struct lwan_request *request, return; error: - lwan_key_value_array_reset(array); + coro_defer_fire_and_disarm(request->conn->coro, reset_defer); } static ssize_t @@ -1710,10 +1709,8 @@ void lwan_request_sleep(struct lwan_request *request, uint64_t ms) coro_yield(conn->coro, CONN_CORO_SUSPEND); - if (defer) { - coro_defer_disarm(conn->coro, defer); - remove_sleep(wheel, &request->timeout); - } + if (defer) + coro_defer_fire_and_disarm(conn->coro, defer); } ALWAYS_INLINE int From 1cca8e5bf34d02ad77e4eca6d5169241bf2259a7 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Fri, 18 Feb 2022 23:23:25 -0800 Subject: [PATCH 1898/2505] Actually free memory in zero_and_free() It was being freed before f17940a and I forgot to do it when moving it to use always_bzero(). --- src/lib/lwan-http-authorize.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-http-authorize.c b/src/lib/lwan-http-authorize.c index 6864640e8..883ed2a27 100644 --- a/src/lib/lwan-http-authorize.c +++ b/src/lib/lwan-http-authorize.c @@ -38,8 +38,10 @@ static struct cache *realm_password_cache = NULL; static void zero_and_free(void *str) { - if (LIKELY(str)) + if (LIKELY(str)) { lwan_always_bzero(str, strlen(str)); + free(str); + } } static struct cache_entry * From bdf3eaa3c06d20a0748eb5fb3701b782eed88a56 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Fri, 18 Feb 2022 23:24:43 -0800 Subject: [PATCH 1899/2505] Make ASan happy when testing the MIME type lookup function --- src/lib/lwan-tables.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index aa2dba4b8..1bcf93449 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -135,9 +135,10 @@ lwan_determine_mime_type_for_file_name(const char *file_name) } if (LIKELY(*last_dot)) { - uint64_t key = string_as_uint64(last_dot + 1); + uint64_t key; const unsigned char *extension; + strncpy((char *)&key, last_dot + 1, 8); key &= ~0x2020202020202020ull; extension = bsearch(&key, uncompressed_mime_entries, MIME_ENTRIES, 8, From 5559efd9de80441ab3b1ed3ee38791d4fc7103c6 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Fri, 18 Feb 2022 23:26:04 -0800 Subject: [PATCH 1900/2505] Reduce number of TCP packets by half for TLS handshakes Use our own I/O wrapper functions instead of the ones provided by mbedTLS so that MSG_MORE is used to bundle parts of the handshake that are sent by the server. This is still not perfect, as we're don't know when mbedTLS is going to switch from "only sending stuff" to "let's read some stuff", so we flush the buffer by setting the corking bit. We have to do this twice. (An alternative would be counting the number of packets, but that is a bit brittle.) --- src/lib/lwan-thread.c | 81 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 75 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 9bfd1e42a..9cb20ed31 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -222,6 +222,76 @@ static void lwan_setup_tls_free_ssl_context(void *data) mbedtls_ssl_free(ssl); } +struct lwan_mbedtls_handshake_ctx { + int fd; + bool last_was_send; +}; + +static int lwan_mbedtls_send(void *ctx, const unsigned char *buf, size_t len) +{ + struct lwan_mbedtls_handshake_ctx *hs_ctx = ctx; + ssize_t r; + + /* We use MSG_MORE -- flushing when we transition from send() to recv() + * -- rather than buffering on our side because this contains key + * material that we would need to only copy, but also zero out after + * finishing the handshake. */ + + r = send(hs_ctx->fd, buf, len, MSG_MORE); + if (UNLIKELY(r < 0)) { + switch (errno) { + case EINTR: + case EAGAIN: + return MBEDTLS_ERR_SSL_WANT_WRITE; + + default: + /* It's not an internal error here, but this seemed the least + * innapropriate error code for this situation. lwan_setup_tls() + * doesn't care. */ + return MBEDTLS_ERR_SSL_INTERNAL_ERROR; + } + } + + if (UNLIKELY((ssize_t)(int)r != r)) + return MBEDTLS_ERR_SSL_INTERNAL_ERROR; + + hs_ctx->last_was_send = true; + return (int)r; +} + +static void flush_pending_output(int fd) +{ + int zero = 0; + setsockopt(fd, SOL_TCP, TCP_CORK, &zero, sizeof(zero)); +} + +static int lwan_mbedtls_recv(void *ctx, unsigned char *buf, size_t len) +{ + struct lwan_mbedtls_handshake_ctx *hs_ctx = ctx; + ssize_t r; + + if (hs_ctx->last_was_send) + flush_pending_output(hs_ctx->fd); + + r = recv(hs_ctx->fd, buf, len, 0); + if (UNLIKELY(r < 0)) { + switch (errno) { + case EINTR: + case EAGAIN: + return MBEDTLS_ERR_SSL_WANT_READ; + + default: + return MBEDTLS_ERR_SSL_INTERNAL_ERROR; + } + } + + if (UNLIKELY((ssize_t)(int)r != r)) + return MBEDTLS_ERR_SSL_INTERNAL_ERROR; + + hs_ctx->last_was_send = false; + return (int)r; +} + static bool lwan_setup_tls(const struct lwan *l, struct lwan_connection *conn) { mbedtls_ssl_context ssl; @@ -249,16 +319,15 @@ static bool lwan_setup_tls(const struct lwan *l, struct lwan_connection *conn) } int fd = lwan_connection_get_fd(l, conn); - /* FIXME: This is only required for the handshake; this uses read() and - * write() under the hood but maybe we can use something like recv() and - * send() instead to force MSG_MORE et al? (strace shows a few - * consecutive calls to write(); this might be sent in separate TCP - * fragments.) */ - mbedtls_ssl_set_bio(&ssl, &fd, mbedtls_net_send, mbedtls_net_recv, NULL); + + struct lwan_mbedtls_handshake_ctx ctx = { .fd = fd }; + mbedtls_ssl_set_bio(&ssl, &ctx, lwan_mbedtls_send, + lwan_mbedtls_recv, NULL); while (true) { switch (mbedtls_ssl_handshake(&ssl)) { case 0: + flush_pending_output(fd); goto enable_tls_ulp; case MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS: case MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS: From a71d1795aed7cdf337876da62a8d2fc9f1926fff Mon Sep 17 00:00:00 2001 From: L Pereira Date: Sat, 19 Feb 2022 10:16:53 -0800 Subject: [PATCH 1901/2505] Fix build of weighttp with GCC 11.2 --- src/bin/tools/weighttp.c | 36 ++++-------------------------------- 1 file changed, 4 insertions(+), 32 deletions(-) diff --git a/src/bin/tools/weighttp.c b/src/bin/tools/weighttp.c index 4c32102f1..66516fb86 100644 --- a/src/bin/tools/weighttp.c +++ b/src/bin/tools/weighttp.c @@ -72,9 +72,6 @@ #ifndef __attribute_noinline__ #define __attribute_noinline__ __attribute__((__noinline__)) #endif -#ifndef __attribute_nonnull__ -#define __attribute_nonnull__ __attribute__((__nonnull__)) -#endif #ifndef __attribute_noreturn__ #define __attribute_noreturn__ __attribute__((__noreturn__)) #endif @@ -97,9 +94,6 @@ #ifndef __attribute_noinline__ #define __attribute_noinline__ #endif -#ifndef __attribute_nonnull__ -#define __attribute_nonnull__ -#endif #ifndef __attribute_noreturn__ #define __attribute_noreturn__ #endif @@ -296,7 +290,6 @@ struct Config { __attribute_cold__ -__attribute_nonnull__ static void client_init (Worker * const restrict worker, const Config * const restrict config, @@ -326,7 +319,6 @@ client_init (Worker * const restrict worker, __attribute_cold__ -__attribute_nonnull__ static void client_delete (const Client * const restrict client) { @@ -336,7 +328,6 @@ client_delete (const Client * const restrict client) __attribute_cold__ -__attribute_nonnull__ __attribute_noinline__ static void worker_init (Worker * const restrict worker, @@ -363,7 +354,6 @@ worker_init (Worker * const restrict worker, __attribute_cold__ -__attribute_nonnull__ __attribute_noinline__ static void worker_delete (Worker * const restrict worker, @@ -391,7 +381,6 @@ worker_delete (Worker * const restrict worker, __attribute_cold__ __attribute_noinline__ -__attribute_nonnull__ static void wconfs_init (Config * const restrict config) { @@ -432,7 +421,6 @@ wconfs_init (Config * const restrict config) __attribute_cold__ __attribute_noinline__ -__attribute_nonnull__ static void wconfs_delete (const Config * const restrict config) { @@ -450,7 +438,6 @@ wconfs_delete (const Config * const restrict config) __attribute_hot__ -__attribute_nonnull__ static void client_reset (Client * const restrict client, const int success) { @@ -502,7 +489,6 @@ client_reset (Client * const restrict client, const int success) __attribute_cold__ __attribute_noinline__ -__attribute_nonnull__ static void client_error (Client * const restrict client) { @@ -522,7 +508,6 @@ client_error (Client * const restrict client) __attribute_cold__ __attribute_noinline__ -__attribute_nonnull__ static void client_perror (Client * const restrict client, const char * const restrict tag) { @@ -540,7 +525,7 @@ client_perror (Client * const restrict client, const char * const restrict tag) } -__attribute_nonnull__ + static void client_connected (Client * const restrict client) { @@ -556,7 +541,6 @@ client_connected (Client * const restrict client) __attribute_noinline__ -__attribute_nonnull__ static int client_connect (Client * const restrict client) { @@ -678,7 +662,7 @@ client_connect (Client * const restrict client) } -__attribute_nonnull__ + static int client_parse_chunks (Client * const restrict client) { @@ -765,7 +749,6 @@ client_parse_chunks (Client * const restrict client) __attribute_hot__ -__attribute_nonnull__ __attribute_pure__ static uint64_t client_parse_uint64 (const char * const restrict str) @@ -785,7 +768,6 @@ client_parse_uint64 (const char * const restrict str) __attribute_hot__ __attribute_noinline__ -__attribute_nonnull__ static int client_parse (Client * const restrict client) { @@ -944,7 +926,7 @@ client_parse (Client * const restrict client) } -__attribute_nonnull__ + static void client_revents (Client * const restrict client) { @@ -1085,7 +1067,7 @@ client_revents (Client * const restrict client) } -__attribute_nonnull__ + static void * worker_thread (void * const arg) { @@ -1150,7 +1132,6 @@ worker_thread (void * const arg) __attribute_cold__ __attribute_noinline__ -__attribute_nonnull__ static void config_error_diagnostic (const char * const restrict errfmt, const int perr, va_list ap) @@ -1183,7 +1164,6 @@ config_error_diagnostic (const char * const restrict errfmt, __attribute_cold__ __attribute_format__((__printf__, 1, 2)) __attribute_noinline__ -__attribute_nonnull__ __attribute_noreturn__ static void config_error (const char * const restrict errfmt, ...) @@ -1199,7 +1179,6 @@ config_error (const char * const restrict errfmt, ...) __attribute_cold__ __attribute_format__((__printf__, 1, 2)) __attribute_noinline__ -__attribute_nonnull__ __attribute_noreturn__ static void config_perror (const char * const restrict errfmt, ...) @@ -1229,7 +1208,6 @@ typedef struct config_params { __attribute_cold__ -__attribute_nonnull__ static int config_laddr (Config * const restrict config, const char * const restrict laddrstr) @@ -1256,7 +1234,6 @@ config_laddr (Config * const restrict config, __attribute_cold__ -__attribute_nonnull__ static int config_laddrs (Config * const restrict config, char * const restrict laddrstr) @@ -1295,7 +1272,6 @@ config_laddrs (Config * const restrict config, __attribute_cold__ -__attribute_nonnull__ static void config_raddr (Config * const restrict config, const char * restrict hostname, uint16_t port, const int use_ipv6) @@ -1377,7 +1353,6 @@ config_raddr (Config * const restrict config, __attribute_cold__ -__attribute_nonnull__ static int config_base64_encode_pad (char * const restrict dst, const size_t dstsz, const char * const restrict ssrc) @@ -1426,7 +1401,6 @@ config_base64_encode_pad (char * const restrict dst, const size_t dstsz, __attribute_cold__ -__attribute_nonnull__ static void config_request (Config * const restrict config, const config_params * const restrict params) @@ -1685,7 +1659,6 @@ config_request (Config * const restrict config, __attribute_cold__ __attribute_noinline__ -__attribute_nonnull__ static void weighttp_setup (Config * const restrict config, const int argc, char *argv[]) { @@ -1888,7 +1861,6 @@ weighttp_setup (Config * const restrict config, const int argc, char *argv[]) __attribute_cold__ __attribute_noinline__ -__attribute_nonnull__ static void weighttp_report (const Config * const restrict config) { From c9eeb26c2a5c655ab555604636029bc3664ef5d8 Mon Sep 17 00:00:00 2001 From: L Pereira Date: Sat, 19 Feb 2022 10:19:10 -0800 Subject: [PATCH 1902/2505] Fix compilation warnings in coro library --- src/lib/lwan-coro.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 48b96d06c..d487ef1d0 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -55,9 +55,9 @@ void __asan_unpoison_memory_region(void const volatile *addr, size_t size); #endif #ifdef HAVE_BROTLI -#define CORO_STACK_SIZE (8 * SIGSTKSZ) +#define CORO_STACK_SIZE ((size_t)(8 * SIGSTKSZ)) #else -#define CORO_STACK_SIZE (4 * SIGSTKSZ) +#define CORO_STACK_SIZE ((size_t)(4 * SIGSTKSZ)) #endif #define CORO_BUMP_PTR_ALLOC_SIZE 1024 From ee6d6fbbaa56098ae1d9fa16ee07488fff6fd9e5 Mon Sep 17 00:00:00 2001 From: Zoltan Ponekker Date: Wed, 23 Feb 2022 15:51:16 +0100 Subject: [PATCH 1903/2505] WebSocket: separate text and binary frame handling --- README.md | 5 +++-- src/lib/lwan-lua.c | 15 +++++++++++++-- src/lib/lwan-websocket.c | 31 ++++++++++++++++++++----------- src/lib/lwan.h | 3 ++- src/samples/websocket/main.c | 8 ++++---- 5 files changed, 42 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index c3a6db8fd..b9963b029 100644 --- a/README.md +++ b/README.md @@ -414,7 +414,7 @@ tls_listener *:8081 { # Listen on all interfaces, port 8081, HTTPS } # Use named systemd socket activation for HTTP listener -listener systemd:my-service-http.socket +listener systemd:my-service-http.socket # Use named systemd socket activation for HTTPS listener tls_listener systemd:my-service-https.socket { @@ -515,7 +515,8 @@ information from the request, or to set the response, as seen below: - `req:header(name)` obtains the header from the request with the given name or `nil` if not found - `req:sleep(ms)` pauses the current handler for the specified amount of milliseconds - `req:ws_upgrade()` returns `1` if the connection could be upgraded to a WebSocket; `0` otherwise - - `req:ws_write(str)` sends `str` through the WebSocket-upgraded connection + - `req:ws_write_text(str)` sends `str` through the WebSocket-upgraded connection as text frame + - `req:ws_write_binary(str)` sends `str` through the WebSocket-upgraded connection as binary frame - `req:ws_read()` returns a string with the contents of the last WebSocket frame, or a number indicating an status (ENOTCONN/107 on Linux if it has been disconnected; EAGAIN/11 on Linux if nothing was available; ENOMSG/42 on Linux otherwise). The return value here might change in the future for something more Lua-like. - `req:remote_address()` returns a string with the remote IP address. - `req:path()` returns a string with the request path. diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index 03550f325..6a1f6adca 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -175,13 +175,24 @@ LWAN_LUA_METHOD(ws_upgrade) return 1; } -LWAN_LUA_METHOD(ws_write) +LWAN_LUA_METHOD(ws_write_text) { size_t data_len; const char *data_str = lua_tolstring(L, -1, &data_len); lwan_strbuf_set_static(request->response.buffer, data_str, data_len); - lwan_response_websocket_write(request); + lwan_response_websocket_write_text(request); + + return 0; +} + +LWAN_LUA_METHOD(ws_write_binary) +{ + size_t data_len; + const char *data_str = lua_tolstring(L, -1, &data_len); + + lwan_strbuf_set_static(request->response.buffer, data_str, data_len); + lwan_response_websocket_write_binary(request); return 0; } diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index c04e91aff..506c4b57b 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -60,25 +60,24 @@ static void write_websocket_frame(struct lwan_request *request, char *msg, size_t len) { - uint8_t frame[9]; + uint8_t frame[10] = { header_byte }; size_t frame_len; if (len <= 125) { - frame[0] = (uint8_t)len; - frame_len = 1; + frame[1] = (uint8_t)len; + frame_len = 2; } else if (len <= 65535) { - frame[0] = 0x7e; - memcpy(frame + 1, &(uint16_t){htons((uint16_t)len)}, sizeof(uint16_t)); - frame_len = 3; + frame[1] = 0x7e; + memcpy(frame + 2, &(uint16_t){htons((uint16_t)len)}, sizeof(uint16_t)); + frame_len = 4; } else { - frame[0] = 0x7f; - memcpy(frame + 1, &(uint64_t){htobe64((uint64_t)len)}, + frame[1] = 0x7f; + memcpy(frame + 2, &(uint64_t){htobe64((uint64_t)len)}, sizeof(uint64_t)); - frame_len = 9; + frame_len = 10; } struct iovec vec[] = { - {.iov_base = &header_byte, .iov_len = 1}, {.iov_base = frame, .iov_len = frame_len}, {.iov_base = msg, .iov_len = len}, }; @@ -86,7 +85,7 @@ static void write_websocket_frame(struct lwan_request *request, lwan_writev(request, vec, N_ELEMENTS(vec)); } -void lwan_response_websocket_write(struct lwan_request *request) +static inline void lwan_response_websocket_write(struct lwan_request *request, unsigned char op) { size_t len = lwan_strbuf_get_length(request->response.buffer); char *msg = lwan_strbuf_get_buffer(request->response.buffer); @@ -101,6 +100,16 @@ void lwan_response_websocket_write(struct lwan_request *request) lwan_strbuf_reset(request->response.buffer); } +void lwan_response_websocket_write_text(struct lwan_request *request) +{ + lwan_response_websocket_write(request, WS_OPCODE_TEXT); +} + +void lwan_response_websocket_write_binary(struct lwan_request *request) +{ + lwan_response_websocket_write(request, WS_OPCODE_BINARY); +} + static void send_websocket_pong(struct lwan_request *request, size_t len) { char temp[128]; diff --git a/src/lib/lwan.h b/src/lib/lwan.h index e1c3fd2a5..07d2afc88 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -575,7 +575,8 @@ lwan_request_get_accept_encoding(struct lwan_request *request); enum lwan_http_status lwan_request_websocket_upgrade(struct lwan_request *request); -void lwan_response_websocket_write(struct lwan_request *request); +void lwan_response_websocket_write_text(struct lwan_request *request); +void lwan_response_websocket_write_binary(struct lwan_request *request); int lwan_response_websocket_read(struct lwan_request *request); int lwan_response_websocket_read_hint(struct lwan_request *request, size_t size_hint); diff --git a/src/samples/websocket/main.c b/src/samples/websocket/main.c index 969b2e0d5..be89fb69c 100644 --- a/src/samples/websocket/main.c +++ b/src/samples/websocket/main.c @@ -37,7 +37,7 @@ LWAN_HANDLER(ws_write) while (true) { lwan_strbuf_printf(response->buffer, "Some random integer: %d", rand()); - lwan_response_websocket_write(request); + lwan_response_websocket_write_text(request); lwan_request_sleep(request, 1000); } @@ -78,7 +78,7 @@ LWAN_HANDLER(ws_read) seconds_since_last_msg, (int)lwan_strbuf_get_length(last_msg_recv), lwan_strbuf_get_buffer(last_msg_recv)); - lwan_response_websocket_write(request); + lwan_response_websocket_write_text(request); lwan_request_sleep(request, 1000); seconds_since_last_msg++; @@ -143,7 +143,7 @@ LWAN_HANDLER(ws_chat) lwan_strbuf_printf(response->buffer, "*** Welcome to the chat, User%d!\n", user_id); - lwan_response_websocket_write(request); + lwan_response_websocket_write_text(request); coro_defer2(request->conn->coro, pub_depart_message, chat, (void *)(intptr_t)user_id); @@ -166,7 +166,7 @@ LWAN_HANDLER(ws_chat) * happens. */ lwan_pubsub_msg_done(msg); - lwan_response_websocket_write(request); + lwan_response_websocket_write_text(request); sleep_time = 500; } From f1af09508a6f9ab2a17bdc0ba23e30b29d69bc7e Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 23 Feb 2022 08:47:25 -0800 Subject: [PATCH 1904/2505] Fix timeout in configuration parsing when parsing time periods When parsing time periods, we were using sscanf(), which takes an incredible amount of time trying to ignore leading spaces. Detect long runs and avoid long loops inside sscanf(). Thanks to OSS-Fuzz: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=44910 --- ...h-9df31650ba7cf7fa3aae24d59b3e0a335ab6c23a} | 0 src/lib/lwan-config.c | 18 +++++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) rename fuzz/regression/{crash-9df31650ba7cf7fa3aae24d59b3e0a335ab6c23a => request_fuzzer-crash-9df31650ba7cf7fa3aae24d59b3e0a335ab6c23a} (100%) diff --git a/fuzz/regression/crash-9df31650ba7cf7fa3aae24d59b3e0a335ab6c23a b/fuzz/regression/request_fuzzer-crash-9df31650ba7cf7fa3aae24d59b3e0a335ab6c23a similarity index 100% rename from fuzz/regression/crash-9df31650ba7cf7fa3aae24d59b3e0a335ab6c23a rename to fuzz/regression/request_fuzzer-crash-9df31650ba7cf7fa3aae24d59b3e0a335ab6c23a diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 2cfd71adc..df0dc68a5 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -113,12 +113,28 @@ unsigned int parse_time_period(const char *str, unsigned int default_value) { unsigned int total = 0; unsigned int period; + int ignored_spaces = 0; char multiplier; if (!str) return default_value; - while (*str && sscanf(str, "%u%c", &period, &multiplier) == 2) { + while (*str) { + /* This check is necessary to avoid making sscanf() take an incredible + * amount of time while trying to scan the input for a number. Fix for + * https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=44910 */ + if (isspace(*str)) { + ignored_spaces++; + + if (ignored_spaces > 1024) + break; + + continue; + } + + if (sscanf(str, "%u%c", &period, &multiplier) != 2) + break; + switch (multiplier) { case 's': total += period; break; case 'm': total += period * ONE_MINUTE; break; From 42a4af402c34b689582d2c82fa6bd8543b330833 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 26 Feb 2022 07:48:49 -0800 Subject: [PATCH 1905/2505] Ensure only request_fuzzer inputs are used for server tests --- src/scripts/testsuite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index b06cd9e5f..07b2c8a32 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -1010,7 +1010,7 @@ def run_test_wrapped(self): TestFuzzRegression = type('TestFuzzRegression', (TestFuzzRegressionBase,), { "test_" + name.replace("-", "_"): TestFuzzRegressionBase.wrap(name) for name in ( - cf for cf in os.listdir("fuzz/regression") if cf.startswith(("clusterfuzz-", "crash-")) + cf for cf in os.listdir("fuzz/regression") if cf.startswith(("clusterfuzz-", "crash-") and "request_fuzzer" in cf) ) }) From 279ea1857de7e9d30f910fecf2820e93779d0118 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 26 Feb 2022 13:58:37 -0800 Subject: [PATCH 1906/2505] Prefix HAVE_* build config macros with LWAN_* to avoid clashes --- CMakeLists.txt | 136 +++++++++++++++------------- src/bin/fuzz/config_fuzzer.cc | 9 +- src/bin/lwan/main.c | 25 ++--- src/bin/tools/CMakeLists.txt | 6 +- src/bin/tools/mimegen.c | 18 ++-- src/cmake/lwan-build-config.h.cmake | 80 ++++++++-------- src/lib/CMakeLists.txt | 8 +- src/lib/hash.c | 4 +- src/lib/lwan-array.c | 2 +- src/lib/lwan-config.c | 22 +++-- src/lib/lwan-coro.c | 8 +- src/lib/lwan-coro.h | 2 +- src/lib/lwan-lua.c | 3 +- src/lib/lwan-mod-rewrite.c | 14 +-- src/lib/lwan-mod-serve-files.c | 36 ++++---- src/lib/lwan-private.h | 6 +- src/lib/lwan-request.c | 4 +- src/lib/lwan-status.c | 2 +- src/lib/lwan-tables.c | 8 +- src/lib/lwan-template.c | 2 +- src/lib/lwan-thread.c | 27 +++--- src/lib/lwan.c | 4 +- src/lib/lwan.h | 2 +- src/lib/missing-pthread.c | 2 +- src/lib/missing.c | 42 ++++----- src/lib/missing/assert.h | 2 +- src/lib/missing/fcntl.h | 2 +- src/lib/missing/linux/capability.h | 2 +- src/lib/missing/pthread.h | 4 +- src/lib/missing/stdio.h | 2 +- src/lib/missing/stdlib.h | 14 +-- src/lib/missing/string.h | 6 +- src/lib/missing/sys/epoll.h | 2 +- src/lib/missing/sys/socket.h | 2 +- src/lib/missing/sys/types.h | 2 +- src/lib/missing/sys/vfs.h | 2 +- src/lib/missing/time.h | 2 +- src/lib/missing/unistd.h | 4 +- src/samples/clock/numbers.c | 2 +- 39 files changed, 278 insertions(+), 242 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ebac1f97..21354601d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,10 +39,20 @@ if (NOT ZLIB_INCLUDES STREQUAL "") include_directories(${ZLIB_INCLUDES}) endif () +find_package(Python3 3.9 COMPONENTS Development) +if (NOT Python3_FOUND) + message(STATUS "Disabling WSGI support") +else () + message(STATUS "Building with WSGI support using ${PYTHON_LIBRARIES}") + list(APPEND ADDITIONAL_LIBRARIES "${Python3_LIBRARIES}") + include_directories(${Python3_INCLUDE_DIRS}) + set(LWAN_HAVE_PYTHON 1) +endif () + foreach (pc_file luajit lua lua51 lua5.1 lua-5.1) if (${pc_file} STREQUAL "luajit") pkg_check_modules(LUA luajit>=2.0 luajit<2.2) - set(HAVE_LUA_JIT 1) + set(LWAN_HAVE_LUA_JIT 1) else () pkg_check_modules(LUA ${pc_file}>=5.1.0 ${pc_file}<=5.1.999) endif () @@ -56,7 +66,7 @@ if (NOT LUA_FOUND) message(STATUS "Disabling Lua support") else () message(STATUS "Building with Lua support using ${LUA_LIBRARIES}") - set(HAVE_LUA 1) + set(LWAN_HAVE_LUA 1) endif () option(ENABLE_BROTLI "Enable support for brotli" "ON") @@ -68,7 +78,7 @@ if (BROTLI_FOUND) if (NOT BROTLI_INCLUDE_DIRS STREQUAL "") include_directories(${BROTLI_INCLUDE_DIRS}) endif () - set(HAVE_BROTLI 1) + set(LWAN_HAVE_BROTLI 1) endif () option(ENABLE_ZSTD "Enable support for zstd" "ON") @@ -80,12 +90,12 @@ if (ZSTD_FOUND) if (NOT ZSTD_INCLUDE_DIRS STREQUAL "") include_directories(${ZSTD_INCLUDE_DIRS}) endif () - set(HAVE_ZSTD 1) + set(LWAN_HAVE_ZSTD 1) endif () option(ENABLE_TLS "Enable support for TLS (Linux-only)" "ON") if (ENABLE_TLS) - check_include_file(linux/tls.h HAVE_LINUX_TLS_H) + check_include_file(linux/tls.h LWAN_HAVE_LINUX_TLS_H) if (HAVE_LINUX_TLS_H) # TLS support requires Linux, as Lwan uses the kTLS flavor # only supported there. @@ -98,7 +108,7 @@ if (ENABLE_TLS) find_library(MBEDTLS_X509 NAMES mbedx509 REQUIRED) message(STATUS "Building with Linux kTLS + mbedTLS at ${MBEDTLS}") - set(HAVE_MBEDTLS 1) + set(LWAN_HAVE_MBEDTLS 1) list(APPEND ADDITIONAL_LIBRARIES ${MBEDTLS} ${MBEDTLS_CRYPTO} ${MBEDTLS_X509}) else () message(STATUS "mbedTLS not found: not building with TLS support") @@ -133,8 +143,8 @@ endif () ### # syslog option(USE_SYSLOG "Enable syslog" "OFF") -if (${USE_SYSLOG} STREQUAL "ON" AND HAVE_SYSLOG_FUNC) - set(HAVE_SYSLOG 1) +if (${USE_SYSLOG} STREQUAL "ON" AND LWAN_HAVE_SYSLOG_FUNC) + set(LWAN_HAVE_SYSLOG 1) endif () if (HAVE_SYSLOG) message(STATUS "Using syslog/rsyslog for logging.") @@ -155,54 +165,54 @@ set(CMAKE_EXTRA_INCLUDE_FILES dlfcn.h syslog.h ) -check_include_file(linux/capability.h HAVE_LINUX_CAPABILITY) -check_include_file(sys/auxv.h HAVE_SYS_AUXV) -check_include_file(sys/epoll.h HAVE_EPOLL) -check_include_files("sys/time.h;sys/types.h;sys/event.h" HAVE_SYS_EVENT) +check_include_file(linux/capability.h LWAN_HAVE_LINUX_CAPABILITY) +check_include_file(sys/auxv.h LWAN_HAVE_SYS_AUXV) +check_include_file(sys/epoll.h LWAN_HAVE_EPOLL) +check_include_files("sys/time.h;sys/types.h;sys/event.h" LWAN_HAVE_SYS_EVENT) if (HAVE_SYS_EVENT) set(CMAKE_EXTRA_INCLUDE_FILES ${CMAKE_EXTRA_INCLUDE_FILES} sys/event.h sys/types.h sys/time.h ) - check_function_exists(kqueue HAVE_KQUEUE) + check_function_exists(kqueue LWAN_HAVE_KQUEUE) endif () -check_include_file(alloca.h HAVE_ALLOCA_H) +check_include_file(alloca.h LWAN_HAVE_ALLOCA_H) if (HAVE_SYS_AUXV) set(CMAKE_EXTRA_INCLUDE_FILES ${CMAKE_EXTRA_INCLUDE_FILES} sys/auxv.h ) - check_function_exists(getauxval HAVE_GETAUXVAL) + check_function_exists(getauxval LWAN_HAVE_GETAUXVAL) endif () set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) -check_function_exists(get_current_dir_name HAVE_GET_CURRENT_DIR_NAME) -check_symbol_exists(reallocarray stdlib.h HAVE_REALLOCARRAY) -check_function_exists(mempcpy HAVE_MEMPCPY) -check_function_exists(memrchr HAVE_MEMRCHR) -check_function_exists(pipe2 HAVE_PIPE2) -check_function_exists(accept4 HAVE_ACCEPT4) -check_function_exists(readahead HAVE_READAHEAD) -check_function_exists(mkostemp HAVE_MKOSTEMP) -check_function_exists(clock_gettime HAVE_CLOCK_GETTIME) -check_function_exists(pthread_barrier_init HAVE_PTHREADBARRIER) -check_function_exists(pthread_set_name_np HAVE_PTHREAD_SET_NAME_NP) -check_function_exists(posix_fadvise HAVE_POSIX_FADVISE) -check_function_exists(getentropy HAVE_GETENTROPY) -check_function_exists(fwrite_unlocked HAVE_FWRITE_UNLOCKED) -check_function_exists(gettid HAVE_GETTID) -check_function_exists(secure_getenv HAVE_SECURE_GETENV) -check_function_exists(statfs HAVE_STATFS) -check_function_exists(syslog HAVE_SYSLOG_FUNC) +check_function_exists(get_current_dir_name LWAN_HAVE_GET_CURRENT_DIR_NAME) +check_symbol_exists(reallocarray stdlib.h LWAN_HAVE_REALLOCARRAY) +check_function_exists(mempcpy LWAN_HAVE_MEMPCPY) +check_function_exists(memrchr LWAN_HAVE_MEMRCHR) +check_function_exists(pipe2 LWAN_HAVE_PIPE2) +check_function_exists(accept4 LWAN_HAVE_ACCEPT4) +check_function_exists(readahead LWAN_HAVE_READAHEAD) +check_function_exists(mkostemp LWAN_HAVE_MKOSTEMP) +check_function_exists(clock_gettime LWAN_HAVE_CLOCK_GETTIME) +check_function_exists(pthread_barrier_init LWAN_HAVE_PTHREADBARRIER) +check_function_exists(pthread_set_name_np LWAN_HAVE_PTHREAD_SET_NAME_NP) +check_function_exists(posix_fadvise LWAN_HAVE_POSIX_FADVISE) +check_function_exists(getentropy LWAN_HAVE_GETENTROPY) +check_function_exists(fwrite_unlocked LWAN_HAVE_FWRITE_UNLOCKED) +check_function_exists(gettid LWAN_HAVE_GETTID) +check_function_exists(secure_getenv LWAN_HAVE_SECURE_GETENV) +check_function_exists(statfs LWAN_HAVE_STATFS) +check_function_exists(syslog LWAN_HAVE_SYSLOG_FUNC) # This is available on -ldl in glibc, but some systems (such as OpenBSD) # will bundle these in the C library. This isn't required for glibc anyway, # as there's getauxval(), with a fallback to reading the link # /proc/self/exe. -check_function_exists(dladdr HAVE_DLADDR) +check_function_exists(dladdr LWAN_HAVE_DLADDR) -if (NOT HAVE_CLOCK_GETTIME AND ${CMAKE_SYSTEM_NAME} MATCHES "Linux") +if (NOT LWAN_HAVE_CLOCK_GETTIME AND ${CMAKE_SYSTEM_NAME} MATCHES "Linux") list(APPEND ADDITIONAL_LIBRARIES rt) endif () @@ -210,8 +220,8 @@ endif () # # Ensure compiler is compatible with GNU99 standard # -check_c_compiler_flag(-std=gnu99 HAVE_STD_GNU99) -if (NOT HAVE_STD_GNU99) +check_c_compiler_flag(-std=gnu99 LWAN_HAVE_STD_GNU99) +if (NOT LWAN_HAVE_STD_GNU99) message(FATAL_ERROR "Compiler does not support -std=gnu99. Consider using a newer compiler") endif() @@ -219,23 +229,23 @@ endif() # # Check for GCC builtin functions # -check_c_source_compiles("int main(void) { __builtin_cpu_init(); }" HAVE_BUILTIN_CPU_INIT) -check_c_source_compiles("int main(void) { __builtin_clzll(0); }" HAVE_BUILTIN_CLZLL) -check_c_source_compiles("int main(void) { __builtin_fpclassify(0, 0, 0, 0, 0, 0.0f); }" HAVE_BUILTIN_FPCLASSIFY) -check_c_source_compiles("int main(void) { unsigned long long p; (void)__builtin_mul_overflow(0, 0, &p); }" HAVE_BUILTIN_MUL_OVERFLOW) -check_c_source_compiles("int main(void) { unsigned long long p; (void)__builtin_add_overflow(0, 0, &p); }" HAVE_BUILTIN_ADD_OVERFLOW) -check_c_source_compiles("int main(void) { _Static_assert(1, \"\"); }" HAVE_STATIC_ASSERT) +check_c_source_compiles("int main(void) { __builtin_cpu_init(); }" LWAN_HAVE_BUILTIN_CPU_INIT) +check_c_source_compiles("int main(void) { __builtin_clzll(0); }" LWAN_HAVE_BUILTIN_CLZLL) +check_c_source_compiles("int main(void) { __builtin_fpclassify(0, 0, 0, 0, 0, 0.0f); }" LWAN_HAVE_BUILTIN_FPCLASSIFY) +check_c_source_compiles("int main(void) { unsigned long long p; (void)__builtin_mul_overflow(0, 0, &p); }" LWAN_HAVE_BUILTIN_MUL_OVERFLOW) +check_c_source_compiles("int main(void) { unsigned long long p; (void)__builtin_add_overflow(0, 0, &p); }" LWAN_HAVE_BUILTIN_ADD_OVERFLOW) +check_c_source_compiles("int main(void) { _Static_assert(1, \"\"); }" LWAN_HAVE_STATIC_ASSERT) check_c_source_compiles("#include #include #include int main(void) { setsockopt(0, SOL_SOCKET, SO_ATTACH_REUSEPORT_CBPF, NULL, 0); -}" HAVE_SO_ATTACH_REUSEPORT_CBPF) +}" LWAN_HAVE_SO_ATTACH_REUSEPORT_CBPF) check_c_source_compiles("#include #include int main(void) { setsockopt(0, SOL_SOCKET, SO_INCOMING_CPU, NULL, 0); -}" HAVE_SO_INCOMING_CPU) +}" LWAN_HAVE_SO_INCOMING_CPU) # # Look for Valgrind header @@ -245,16 +255,16 @@ if (VALGRIND_INCLUDE_DIR) message(STATUS "Building with Valgrind support") include_directories(${VALGRIND_INCLUDE_DIR}) - set(HAVE_VALGRIND 1) + set(LWAN_HAVE_VALGRIND 1) else () message(STATUS "Valgrind headers not found -- disabling valgrind support") endif() -enable_c_flag_if_avail(-mtune=native C_FLAGS_REL HAVE_MTUNE_NATIVE) -enable_c_flag_if_avail(-march=native C_FLAGS_REL HAVE_MARCH_NATIVE) +enable_c_flag_if_avail(-mtune=native C_FLAGS_REL LWAN_HAVE_MTUNE_NATIVE) +enable_c_flag_if_avail(-march=native C_FLAGS_REL LWAN_HAVE_MARCH_NATIVE) enable_c_flag_if_avail(-fstack-protector-explicit CMAKE_C_FLAGS - HAVE_STACK_PROTECTOR_EXPLICIT) + LWAN_HAVE_STACK_PROTECTOR_EXPLICIT) # # Check if immediate binding and read-only global offset table flags @@ -262,30 +272,30 @@ enable_c_flag_if_avail(-fstack-protector-explicit CMAKE_C_FLAGS # if (APPLE) enable_c_flag_if_avail(-Wl,-bind_at_load CMAKE_EXE_LINKER_FLAGS - HAVE_IMMEDIATE_BINDING) + LWAN_HAVE_IMMEDIATE_BINDING) else () enable_c_flag_if_avail(-Wl,-z,now CMAKE_EXE_LINKER_FLAGS - HAVE_IMMEDIATE_BINDING) + LWAN_HAVE_IMMEDIATE_BINDING) enable_c_flag_if_avail(-Wl,-z,relro CMAKE_EXE_LINKER_FLAGS - HAVE_READ_ONLY_GOT) + LWAN_HAVE_READ_ONLY_GOT) enable_c_flag_if_avail(-fno-plt CMAKE_C_FLAGS - HAVE_NO_PLT) + LWAN_HAVE_NO_PLT) enable_c_flag_if_avail(-no-pie CMAKE_C_FLAGS - HAVE_NO_PIE) + LWAN_HAVE_NO_PIE) enable_c_flag_if_avail(-Wl,-z,noexecstack CMAKE_EXE_LINKER_FLAGS - HAVE_NOEXEC_STACK) + LWAN_HAVE_NOEXEC_STACK) endif () if (${CMAKE_BUILD_TYPE} MATCHES "Rel") - enable_c_flag_if_avail(-falign-functions=32 C_FLAGS_REL HAVE_ALIGN_FNS) - enable_c_flag_if_avail(-fno-semantic-interposition C_FLAGS_REL HAVE_NO_SEMANTIC_INTERPOSITION) - enable_c_flag_if_avail(-malign-data=abi C_FLAGS_REL HAVE_ALIGN_DATA) - enable_c_flag_if_avail(-fno-asynchronous-unwind-tables C_FLAGS_REL HAVE_NO_ASYNC_UNWIND_TABLES) + enable_c_flag_if_avail(-falign-functions=32 C_FLAGS_REL LWAN_HAVE_ALIGN_FNS) + enable_c_flag_if_avail(-fno-semantic-interposition C_FLAGS_REL LWAN_HAVE_NO_SEMANTIC_INTERPOSITION) + enable_c_flag_if_avail(-malign-data=abi C_FLAGS_REL LWAN_HAVE_ALIGN_DATA) + enable_c_flag_if_avail(-fno-asynchronous-unwind-tables C_FLAGS_REL LWAN_HAVE_NO_ASYNC_UNWIND_TABLES) - enable_c_flag_if_avail(-fPIC -flto C_FLAGS_REL HAVE_LTO) + enable_c_flag_if_avail(-fPIC -flto C_FLAGS_REL LWAN_HAVE_LTO) - enable_c_flag_if_avail(-ffat-lto-objects C_FLAGS_REL HAVE_LTO_FAT_OBJS) - enable_c_flag_if_avail(-mcrc32 C_FLAGS_REL HAVE_BUILTIN_IA32_CRC32) + enable_c_flag_if_avail(-ffat-lto-objects C_FLAGS_REL LWAN_HAVE_LTO_FAT_OBJS) + enable_c_flag_if_avail(-mcrc32 C_FLAGS_REL LWAN_HAVE_BUILTIN_IA32_CRC32) endif () if (${CMAKE_BUILD_TYPE} MATCHES "Deb") @@ -332,7 +342,7 @@ else () endif () if (NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^x86_64|amd64") - set(HAVE_LIBUCONTEXT 1) + set(LWAN_HAVE_LIBUCONTEXT 1) endif () include_directories(src/lib) diff --git a/src/bin/fuzz/config_fuzzer.cc b/src/bin/fuzz/config_fuzzer.cc index b2c426382..761c557de 100644 --- a/src/bin/fuzz/config_fuzzer.cc +++ b/src/bin/fuzz/config_fuzzer.cc @@ -37,7 +37,14 @@ static bool dump(struct config *config, int indent_level) } } - return config_last_error(config); + const char *error = config_last_error(config); + + if (error) { + printf("Error: %s\n", error); + return false; + } + + return true; } extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) diff --git a/src/bin/lwan/main.c b/src/bin/lwan/main.c index 5c0b11d27..d288fa065 100644 --- a/src/bin/lwan/main.c +++ b/src/bin/lwan/main.c @@ -57,38 +57,41 @@ static void print_build_time_configuration(void) { printf("Build-time configuration:"); -#ifdef HAVE_LUA +#ifdef LWAN_HAVE_LUA printf(" Lua"); #endif -#ifdef HAVE_BROTLI +#ifdef LWAN_HAVE_BROTLI printf(" Brotli"); #endif -#ifdef HAVE_ZSTD +#ifdef LWAN_HAVE_ZSTD printf(" zstd"); #endif -#ifdef HAVE_MBEDTLS +#ifdef LWAN_HAVE_MBEDTLS printf(" mbedTLS"); #endif -#ifdef HAVE_LIBUCONTEXT +#ifdef LWAN_HAVE_LIBUCONTEXT printf(" libucontext"); #endif -#ifdef HAVE_EPOLL +#ifdef LWAN_HAVE_EPOLL printf(" epoll"); #endif -#ifdef HAVE_KQUEUE +#ifdef LWAN_HAVE_KQUEUE printf(" kqueue"); #endif -#ifdef HAVE_SO_ATTACH_REUSEPORT_CBPF +#ifdef LWAN_HAVE_SO_ATTACH_REUSEPORT_CBPF printf(" sockopt-reuseport-CBPF"); #endif -#ifdef HAVE_SO_INCOMING_CPU +#ifdef LWAN_HAVE_SO_INCOMING_CPU printf(" sockopt-reuseport-incoming-cpu"); #endif -#ifdef HAVE_VALGRIND +#ifdef LWAN_HAVE_VALGRIND printf(" valgrind"); #endif -#ifdef HAVE_SYSLOG +#ifdef LWAN_HAVE_SYSLOG printf(" syslog"); +#endif +#ifdef HAVE_PYTHON + printf(" python"); #endif printf(".\n"); } diff --git a/src/bin/tools/CMakeLists.txt b/src/bin/tools/CMakeLists.txt index e5cc0b96b..87c32f75e 100644 --- a/src/bin/tools/CMakeLists.txt +++ b/src/bin/tools/CMakeLists.txt @@ -8,10 +8,10 @@ else () ${CMAKE_SOURCE_DIR}/src/lib/murmur3.c ${CMAKE_SOURCE_DIR}/src/lib/missing.c ) - if (HAVE_BROTLI) + if (LWAN_HAVE_BROTLI) message(STATUS "Using Brotli for mimegen") target_link_libraries(mimegen ${BROTLI_LDFLAGS}) - elseif (HAVE_ZSTD) + elseif (LWAN_HAVE_ZSTD) message(STATUS "Using Zstd for mimegen") target_link_libraries(mimegen ${ZSTD_LDFLAGS}) else () @@ -19,7 +19,7 @@ else () if (ZOPFLI_LIBRARY) message(STATUS "Using Zopfli (${ZOPFLI_LIBRARY}) for mimegen") target_link_libraries(mimegen ${ZOPFLI_LIBRARY}) - target_compile_definitions(mimegen PUBLIC -DHAVE_ZOPFLI=1) + target_compile_definitions(mimegen PUBLIC -DLWAN_HAVE_ZOPFLI=1) else () message(STATUS "Using zlib (${ZLIB_LIBRARIES}) for mimegen") target_link_libraries(mimegen ${ZLIB_LIBRARIES}) diff --git a/src/bin/tools/mimegen.c b/src/bin/tools/mimegen.c index 1ea5e96e9..b7c88c269 100644 --- a/src/bin/tools/mimegen.c +++ b/src/bin/tools/mimegen.c @@ -26,11 +26,11 @@ #include #include -#if defined(HAVE_BROTLI) +#if defined(LWAN_HAVE_BROTLI) #include -#elif defined(HAVE_ZSTD) +#elif defined(LWAN_HAVE_ZSTD) #include -#elif defined(HAVE_ZOPFLI) +#elif defined(LWAN_HAVE_ZOPFLI) #include #else #include @@ -110,7 +110,7 @@ static char *compress_output(const struct output *output, size_t *outlen) { char *compressed; -#if defined(HAVE_BROTLI) +#if defined(LWAN_HAVE_BROTLI) *outlen = BrotliEncoderMaxCompressedSize(output->used); compressed = malloc(*outlen); @@ -126,7 +126,7 @@ static char *compress_output(const struct output *output, size_t *outlen) fprintf(stderr, "Could not compress mime type table with Brotli\n"); exit(1); } -#elif defined(HAVE_ZSTD) +#elif defined(LWAN_HAVE_ZSTD) *outlen = ZSTD_compressBound(output->used); compressed = malloc(*outlen); @@ -141,7 +141,7 @@ static char *compress_output(const struct output *output, size_t *outlen) fprintf(stderr, "Could not compress mime type table with ZSTD\n"); exit(1); } -#elif defined(HAVE_ZOPFLI) +#elif defined(LWAN_HAVE_ZOPFLI) ZopfliOptions opts; *outlen = 0; @@ -332,11 +332,11 @@ int main(int argc, char *argv[]) } /* Print output. */ -#if defined(HAVE_BROTLI) +#if defined(LWAN_HAVE_BROTLI) printf("/* Compressed with brotli */\n"); -#elif defined(HAVE_ZSTD) +#elif defined(LWAN_HAVE_ZSTD) printf("/* Compressed with zstd */\n"); -#elif defined(HAVE_ZOPFLI) +#elif defined(LWAN_HAVE_ZOPFLI) printf("/* Compressed with zopfli (deflate) */\n"); #else printf("/* Compressed with zlib (deflate) */\n"); diff --git a/src/cmake/lwan-build-config.h.cmake b/src/cmake/lwan-build-config.h.cmake index 7baf895c5..8977e2d71 100644 --- a/src/cmake/lwan-build-config.h.cmake +++ b/src/cmake/lwan-build-config.h.cmake @@ -20,51 +20,51 @@ #pragma once /* API available in Glibc/Linux, but possibly not elsewhere */ -#cmakedefine HAVE_ACCEPT4 -#cmakedefine HAVE_ALLOCA_H -#cmakedefine HAVE_CLOCK_GETTIME -#cmakedefine HAVE_GET_CURRENT_DIR_NAME -#cmakedefine HAVE_GETAUXVAL -#cmakedefine HAVE_MEMPCPY -#cmakedefine HAVE_MEMRCHR -#cmakedefine HAVE_MKOSTEMP -#cmakedefine HAVE_PIPE2 -#cmakedefine HAVE_PTHREADBARRIER -#cmakedefine HAVE_READAHEAD -#cmakedefine HAVE_REALLOCARRAY -#cmakedefine HAVE_EPOLL -#cmakedefine HAVE_KQUEUE -#cmakedefine HAVE_DLADDR -#cmakedefine HAVE_POSIX_FADVISE -#cmakedefine HAVE_LINUX_CAPABILITY -#cmakedefine HAVE_PTHREAD_SET_NAME_NP -#cmakedefine HAVE_GETENTROPY -#cmakedefine HAVE_FWRITE_UNLOCKED -#cmakedefine HAVE_GETTID -#cmakedefine HAVE_SECURE_GETENV -#cmakedefine HAVE_STATFS -#cmakedefine HAVE_SO_ATTACH_REUSEPORT_CBPF -#cmakedefine HAVE_SO_INCOMING_CPU -#cmakedefine HAVE_SYSLOG +#cmakedefine LWAN_HAVE_ACCEPT4 +#cmakedefine LWAN_HAVE_ALLOCA_H +#cmakedefine LWAN_HAVE_CLOCK_GETTIME +#cmakedefine LWAN_HAVE_GET_CURRENT_DIR_NAME +#cmakedefine LWAN_HAVE_GETAUXVAL +#cmakedefine LWAN_HAVE_MEMPCPY +#cmakedefine LWAN_HAVE_MEMRCHR +#cmakedefine LWAN_HAVE_MKOSTEMP +#cmakedefine LWAN_HAVE_PIPE2 +#cmakedefine LWAN_HAVE_PTHREADBARRIER +#cmakedefine LWAN_HAVE_READAHEAD +#cmakedefine LWAN_HAVE_REALLOCARRAY +#cmakedefine LWAN_HAVE_EPOLL +#cmakedefine LWAN_HAVE_KQUEUE +#cmakedefine LWAN_HAVE_DLADDR +#cmakedefine LWAN_HAVE_POSIX_FADVISE +#cmakedefine LWAN_HAVE_LINUX_CAPABILITY +#cmakedefine LWAN_HAVE_PTHREAD_SET_NAME_NP +#cmakedefine LWAN_HAVE_GETENTROPY +#cmakedefine LWAN_HAVE_FWRITE_UNLOCKED +#cmakedefine LWAN_HAVE_GETTID +#cmakedefine LWAN_HAVE_SECURE_GETENV +#cmakedefine LWAN_HAVE_STATFS +#cmakedefine LWAN_HAVE_SO_ATTACH_REUSEPORT_CBPF +#cmakedefine LWAN_HAVE_SO_INCOMING_CPU +#cmakedefine LWAN_HAVE_SYSLOG /* Compiler builtins for specific CPU instruction support */ -#cmakedefine HAVE_BUILTIN_CLZLL -#cmakedefine HAVE_BUILTIN_CPU_INIT -#cmakedefine HAVE_BUILTIN_IA32_CRC32 -#cmakedefine HAVE_BUILTIN_MUL_OVERFLOW -#cmakedefine HAVE_BUILTIN_ADD_OVERFLOW -#cmakedefine HAVE_BUILTIN_FPCLASSIFY +#cmakedefine LWAN_HAVE_BUILTIN_CLZLL +#cmakedefine LWAN_HAVE_BUILTIN_CPU_INIT +#cmakedefine LWAN_HAVE_BUILTIN_IA32_CRC32 +#cmakedefine LWAN_HAVE_BUILTIN_MUL_OVERFLOW +#cmakedefine LWAN_HAVE_BUILTIN_ADD_OVERFLOW +#cmakedefine LWAN_HAVE_BUILTIN_FPCLASSIFY /* C11 _Static_assert() */ -#cmakedefine HAVE_STATIC_ASSERT +#cmakedefine LWAN_HAVE_STATIC_ASSERT /* Libraries */ -#cmakedefine HAVE_LUA -#cmakedefine HAVE_LUA_JIT -#cmakedefine HAVE_BROTLI -#cmakedefine HAVE_ZSTD -#cmakedefine HAVE_LIBUCONTEXT -#cmakedefine HAVE_MBEDTLS +#cmakedefine LWAN_HAVE_LUA +#cmakedefine LWAN_HAVE_LUA_JIT +#cmakedefine LWAN_HAVE_BROTLI +#cmakedefine LWAN_HAVE_ZSTD +#cmakedefine LWAN_HAVE_LIBUCONTEXT +#cmakedefine LWAN_HAVE_MBEDTLS /* Valgrind support for coroutines */ -#cmakedefine HAVE_VALGRIND +#cmakedefine LWAN_HAVE_VALGRIND diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index bfc9321e1..d9469faa0 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -45,10 +45,14 @@ set(SOURCES timeout.c ) -if (HAVE_LUA) +if (LWAN_HAVE_LUA) list(APPEND SOURCES lwan-lua.c lwan-mod-lua.c) endif () +if (LWAN_HAVE_PYTHON) + list(APPEND SOURCES lwan-mod-wsgi.c) +endif () + add_library(lwan-static STATIC ${SOURCES}) set_target_properties(lwan-static PROPERTIES OUTPUT_NAME lwan CLEAN_DIRECT_OUTPUT 1) @@ -154,6 +158,8 @@ install(FILES lwan-mod-rewrite.h lwan-mod-response.h lwan-mod-redirect.h + lwan-mod-lua.h + lwan-mod-wsgi.h lwan-status.h lwan-template.h lwan-trie.h diff --git a/src/lib/hash.c b/src/lib/hash.c index d2048213e..bc01fef8a 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -125,7 +125,7 @@ static inline unsigned int hash_int_shift_mult(const void *keyptr) return key; } -#if defined(HAVE_BUILTIN_CPU_INIT) && defined(HAVE_BUILTIN_IA32_CRC32) +#if defined(LWAN_HAVE_BUILTIN_CPU_INIT) && defined(LWAN_HAVE_BUILTIN_IA32_CRC32) static inline unsigned int hash_str_crc32(const void *keyptr) { unsigned int hash = odd_constant; @@ -178,7 +178,7 @@ __attribute__((constructor(65535))) static void initialize_odd_constant(void) odd_constant |= 1; murmur3_set_seed(odd_constant); -#if defined(HAVE_BUILTIN_CPU_INIT) && defined(HAVE_BUILTIN_IA32_CRC32) +#if defined(LWAN_HAVE_BUILTIN_CPU_INIT) && defined(LWAN_HAVE_BUILTIN_IA32_CRC32) __builtin_cpu_init(); if (__builtin_cpu_supports("sse4.2")) { hash_str = hash_str_crc32; diff --git a/src/lib/lwan-array.c b/src/lib/lwan-array.c index 5a5fee8bf..1fdd7ce4f 100644 --- a/src/lib/lwan-array.c +++ b/src/lib/lwan-array.c @@ -41,7 +41,7 @@ int lwan_array_reset(struct lwan_array *a, void *inline_storage) return 0; } -#if !defined(HAVE_BUILTIN_ADD_OVERFLOW) +#if !defined(LWAN_HAVE_BUILTIN_ADD_OVERFLOW) static inline bool add_overflow(size_t a, size_t b, size_t *out) { if (UNLIKELY(a > 0 && b > SIZE_MAX - a)) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index df0dc68a5..eef966ea6 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -236,14 +236,16 @@ static void emit_lexeme(struct lexer *lexer, struct lexeme *lexeme) lexer->start = lexer->pos; } +static size_t current_len(struct lexer *lexer) +{ + return (size_t)(lexer->pos - lexer->start); +} + static void emit(struct lexer *lexer, enum lexeme_type type) { struct lexeme lexeme = { .type = type, - .value = { - .value = lexer->start, - .len = (size_t)(lexer->pos - lexer->start) - } + .value = {.value = lexer->start, .len = current_len(lexer)}, }; emit_lexeme(lexer, &lexeme); } @@ -402,6 +404,10 @@ static void *lex_variable(struct lexer *lexer) if (chr == ':') { backup(lexer); + + if (!current_len(lexer)) + return LEX_ERROR(lexer, "Expecting environment variable name"); + emit(lexer, LEXEME_VARIABLE_DEFAULT); advance_n(lexer, strlen(":")); return lex_variable_default; @@ -409,6 +415,10 @@ static void *lex_variable(struct lexer *lexer) if (chr == '}') { backup(lexer); + + if (!current_len(lexer)) + return LEX_ERROR(lexer, "Expecting environment variable name"); + emit(lexer, LEXEME_VARIABLE); advance_n(lexer, strlen("}")); @@ -851,7 +861,7 @@ struct config *config_open(const char *path) return config ? config_init_data(config, data, len) : NULL; } -#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) +//#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) struct config *config_open_for_fuzzing(const uint8_t *data, size_t len) { struct config *config = malloc(sizeof(*config)); @@ -865,7 +875,7 @@ struct config *config_open_for_fuzzing(const uint8_t *data, size_t len) return NULL; } -#endif +//#endif void config_close(struct config *config) { diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index d487ef1d0..9628a1874 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -33,7 +33,7 @@ #include "lwan-array.h" #include "lwan-coro.h" -#if !defined(NDEBUG) && defined(HAVE_VALGRIND) +#if !defined(NDEBUG) && defined(LWAN_HAVE_VALGRIND) #define INSTRUMENT_FOR_VALGRIND #include #include @@ -54,7 +54,7 @@ void __asan_unpoison_memory_region(void const volatile *addr, size_t size); #define SIGSTKSZ 16384 #endif -#ifdef HAVE_BROTLI +#ifdef LWAN_HAVE_BROTLI #define CORO_STACK_SIZE ((size_t)(8 * SIGSTKSZ)) #else #define CORO_STACK_SIZE ((size_t)(4 * SIGSTKSZ)) @@ -181,7 +181,7 @@ asm(".text\n\t" "movq 64(%rsi),%rcx\n\t" "movq 56(%rsi),%rsi\n\t" "jmpq *%rcx\n\t"); -#elif defined(HAVE_LIBUCONTEXT) +#elif defined(LWAN_HAVE_LIBUCONTEXT) #define coro_swapcontext(cur, oth) libucontext_swapcontext(cur, oth) #else #error Unsupported platform. @@ -256,7 +256,7 @@ void coro_reset(struct coro *coro, coro_function_t func, void *data) #define STACK_PTR 9 coro->context[STACK_PTR] = (rsp & ~0xful) - 0x8ul; -#elif defined(HAVE_LIBUCONTEXT) +#elif defined(LWAN_HAVE_LIBUCONTEXT) libucontext_getcontext(&coro->context); coro->context.uc_stack.ss_sp = stack; diff --git a/src/lib/lwan-coro.h b/src/lib/lwan-coro.h index bbe52698f..e97ee2f03 100644 --- a/src/lib/lwan-coro.h +++ b/src/lib/lwan-coro.h @@ -25,7 +25,7 @@ #if defined(__x86_64__) typedef uintptr_t coro_context[10]; -#elif defined(HAVE_LIBUCONTEXT) +#elif defined(LWAN_HAVE_LIBUCONTEXT) #include typedef libucontext_ucontext_t coro_context; #else diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index 6a1f6adca..4161a3ec2 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -27,12 +27,11 @@ #include #include -#include "lwan-build-config.h" #include "lwan-private.h" #include "lwan-lua.h" -#if defined(HAVE_LUA_JIT) +#if defined(LWAN_HAVE_LUA_JIT) #define luaL_reg luaL_Reg #endif diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index ac3a7a2d1..7ce67c957 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -32,7 +32,7 @@ #include "lwan-mod-rewrite.h" #include "lwan-strbuf.h" -#ifdef HAVE_LUA +#ifdef LWAN_HAVE_LUA #include #include #include @@ -221,7 +221,7 @@ static ALWAYS_INLINE const char *expand(const struct pattern *pattern, return expand_string(pattern->expand_pattern, orig, buffer, sf, captures); } -#ifdef HAVE_LUA +#ifdef LWAN_HAVE_LUA static void lua_close_defer(void *data) { @@ -413,7 +413,7 @@ static bool condition_matches(struct lwan_request *request, return false; } -#ifdef HAVE_LUA +#ifdef LWAN_HAVE_LUA if (p->flags & PATTERN_COND_LUA) { assert(p->condition.lua.script); @@ -472,7 +472,7 @@ rewrite_handle_request(struct lwan_request *request, continue; switch (p->flags & PATTERN_EXPAND_MASK) { -#ifdef HAVE_LUA +#ifdef LWAN_HAVE_LUA case PATTERN_EXPAND_LUA: expanded = expand_lua(request, p, url, final_url, sf, captures); break; @@ -541,7 +541,7 @@ static void rewrite_destroy(void *instance) if (iter->flags & PATTERN_COND_STAT) { free(iter->condition.stat.path); } -#ifdef HAVE_LUA +#ifdef LWAN_HAVE_LUA if (iter->flags & PATTERN_COND_LUA) { free(iter->condition.lua.script); } @@ -819,7 +819,7 @@ static bool rewrite_parse_conf_pattern(struct private_data *pd, } pattern->flags |= PATTERN_COND_METHOD; } else -#ifdef HAVE_LUA +#ifdef LWAN_HAVE_LUA if (streq(line->key, "condition_lua")) { pattern->condition.lua.script = strdup(line->value); if (!pattern->condition.lua.script) @@ -859,7 +859,7 @@ static bool rewrite_parse_conf_pattern(struct private_data *pd, goto out; } if (expand_with_lua) { -#ifdef HAVE_LUA +#ifdef LWAN_HAVE_LUA pattern->flags |= PATTERN_EXPAND_LUA; #else config_error(config, "Lwan has been built without Lua. " diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 1f3c8c31e..471fffebe 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -42,11 +42,11 @@ #include "auto-index-icons.h" -#if defined(HAVE_BROTLI) +#if defined(LWAN_HAVE_BROTLI) #include #endif -#if defined(HAVE_ZSTD) +#if defined(LWAN_HAVE_ZSTD) #include #endif @@ -56,12 +56,12 @@ static const struct lwan_key_value deflate_compression_hdr[] = { static const struct lwan_key_value gzip_compression_hdr[] = { {"Content-Encoding", "gzip"}, {} }; -#if defined(HAVE_BROTLI) +#if defined(LWAN_HAVE_BROTLI) static const struct lwan_key_value br_compression_hdr[] = { {"Content-Encoding", "br"}, {} }; #endif -#if defined(HAVE_ZSTD) +#if defined(LWAN_HAVE_ZSTD) static const struct lwan_key_value zstd_compression_hdr[] = { {"Content-Encoding", "zstd"}, {} }; @@ -107,10 +107,10 @@ struct mmap_cache_data { struct lwan_value uncompressed; struct lwan_value gzip; struct lwan_value deflated; -#if defined(HAVE_BROTLI) +#if defined(LWAN_HAVE_BROTLI) struct lwan_value brotli; #endif -#if defined(HAVE_ZSTD) +#if defined(LWAN_HAVE_ZSTD) struct lwan_value zstd; #endif }; @@ -125,7 +125,7 @@ struct sendfile_cache_data { struct dir_list_cache_data { struct lwan_strbuf rendered; struct lwan_value deflated; -#if defined(HAVE_BROTLI) +#if defined(LWAN_HAVE_BROTLI) struct lwan_value brotli; #endif }; @@ -409,7 +409,7 @@ static void deflate_value(const struct lwan_value *uncompressed, compressed->len = 0; } -#if defined(HAVE_BROTLI) +#if defined(LWAN_HAVE_BROTLI) static void brotli_value(const struct lwan_value *uncompressed, struct lwan_value *brotli, const struct lwan_value *deflated) @@ -442,7 +442,7 @@ static void brotli_value(const struct lwan_value *uncompressed, } #endif -#if defined(HAVE_ZSTD) +#if defined(LWAN_HAVE_ZSTD) static void zstd_value(const struct lwan_value *uncompressed, struct lwan_value *zstd, const struct lwan_value *deflated) @@ -579,10 +579,10 @@ static bool mmap_init(struct file_cache_entry *ce, md->uncompressed.len = (size_t)st->st_size; deflate_value(&md->uncompressed, &md->deflated); -#if defined(HAVE_BROTLI) +#if defined(LWAN_HAVE_BROTLI) brotli_value(&md->uncompressed, &md->brotli, &md->deflated); #endif -#if defined(HAVE_ZSTD) +#if defined(LWAN_HAVE_ZSTD) zstd_value(&md->uncompressed, &md->zstd, &md->deflated); #endif @@ -734,7 +734,7 @@ static bool dirlist_init(struct file_cache_entry *ce, .len = lwan_strbuf_get_length(&dd->rendered), }; deflate_value(&rendered, &dd->deflated); -#if defined(HAVE_BROTLI) +#if defined(LWAN_HAVE_BROTLI) brotli_value(&rendered, &dd->brotli, &dd->deflated); #endif @@ -907,10 +907,10 @@ static void mmap_free(struct file_cache_entry *fce) if (md->gzip.value) munmap(md->gzip.value, md->gzip.len); free(md->deflated.value); -#if defined(HAVE_BROTLI) +#if defined(LWAN_HAVE_BROTLI) free(md->brotli.value); #endif -#if defined(HAVE_ZSTD) +#if defined(LWAN_HAVE_ZSTD) free(md->zstd.value); #endif } @@ -931,7 +931,7 @@ static void dirlist_free(struct file_cache_entry *fce) lwan_strbuf_free(&dd->rendered); free(dd->deflated.value); -#if defined(HAVE_BROTLI) +#if defined(LWAN_HAVE_BROTLI) free(dd->brotli.value); #endif } @@ -1272,7 +1272,7 @@ mmap_best_data(struct lwan_request *request, *header = NULL; -#if defined(HAVE_ZSTD) +#if defined(LWAN_HAVE_ZSTD) if (md->zstd.len && md->zstd.len < best->len && accepts_encoding(request, REQUEST_ACCEPT_ZSTD)) { best = &md->zstd; @@ -1280,7 +1280,7 @@ mmap_best_data(struct lwan_request *request, } #endif -#if defined(HAVE_BROTLI) +#if defined(LWAN_HAVE_BROTLI) if (md->brotli.len && md->brotli.len < best->len && accepts_encoding(request, REQUEST_ACCEPT_BROTLI)) { best = &md->brotli; @@ -1334,7 +1334,7 @@ static enum lwan_http_status dirlist_serve(struct lwan_request *request, const char *icon = lwan_request_get_query_param(request, "icon"); if (!icon) { -#if defined(HAVE_BROTLI) +#if defined(LWAN_HAVE_BROTLI) if (dd->brotli.len && accepts_encoding(request, REQUEST_ACCEPT_BROTLI)) { return serve_value_ok(request, fce->mime_type, &dd->brotli, br_compression_hdr); diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 53ff4a36c..a0d35b762 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -136,7 +136,7 @@ uint8_t lwan_char_isdigit(char ch) __attribute__((pure)); static ALWAYS_INLINE __attribute__((pure)) size_t lwan_nextpow2(size_t number) { -#if defined(HAVE_BUILTIN_CLZLL) +#if defined(LWAN_HAVE_BUILTIN_CLZLL) static const int size_bits = (int)sizeof(number) * CHAR_BIT; if (sizeof(size_t) == sizeof(unsigned int)) { @@ -163,7 +163,7 @@ static ALWAYS_INLINE __attribute__((pure)) size_t lwan_nextpow2(size_t number) return number + 1; } -#if defined(HAVE_MBEDTLS) +#if defined(LWAN_HAVE_MBEDTLS) #include #include #include @@ -179,7 +179,7 @@ struct lwan_tls_context { }; #endif -#ifdef HAVE_LUA +#ifdef LWAN_HAVE_LUA #include lua_State *lwan_lua_create_state(const char *script_file, const char *script); diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 5720dbaa4..20084b32b 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -688,13 +688,13 @@ parse_accept_encoding(struct lwan_request *request) case STR4_INT(' ','g','z','i'): request->flags |= REQUEST_ACCEPT_GZIP; break; -#if defined(HAVE_ZSTD) +#if defined(LWAN_HAVE_ZSTD) case STR4_INT('z','s','t','d'): case STR4_INT(' ','z','s','t'): request->flags |= REQUEST_ACCEPT_ZSTD; break; #endif -#if defined(HAVE_BROTLI) +#if defined(LWAN_HAVE_BROTLI) default: while (lwan_char_isspace(*p)) p++; diff --git a/src/lib/lwan-status.c b/src/lib/lwan-status.c index 389df97aa..c1ea8c9a3 100644 --- a/src/lib/lwan-status.c +++ b/src/lib/lwan-status.c @@ -134,7 +134,7 @@ static long gettid_cached(void) #define FORMAT_WITH_COLOR(fmt, color) "\033[" color "m" fmt "\033[0m" -#ifdef HAVE_SYSLOG +#ifdef LWAN_HAVE_SYSLOG #include #include diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index 1bcf93449..0803cec61 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -22,9 +22,9 @@ #include #include -#if defined(HAVE_BROTLI) +#if defined(LWAN_HAVE_BROTLI) #include -#elif defined(HAVE_ZSTD) +#elif defined(LWAN_HAVE_ZSTD) #include #else #include @@ -46,7 +46,7 @@ void lwan_tables_init(void) lwan_status_debug("Uncompressing MIME type table: %u->%u bytes, %d entries", MIME_COMPRESSED_LEN, MIME_UNCOMPRESSED_LEN, MIME_ENTRIES); -#if defined(HAVE_BROTLI) +#if defined(LWAN_HAVE_BROTLI) size_t uncompressed_length = MIME_UNCOMPRESSED_LEN; BrotliDecoderResult ret; @@ -55,7 +55,7 @@ void lwan_tables_init(void) uncompressed_mime_entries); if (ret != BROTLI_DECODER_RESULT_SUCCESS) lwan_status_critical("Error while uncompressing table with Brotli"); -#elif defined(HAVE_ZSTD) +#elif defined(LWAN_HAVE_ZSTD) size_t uncompressed_length = ZSTD_decompress(uncompressed_mime_entries, MIME_UNCOMPRESSED_LEN, mime_entries_compressed, MIME_COMPRESSED_LEN); diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index c5f308041..71070740b 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -897,7 +897,7 @@ void lwan_append_double_to_strbuf(struct lwan_strbuf *buf, void *ptr) bool lwan_tpl_double_is_empty(void *ptr) { -#if defined(HAVE_BUILTIN_FPCLASSIFY) +#if defined(LWAN_HAVE_BUILTIN_FPCLASSIFY) return __builtin_fpclassify(FP_NAN, FP_INFINITE, FP_NORMAL, FP_SUBNORMAL, FP_ZERO, *(double *)ptr); #else diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 9cb20ed31..194e9031e 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -31,11 +31,11 @@ #include #include -#if defined(HAVE_SO_ATTACH_REUSEPORT_CBPF) +#if defined(LWAN_HAVE_SO_ATTACH_REUSEPORT_CBPF) #include #endif -#if defined(HAVE_MBEDTLS) +#if defined(LWAN_HAVE_MBEDTLS) #include #include #include @@ -148,7 +148,7 @@ uint64_t lwan_request_get_id(struct lwan_request *request) return helper->request_id; } -#if defined(HAVE_MBEDTLS) +#if defined(LWAN_HAVE_MBEDTLS) static bool lwan_setup_tls_keys(int fd, const mbedtls_ssl_context *ssl, int rx_or_tx) { @@ -270,8 +270,10 @@ static int lwan_mbedtls_recv(void *ctx, unsigned char *buf, size_t len) struct lwan_mbedtls_handshake_ctx *hs_ctx = ctx; ssize_t r; - if (hs_ctx->last_was_send) + if (hs_ctx->last_was_send) { flush_pending_output(hs_ctx->fd); + hs_ctx->last_was_send = false; + } r = recv(hs_ctx->fd, buf, len, 0); if (UNLIKELY(r < 0)) { @@ -288,7 +290,6 @@ static int lwan_mbedtls_recv(void *ctx, unsigned char *buf, size_t len) if (UNLIKELY((ssize_t)(int)r != r)) return MBEDTLS_ERR_SSL_INTERNAL_ERROR; - hs_ctx->last_was_send = false; return (int)r; } @@ -382,7 +383,7 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, const size_t init_gen = 1; /* 1 call to coro_defer() */ assert(init_gen == coro_deferred_get_generation(coro)); -#if defined(HAVE_MBEDTLS) +#if defined(LWAN_HAVE_MBEDTLS) if (conn->flags & CONN_TLS) { if (UNLIKELY(!lwan_setup_tls(lwan, conn))) { coro_yield(conn->coro, CONN_CORO_ABORT); @@ -677,7 +678,7 @@ static ALWAYS_INLINE bool spawn_coro(struct lwan_connection *conn, struct timeout_queue *tq) { struct lwan_thread *t = conn->thread; -#if defined(HAVE_MBEDTLS) +#if defined(LWAN_HAVE_MBEDTLS) const enum lwan_connection_flags flags_to_keep = conn->flags & CONN_TLS; #else const enum lwan_connection_flags flags_to_keep = 0; @@ -788,7 +789,7 @@ static bool accept_waiting_clients(const struct lwan_thread *t, int listen_fd = (int)(intptr_t)(listen_socket - conns); enum lwan_connection_flags new_conn_flags = 0; -#if defined(HAVE_MBEDTLS) +#if defined(LWAN_HAVE_MBEDTLS) if (listen_socket->flags & CONN_LISTENER_HTTPS) { assert(listen_fd == t->tls_listen_fd); assert(!(listen_socket->flags & CONN_LISTENER_HTTP)); @@ -852,7 +853,7 @@ static int create_listen_socket(struct lwan_thread *t, lwan_status_critical("Could not create listen_fd"); /* Ignore errors here, as this is just a hint */ -#if defined(HAVE_SO_ATTACH_REUSEPORT_CBPF) +#if defined(LWAN_HAVE_SO_ATTACH_REUSEPORT_CBPF) /* From socket(7): "These options may be set repeatedly at any time on * any socket in the group to replace the current BPF program used by * all sockets in the group." */ @@ -872,7 +873,7 @@ static int create_listen_socket(struct lwan_thread *t, (void)setsockopt(listen_fd, SOL_SOCKET, SO_LOCK_FILTER, (int[]){1}, sizeof(int)); } -#elif defined(HAVE_SO_INCOMING_CPU) && defined(__x86_64__) +#elif defined(LWAN_HAVE_SO_INCOMING_CPU) && defined(__x86_64__) (void)setsockopt(listen_fd, SOL_SOCKET, SO_INCOMING_CPU, &t->cpu, sizeof(t->cpu)); #endif @@ -1118,7 +1119,7 @@ adjust_thread_affinity(const struct lwan_thread *thread) } #endif -#if defined(HAVE_MBEDTLS) +#if defined(LWAN_HAVE_MBEDTLS) static bool is_tls_ulp_supported(void) { FILE *available_ulp = fopen("/proc/sys/net/ipv4/tcp_available_ulp", "re"); @@ -1243,7 +1244,7 @@ static bool lwan_init_tls(struct lwan *l) void lwan_thread_init(struct lwan *l) { const unsigned int total_conns = l->thread.max_fd * l->thread.count; -#if defined(HAVE_MBEDTLS) +#if defined(LWAN_HAVE_MBEDTLS) const bool tls_initialized = lwan_init_tls(l); #else const bool tls_initialized = false; @@ -1382,7 +1383,7 @@ void lwan_thread_shutdown(struct lwan *l) free(l->thread.threads); -#if defined(HAVE_MBEDTLS) +#if defined(LWAN_HAVE_MBEDTLS) if (l->tls) { mbedtls_ssl_config_free(&l->tls->config); mbedtls_x509_crt_free(&l->tls->server_cert); diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 842afa36a..d099ae1ad 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -39,7 +39,7 @@ #include "lwan-config.h" #include "lwan-http-authorize.h" -#if defined(HAVE_LUA) +#if defined(LWAN_HAVE_LUA) #include "lwan-lua.h" #endif @@ -453,7 +453,7 @@ const char *lwan_get_config_path(char *path_buf, size_t path_buf_len) static void parse_tls_listener(struct config *conf, const struct config_line *line, struct lwan *lwan) { -#if !defined(HAVE_MBEDTLS) +#if !defined(LWAN_HAVE_MBEDTLS) config_error(conf, "Lwan has been built without mbedTLS support"); return; #endif diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 07d2afc88..7e332855f 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -481,7 +481,7 @@ struct lwan { struct lwan_connection *conns; struct lwan_value headers; -#if defined(HAVE_MBEDTLS) +#if defined(LWAN_HAVE_MBEDTLS) struct lwan_tls_context *tls; #endif diff --git a/src/lib/missing-pthread.c b/src/lib/missing-pthread.c index a9ff10a1a..4d5180a12 100644 --- a/src/lib/missing-pthread.c +++ b/src/lib/missing-pthread.c @@ -28,7 +28,7 @@ #include "lwan-private.h" -#ifndef HAVE_PTHREADBARRIER +#ifndef LWAN_HAVE_PTHREADBARRIER #define PTHREAD_BARRIER_SERIAL_THREAD -1 int diff --git a/src/lib/missing.c b/src/lib/missing.c index e3d42de38..311e8745b 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -34,7 +34,7 @@ #include "lwan.h" -#ifndef HAVE_MEMPCPY +#ifndef LWAN_HAVE_MEMPCPY void *mempcpy(void *dest, const void *src, size_t len) { char *p = memcpy(dest, src, len); @@ -42,7 +42,7 @@ void *mempcpy(void *dest, const void *src, size_t len) } #endif -#ifndef HAVE_MEMRCHR +#ifndef LWAN_HAVE_MEMRCHR void *memrchr(const void *s, int c, size_t n) { const char *end = (const char *)s + n + 1; @@ -58,7 +58,7 @@ void *memrchr(const void *s, int c, size_t n) } #endif -#ifndef HAVE_PIPE2 +#ifndef LWAN_HAVE_PIPE2 int pipe2(int pipefd[2], int flags) { int r; @@ -82,7 +82,7 @@ int pipe2(int pipefd[2], int flags) } #endif -#ifndef HAVE_ACCEPT4 +#ifndef LWAN_HAVE_ACCEPT4 int accept4(int sock, struct sockaddr *addr, socklen_t *addrlen, int flags) { int fd = accept(sock, addr, addrlen); @@ -117,7 +117,7 @@ int accept4(int sock, struct sockaddr *addr, socklen_t *addrlen, int flags) } #endif -#ifndef HAVE_CLOCK_GETTIME +#ifndef LWAN_HAVE_CLOCK_GETTIME int clock_gettime(clockid_t clk_id, struct timespec *ts) { switch (clk_id) { @@ -134,7 +134,7 @@ int clock_gettime(clockid_t clk_id, struct timespec *ts) } #endif -#if !defined(HAVE_EPOLL) && defined(HAVE_KQUEUE) +#if !defined(LWAN_HAVE_EPOLL) && defined(LWAN_HAVE_KQUEUE) #include #include #include @@ -256,12 +256,12 @@ int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) hash_free(coalesce); return (int)(intptr_t)(ev - events); } -#elif !defined(HAVE_EPOLL) +#elif !defined(LWAN_HAVE_EPOLL) #error epoll() not implemented for this platform #endif #if defined(__linux__) || defined(__CYGWIN__) -#if defined(HAVE_GETAUXVAL) +#if defined(LWAN_HAVE_GETAUXVAL) #include #endif @@ -275,7 +275,7 @@ int proc_pidpath(pid_t pid, void *buffer, size_t buffersize) return -1; } -#if defined(HAVE_GETAUXVAL) +#if defined(LWAN_HAVE_GETAUXVAL) const char *execfn = (const char *)getauxval(AT_EXECFN); if (execfn) { @@ -322,7 +322,7 @@ int proc_pidpath(pid_t pid, void *buffer, size_t buffersize) return 0; } -#elif defined(HAVE_DLADDR) && !defined(__APPLE__) +#elif defined(LWAN_HAVE_DLADDR) && !defined(__APPLE__) #include int proc_pidpath(pid_t pid, void *buffer, size_t buffersize) @@ -360,7 +360,7 @@ int proc_pidpath(pid_t pid, void *buffer, size_t buffersize) #if defined(__linux__) -#if !defined(HAVE_GETTID) +#if !defined(LWAN_HAVE_GETTID) #include pid_t gettid(void) { return (pid_t)syscall(SYS_gettid); } @@ -442,7 +442,7 @@ int getresgid(gid_t *rgid, gid_t *egid, gid_t *sgid) } #endif -#if !defined(HAVE_MKOSTEMP) +#if !defined(LWAN_HAVE_MKOSTEMP) int mkostemp(char *tmpl, int flags) { int fd, fl; @@ -469,7 +469,7 @@ int mkostemp(char *tmpl, int flags) } #endif -#if !defined(HAVE_REALLOCARRAY) +#if !defined(LWAN_HAVE_REALLOCARRAY) /* $OpenBSD: reallocarray.c,v 1.2 2014/12/08 03:45:00 bcook Exp $ */ /* * Copyright (c) 2008 Otto Moerbeek @@ -493,7 +493,7 @@ int mkostemp(char *tmpl, int flags) #include #include -#if !defined(HAVE_BUILTIN_MUL_OVERFLOW) +#if !defined(LWAN_HAVE_BUILTIN_MUL_OVERFLOW) /* * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW @@ -525,12 +525,12 @@ void *reallocarray(void *optr, size_t nmemb, size_t size) } return realloc(optr, total_size); } -#endif /* HAVE_REALLOCARRAY */ +#endif /* LWAN_HAVE_REALLOCARRAY */ -#if !defined(HAVE_READAHEAD) +#if !defined(LWAN_HAVE_READAHEAD) ssize_t readahead(int fd, off_t offset, size_t count) { -#if defined(HAVE_POSIX_FADVISE) +#if defined(LWAN_HAVE_POSIX_FADVISE) return (ssize_t)posix_fadvise(fd, offset, (off_t)count, POSIX_FADV_WILLNEED); #else @@ -543,7 +543,7 @@ ssize_t readahead(int fd, off_t offset, size_t count) } #endif -#if !defined(HAVE_GET_CURRENT_DIR_NAME) +#if !defined(LWAN_HAVE_GET_CURRENT_DIR_NAME) #include char *get_current_dir_name(void) @@ -576,7 +576,7 @@ int capset(struct __user_cap_header_struct *header, } #endif -#if !defined(HAVE_FWRITE_UNLOCKED) +#if !defined(LWAN_HAVE_FWRITE_UNLOCKED) size_t fwrite_unlocked(const void *ptr, size_t size, size_t n, FILE *stream) { size_t to_write = size * n; @@ -602,7 +602,7 @@ size_t fwrite_unlocked(const void *ptr, size_t size, size_t n, FILE *stream) } #endif -#if !defined(HAVE_STATFS) +#if !defined(LWAN_HAVE_STATFS) int statfs(const char *path, struct statfs *buf) { (void)path; @@ -639,7 +639,7 @@ long int lwan_getentropy(void *buffer, size_t buffer_len, int flags) return r; } -#elif defined(HAVE_GETENTROPY) +#elif defined(LWAN_HAVE_GETENTROPY) long int lwan_getentropy(void *buffer, size_t buffer_len, int flags) { (void)flags; diff --git a/src/lib/missing/assert.h b/src/lib/missing/assert.h index e99df3dd1..8523dd9e9 100644 --- a/src/lib/missing/assert.h +++ b/src/lib/missing/assert.h @@ -23,7 +23,7 @@ #define MISSING_ASSERT_H #undef static_assert -#if defined(HAVE_STATIC_ASSERT) +#if defined(LWAN_HAVE_STATIC_ASSERT) # define static_assert(expr, msg) _Static_assert(expr, msg) #else # define static_assert(expr, msg) diff --git a/src/lib/missing/fcntl.h b/src/lib/missing/fcntl.h index 0bc14c705..f64221f30 100644 --- a/src/lib/missing/fcntl.h +++ b/src/lib/missing/fcntl.h @@ -53,7 +53,7 @@ #endif /* __linux__ */ -#ifndef HAVE_READAHEAD +#ifndef LWAN_HAVE_READAHEAD #include ssize_t readahead(int fd, off_t offset, size_t count); diff --git a/src/lib/missing/linux/capability.h b/src/lib/missing/linux/capability.h index 1699249e5..17ee9519c 100644 --- a/src/lib/missing/linux/capability.h +++ b/src/lib/missing/linux/capability.h @@ -21,7 +21,7 @@ #ifndef __MISSING_CAPABILITY_H__ #define __MISSING_CAPABILITY_H__ -#if defined(__linux__) && defined(HAVE_LINUX_CAPABILITY) +#if defined(__linux__) && defined(LWAN_HAVE_LINUX_CAPABILITY) #include_next #include diff --git a/src/lib/missing/pthread.h b/src/lib/missing/pthread.h index d6e86fa7c..86476ac10 100644 --- a/src/lib/missing/pthread.h +++ b/src/lib/missing/pthread.h @@ -23,7 +23,7 @@ #ifndef MISSING_PTHREAD_H #define MISSING_PTHREAD_H -#ifndef HAVE_PTHREADBARRIER +#ifndef LWAN_HAVE_PTHREADBARRIER typedef int pthread_barrierattr_t; typedef struct pthread_barrier { unsigned int count; @@ -43,7 +43,7 @@ int pthread_barrier_wait(pthread_barrier_t *barrier); #include #endif -#ifndef HAVE_PTHREAD_SET_NAME_NP +#ifndef LWAN_HAVE_PTHREAD_SET_NAME_NP int pthread_set_name_np(pthread_t thread, const char *name); #endif diff --git a/src/lib/missing/stdio.h b/src/lib/missing/stdio.h index 7a5abaf2e..84cd2ffa1 100644 --- a/src/lib/missing/stdio.h +++ b/src/lib/missing/stdio.h @@ -23,7 +23,7 @@ #ifndef MISSING_STDIO_H #define MISSING_STDIO_H -#if !defined(HAVE_FWRITE_UNLOCKED) +#if !defined(LWAN_HAVE_FWRITE_UNLOCKED) size_t fwrite_unlocked(const void *ptr, size_t size, size_t n, FILE *stream); #endif diff --git a/src/lib/missing/stdlib.h b/src/lib/missing/stdlib.h index bc54fbc62..727a6756c 100644 --- a/src/lib/missing/stdlib.h +++ b/src/lib/missing/stdlib.h @@ -26,25 +26,25 @@ #if defined(__GLIBC__) #include #if defined(__GLIBC_PREREQ) && __GLIBC_PREREQ(2, 17) -#define HAVE_SECURE_GETENV +#define LWAN_HAVE_SECURE_GETENV #endif #if defined(__GLIBC_PREREQ) && __GLIBC_PREREQ(2, 19) -#define HAVE_MKOSTEMPS +#define LWAN_HAVE_MKOSTEMPS #endif #if defined(__GLIBC_PREREQ) && __GLIBC_PREREQ(2, 7) -#define HAVE_CORRECT_UMASK_TMPFILE +#define LWAN_HAVE_CORRECT_UMASK_TMPFILE #endif #endif -#if !defined(HAVE_SECURE_GETENV) +#if !defined(LWAN_HAVE_SECURE_GETENV) static inline char *secure_getenv(const char *name) { return getenv(name); } #endif -#if !defined(HAVE_MKOSTEMP) +#if !defined(LWAN_HAVE_MKOSTEMP) int mkostemp(char *tmpl, int flags); #endif -#if defined(HAVE_CORRECT_UMASK_TMPFILE) +#if defined(LWAN_HAVE_CORRECT_UMASK_TMPFILE) #define umask_for_tmpfile(mask_) \ ({ \ (void)(mask_); \ @@ -54,7 +54,7 @@ int mkostemp(char *tmpl, int flags); #define umask_for_tmpfile(mask_) umask(mask_) #endif -#if !defined(HAVE_REALLOCARRAY) +#if !defined(LWAN_HAVE_REALLOCARRAY) void *reallocarray(void *optr, size_t nmemb, size_t size); #endif diff --git a/src/lib/missing/string.h b/src/lib/missing/string.h index b0f24572e..290a19c25 100644 --- a/src/lib/missing/string.h +++ b/src/lib/missing/string.h @@ -44,18 +44,18 @@ #ifdef NEED_ALLOCA_H #undef NEED_ALLOCA_H -#ifdef HAVE_ALLOCA_H +#ifdef LWAN_HAVE_ALLOCA_H #include #else #include #endif #endif -#ifndef HAVE_MEMPCPY +#ifndef LWAN_HAVE_MEMPCPY void *mempcpy(void *dest, const void *src, size_t len); #endif -#ifndef HAVE_MEMRCHR +#ifndef LWAN_HAVE_MEMRCHR void *memrchr(const void *s, int c, size_t n); #endif diff --git a/src/lib/missing/sys/epoll.h b/src/lib/missing/sys/epoll.h index 7e0fded47..449ade775 100644 --- a/src/lib/missing/sys/epoll.h +++ b/src/lib/missing/sys/epoll.h @@ -18,7 +18,7 @@ * USA. */ -#if !defined(HAVE_EPOLL) +#if !defined(LWAN_HAVE_EPOLL) #pragma once #include diff --git a/src/lib/missing/sys/socket.h b/src/lib/missing/sys/socket.h index 6fcaae7fe..b34d3b140 100644 --- a/src/lib/missing/sys/socket.h +++ b/src/lib/missing/sys/socket.h @@ -39,7 +39,7 @@ #define SOMAXCONN 128 #endif -#ifndef HAVE_ACCEPT4 +#ifndef LWAN_HAVE_ACCEPT4 int accept4(int sock, struct sockaddr *addr, socklen_t *addrlen, int flags); #endif diff --git a/src/lib/missing/sys/types.h b/src/lib/missing/sys/types.h index a749b4548..4f75f32ea 100644 --- a/src/lib/missing/sys/types.h +++ b/src/lib/missing/sys/types.h @@ -23,7 +23,7 @@ #ifndef MISSING_SYS_TYPES_H #define MISSING_SYS_TYPES_H -#ifndef HAVE_GETTID +#ifndef LWAN_HAVE_GETTID pid_t gettid(void); #endif diff --git a/src/lib/missing/sys/vfs.h b/src/lib/missing/sys/vfs.h index c6de417eb..abba29d50 100644 --- a/src/lib/missing/sys/vfs.h +++ b/src/lib/missing/sys/vfs.h @@ -29,7 +29,7 @@ #ifndef _MISSING_VFS_H_ #define _MISSING_VFS_H_ -#if !defined(HAVE_STATFS) +#if !defined(LWAN_HAVE_STATFS) struct statfs { int f_type; }; diff --git a/src/lib/missing/time.h b/src/lib/missing/time.h index 9e71e5480..af1c60404 100644 --- a/src/lib/missing/time.h +++ b/src/lib/missing/time.h @@ -23,7 +23,7 @@ #ifndef MISSING_TIME_H #define MISSING_TIME_H -#ifndef HAVE_CLOCK_GETTIME +#ifndef LWAN_HAVE_CLOCK_GETTIME typedef int clockid_t; int clock_gettime(clockid_t clk_id, struct timespec *ts); #endif diff --git a/src/lib/missing/unistd.h b/src/lib/missing/unistd.h index eaca0dac1..8d6f018da 100644 --- a/src/lib/missing/unistd.h +++ b/src/lib/missing/unistd.h @@ -23,7 +23,7 @@ #ifndef MISSING_UNISTD_H #define MISSING_UNISTD_H -#ifndef HAVE_PIPE2 +#ifndef LWAN_HAVE_PIPE2 int pipe2(int pipefd[2], int flags); #endif @@ -38,7 +38,7 @@ int getresgid(gid_t *rgid, gid_t *egid, gid_t *sgid) __attribute__((warn_unused_result)); #endif -#if !defined(HAVE_GET_CURRENT_DIR_NAME) +#if !defined(LWAN_HAVE_GET_CURRENT_DIR_NAME) char *get_current_dir_name(void); #endif diff --git a/src/samples/clock/numbers.c b/src/samples/clock/numbers.c index c8da3bdac..4e39f577e 100644 --- a/src/samples/clock/numbers.c +++ b/src/samples/clock/numbers.c @@ -10,7 +10,7 @@ * implied warranty. */ -#ifdef HAVE_CONFIG_H +#ifdef LWAN_HAVE_CONFIG_H #include "config.h" #endif From 8f7f539032b434d6d6a629325aeee208ede1f02a Mon Sep 17 00:00:00 2001 From: eecheng87 Date: Tue, 15 Mar 2022 12:37:30 +0800 Subject: [PATCH 1907/2505] Tweak the chunk size to be sent According to the experiment done in issue#334, the performance (throughput) sharply drops after 20 KiB. Follow Nginx, it allow to upload file with limit 2MB. In this commit, I tweak the chunk size from 1<<14 to 1<<21. Close #334 --- src/lib/lwan-io-wrappers.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index 7ed955de5..fb0c2ba2c 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -218,16 +218,22 @@ void lwan_sendfile(struct lwan_request *request, const char *header, size_t header_len) { - /* Clamp each chunk to 2^14 bytes as that's the maximum TLS record size[1]. - * First chunk is clamped to 2^14 - header_len, because the header is sent - * using MSG_MORE. Subsequent chunks are sized 2^14 bytes. - * (Do this regardless of this connection being TLS or not for simplicity.) + /* Clamp each chunk to 2^21 bytes[1] to balance throughput and + * scalability. This used to be capped to 2^14 bytes, as that's the + * maximum TLS record size[2], but was found to be hurtful for + * performance[2], so use the same default value that Nginx uses. + * + * First chunk is clamped to 2^21 - header_len, because the header is + * sent using MSG_MORE. Subsequent chunks are sized 2^21 bytes. (Do + * this regardless of this connection being TLS or not for simplicity.) + * * [1] https://www.kernel.org/doc/html/v5.12/networking/tls.html#sending-tls-application-data + * [2] https://github.com/lpereira/lwan/issues/334 */ - size_t chunk_size = LWAN_MIN(count, (1ul << 14) - header_len); + size_t chunk_size = LWAN_MIN(count, (1ul << 21) - header_len); size_t to_be_written = count; - assert(header_len < (1ul << 14)); + assert(header_len < (1ul << 21)); lwan_send(request, header, header_len, MSG_MORE); @@ -248,7 +254,7 @@ void lwan_sendfile(struct lwan_request *request, if (!to_be_written) break; - chunk_size = LWAN_MIN(to_be_written, 1ul << 14); + chunk_size = LWAN_MIN(to_be_written, 1ul << 21); lwan_readahead_queue(in_fd, offset, chunk_size); try_again: From 440e61b0be772bec2edf78840860f4d408301f8f Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 19 Mar 2022 20:06:54 -0700 Subject: [PATCH 1908/2505] Fix set up of 1ms timer in each I/O thread The 1000ms timer was set up in the thread that accepted the connection, which isn't necessarily the thread that would service it. Because of this, things that required that timeout weren't working; notably, the timeout queue and the date cache. --- src/lib/lwan-thread.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 194e9031e..ea85eed13 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -917,7 +917,7 @@ static void *thread_io_loop(void *data) for (;;) { int timeout = turn_timer_wheel(&tq, t, epoll_fd); int n_fds = epoll_wait(epoll_fd, events, max_events, timeout); - bool accepted_connections = false; + bool created_coros = false; if (UNLIKELY(n_fds < 0)) { if (errno == EBADF || errno == EINVAL) @@ -934,10 +934,8 @@ static void *thread_io_loop(void *data) } if (conn->flags & (CONN_LISTENER_HTTP | CONN_LISTENER_HTTPS)) { - if (LIKELY(accept_waiting_clients(t, conn))) { - accepted_connections = true; + if (LIKELY(accept_waiting_clients(t, conn))) continue; - } close(epoll_fd); epoll_fd = -1; break; @@ -948,13 +946,15 @@ static void *thread_io_loop(void *data) send_last_response_without_coro(t->lwan, conn, HTTP_INTERNAL_ERROR); continue; } + + created_coros = true; } resume_coro(&tq, conn, epoll_fd); timeout_queue_move_to_last(&tq, conn); } - if (accepted_connections) + if (created_coros) timeouts_add(t->wheel, &tq.timeout, 1000); } From 532f852633a2ec1b37b907e4ebb9f514d13b4c78 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 19 Mar 2022 23:16:36 -0700 Subject: [PATCH 1909/2505] Fix compilation-time checks Some compilation-time checks weren't being honored after prefixing variables with the LWAN_* prefix because I ended up not changing all of the checks to use the new variable names. --- CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 21354601d..163c967f3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,7 +96,7 @@ endif () option(ENABLE_TLS "Enable support for TLS (Linux-only)" "ON") if (ENABLE_TLS) check_include_file(linux/tls.h LWAN_HAVE_LINUX_TLS_H) - if (HAVE_LINUX_TLS_H) + if (LWAN_HAVE_LINUX_TLS_H) # TLS support requires Linux, as Lwan uses the kTLS flavor # only supported there. # TODO: Try using BearSSL instead of mbedTLS and only link @@ -146,7 +146,7 @@ option(USE_SYSLOG "Enable syslog" "OFF") if (${USE_SYSLOG} STREQUAL "ON" AND LWAN_HAVE_SYSLOG_FUNC) set(LWAN_HAVE_SYSLOG 1) endif () -if (HAVE_SYSLOG) +if (LWAN_HAVE_SYSLOG) message(STATUS "Using syslog/rsyslog for logging.") endif () @@ -169,7 +169,7 @@ check_include_file(linux/capability.h LWAN_HAVE_LINUX_CAPABILITY) check_include_file(sys/auxv.h LWAN_HAVE_SYS_AUXV) check_include_file(sys/epoll.h LWAN_HAVE_EPOLL) check_include_files("sys/time.h;sys/types.h;sys/event.h" LWAN_HAVE_SYS_EVENT) -if (HAVE_SYS_EVENT) +if (LWAN_HAVE_SYS_EVENT) set(CMAKE_EXTRA_INCLUDE_FILES ${CMAKE_EXTRA_INCLUDE_FILES} sys/event.h sys/types.h sys/time.h @@ -177,7 +177,7 @@ if (HAVE_SYS_EVENT) check_function_exists(kqueue LWAN_HAVE_KQUEUE) endif () check_include_file(alloca.h LWAN_HAVE_ALLOCA_H) -if (HAVE_SYS_AUXV) +if (LWAN_HAVE_SYS_AUXV) set(CMAKE_EXTRA_INCLUDE_FILES ${CMAKE_EXTRA_INCLUDE_FILES} sys/auxv.h From e637f1ea724389a36dcab02affb6ec3fe5ecb0b6 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 19 Mar 2022 23:41:23 -0700 Subject: [PATCH 1910/2505] Remove temporary references to WSGI module This module isn't ready yet and I ended up forgetting to skip these references when committing changes to this file. --- src/lib/CMakeLists.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index d9469faa0..abdc81f90 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -49,10 +49,6 @@ if (LWAN_HAVE_LUA) list(APPEND SOURCES lwan-lua.c lwan-mod-lua.c) endif () -if (LWAN_HAVE_PYTHON) - list(APPEND SOURCES lwan-mod-wsgi.c) -endif () - add_library(lwan-static STATIC ${SOURCES}) set_target_properties(lwan-static PROPERTIES OUTPUT_NAME lwan CLEAN_DIRECT_OUTPUT 1) @@ -159,7 +155,6 @@ install(FILES lwan-mod-response.h lwan-mod-redirect.h lwan-mod-lua.h - lwan-mod-wsgi.h lwan-status.h lwan-template.h lwan-trie.h From a7385f115ff183988eb6bc6fd6a27240ce0a3336 Mon Sep 17 00:00:00 2001 From: pontscho Date: Tue, 8 Mar 2022 11:50:01 +0100 Subject: [PATCH 1911/2505] WebSocket: fixing opcode handling --- src/lib/lwan-websocket.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index 506c4b57b..3ed194725 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -89,9 +89,7 @@ static inline void lwan_response_websocket_write(struct lwan_request *request, u { size_t len = lwan_strbuf_get_length(request->response.buffer); char *msg = lwan_strbuf_get_buffer(request->response.buffer); - /* FIXME: does it make a difference if we use WS_OPCODE_TEXT or - * WS_OPCODE_BINARY? */ - unsigned char header = 0x80 | WS_OPCODE_TEXT; + unsigned char header = 0x80 | op; if (!(request->conn->flags & CONN_IS_WEBSOCKET)) return; From b5bdd1bfa02b13cb86b2ae4cb3b9fb89422234e3 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 2 Apr 2022 18:36:49 -0700 Subject: [PATCH 1912/2505] Add fuzzer for HPACK Huffman decoder While I (slowly) work on HTTP/2, let's fuzz our Huffman decoder for HPACK in the meantime. This has survived a few minutes on my laptop, but I'm curious what can be done over weeks in oss-fuzz. --- fuzz/corpus/corpus-h2_huffman-1 | 11 + fuzz/corpus/corpus-h2_huffman-2 | 2 + fuzz/corpus/corpus-h2_huffman-3 | 1 + src/bin/fuzz/h2_huffman_fuzzer.cc | 17 ++ src/lib/CMakeLists.txt | 1 + src/lib/lwan-h2-huffman.c | 381 ++++++++++++++++++++++++++++++ 6 files changed, 413 insertions(+) create mode 100644 fuzz/corpus/corpus-h2_huffman-1 create mode 100644 fuzz/corpus/corpus-h2_huffman-2 create mode 100644 fuzz/corpus/corpus-h2_huffman-3 create mode 100644 src/bin/fuzz/h2_huffman_fuzzer.cc create mode 100644 src/lib/lwan-h2-huffman.c diff --git a/fuzz/corpus/corpus-h2_huffman-1 b/fuzz/corpus/corpus-h2_huffman-1 new file mode 100644 index 000000000..8edd843c5 --- /dev/null +++ b/fuzz/corpus/corpus-h2_huffman-1 @@ -0,0 +1,11 @@ +եK�2iĦK��.��O��D�d(d(5l} +D�z��9�:����%�J} �J�E,(�҃�H�j��ɉu�9T��&��:��9���9jS"��=ڦ�R1�rȥ%PiS��!��HRSԕ%=�Tj��֚�� + D��VZ��_)�hYkPYI�@�)9T���b�j/��g���Ρm)!Ԡ�B�gU R��¨�R_�T�dR��Zc-�S�J�{���%'L�C�B�j�i RGE��Q��'T���)8�D�U$(�P�H����B��z�)=.���>�I�EjR_�L�N>�RC�U��<�R:�(��T�Q3�*dP��:T��Q'Rq�*���(j�EfIT<�S�J��]K��L�N>�R[h�҄=R��K"��� ����҇�I�b�-�jKRTAԔv(4)Qf��I��Jm$�&q�L�٧J�*$�N>�R��AH�ȡ��Q eE�u2IA��A��~���B���)O��T�VsT��ȢGԪTu ad2TIԧ� D��9T�iIlYc%C!A�1�J�a/�+:T!C%u7�[ +��*��Z��Cʥ��J�z�*��E+��'҇�DP �$�Gґڕ�Ū�TU %�C!A�`�2}.�2�d)QҠԤu)�s��i'*g�*x2gRs!I�T�P�)9��g'R� ���9T!�M���ғ� +Rt%D$v&�U2��v �-�BLE �8Ҥ�P�Ptx�(j�Z�Z=)����dJ�B���������Z�҆��U2���٧�Җ�AP�QT�hR� R}(yT�{:���!�S|尪 +Tu%�� R��L�GL�e��k-���R¡+�? \ No newline at end of file diff --git a/fuzz/corpus/corpus-h2_huffman-2 b/fuzz/corpus/corpus-h2_huffman-2 new file mode 100644 index 000000000..539971170 --- /dev/null +++ b/fuzz/corpus/corpus-h2_huffman-2 @@ -0,0 +1,2 @@ +��c��ɻ��^R�j�f�q7 +�]c�{*��d��~/������6A \ No newline at end of file diff --git a/fuzz/corpus/corpus-h2_huffman-3 b/fuzz/corpus/corpus-h2_huffman-3 new file mode 100644 index 000000000..4eabe97b7 --- /dev/null +++ b/fuzz/corpus/corpus-h2_huffman-3 @@ -0,0 +1 @@ +�d�ue,� \ No newline at end of file diff --git a/src/bin/fuzz/h2_huffman_fuzzer.cc b/src/bin/fuzz/h2_huffman_fuzzer.cc new file mode 100644 index 000000000..270ae7a5e --- /dev/null +++ b/src/bin/fuzz/h2_huffman_fuzzer.cc @@ -0,0 +1,17 @@ +#include +#include + +extern "C" { +uint8_t *lwan_h2_huffman_decode_for_fuzzing(const uint8_t *input, + size_t input_len); + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + uint8_t *decoded = lwan_h2_huffman_decode_for_fuzzing(data, size); + if (decoded) { + free(decoded); + return 0; + } + return 1; +} +} diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index abdc81f90..f371a799e 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -43,6 +43,7 @@ set(SOURCES sd-daemon.c sha1.c timeout.c + lwan-h2-huffman.c ) if (LWAN_HAVE_LUA) diff --git a/src/lib/lwan-h2-huffman.c b/src/lib/lwan-h2-huffman.c new file mode 100644 index 000000000..a8d0f624a --- /dev/null +++ b/src/lib/lwan-h2-huffman.c @@ -0,0 +1,381 @@ +/* + * This code has been automatically generated by gentables.py from + * the table provided in RFC7541. Do not modify! + * + * This is being included here so we can fuzz-test the decoder in + * oss-fuzz before we go forward with the HTTP/2 implementation. + * + * lwan - simple web server + * Copyright (c) 2022 L. A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) + +#include +#include +#include +#include +#include +#include +#include + +#define LIKELY(x) x +#define UNLIKELY(x) x + +static inline uint64_t read64be(const void *ptr) +{ + uint64_t v; + memcpy(&v, ptr, 8); + return htobe64(v); +} + +static inline uint32_t read32be(const void *ptr) +{ + uint32_t v; + memcpy(&v, ptr, 4); + return htobe32(v); +} + +struct h2_huffman_code { + uint8_t symbol; + int8_t num_bits; +}; + +static const struct h2_huffman_code level0[256] = { + [0 ... 7] = {48, 5}, [8 ... 15] = {49, 5}, + [16 ... 23] = {50, 5}, [24 ... 31] = {97, 5}, + [32 ... 39] = {99, 5}, [40 ... 47] = {101, 5}, + [48 ... 55] = {105, 5}, [56 ... 63] = {111, 5}, + [64 ... 71] = {115, 5}, [72 ... 79] = {116, 5}, + [80 ... 83] = {32, 6}, [84 ... 87] = {37, 6}, + [88 ... 91] = {45, 6}, [92 ... 95] = {46, 6}, + [96 ... 99] = {47, 6}, [100 ... 103] = {51, 6}, + [104 ... 107] = {52, 6}, [108 ... 111] = {53, 6}, + [112 ... 115] = {54, 6}, [116 ... 119] = {55, 6}, + [120 ... 123] = {56, 6}, [124 ... 127] = {57, 6}, + [128 ... 131] = {61, 6}, [132 ... 135] = {65, 6}, + [136 ... 139] = {95, 6}, [140 ... 143] = {98, 6}, + [144 ... 147] = {100, 6}, [148 ... 151] = {102, 6}, + [152 ... 155] = {103, 6}, [156 ... 159] = {104, 6}, + [160 ... 163] = {108, 6}, [164 ... 167] = {109, 6}, + [168 ... 171] = {110, 6}, [172 ... 175] = {112, 6}, + [176 ... 179] = {114, 6}, [180 ... 183] = {117, 6}, + [184 ... 185] = {58, 7}, [186 ... 187] = {66, 7}, + [188 ... 189] = {67, 7}, [190 ... 191] = {68, 7}, + [192 ... 193] = {69, 7}, [194 ... 195] = {70, 7}, + [196 ... 197] = {71, 7}, [198 ... 199] = {72, 7}, + [200 ... 201] = {73, 7}, [202 ... 203] = {74, 7}, + [204 ... 205] = {75, 7}, [206 ... 207] = {76, 7}, + [208 ... 209] = {77, 7}, [210 ... 211] = {78, 7}, + [212 ... 213] = {79, 7}, [214 ... 215] = {80, 7}, + [216 ... 217] = {81, 7}, [218 ... 219] = {82, 7}, + [220 ... 221] = {83, 7}, [222 ... 223] = {84, 7}, + [224 ... 225] = {85, 7}, [226 ... 227] = {86, 7}, + [228 ... 229] = {87, 7}, [230 ... 231] = {89, 7}, + [232 ... 233] = {106, 7}, [234 ... 235] = {107, 7}, + [236 ... 237] = {113, 7}, [238 ... 239] = {118, 7}, + [240 ... 241] = {119, 7}, [242 ... 243] = {120, 7}, + [244 ... 245] = {121, 7}, [246 ... 247] = {122, 7}, + [248] = {38, 8}, [249] = {42, 8}, + [250] = {44, 8}, [251] = {59, 8}, + [252] = {88, 8}, [253] = {90, 8}, +}; + +static inline const struct h2_huffman_code *next_level0(uint8_t peeked_byte) +{ + static const struct h2_huffman_code level0_11111111[256] = { + [0 ... 63] = {63, 2}, [64 ... 95] = {39, 3}, + [96 ... 127] = {43, 3}, [128 ... 159] = {124, 3}, + [160 ... 175] = {35, 4}, [176 ... 191] = {62, 4}, + [192 ... 199] = {0, 5}, [200 ... 207] = {36, 5}, + [208 ... 215] = {64, 5}, [216 ... 223] = {91, 5}, + [224 ... 231] = {93, 5}, [232 ... 239] = {126, 5}, + [240 ... 243] = {94, 6}, [244 ... 247] = {125, 6}, + [248 ... 249] = {60, 7}, [250 ... 251] = {96, 7}, + [252 ... 253] = {123, 7}, + }; + static const struct h2_huffman_code level0_11111110[256] = { + [0 ... 63] = {33, 2}, + [64 ... 127] = {34, 2}, + [128 ... 191] = {40, 2}, + [192 ... 255] = {41, 2}, + }; + return peeked_byte & 1 ? level0_11111111 : level0_11111110; +} + +static inline const struct h2_huffman_code *next_level1(uint8_t peeked_byte) +{ + static const struct h2_huffman_code level1_11111111[256] = { + [0 ... 7] = {176, 5}, [8 ... 15] = {177, 5}, + [16 ... 23] = {179, 5}, [24 ... 31] = {209, 5}, + [32 ... 39] = {216, 5}, [40 ... 47] = {217, 5}, + [48 ... 55] = {227, 5}, [56 ... 63] = {229, 5}, + [64 ... 71] = {230, 5}, [72 ... 75] = {129, 6}, + [76 ... 79] = {132, 6}, [80 ... 83] = {133, 6}, + [84 ... 87] = {134, 6}, [88 ... 91] = {136, 6}, + [92 ... 95] = {146, 6}, [96 ... 99] = {154, 6}, + [100 ... 103] = {156, 6}, [104 ... 107] = {160, 6}, + [108 ... 111] = {163, 6}, [112 ... 115] = {164, 6}, + [116 ... 119] = {169, 6}, [120 ... 123] = {170, 6}, + [124 ... 127] = {173, 6}, [128 ... 131] = {178, 6}, + [132 ... 135] = {181, 6}, [136 ... 139] = {185, 6}, + [140 ... 143] = {186, 6}, [144 ... 147] = {187, 6}, + [148 ... 151] = {189, 6}, [152 ... 155] = {190, 6}, + [156 ... 159] = {196, 6}, [160 ... 163] = {198, 6}, + [164 ... 167] = {228, 6}, [168 ... 171] = {232, 6}, + [172 ... 175] = {233, 6}, [176 ... 177] = {1, 7}, + [178 ... 179] = {135, 7}, [180 ... 181] = {137, 7}, + [182 ... 183] = {138, 7}, [184 ... 185] = {139, 7}, + [186 ... 187] = {140, 7}, [188 ... 189] = {141, 7}, + [190 ... 191] = {143, 7}, [192 ... 193] = {147, 7}, + [194 ... 195] = {149, 7}, [196 ... 197] = {150, 7}, + [198 ... 199] = {151, 7}, [200 ... 201] = {152, 7}, + [202 ... 203] = {155, 7}, [204 ... 205] = {157, 7}, + [206 ... 207] = {158, 7}, [208 ... 209] = {165, 7}, + [210 ... 211] = {166, 7}, [212 ... 213] = {168, 7}, + [214 ... 215] = {174, 7}, [216 ... 217] = {175, 7}, + [218 ... 219] = {180, 7}, [220 ... 221] = {182, 7}, + [222 ... 223] = {183, 7}, [224 ... 225] = {188, 7}, + [226 ... 227] = {191, 7}, [228 ... 229] = {197, 7}, + [230 ... 231] = {231, 7}, [232 ... 233] = {239, 7}, + [234] = {9, 8}, [235] = {142, 8}, + [236] = {144, 8}, [237] = {145, 8}, + [238] = {148, 8}, [239] = {159, 8}, + [240] = {171, 8}, [241] = {206, 8}, + [242] = {215, 8}, [243] = {225, 8}, + [244] = {236, 8}, [245] = {237, 8}, + }; + static const struct h2_huffman_code level1_11111110[256] = { + [0 ... 31] = {92, 3}, [32 ... 63] = {195, 3}, + [64 ... 95] = {208, 3}, [96 ... 111] = {128, 4}, + [112 ... 127] = {130, 4}, [128 ... 143] = {131, 4}, + [144 ... 159] = {162, 4}, [160 ... 175] = {184, 4}, + [176 ... 191] = {194, 4}, [192 ... 207] = {224, 4}, + [208 ... 223] = {226, 4}, [224 ... 231] = {153, 5}, + [232 ... 239] = {161, 5}, [240 ... 247] = {167, 5}, + [248 ... 255] = {172, 5}, + }; + return peeked_byte & 1 ? level1_11111111 : level1_11111110; +} + +static inline const struct h2_huffman_code *next_level2(uint8_t peeked_byte) +{ + static const struct h2_huffman_code level2_11111110[256] = { + [0 ... 31] = {254, 3}, [32 ... 47] = {2, 4}, + [48 ... 63] = {3, 4}, [64 ... 79] = {4, 4}, + [80 ... 95] = {5, 4}, [96 ... 111] = {6, 4}, + [112 ... 127] = {7, 4}, [128 ... 143] = {8, 4}, + [144 ... 159] = {11, 4}, [160 ... 175] = {12, 4}, + [176 ... 191] = {14, 4}, [192 ... 207] = {15, 4}, + [208 ... 223] = {16, 4}, [224 ... 239] = {17, 4}, + [240 ... 255] = {18, 4}, + }; + static const struct h2_huffman_code level2_11111111[256] = { + [0 ... 15] = {19, 4}, [16 ... 31] = {20, 4}, + [32 ... 47] = {21, 4}, [48 ... 63] = {23, 4}, + [64 ... 79] = {24, 4}, [80 ... 95] = {25, 4}, + [96 ... 111] = {26, 4}, [112 ... 127] = {27, 4}, + [128 ... 143] = {28, 4}, [144 ... 159] = {29, 4}, + [160 ... 175] = {30, 4}, [176 ... 191] = {31, 4}, + [192 ... 207] = {127, 4}, [208 ... 223] = {220, 4}, + [224 ... 239] = {249, 4}, [240 ... 243] = {10, 6}, + [244 ... 247] = {13, 6}, [248 ... 251] = {22, 6}, + [252 ... 255] = {0, -1}, + }; + static const struct h2_huffman_code level2_11111000[256] = { + [0 ... 63] = {192, 2}, + [64 ... 127] = {193, 2}, + [128 ... 191] = {200, 2}, + [192 ... 255] = {201, 2}, + }; + static const struct h2_huffman_code level2_11110110[256] = { + [0 ... 127] = {199, 1}, + [128 ... 255] = {207, 1}, + }; + static const struct h2_huffman_code level2_11111001[256] = { + [0 ... 63] = {202, 2}, + [64 ... 127] = {205, 2}, + [128 ... 191] = {210, 2}, + [192 ... 255] = {213, 2}, + }; + static const struct h2_huffman_code level2_11111011[256] = { + [0 ... 63] = {242, 2}, [64 ... 127] = {243, 2}, + [128 ... 191] = {255, 2}, [192 ... 223] = {203, 3}, + [224 ... 255] = {204, 3}, + }; + static const struct h2_huffman_code level2_11111100[256] = { + [0 ... 31] = {211, 3}, [32 ... 63] = {212, 3}, + [64 ... 95] = {214, 3}, [96 ... 127] = {221, 3}, + [128 ... 159] = {222, 3}, [160 ... 191] = {223, 3}, + [192 ... 223] = {241, 3}, [224 ... 255] = {244, 3}, + }; + static const struct h2_huffman_code level2_11111010[256] = { + [0 ... 63] = {218, 2}, + [64 ... 127] = {219, 2}, + [128 ... 191] = {238, 2}, + [192 ... 255] = {240, 2}, + }; + static const struct h2_huffman_code level2_11110111[256] = { + [0 ... 127] = {234, 1}, + [128 ... 255] = {235, 1}, + }; + static const struct h2_huffman_code level2_11111101[256] = { + [0 ... 31] = {245, 3}, [32 ... 63] = {246, 3}, + [64 ... 95] = {247, 3}, [96 ... 127] = {248, 3}, + [128 ... 159] = {250, 3}, [160 ... 191] = {251, 3}, + [192 ... 223] = {252, 3}, [224 ... 255] = {253, 3}, + }; + switch (peeked_byte) { + case 0b11111110: + return level2_11111110; + case 0b11111111: + return level2_11111111; + case 0b11111000: + return level2_11111000; + case 0b11110110: + return level2_11110110; + case 0b11111001: + return level2_11111001; + case 0b11111011: + return level2_11111011; + case 0b11111100: + return level2_11111100; + case 0b11111010: + return level2_11111010; + case 0b11110111: + return level2_11110111; + case 0b11111101: + return level2_11111101; + default: + return NULL; + } +} + +struct bit_reader { + const uint8_t *bitptr; + uint64_t bitbuf; + uint64_t total_bitcount; + int bitcount; +}; + +static inline uint8_t peek_byte(struct bit_reader *reader) +{ + if (reader->bitcount < 8) { + if (reader->total_bitcount >= 64) { + reader->bitbuf |= read64be(reader->bitptr) >> reader->bitcount; + reader->bitptr += + (63 - reader->bitcount + (reader->bitcount & 1)) >> 3; + reader->bitcount |= 56; + } else if (reader->total_bitcount >= 32) { + reader->bitbuf |= read32be(reader->bitptr) >> reader->bitcount; + reader->bitptr += + (31 - reader->bitcount + (reader->bitcount & 1)) >> 3; + reader->bitcount |= 24; + } else { + reader->bitbuf |= *reader->bitptr >> reader->bitcount; + reader->bitptr += + (7 - reader->bitcount + (reader->bitcount & 1)) >> 3; + reader->bitcount |= 8; + } + } + return reader->bitbuf >> 56; +} + +static inline bool consume(struct bit_reader *reader, int count) +{ + assert(count > 0); + reader->bitbuf <<= count; + reader->bitcount -= count; + reader->total_bitcount -= count; + return !__builtin_sub_overflow(reader->total_bitcount, count, + &reader->total_bitcount); +} + +static inline size_t output_size(size_t input_size) +{ + /* Smallest input is 5 bits which produces 8 bits. Scaling that to 8 bits, + * we get 12.8 bits of output per 8 bits of input. */ + return (input_size * 128) / 10; +} + +uint8_t *lwan_h2_huffman_decode_for_fuzzing(const uint8_t *input, + size_t input_len) +{ + uint8_t *output = malloc(output_size(input_len)); + uint8_t *ret = output; + struct bit_reader bit_reader = {.bitptr = input, + .total_bitcount = input_len * 8}; + + while ((int64_t)bit_reader.total_bitcount > 0) { + uint8_t peeked_byte = peek_byte(&bit_reader); + if (LIKELY(level0[peeked_byte].num_bits)) { + *output++ = level0[peeked_byte].symbol; + consume(&bit_reader, level0[peeked_byte].num_bits); + continue; + } + + if (!consume(&bit_reader, 8)) + goto fail; + + const struct h2_huffman_code *level1 = next_level0(peeked_byte); + peeked_byte = peek_byte(&bit_reader); + if (level1[peeked_byte].num_bits) { + *output++ = level1[peeked_byte].symbol; + consume(&bit_reader, level1[peeked_byte].num_bits); + continue; + } + + if (!consume(&bit_reader, 8)) + goto fail; + + const struct h2_huffman_code *level2 = next_level1(peeked_byte); + peeked_byte = peek_byte(&bit_reader); + if (level2[peeked_byte].num_bits) { + *output++ = level2[peeked_byte].symbol; + consume(&bit_reader, level2[peeked_byte].num_bits); + continue; + } + + if (!consume(&bit_reader, 8)) + goto fail; + + const struct h2_huffman_code *level3 = next_level2(peeked_byte); + if (LIKELY(level3)) { + peeked_byte = peek_byte(&bit_reader); + if (level3[peeked_byte].num_bits < 0) { + /* EOS found */ + return ret; + } + if (LIKELY(level3[peeked_byte].num_bits)) { + *output++ = level3[peeked_byte].symbol; + consume(&bit_reader, level3[peeked_byte].num_bits); + continue; + } + } + + goto fail; + } + + return ret; + +fail: + free(ret); + return NULL; +} + +#endif From 7d4a98fd568701cf5a6be3d5d83298a4a79258c8 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 2 Apr 2022 18:41:29 -0700 Subject: [PATCH 1913/2505] Make straitjacket functions available from public headers again This was requested by a user. --- src/lib/liblwan.sym | 2 ++ src/lib/lwan-private.h | 3 +-- src/lib/lwan.h | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib/liblwan.sym b/src/lib/liblwan.sym index 039230042..4c8088401 100644 --- a/src/lib/liblwan.sym +++ b/src/lib/liblwan.sym @@ -74,6 +74,8 @@ global: lwan_request_await_*; lwan_request_async_*; + lwan_straitjacket_enforce_*; + local: *; }; diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index a0d35b762..2534aa727 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -126,7 +126,6 @@ void lwan_default_response(struct lwan_request *request, void lwan_fill_default_response(struct lwan_strbuf *buffer, enum lwan_http_status status); -void lwan_straitjacket_enforce_from_config(struct config *c); const char *lwan_get_config_path(char *path_buf, size_t path_buf_len); @@ -266,7 +265,7 @@ int lwan_connection_get_fd(const struct lwan *lwan, int lwan_format_rfc_time(const time_t in, char out LWAN_ARRAY_PARAM(30)); int lwan_parse_rfc_time(const char in LWAN_ARRAY_PARAM(30), time_t *out); -void lwan_straitjacket_enforce(const struct lwan_straitjacket *sj); +void lwan_straitjacket_enforce_from_config(struct config *c); uint64_t lwan_request_get_id(struct lwan_request *request); diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 7e332855f..4821ee380 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -586,6 +586,8 @@ void lwan_request_await_read_write(struct lwan_request *r, int fd); ssize_t lwan_request_async_read(struct lwan_request *r, int fd, void *buf, size_t len); ssize_t lwan_request_async_write(struct lwan_request *r, int fd, const void *buf, size_t len); +void lwan_straitjacket_enforce(const struct lwan_straitjacket *sj); + #if defined(__cplusplus) } #endif From d7fa49968a50107519421d388fd3de60ecb3f17a Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 2 Apr 2022 21:09:32 -0700 Subject: [PATCH 1914/2505] Handle remaining bits while decoding compressed HPACK data This improves the condition to stop the decoding loop and handle possible errors as required by the RFC. --- src/lib/lwan-h2-huffman.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-h2-huffman.c b/src/lib/lwan-h2-huffman.c index a8d0f624a..15898bf71 100644 --- a/src/lib/lwan-h2-huffman.c +++ b/src/lib/lwan-h2-huffman.c @@ -321,7 +321,7 @@ uint8_t *lwan_h2_huffman_decode_for_fuzzing(const uint8_t *input, struct bit_reader bit_reader = {.bitptr = input, .total_bitcount = input_len * 8}; - while ((int64_t)bit_reader.total_bitcount > 0) { + while ((int64_t)bit_reader.total_bitcount > 7) { uint8_t peeked_byte = peek_byte(&bit_reader); if (LIKELY(level0[peeked_byte].num_bits)) { *output++ = level0[peeked_byte].symbol; @@ -371,6 +371,29 @@ uint8_t *lwan_h2_huffman_decode_for_fuzzing(const uint8_t *input, goto fail; } + /* FIXME: ensure we're not promoting types unnecessarily here */ + if (bit_reader.total_bitcount) { + const uint8_t peeked_byte = peek_byte(&bit_reader); + const uint8_t eos_prefix = ((1 << bit_reader.total_bitcount) - 1) + << (8 - bit_reader.total_bitcount); + + if ((peeked_byte & eos_prefix) == eos_prefix) + goto done; + + if (level0[peeked_byte].num_bits == (int8_t)bit_reader.total_bitcount) { + *output = level0[peeked_byte].symbol; + goto done; + } + + /* If we get here, then the remaining bits are either: + * - Not a prefix of EOS + * - Incomplete sequence + * - Has overlong padding + */ + goto fail; + } + +done: return ret; fail: From 2cc2abeed89a5ea16609dce61188fee83d520f72 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 2 Apr 2022 21:22:20 -0700 Subject: [PATCH 1915/2505] Add code to generate the prototype Huffman decoder --- src/scripts/gentables.py | 318 ++++++++++++++++++++++++++++++++++ src/scripts/huffman-table.txt | 257 +++++++++++++++++++++++++++ 2 files changed, 575 insertions(+) create mode 100755 src/scripts/gentables.py create mode 100644 src/scripts/huffman-table.txt diff --git a/src/scripts/gentables.py b/src/scripts/gentables.py new file mode 100755 index 000000000..e957f3673 --- /dev/null +++ b/src/scripts/gentables.py @@ -0,0 +1,318 @@ +#!/usr/bin/python +# Prototype decoder for Huffman-encoded HPACK data +# Copyright (c) 2022 L. A. F. Pereira +# +# How this works: This is a standard Huffman decoder, but instead of +# using a traditional binary tree and use each input bit to drive the +# traversal, going down a tree level for every input bit, it has a +# table where the index helps traverse this tree multiple levels in +# parallel. In practice, this means that for the shortest -- and thus +# common -- symbols, one can traverse the binary tree with a single +# memory access. This improves cache utilization and makes memory +# prefetching and branch prediction more useful. +# +# This is my first foray into data compression, so I'm sure I'm missing +# some obvious trick known by folks used to implementing these kinds of +# things. Some of the things I've identified that could be improved, +# so far: +# +# - Some branches could be removed, especially when processing +# incorrect inputs. +# - Some tables can be reduced and be converted to pure code. For +# instance, level2_11111010[] could be a tiny switch/case statement +# instead of 8 lines of L1 cache. Some tables, like +# level2_11110110[], do not have a lot of information stored in them +# (two entries, only 1 bit per entry is useful, etc.), which is a +# good candidate for something like this too. +# - GCC and Clang offer a way to say if a branch is likely or unlikely, +# and specify the probability for them. This is perfect because we +# know the probability -- it's exactly what the length of a Huffman +# code conveys. Might be a good idea to find a way to exploit this. +# +# The input for this script is a table obtained directly from the HPACK +# RFC. +# +# This script could potentially be cleaned up and written in a more +# generic way, but I just wanted to experiment with the idea without +# spending a lot of time on something that could potentially not work. + +from collections import defaultdict + +def pad_table(symbols): + table = {} + to_delete = set() + for symbol, bins in symbols.items(): + short_bin = int(bins[0], 2) + shift_by = len(bins[0]) + + if symbol == 256: # EOS + element = (0, -1) + else: + element = (symbol, shift_by) + + if len(bins) == 1: + if shift_by == 8: + table[(short_bin, short_bin)] = element + else: + short_bin <<= 8 - shift_by + from_code = short_bin + to_code = short_bin + (1 << (8 - shift_by)) - 1 + + table[(from_code, to_code)] = element + to_delete.add(symbol) + + next_table = defaultdict(lambda: {}) + for symbol, bins in symbols.items(): + if not symbol in to_delete: + next_table[bins[0]][symbol] = bins[1:] + + return table, next_table + +def print_table_contents(table): + for code in sorted(table.keys()): + symbol, length = table[code] + if length == 0: + continue + code_start, code_end = code + + # Instead of storing length as the total length of the symbol, we store + # the relative length from the current point. This simplifies the + # implementation of peek_byte() and (especially) consume(). + if code_start == code_end: + print(f"[{code_start}] = {{ {symbol}, {length} }},") + else: + print(f"[{code_start} ... {code_end}] = {{ {symbol}, {length} }},") + +def generate_level(level, next_table): + next_tables = [] + + print(f"static inline const struct h2_huffman_code *next_level{level}(uint8_t peeked_byte) {{") + + for bin, table in next_table.items(): + print(f"static const struct h2_huffman_code level{level}_{bin}[256] = {{") + table, next_table_ = pad_table(table) + print_table_contents(table) + if next_table_: + next_tables.append(next_table_) + print("};") + + generated = False + if len(next_table) == 2: + values = tuple(next_table.keys()) + value0 = int(values[0], 2) + value1 = int(values[1], 2) + + mask = value0 & ~value1 + if mask.bit_length() == 1: + print(f"return peeked_byte & {mask} ? level{level}_{values[0]} : level{level}_{values[1]};") + generated = True + + if not generated: + print("switch (peeked_byte) {") + for bin0 in next_table.keys(): + print(f"case 0b{bin0}: ") + print(f"return level{level}_{bin0};") + print("default: return NULL;") + print("}") + + print("}") + + return next_tables + +if __name__ == '__main__': + print("""#include +#include +#include +#include +#include +#include +#include +#define LIKELY(x) x +#define UNLIKELY(x) x +static inline uint64_t read64be(const void *ptr) { + uint64_t v; + memcpy(&v, ptr, 8); + return htobe64(v); +} + +""") + + symbols = {} + for symbol, line in enumerate(open("huffman-table.txt")): + _, code = line.strip().split(") |", 1) + code, _ = code.split(" ", 1) + symbols[symbol] = code.split("|") + + first_level, next_table_first_level = pad_table(symbols) + + print("struct h2_huffman_code {") + print(" uint8_t symbol;") + print(" int8_t num_bits;") + print("};") + + print(f"static const struct h2_huffman_code level0[256] = {{") + print_table_contents(first_level) + print("};") + + for table in generate_level(0, next_table_first_level): + for table in generate_level(1, table): + # FIXME: some of the tables in level 2 are too big and convey very + # little information. maybe for these levels we could generate + # C code instead? these symbols should be really rare anyway + level2 = generate_level(2, table) + assert(not level2) + + # These have been inspired by Fabian Giesen's blog posts on "reading bits in + # far too many ways". Part 2, specifically: https://fgiesen.wordpress.com/2018/02/20/reading-bits-in-far-too-many-ways-part-2/ + print("""struct bit_reader { + const uint8_t *bitptr; + uint64_t bitbuf; + uint64_t total_bitcount; + int bitcount; +}; + +static inline uint8_t peek_byte(struct bit_reader *reader) +{ + if (reader->bitcount < 8) { + // FIXME: need to use shorter reads depending on total_bitcount! + reader->bitbuf |= read64be(reader->bitptr) >> reader->bitcount; + reader->bitptr += (63 - reader->bitcount + (reader->bitcount & 1)) >> 3; + reader->bitcount |= 56; + } + return reader->bitbuf >> 56; +} + +static inline bool consume(struct bit_reader *reader, int count) +{ + assert(count > 0); + reader->bitbuf <<= count; + reader->bitcount -= count; + return !__builtin_sub_overflow(reader->total_bitcount, count, &reader->total_bitcount); +} +""") + + print("""static inline size_t output_size(size_t input_size) { + /* Smallest input is 5 bits which produces 8 bits. Scaling that to 8 bits, we + * get 12.8 bits of output per 8 bits of input. */ + return (input_size * 128) / 10; +}""") + + print("""uint8_t *h2_huffman_decode(const uint8_t *input, size_t input_len) +{ + uint8_t *output = malloc(output_size(input_len)); + uint8_t *ret = output; + struct bit_reader bit_reader = {.bitptr = input, + .total_bitcount = input_len * 8}; + + while ((int64_t)bit_reader.total_bitcount > 7) { + uint8_t peeked_byte = peek_byte(&bit_reader); + if (LIKELY(level0[peeked_byte].num_bits)) { + *output++ = level0[peeked_byte].symbol; + consume(&bit_reader, level0[peeked_byte].num_bits); + continue; + } + + if (!consume(&bit_reader, 8)) + goto fail; + + const struct h2_huffman_code *level1 = next_level0(peeked_byte); + peeked_byte = peek_byte(&bit_reader); + if (level1[peeked_byte].num_bits) { + *output++ = level1[peeked_byte].symbol; + consume(&bit_reader, level1[peeked_byte].num_bits); + continue; + } + + if (!consume(&bit_reader, 8)) + goto fail; + + const struct h2_huffman_code *level2 = next_level1(peeked_byte); + peeked_byte = peek_byte(&bit_reader); + if (level2[peeked_byte].num_bits) { + *output++ = level2[peeked_byte].symbol; + consume(&bit_reader, level2[peeked_byte].num_bits); + continue; + } + + if (!consume(&bit_reader, 8)) + goto fail; + + const struct h2_huffman_code *level3 = next_level2(peeked_byte); + if (LIKELY(level3)) { + peeked_byte = peek_byte(&bit_reader); + if (UNLIKELY(level3[peeked_byte].num_bits < 0)) { + /* EOS found */ + return ret; + } + if (LIKELY(level3[peeked_byte].num_bits)) { + *output++ = level3[peeked_byte].symbol; + consume(&bit_reader, level3[peeked_byte].num_bits); + continue; + } + } + + goto fail; + } + + /* FIXME: ensure we're not promoting types unnecessarily here */ + if (bit_reader.total_bitcount) { + const uint8_t peeked_byte = peek_byte(&bit_reader); + const uint8_t eos_prefix = ((1 << bit_reader.total_bitcount) - 1) + << (8 - bit_reader.total_bitcount); + + if ((peeked_byte & eos_prefix) == eos_prefix) + goto done; + + if (level0[peeked_byte].num_bits == (int8_t)bit_reader.total_bitcount) { + *output = level0[peeked_byte].symbol; + goto done; + } + + /* If we get here, then the remaining bits are either: + * - Not a prefix of EOS + * - Incomplete sequence + * - Has overlong padding + */ + goto fail; + } + +done: + return ret; + +fail: + free(ret); + return NULL; +} + +int main(int argc, char *argv[]) { + /* "litespeed" */ + unsigned char litespeed_huff[128] = {0xce, 0x64, 0x97, 0x75, 0x65, 0x2c, 0x9f}; + unsigned char *decoded; + + decoded = h2_huffman_decode(litespeed_huff, 7); + if (!decoded) { + puts("could not decode"); + return 1; + } + printf("%s\\n", !strcmp(decoded, "LiteSpeed") ? "pass!" : "fail!"); + printf("decoded: '%s'\\n", decoded); + free(decoded); + + unsigned char x_fb_debug[128] = { + 0xa7, 0x06, 0xa7, 0x63, 0x97, 0xc6, 0x1d, 0xc9, 0xbb, 0xa3, 0xc6, 0x5e, + 0x52, 0xf2, 0x6a, 0xba, 0x66, 0x17, 0xe6, 0x71, 0x37, 0x0a, 0x3c, 0x74, + 0xb3, 0x8d, 0x12, 0x92, 0x5e, 0x71, 0xf9, 0xea, 0x4d, 0xc2, 0x42, 0x24, + 0xb7, 0xf6, 0x93, 0x66, 0x39, 0xab, 0xd1, 0x8d, 0xff, 0xcf, 0x07, 0xdf, + 0x8b, 0xac, 0x7f, 0xef, 0x65, 0x5d, 0x9f, 0x8c, 0x9d, 0x3c, 0x72, 0x8f, + 0xc5, 0xfd, 0x9e, 0xd0, 0x51, 0xb1, 0xdf, 0x46, 0xc8, 0x20, + }; + decoded = h2_huffman_decode(x_fb_debug, 6*12-2); + if (!decoded) { + puts("could not decode"); + return 1; + } + printf("%s\\n", !strcmp(decoded, "mEO7bfwFStBMwJWfW4pmg2XL25AswjrVlfcfYbxkcS2ssduZmiKoipMH9XwoTGkb+Qnq9bcjwWbwDQzsea/vMQ==") ? "pass!" : "fail!"); + printf("decoded: '%s'\\n", decoded); + free(decoded); +} +""") diff --git a/src/scripts/huffman-table.txt b/src/scripts/huffman-table.txt new file mode 100644 index 000000000..05218ebf1 --- /dev/null +++ b/src/scripts/huffman-table.txt @@ -0,0 +1,257 @@ + ( 0) |11111111|11000 1ff8 [13] + ( 1) |11111111|11111111|1011000 7fffd8 [23] + ( 2) |11111111|11111111|11111110|0010 fffffe2 [28] + ( 3) |11111111|11111111|11111110|0011 fffffe3 [28] + ( 4) |11111111|11111111|11111110|0100 fffffe4 [28] + ( 5) |11111111|11111111|11111110|0101 fffffe5 [28] + ( 6) |11111111|11111111|11111110|0110 fffffe6 [28] + ( 7) |11111111|11111111|11111110|0111 fffffe7 [28] + ( 8) |11111111|11111111|11111110|1000 fffffe8 [28] + ( 9) |11111111|11111111|11101010 ffffea [24] + ( 10) |11111111|11111111|11111111|111100 3ffffffc [30] + ( 11) |11111111|11111111|11111110|1001 fffffe9 [28] + ( 12) |11111111|11111111|11111110|1010 fffffea [28] + ( 13) |11111111|11111111|11111111|111101 3ffffffd [30] + ( 14) |11111111|11111111|11111110|1011 fffffeb [28] + ( 15) |11111111|11111111|11111110|1100 fffffec [28] + ( 16) |11111111|11111111|11111110|1101 fffffed [28] + ( 17) |11111111|11111111|11111110|1110 fffffee [28] + ( 18) |11111111|11111111|11111110|1111 fffffef [28] + ( 19) |11111111|11111111|11111111|0000 ffffff0 [28] + ( 20) |11111111|11111111|11111111|0001 ffffff1 [28] + ( 21) |11111111|11111111|11111111|0010 ffffff2 [28] + ( 22) |11111111|11111111|11111111|111110 3ffffffe [30] + ( 23) |11111111|11111111|11111111|0011 ffffff3 [28] + ( 24) |11111111|11111111|11111111|0100 ffffff4 [28] + ( 25) |11111111|11111111|11111111|0101 ffffff5 [28] + ( 26) |11111111|11111111|11111111|0110 ffffff6 [28] + ( 27) |11111111|11111111|11111111|0111 ffffff7 [28] + ( 28) |11111111|11111111|11111111|1000 ffffff8 [28] + ( 29) |11111111|11111111|11111111|1001 ffffff9 [28] + ( 30) |11111111|11111111|11111111|1010 ffffffa [28] + ( 31) |11111111|11111111|11111111|1011 ffffffb [28] +' ' ( 32) |010100 14 [ 6] +'!' ( 33) |11111110|00 3f8 [10] +'"' ( 34) |11111110|01 3f9 [10] +'#' ( 35) |11111111|1010 ffa [12] +'$' ( 36) |11111111|11001 1ff9 [13] +'%' ( 37) |010101 15 [ 6] +'&' ( 38) |11111000 f8 [ 8] +''' ( 39) |11111111|010 7fa [11] +'(' ( 40) |11111110|10 3fa [10] +')' ( 41) |11111110|11 3fb [10] +'*' ( 42) |11111001 f9 [ 8] +'+' ( 43) |11111111|011 7fb [11] +',' ( 44) |11111010 fa [ 8] +'-' ( 45) |010110 16 [ 6] +'.' ( 46) |010111 17 [ 6] +'/' ( 47) |011000 18 [ 6] +'0' ( 48) |00000 0 [ 5] +'1' ( 49) |00001 1 [ 5] +'2' ( 50) |00010 2 [ 5] +'3' ( 51) |011001 19 [ 6] +'4' ( 52) |011010 1a [ 6] +'5' ( 53) |011011 1b [ 6] +'6' ( 54) |011100 1c [ 6] +'7' ( 55) |011101 1d [ 6] +'8' ( 56) |011110 1e [ 6] +'9' ( 57) |011111 1f [ 6] +':' ( 58) |1011100 5c [ 7] +';' ( 59) |11111011 fb [ 8] +'<' ( 60) |11111111|1111100 7ffc [15] +'=' ( 61) |100000 20 [ 6] +'>' ( 62) |11111111|1011 ffb [12] +'?' ( 63) |11111111|00 3fc [10] +'@' ( 64) |11111111|11010 1ffa [13] +'A' ( 65) |100001 21 [ 6] +'B' ( 66) |1011101 5d [ 7] +'C' ( 67) |1011110 5e [ 7] +'D' ( 68) |1011111 5f [ 7] +'E' ( 69) |1100000 60 [ 7] +'F' ( 70) |1100001 61 [ 7] +'G' ( 71) |1100010 62 [ 7] +'H' ( 72) |1100011 63 [ 7] +'I' ( 73) |1100100 64 [ 7] +'J' ( 74) |1100101 65 [ 7] +'K' ( 75) |1100110 66 [ 7] +'L' ( 76) |1100111 67 [ 7] +'M' ( 77) |1101000 68 [ 7] +'N' ( 78) |1101001 69 [ 7] +'O' ( 79) |1101010 6a [ 7] +'P' ( 80) |1101011 6b [ 7] +'Q' ( 81) |1101100 6c [ 7] +'R' ( 82) |1101101 6d [ 7] +'S' ( 83) |1101110 6e [ 7] +'T' ( 84) |1101111 6f [ 7] +'U' ( 85) |1110000 70 [ 7] +'V' ( 86) |1110001 71 [ 7] +'W' ( 87) |1110010 72 [ 7] +'X' ( 88) |11111100 fc [ 8] +'Y' ( 89) |1110011 73 [ 7] +'Z' ( 90) |11111101 fd [ 8] +'[' ( 91) |11111111|11011 1ffb [13] +'\' ( 92) |11111111|11111110|000 7fff0 [19] +']' ( 93) |11111111|11100 1ffc [13] +'^' ( 94) |11111111|111100 3ffc [14] +'_' ( 95) |100010 22 [ 6] +'`' ( 96) |11111111|1111101 7ffd [15] +'a' ( 97) |00011 3 [ 5] +'b' ( 98) |100011 23 [ 6] +'c' ( 99) |00100 4 [ 5] +'d' (100) |100100 24 [ 6] +'e' (101) |00101 5 [ 5] +'f' (102) |100101 25 [ 6] +'g' (103) |100110 26 [ 6] +'h' (104) |100111 27 [ 6] +'i' (105) |00110 6 [ 5] +'j' (106) |1110100 74 [ 7] +'k' (107) |1110101 75 [ 7] +'l' (108) |101000 28 [ 6] +'m' (109) |101001 29 [ 6] +'n' (110) |101010 2a [ 6] +'o' (111) |00111 7 [ 5] +'p' (112) |101011 2b [ 6] +'q' (113) |1110110 76 [ 7] +'r' (114) |101100 2c [ 6] +'s' (115) |01000 8 [ 5] +'t' (116) |01001 9 [ 5] +'u' (117) |101101 2d [ 6] +'v' (118) |1110111 77 [ 7] +'w' (119) |1111000 78 [ 7] +'x' (120) |1111001 79 [ 7] +'y' (121) |1111010 7a [ 7] +'z' (122) |1111011 7b [ 7] +'{' (123) |11111111|1111110 7ffe [15] +'|' (124) |11111111|100 7fc [11] +'}' (125) |11111111|111101 3ffd [14] +'~' (126) |11111111|11101 1ffd [13] + (127) |11111111|11111111|11111111|1100 ffffffc [28] + (128) |11111111|11111110|0110 fffe6 [20] + (129) |11111111|11111111|010010 3fffd2 [22] + (130) |11111111|11111110|0111 fffe7 [20] + (131) |11111111|11111110|1000 fffe8 [20] + (132) |11111111|11111111|010011 3fffd3 [22] + (133) |11111111|11111111|010100 3fffd4 [22] + (134) |11111111|11111111|010101 3fffd5 [22] + (135) |11111111|11111111|1011001 7fffd9 [23] + (136) |11111111|11111111|010110 3fffd6 [22] + (137) |11111111|11111111|1011010 7fffda [23] + (138) |11111111|11111111|1011011 7fffdb [23] + (139) |11111111|11111111|1011100 7fffdc [23] + (140) |11111111|11111111|1011101 7fffdd [23] + (141) |11111111|11111111|1011110 7fffde [23] + (142) |11111111|11111111|11101011 ffffeb [24] + (143) |11111111|11111111|1011111 7fffdf [23] + (144) |11111111|11111111|11101100 ffffec [24] + (145) |11111111|11111111|11101101 ffffed [24] + (146) |11111111|11111111|010111 3fffd7 [22] + (147) |11111111|11111111|1100000 7fffe0 [23] + (148) |11111111|11111111|11101110 ffffee [24] + (149) |11111111|11111111|1100001 7fffe1 [23] + (150) |11111111|11111111|1100010 7fffe2 [23] + (151) |11111111|11111111|1100011 7fffe3 [23] + (152) |11111111|11111111|1100100 7fffe4 [23] + (153) |11111111|11111110|11100 1fffdc [21] + (154) |11111111|11111111|011000 3fffd8 [22] + (155) |11111111|11111111|1100101 7fffe5 [23] + (156) |11111111|11111111|011001 3fffd9 [22] + (157) |11111111|11111111|1100110 7fffe6 [23] + (158) |11111111|11111111|1100111 7fffe7 [23] + (159) |11111111|11111111|11101111 ffffef [24] + (160) |11111111|11111111|011010 3fffda [22] + (161) |11111111|11111110|11101 1fffdd [21] + (162) |11111111|11111110|1001 fffe9 [20] + (163) |11111111|11111111|011011 3fffdb [22] + (164) |11111111|11111111|011100 3fffdc [22] + (165) |11111111|11111111|1101000 7fffe8 [23] + (166) |11111111|11111111|1101001 7fffe9 [23] + (167) |11111111|11111110|11110 1fffde [21] + (168) |11111111|11111111|1101010 7fffea [23] + (169) |11111111|11111111|011101 3fffdd [22] + (170) |11111111|11111111|011110 3fffde [22] + (171) |11111111|11111111|11110000 fffff0 [24] + (172) |11111111|11111110|11111 1fffdf [21] + (173) |11111111|11111111|011111 3fffdf [22] + (174) |11111111|11111111|1101011 7fffeb [23] + (175) |11111111|11111111|1101100 7fffec [23] + (176) |11111111|11111111|00000 1fffe0 [21] + (177) |11111111|11111111|00001 1fffe1 [21] + (178) |11111111|11111111|100000 3fffe0 [22] + (179) |11111111|11111111|00010 1fffe2 [21] + (180) |11111111|11111111|1101101 7fffed [23] + (181) |11111111|11111111|100001 3fffe1 [22] + (182) |11111111|11111111|1101110 7fffee [23] + (183) |11111111|11111111|1101111 7fffef [23] + (184) |11111111|11111110|1010 fffea [20] + (185) |11111111|11111111|100010 3fffe2 [22] + (186) |11111111|11111111|100011 3fffe3 [22] + (187) |11111111|11111111|100100 3fffe4 [22] + (188) |11111111|11111111|1110000 7ffff0 [23] + (189) |11111111|11111111|100101 3fffe5 [22] + (190) |11111111|11111111|100110 3fffe6 [22] + (191) |11111111|11111111|1110001 7ffff1 [23] + (192) |11111111|11111111|11111000|00 3ffffe0 [26] + (193) |11111111|11111111|11111000|01 3ffffe1 [26] + (194) |11111111|11111110|1011 fffeb [20] + (195) |11111111|11111110|001 7fff1 [19] + (196) |11111111|11111111|100111 3fffe7 [22] + (197) |11111111|11111111|1110010 7ffff2 [23] + (198) |11111111|11111111|101000 3fffe8 [22] + (199) |11111111|11111111|11110110|0 1ffffec [25] + (200) |11111111|11111111|11111000|10 3ffffe2 [26] + (201) |11111111|11111111|11111000|11 3ffffe3 [26] + (202) |11111111|11111111|11111001|00 3ffffe4 [26] + (203) |11111111|11111111|11111011|110 7ffffde [27] + (204) |11111111|11111111|11111011|111 7ffffdf [27] + (205) |11111111|11111111|11111001|01 3ffffe5 [26] + (206) |11111111|11111111|11110001 fffff1 [24] + (207) |11111111|11111111|11110110|1 1ffffed [25] + (208) |11111111|11111110|010 7fff2 [19] + (209) |11111111|11111111|00011 1fffe3 [21] + (210) |11111111|11111111|11111001|10 3ffffe6 [26] + (211) |11111111|11111111|11111100|000 7ffffe0 [27] + (212) |11111111|11111111|11111100|001 7ffffe1 [27] + (213) |11111111|11111111|11111001|11 3ffffe7 [26] + (214) |11111111|11111111|11111100|010 7ffffe2 [27] + (215) |11111111|11111111|11110010 fffff2 [24] + (216) |11111111|11111111|00100 1fffe4 [21] + (217) |11111111|11111111|00101 1fffe5 [21] + (218) |11111111|11111111|11111010|00 3ffffe8 [26] + (219) |11111111|11111111|11111010|01 3ffffe9 [26] + (220) |11111111|11111111|11111111|1101 ffffffd [28] + (221) |11111111|11111111|11111100|011 7ffffe3 [27] + (222) |11111111|11111111|11111100|100 7ffffe4 [27] + (223) |11111111|11111111|11111100|101 7ffffe5 [27] + (224) |11111111|11111110|1100 fffec [20] + (225) |11111111|11111111|11110011 fffff3 [24] + (226) |11111111|11111110|1101 fffed [20] + (227) |11111111|11111111|00110 1fffe6 [21] + (228) |11111111|11111111|101001 3fffe9 [22] + (229) |11111111|11111111|00111 1fffe7 [21] + (230) |11111111|11111111|01000 1fffe8 [21] + (231) |11111111|11111111|1110011 7ffff3 [23] + (232) |11111111|11111111|101010 3fffea [22] + (233) |11111111|11111111|101011 3fffeb [22] + (234) |11111111|11111111|11110111|0 1ffffee [25] + (235) |11111111|11111111|11110111|1 1ffffef [25] + (236) |11111111|11111111|11110100 fffff4 [24] + (237) |11111111|11111111|11110101 fffff5 [24] + (238) |11111111|11111111|11111010|10 3ffffea [26] + (239) |11111111|11111111|1110100 7ffff4 [23] + (240) |11111111|11111111|11111010|11 3ffffeb [26] + (241) |11111111|11111111|11111100|110 7ffffe6 [27] + (242) |11111111|11111111|11111011|00 3ffffec [26] + (243) |11111111|11111111|11111011|01 3ffffed [26] + (244) |11111111|11111111|11111100|111 7ffffe7 [27] + (245) |11111111|11111111|11111101|000 7ffffe8 [27] + (246) |11111111|11111111|11111101|001 7ffffe9 [27] + (247) |11111111|11111111|11111101|010 7ffffea [27] + (248) |11111111|11111111|11111101|011 7ffffeb [27] + (249) |11111111|11111111|11111111|1110 ffffffe [28] + (250) |11111111|11111111|11111101|100 7ffffec [27] + (251) |11111111|11111111|11111101|101 7ffffed [27] + (252) |11111111|11111111|11111101|110 7ffffee [27] + (253) |11111111|11111111|11111101|111 7ffffef [27] + (254) |11111111|11111111|11111110|000 7fffff0 [27] + (255) |11111111|11111111|11111011|10 3ffffee [26] +EOS (256) |11111111|11111111|11111111|111111 3fffffff [30] From 0e9355a0aac992fe321abcc5b547602306fe4343 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 5 Apr 2022 21:41:22 -0700 Subject: [PATCH 1916/2505] Remove refactoring leftover from the H2 huffman decoder --- src/lib/lwan-h2-huffman.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/lwan-h2-huffman.c b/src/lib/lwan-h2-huffman.c index 15898bf71..70cf23d63 100644 --- a/src/lib/lwan-h2-huffman.c +++ b/src/lib/lwan-h2-huffman.c @@ -301,7 +301,6 @@ static inline bool consume(struct bit_reader *reader, int count) assert(count > 0); reader->bitbuf <<= count; reader->bitcount -= count; - reader->total_bitcount -= count; return !__builtin_sub_overflow(reader->total_bitcount, count, &reader->total_bitcount); } From a04bc75dc7a750f774e3becb87ff102fc0366b98 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 9 Apr 2022 12:07:43 -0700 Subject: [PATCH 1917/2505] Add API to fetch remote port --- src/lib/lwan-request.c | 26 +++++++++++++++++++------- src/lib/lwan.h | 5 +++++ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 20084b32b..b2c1fa85f 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1632,12 +1632,15 @@ lwan_connection_get_fd(const struct lwan *lwan, const struct lwan_connection *co } const char * -lwan_request_get_remote_address(struct lwan_request *request, - char buffer[static INET6_ADDRSTRLEN]) +lwan_request_get_remote_address_and_port(struct lwan_request *request, + char buffer[static INET6_ADDRSTRLEN], + uint16_t *port) { struct sockaddr_storage non_proxied_addr = {.ss_family = AF_UNSPEC}; struct sockaddr_storage *sock_addr; + *port = 0; + if (request->flags & REQUEST_PROXIED) { sock_addr = (struct sockaddr_storage *)&request->proxy->from; @@ -1646,7 +1649,6 @@ lwan_request_get_remote_address(struct lwan_request *request, static_assert(sizeof(unspecified) <= INET6_ADDRSTRLEN, "Enough space for unspecified address family"); - return memcpy(buffer, unspecified, sizeof(unspecified)); } } else { @@ -1661,12 +1663,22 @@ lwan_request_get_remote_address(struct lwan_request *request, } if (sock_addr->ss_family == AF_INET) { - return inet_ntop(AF_INET, &((struct sockaddr_in *)sock_addr)->sin_addr, - buffer, INET6_ADDRSTRLEN); + struct sockaddr_in *sin = (struct sockaddr_in *)sock_addr; + *port = ntohs(sin->sin_port); + return inet_ntop(AF_INET, &sin->sin_addr, buffer, INET6_ADDRSTRLEN); } - return inet_ntop(AF_INET6, &((struct sockaddr_in6 *)sock_addr)->sin6_addr, - buffer, INET6_ADDRSTRLEN); + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sock_addr; + *port = ntohs(sin6->sin6_port); + return inet_ntop(AF_INET6, &sin6->sin6_addr, buffer, INET6_ADDRSTRLEN); +} + +const char * +lwan_request_get_remote_address(struct lwan_request *request, + char buffer[static INET6_ADDRSTRLEN]) +{ + uint16_t port; + return lwan_request_get_remote_address_and_port(request, buffer, &port); } static void remove_sleep(void *data1, void *data2) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 4821ee380..e2222f3e9 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -547,6 +547,11 @@ lwan_request_get_remote_address(struct lwan_request *request, char buffer LWAN_ARRAY_PARAM(INET6_ADDRSTRLEN)) __attribute__((warn_unused_result)); +const char * +lwan_request_get_remote_address_and_port(struct lwan_request *request, + char buffer[static INET6_ADDRSTRLEN], + uint16_t *port) + __attribute__((warn_unused_result)); static inline enum lwan_request_flags lwan_request_get_method(const struct lwan_request *request) From 0a4b2e536d67c62d87780fd5ed4f96d06725d626 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 9 Apr 2022 12:08:45 -0700 Subject: [PATCH 1918/2505] Add API to use async_read() with flags --- src/lib/lwan-request.c | 18 +++++++++++++----- src/lib/lwan.h | 1 + 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index b2c1fa85f..3d5353b48 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1951,13 +1951,11 @@ void lwan_request_await_read_write(struct lwan_request *r, int fd) return async_await_fd(r->conn->coro, fd, CONN_CORO_ASYNC_AWAIT_READ_WRITE); } -ssize_t lwan_request_async_read(struct lwan_request *request, - int fd, - void *buf, - size_t len) +ssize_t lwan_request_async_read_flags( + struct lwan_request *request, int fd, void *buf, size_t len, int flags) { while (true) { - ssize_t r = recv(fd, buf, len, MSG_DONTWAIT); + ssize_t r = recv(fd, buf, len, MSG_DONTWAIT | MSG_NOSIGNAL | flags); if (r < 0) { switch (errno) { @@ -1966,6 +1964,8 @@ ssize_t lwan_request_async_read(struct lwan_request *request, /* Fallthrough */ case EINTR: continue; + case EPIPE: + return -errno; } } @@ -1973,6 +1973,14 @@ ssize_t lwan_request_async_read(struct lwan_request *request, } } +ssize_t lwan_request_async_read(struct lwan_request *request, + int fd, + void *buf, + size_t len) +{ + return lwan_request_async_read_flags(request, fd, buf, len, 0); +} + ssize_t lwan_request_async_write(struct lwan_request *request, int fd, const void *buf, diff --git a/src/lib/lwan.h b/src/lib/lwan.h index e2222f3e9..ca70b453e 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -589,6 +589,7 @@ void lwan_request_await_read(struct lwan_request *r, int fd); void lwan_request_await_write(struct lwan_request *r, int fd); void lwan_request_await_read_write(struct lwan_request *r, int fd); ssize_t lwan_request_async_read(struct lwan_request *r, int fd, void *buf, size_t len); +ssize_t lwan_request_async_read_flags(struct lwan_request *request, int fd, void *buf, size_t len, int flags); ssize_t lwan_request_async_write(struct lwan_request *r, int fd, const void *buf, size_t len); void lwan_straitjacket_enforce(const struct lwan_straitjacket *sj); From 14cea03777febe77373109e3fd7303fcae9e02f8 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 9 Apr 2022 12:10:44 -0700 Subject: [PATCH 1919/2505] Add async_writev() API --- src/lib/lwan-request.c | 55 ++++++++++++++++++++++++++++++++++++++++++ src/lib/lwan.h | 4 +++ 2 files changed, 59 insertions(+) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 3d5353b48..ac32c92cd 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -2002,3 +2002,58 @@ ssize_t lwan_request_async_write(struct lwan_request *request, return r; } } + +ssize_t lwan_request_async_writev(struct lwan_request *request, + int fd, + struct iovec *iov, + int iov_count) +{ + ssize_t total_written = 0; + int curr_iov = 0; + + for (int tries = 10; tries;) { + const int remaining_len = (int)(iov_count - curr_iov); + ssize_t written; + + if (remaining_len == 1) { + const struct iovec *vec = &iov[curr_iov]; + return lwan_request_async_write(request, fd, vec->iov_base, + vec->iov_len); + } + + written = writev(fd, iov + curr_iov, (size_t)remaining_len); + if (UNLIKELY(written < 0)) { + /* FIXME: Consider short writes as another try as well? */ + tries--; + + switch (errno) { + case EAGAIN: + case EINTR: + goto try_again; + default: + goto out; + } + } + + total_written += written; + + while (curr_iov < iov_count && + written >= (ssize_t)iov[curr_iov].iov_len) { + written -= (ssize_t)iov[curr_iov].iov_len; + curr_iov++; + } + + if (curr_iov == iov_count) + return total_written; + + iov[curr_iov].iov_base = (char *)iov[curr_iov].iov_base + written; + iov[curr_iov].iov_len -= (size_t)written; + + try_again: + lwan_request_await_write(request, fd); + } + +out: + coro_yield(request->conn->coro, CONN_CORO_ABORT); + __builtin_unreachable(); +} diff --git a/src/lib/lwan.h b/src/lib/lwan.h index ca70b453e..fbdc22fa9 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -591,6 +591,10 @@ void lwan_request_await_read_write(struct lwan_request *r, int fd); ssize_t lwan_request_async_read(struct lwan_request *r, int fd, void *buf, size_t len); ssize_t lwan_request_async_read_flags(struct lwan_request *request, int fd, void *buf, size_t len, int flags); ssize_t lwan_request_async_write(struct lwan_request *r, int fd, const void *buf, size_t len); +ssize_t lwan_request_async_writev(struct lwan_request *request, + int fd, + struct iovec *iov, + int iov_count); void lwan_straitjacket_enforce(const struct lwan_straitjacket *sj); From ea9060a335d3c572474084bada7156ce0b9182c7 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 9 Apr 2022 12:12:40 -0700 Subject: [PATCH 1920/2505] Add misc. private APIs to support the upcoming FastCGI module --- src/lib/lwan-private.h | 6 ++++ src/lib/lwan-request.c | 63 ++++++++++++++++++++++++++++------------- src/lib/lwan-response.c | 32 +++++++++++++++------ src/lib/lwan.h | 5 ++++ 4 files changed, 77 insertions(+), 29 deletions(-) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 2534aa727..2c27900d2 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -269,3 +269,9 @@ void lwan_straitjacket_enforce_from_config(struct config *c); uint64_t lwan_request_get_id(struct lwan_request *request); +bool lwan_parse_headers(struct lwan_request_parser_helper *helper, + char *buffer); +const char *lwan_request_get_header_from_helper(struct lwan_request_parser_helper *helper, + const char *header); +bool lwan_request_seems_complete(struct lwan_request_parser_helper *helper); + diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index ac32c92cd..118f5a8d9 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -586,6 +586,12 @@ static bool parse_headers(struct lwan_request_parser_helper *helper, #undef HEADER_LENGTH #undef SET_HEADER_VALUE +bool lwan_parse_headers(struct lwan_request_parser_helper *helper, + char *buffer) +{ + return parse_headers(helper, buffer); +} + static void parse_if_modified_since(struct lwan_request_parser_helper *helper) { static const size_t header_len = @@ -884,14 +890,13 @@ client_read(struct lwan_request *request, } static enum lwan_read_finalizer -read_request_finalizer(const struct lwan_value *buffer, - size_t want_to_read __attribute__((unused)), - const struct lwan_request *request, - int n_packets) +read_request_finalizer_from_helper(const struct lwan_value *buffer, + struct lwan_request_parser_helper *helper, + int n_packets, + bool allow_proxy_reqs) { static const size_t min_proxied_request_size = MIN_REQUEST_SIZE + sizeof(struct proxy_header_v2); - struct lwan_request_parser_helper *helper = request->helper; if (LIKELY(buffer->len >= MIN_REQUEST_SIZE)) { STRING_SWITCH (buffer->value + buffer->len - 4) { @@ -911,8 +916,7 @@ read_request_finalizer(const struct lwan_value *buffer, if (crlfcrlf_to_base >= MIN_REQUEST_SIZE - 4) return FINALIZER_DONE; - if (buffer->len > min_proxied_request_size && - request->flags & REQUEST_ALLOW_PROXY_REQS) { + if (buffer->len > min_proxied_request_size && allow_proxy_reqs) { /* FIXME: Checking for PROXYv2 protocol header here is a layering * violation. */ STRING_SWITCH_LARGE (crlfcrlf + 4) { @@ -934,6 +938,17 @@ read_request_finalizer(const struct lwan_value *buffer, return FINALIZER_TRY_AGAIN; } +static inline enum lwan_read_finalizer +read_request_finalizer(const struct lwan_value *buffer, + size_t want_to_read __attribute__((unused)), + const struct lwan_request *request, + int n_packets) +{ + return read_request_finalizer_from_helper( + buffer, request->helper, n_packets, + request->flags & REQUEST_ALLOW_PROXY_REQS); +} + static ALWAYS_INLINE enum lwan_http_status read_request(struct lwan_request *request) { @@ -1591,22 +1606,24 @@ const char *lwan_request_get_cookie(struct lwan_request *request, return value_lookup(lwan_request_get_cookies(request), key); } -const char *lwan_request_get_header(struct lwan_request *request, +const char * +lwan_request_get_header_from_helper(struct lwan_request_parser_helper *helper, const char *header) { const size_t header_len = strlen(header); - const size_t header_len_with_separator = header_len + HEADER_VALUE_SEPARATOR_LEN; + const size_t header_len_with_separator = + header_len + HEADER_VALUE_SEPARATOR_LEN; assert(strchr(header, ':') == NULL); - for (size_t i = 0; i < request->helper->n_header_start; i++) { - const char *start = request->helper->header_start[i]; - char *end = request->helper->header_start[i + 1] - HEADER_TERMINATOR_LEN; + for (size_t i = 0; i < helper->n_header_start; i++) { + const char *start = helper->header_start[i]; + char *end = helper->header_start[i + 1] - HEADER_TERMINATOR_LEN; if (UNLIKELY((size_t)(end - start) < header_len_with_separator)) continue; - STRING_SWITCH_SMALL(start + header_len) { + STRING_SWITCH_SMALL (start + header_len) { case STR2_INT(':', ' '): if (!strncasecmp(start, header, header_len)) { *end = '\0'; @@ -1618,6 +1635,12 @@ const char *lwan_request_get_header(struct lwan_request *request, return NULL; } +inline const char *lwan_request_get_header(struct lwan_request *request, + const char *header) +{ + return lwan_request_get_header_from_helper(request->helper, header); +} + const char *lwan_request_get_host(struct lwan_request *request) { const struct lwan_request_parser_helper *helper = request->helper; @@ -1818,6 +1841,12 @@ lwan_request_get_accept_encoding(struct lwan_request *request) return request->flags & REQUEST_ACCEPT_MASK; } +bool lwan_request_seems_complete(struct lwan_request_parser_helper *helper) +{ + return read_request_finalizer_from_helper(helper->buffer, helper, 1, + false) == FINALIZER_DONE; +} + #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION static int useless_coro_for_fuzzing(struct coro *c __attribute__((unused)), void *data __attribute__((unused))) @@ -1857,16 +1886,10 @@ __attribute__((used)) int fuzz_parse_http_request(const uint8_t *data, .flags = REQUEST_ALLOW_PROXY_REQS, .proxy = &proxy, }; - struct lwan_value buffer = { - .value = data_copy, - .len = length, - }; /* If the finalizer isn't happy with a request, there's no point in * going any further with parsing it. */ - enum lwan_read_finalizer finalizer = - read_request_finalizer(&buffer, sizeof(data_copy), &request, 1); - if (finalizer != FINALIZER_DONE) + if (!lwan_request_seems_complete(&request)) return 0; /* client_read() NUL-terminates the string */ diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 21fdf896a..8cd0fa162 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -388,8 +388,9 @@ ALWAYS_INLINE size_t lwan_prepare_response_header(struct lwan_request *request, request, status, headers, headers_buf_size, request->response.headers); } -bool lwan_response_set_chunked(struct lwan_request *request, - enum lwan_http_status status) +bool lwan_response_set_chunked_full(struct lwan_request *request, + enum lwan_http_status status, + const struct lwan_key_value *additional_headers) { char buffer[DEFAULT_BUFFER_SIZE]; size_t buffer_len; @@ -398,8 +399,9 @@ bool lwan_response_set_chunked(struct lwan_request *request, return false; request->flags |= RESPONSE_CHUNKED_ENCODING; - buffer_len = lwan_prepare_response_header(request, status, buffer, - DEFAULT_BUFFER_SIZE); + buffer_len = lwan_prepare_response_header_full(request, status, buffer, + DEFAULT_BUFFER_SIZE, + additional_headers); if (UNLIKELY(!buffer_len)) return false; @@ -409,14 +411,22 @@ bool lwan_response_set_chunked(struct lwan_request *request, return true; } -void lwan_response_send_chunk(struct lwan_request *request) +inline bool lwan_response_set_chunked(struct lwan_request *request, + enum lwan_http_status status) +{ + return lwan_response_set_chunked_full(request, status, + request->response.headers); +} + +void lwan_response_send_chunk_full(struct lwan_request *request, + struct lwan_strbuf *strbuf) { if (!(request->flags & RESPONSE_SENT_HEADERS)) { if (UNLIKELY(!lwan_response_set_chunked(request, HTTP_OK))) return; } - size_t buffer_len = lwan_strbuf_get_length(request->response.buffer); + size_t buffer_len = lwan_strbuf_get_length(strbuf); if (UNLIKELY(!buffer_len)) { static const char last_chunk[] = "0\r\n\r\n"; lwan_send(request, last_chunk, sizeof(last_chunk) - 1, 0); @@ -435,14 +445,18 @@ void lwan_response_send_chunk(struct lwan_request *request) struct iovec chunk_vec[] = { {.iov_base = chunk_size, .iov_len = chunk_size_len}, - {.iov_base = lwan_strbuf_get_buffer(request->response.buffer), - .iov_len = buffer_len}, + {.iov_base = lwan_strbuf_get_buffer(strbuf), .iov_len = buffer_len}, {.iov_base = "\r\n", .iov_len = 2}, }; lwan_writev(request, chunk_vec, N_ELEMENTS(chunk_vec)); - lwan_strbuf_reset(request->response.buffer); + lwan_strbuf_reset(strbuf); +} + +void lwan_response_send_chunk(struct lwan_request *request) +{ + return lwan_response_send_chunk_full(request, request->response.buffer); } bool lwan_response_set_event_stream(struct lwan_request *request, diff --git a/src/lib/lwan.h b/src/lib/lwan.h index fbdc22fa9..b79712226 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -525,7 +525,12 @@ void lwan_request_sleep(struct lwan_request *request, uint64_t ms); bool lwan_response_set_chunked(struct lwan_request *request, enum lwan_http_status status); +bool lwan_response_set_chunked_full(struct lwan_request *request, + enum lwan_http_status status, + const struct lwan_key_value *additional_headers); void lwan_response_send_chunk(struct lwan_request *request); +void lwan_response_send_chunk_full(struct lwan_request *request, + struct lwan_strbuf *strbuf); bool lwan_response_set_event_stream(struct lwan_request *request, enum lwan_http_status status); From dbb87e2204ab2b91f6c9c9f4fe50cec8fd6a9bc4 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 9 Apr 2022 12:13:40 -0700 Subject: [PATCH 1921/2505] Use MSG_NOSIGNAL flag in async_write() by default --- src/lib/lwan-request.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 118f5a8d9..3b3018122 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -2010,7 +2010,7 @@ ssize_t lwan_request_async_write(struct lwan_request *request, size_t len) { while (true) { - ssize_t r = send(fd, buf, len, MSG_DONTWAIT); + ssize_t r = send(fd, buf, len, MSG_DONTWAIT|MSG_NOSIGNAL); if (r < 0) { switch (errno) { @@ -2019,6 +2019,8 @@ ssize_t lwan_request_async_write(struct lwan_request *request, /* Fallthrough */ case EINTR: continue; + case EPIPE: + return -errno; } } From 4e25d630dc38423ca82ec1f6b6254e602b6557db Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 9 Apr 2022 15:59:12 -0700 Subject: [PATCH 1922/2505] Add API to parse socket addresses --- src/lib/lwan-private.h | 1 + src/lib/lwan-socket.c | 13 +++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 2c27900d2..e708057e3 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -275,3 +275,4 @@ const char *lwan_request_get_header_from_helper(struct lwan_request_parser_helpe const char *header); bool lwan_request_seems_complete(struct lwan_request_parser_helper *helper); +sa_family_t lwan_socket_parse_address(char *listener, char **node, char **port); diff --git a/src/lib/lwan-socket.c b/src/lib/lwan-socket.c index 50daa523a..0bda4e7c3 100644 --- a/src/lib/lwan-socket.c +++ b/src/lib/lwan-socket.c @@ -162,6 +162,14 @@ static sa_family_t parse_listener_ipv6(char *listener, char **node, char **port) return AF_INET6; } +sa_family_t lwan_socket_parse_address(char *listener, char **node, char **port) +{ + if (*listener == '[') + return parse_listener_ipv6(listener, node, port); + + return parse_listener_ipv4(listener, node, port); +} + static sa_family_t parse_listener(char *listener, char **node, char **port) { if (streq(listener, "systemd")) { @@ -171,10 +179,7 @@ static sa_family_t parse_listener(char *listener, char **node, char **port) return AF_MAX; } - if (*listener == '[') - return parse_listener_ipv6(listener, node, port); - - return parse_listener_ipv4(listener, node, port); + return lwan_socket_parse_address(listener, node, port); } static int listen_addrinfo(int fd, From 6b7ef3fa2e640b960ccec403e1afc7f890e4ff35 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 9 Apr 2022 16:03:00 -0700 Subject: [PATCH 1923/2505] Handle closing file descriptors that can still be read This usually happens when reading Unix sockets, and came up while implementing the FastCGI module. --- src/lib/lwan-thread.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index ea85eed13..b9c286c36 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -928,11 +928,6 @@ static void *thread_io_loop(void *data) for (struct epoll_event *event = events; n_fds--; event++) { struct lwan_connection *conn = event->data.ptr; - if (UNLIKELY(event->events & (EPOLLRDHUP | EPOLLHUP))) { - timeout_queue_expire(&tq, conn); - continue; - } - if (conn->flags & (CONN_LISTENER_HTTP | CONN_LISTENER_HTTPS)) { if (LIKELY(accept_waiting_clients(t, conn))) continue; @@ -941,6 +936,16 @@ static void *thread_io_loop(void *data) break; } + bool expire = false; + if (UNLIKELY(event->events & (EPOLLRDHUP | EPOLLHUP))) { + if (!(event->events & EPOLLIN)) { + timeout_queue_expire(&tq, conn); + continue; + } + + expire = true; + } + if (!conn->coro) { if (UNLIKELY(!spawn_coro(conn, &switcher, &tq))) { send_last_response_without_coro(t->lwan, conn, HTTP_INTERNAL_ERROR); @@ -951,7 +956,12 @@ static void *thread_io_loop(void *data) } resume_coro(&tq, conn, epoll_fd); - timeout_queue_move_to_last(&tq, conn); + + if (expire) { + timeout_queue_expire(&tq, conn); + } else { + timeout_queue_move_to_last(&tq, conn); + } } if (created_coros) From a1f6387f2076bd0aab35f42b753dec92ab909a35 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 9 Apr 2022 20:53:24 -0700 Subject: [PATCH 1924/2505] Add preliminary FastCGI support This is sufficient to use Lwan as a proxy to PHP-FPM. I have not tested with other FastCGI implementations, but I suspect it'll work just fine. Right now, this is about 70% as fast as nginx in the same machine. From a cursory glance at strace, it seems that nginx achieves this by reading as much as possible from the socket instead of reading each record every time it goes back to the processing loop like Lwan does. Maybe someday we can optimize this further, but I wanted to get this working version out first. In any case, I'm quite happy with this result, especially since I have given optimization not a lot of thought yet. I have not fuzz-tested or done any rigorous code review, so this might not be that safe. I'm tired and it's possible something trivial slipped. I have to think how I'm going to exercise this code with a fuzzer so I can hook it up to oss-fuzz or something. Some parameters are still hardcoded. I'm not sure if the ones that aren't will be sufficient for most applications. POST requests (or any other requests that will require a request body) won't work, as the STDIN record isn't implemented yet. --- README.md | 17 + src/lib/CMakeLists.txt | 1 + src/lib/lwan-mod-fastcgi.c | 867 +++++++++++++++++++++++++++++++++++++ src/lib/lwan-mod-fastcgi.h | 48 ++ 4 files changed, 933 insertions(+) create mode 100644 src/lib/lwan-mod-fastcgi.c create mode 100644 src/lib/lwan-mod-fastcgi.h diff --git a/README.md b/README.md index b9963b029..6e6c76498 100644 --- a/README.md +++ b/README.md @@ -713,6 +713,23 @@ a `404 Not Found` error will be sent instead. |--------|------|---------|-------------| | `code` | `int` | `999` | A HTTP response code | +### FastCGI + +The `fastcgi` proxies requests between the HTTP client connecting to Lwan +and a FastCGI server accessible by Lwan. This is useful, for instance, +to serve pages from a scripting language such as PHP. + +> :bulb: **Note:** This is a preliminary version of this module, and +> as such, it's not well optimized and some features are missing. Of +> note, requests that send a body (e.g. POST requests) won't work because +> Lwan does not implement the STDIN record yet. + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `address` | `str` | | Address to connect to. Can be a file path (for Unix Domain Sockets), IPv4 address (`aaa.bbb.ccc.ddd:port`), or IPv6 address (`[...]:port`). | +| `script_path` | `str` | | Location where the CGI scripts are located. | +| `default_index` | `str` | `index.php` | Default script to execute if unspecified in the request URI. | + ### Authorization Section Authorization sections can be declared in any module instance or handler, diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index f371a799e..31cc9bcc4 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -20,6 +20,7 @@ set(SOURCES lwan-mod-response.c lwan-mod-rewrite.c lwan-mod-serve-files.c + lwan-mod-fastcgi.c lwan-readahead.c lwan-request.c lwan-response.c diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c new file mode 100644 index 000000000..0cf7776b1 --- /dev/null +++ b/src/lib/lwan-mod-fastcgi.c @@ -0,0 +1,867 @@ +/* + * lwan - simple web server + * Copyright (c) 2022 L. A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ +/* Implementation of a (subset of) FastCGI according to + * https://fastcgi-archives.github.io/FastCGI_Specification.html + * Some things are not fully implemented, but this seems enough to get + * Lwan to proxy PHP-FPM through FastCGI. + */ +/* FIXME: not a lot of the private APIs that needed to be added to + * support this module have a good API. Revisit this someday. */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lwan-private.h" + +#include "int-to-str.h" +#include "patterns.h" +#include "realpathat.h" +#include "lwan-cache.h" +#include "lwan-mod-fastcgi.h" +#include "lwan-strbuf.h" + +#define FASTCGI_ROLE_RESPONDER 1 + +/* FIXME: use this to pool connections later? */ +#define FASTCGI_FLAGS_KEEP_CONN 1 + +#define FASTCGI_TYPE_BEGIN_REQUEST 1 +#define FASTCGI_TYPE_END_REQUEST 3 +#define FASTCGI_TYPE_PARAMS 4 +#define FASTCGI_TYPE_STDIN 5 +#define FASTCGI_TYPE_STDOUT 6 +#define FASTCGI_TYPE_STDERR 7 + +struct private_data { + union { + struct sockaddr_un un_addr; + struct sockaddr_in in_addr; + struct sockaddr_in6 in6_addr; + struct sockaddr_storage sock_addr; + }; + sa_family_t addr_family; + socklen_t addr_size; + + struct cache *script_name_cache; + + struct lwan_value default_index; + + char *script_path; + int script_path_fd; +}; + +struct record { + uint8_t version; + uint8_t type; + uint16_t id; + uint16_t len_content; + uint8_t len_padding; + uint8_t reserved; +} __attribute__((packed)); + +static_assert(sizeof(struct record) == 8, "Sane record size"); + +struct begin_request_body { + uint16_t role; + uint8_t flags; + uint8_t padding[5]; +} __attribute__((packed)); + +static_assert(sizeof(struct begin_request_body) == 8, + "Sane begin_request_body size"); + +struct request_header { + struct record begin_request; + struct begin_request_body begin_request_body; + struct record begin_params; +} __attribute__((packed)); + +struct request_footer { + struct record end_params; + struct record empty_stdin; +} __attribute__((packed)); + +struct script_name_cache_entry { + struct cache_entry base; + char *script_name; + char *script_filename; +}; + +static void close_fd(void *data) +{ + int fd = (int)(intptr_t)data; + + close(fd); +} + +static void add_param_len(struct lwan_strbuf *strbuf, + const char *key, + size_t len_key, + const char *value, + size_t len_value) +{ + const uint32_t large_mask = htonl(0x80000000u); + + /* FIXME: these should be enabled for release builds, too! */ + assert(len_key <= INT_MAX); + assert(len_value <= INT_MAX); + + if (len_key <= 127) { + lwan_strbuf_append_char(strbuf, (char)len_key); + } else { + uint32_t len_net = htonl((uint32_t)len_key) | large_mask; + lwan_strbuf_append_str(strbuf, (char *)&len_net, 4); + } + + if (len_value <= 127) { + lwan_strbuf_append_char(strbuf, (char)len_value); + } else { + uint32_t len_net = htonl((uint32_t)len_value) | large_mask; + lwan_strbuf_append_str(strbuf, (char *)&len_net, 4); + } + + lwan_strbuf_append_str(strbuf, key, len_key); + lwan_strbuf_append_str(strbuf, value, len_value); +} + +static inline void +add_param(struct lwan_strbuf *strbuf, const char *key, const char *value) +{ + return add_param_len(strbuf, key, strlen(key), value, strlen(value)); +} + +static inline void +add_int_param(struct lwan_strbuf *strbuf, const char *key, ssize_t value) +{ + size_t len; + char buffer[INT_TO_STR_BUFFER_SIZE]; + char *p = int_to_string(value, buffer, &len); + + return add_param_len(strbuf, key, strlen(key), p, len); +} + +static struct cache_entry *create_script_name(const char *key, void *context) +{ + struct private_data *pd = context; + struct script_name_cache_entry *entry; + struct lwan_value url; + char temp[PATH_MAX]; + char rp_buf[PATH_MAX]; + int r; + + entry = malloc(sizeof(*entry)); + if (!entry) + return NULL; + + if (*key) { + url = (struct lwan_value){.value = (char *)key, .len = strlen(key)}; + } else { + url = pd->default_index; + } + + /* SCRIPT_NAME */ + r = snprintf(temp, sizeof(temp), "/%.*s", (int)url.len, url.value); + if (r < 0 || r >= (int)sizeof(temp)) + goto free_entry; + + entry->script_name = strdup(temp); + if (!entry->script_name) + goto free_entry; + + /* SCRIPT_FILENAME */ + r = snprintf(temp, sizeof(temp), "%s/%.*s", pd->script_path, (int)url.len, + url.value); + if (r < 0 || r >= (int)sizeof(temp)) + goto free_script_name; + + char *rp = realpathat(pd->script_path_fd, pd->script_path, temp, rp_buf); + if (!rp) + goto free_script_name; + + if (strncmp(rp_buf, pd->script_path, strlen(pd->script_path))) + goto free_script_name; + + entry->script_filename = strdup(rp); + if (!entry->script_filename) + goto free_script_filename; + + return &entry->base; + +free_script_filename: + free(entry->script_filename); +free_script_name: + free(entry->script_name); +free_entry: + free(entry); + + return NULL; +} + +static void destroy_script_name(struct cache_entry *entry, void *context) +{ + struct script_name_cache_entry *snce = + (struct script_name_cache_entry *)entry; + + free(snce->script_name); + free(snce->script_filename); + free(snce); +} + +static bool add_script_paths(const struct private_data *pd, + struct lwan_request *request, + struct lwan_response *response) +{ + struct script_name_cache_entry *snce = + (struct script_name_cache_entry *)cache_coro_get_and_ref_entry( + pd->script_name_cache, request->conn->coro, request->url.value); + + if (snce) { + add_param(response->buffer, "SCRIPT_NAME", snce->script_name); + add_param(response->buffer, "SCRIPT_FILENAME", snce->script_filename); + return true; + } + + return false; +} + +static bool add_params(const struct private_data *pd, + struct lwan_request *request, + struct lwan_response *response) +{ + const struct lwan_request_parser_helper *request_helper = request->helper; + struct lwan_strbuf *strbuf = response->buffer; + + char remote_addr[INET6_ADDRSTRLEN]; + uint16_t remote_port; + + /* FIXME: let's use some hardcoded values for now so that we can + * verify that the implementation is working first */ + + /* Very compliant. Much CGI. Wow. */ + add_param(strbuf, "GATEWAY_INTERFACE", "CGI/1.1"); + + add_param(strbuf, "REMOTE_ADDR", + lwan_request_get_remote_address_and_port(request, remote_addr, + &remote_port)); + add_int_param(strbuf, "REMOTE_PORT", remote_port); + + add_param(strbuf, "SERVER_ADDR", "127.0.0.1"); + + /* FIXME: get the actual port from thread->listen_fd or + * thread->tls_listen_fd */ + if (request->conn->flags & CONN_TLS) { + add_param(strbuf, "SERVER_PORT", "0"); + add_param(strbuf, "HTTPS", "on"); + } else { + add_param(strbuf, "SERVER_PORT", "0"); + add_param(strbuf, "HTTPS", ""); + } + + add_param(strbuf, "SERVER_SOFTWARE", "Lwan"); + add_param(strbuf, "SERVER_PROTOCOL", + request->flags & REQUEST_IS_HTTP_1_0 ? "HTTP/1.0" : "HTTP/1.1"); + + add_param(strbuf, "REQUEST_METHOD", lwan_request_get_method_str(request)); + + /* FIXME: Should we support PATH_INFO? This is pretty shady. See + * e.g. https://httpd.apache.org/docs/2.4/mod/core.html#acceptpathinfo */ + add_param(strbuf, "PATH_INFO", ""); + + add_param(strbuf, "DOCUMENT_URI", request->original_url.value); + add_param(strbuf, "DOCUMENT_ROOT", pd->script_path); + + const char *query_string = request_helper->query_string.value; + if (query_string) { + char *temp; + + /* FIXME: maybe we should add something that lets us + * forward a va_arg to strbuf_append_printf() instead? */ + if (asprintf(&temp, "%s?%s", request->original_url.value, + query_string) < 0) { + return false; + } + + add_param(strbuf, "QUERY_STRING", query_string ? query_string : ""); + add_param(strbuf, "REQUEST_URI", temp); + + free(temp); + } else { + add_param(strbuf, "QUERY_STRING", ""); + add_param(strbuf, "REQUEST_URI", request->original_url.value); + } + + for (size_t i = 0; i < request_helper->n_header_start; i++) { + const char *header = request_helper->header_start[i]; + const char *next_header = request_helper->header_start[i + 1]; + const char *colon = memchr(header, ':', 127 - sizeof("HTTP_: ") - 1); + char header_name[128]; + int r; + + if (!colon) + continue; + + const size_t header_len = (size_t)(colon - header); + const size_t value_len = (size_t)(next_header - colon - 4); + + r = snprintf(header_name, sizeof(header_name), "HTTP_%.*s", + (int)header_len, header); + if (r < 0 || r >= (int)sizeof(header_name)) + continue; + + /* FIXME: RFC7230/RFC3875 compliance */ + for (char *p = header_name; *p; p++) { + if (isalpha(*p)) + *p &= ~0x20; + else if (!isdigit(*p)) + *p = '_'; + } + + if (streq(header_name, "HTTP_PROXY")) { + /* Mitigation for https://httpoxy.org */ + continue; + } + + add_param_len(strbuf, header_name, header_len + sizeof("HTTP_") - 1, + colon + 2, value_len); + } + + if (UNLIKELY(lwan_strbuf_get_length(strbuf) > 0xffffu)) { + /* Should not happen because DEFAULT_BUFFER_SIZE is a lot smaller + * than 65535, but check anyway. (If anything, we could send multiple + * PARAMS records, but that's very unlikely to happen anyway until + * we change how request headers are read.) */ + static_assert(DEFAULT_BUFFER_SIZE <= 0xffffu, + "only needs one PARAMS record"); + return false; + } + + return true; +} + +static bool +handle_stdout(struct lwan_request *request, const struct record *record, int fd) +{ + size_t to_read = (size_t)ntohs(record->len_content); + char *buffer = lwan_strbuf_extend_unsafe(request->response.buffer, to_read); + + if (!buffer) + return false; + + while (to_read) { + ssize_t r = lwan_request_async_read(request, fd, buffer, to_read); + + if (r < 0) + return false; + + to_read -= (size_t)r; + buffer += r; + } + + if (record->len_padding) { + char padding[256]; + lwan_request_async_read_flags(request, fd, padding, + (size_t)record->len_padding, MSG_TRUNC); + } + + return true; +} + +static bool +handle_stderr(struct lwan_request *request, const struct record *record, int fd) +{ + /* Even though we only use buffer within this function, we need to use + * coro_malloc() to allocate this buffer. Otherwise, if + * lwan_request_async_read() yields the coroutine, the main loop might + * terminate the coroutine if either connection is dropped. */ + size_t to_read = (size_t)ntohs(record->len_content); + char *buffer = coro_malloc(request->conn->coro, to_read); + + if (!buffer) + return false; + + for (char *p = buffer; to_read;) { + ssize_t r = lwan_request_async_read(request, fd, p, to_read); + + if (r < 0) + return false; + + p += r; + to_read -= (size_t)r; + } + + lwan_status_error("FastCGI stderr output: %.*s", + (int)ntohs(record->len_content), buffer); + + if (record->len_padding) { + char padding[256]; + lwan_request_async_read_flags(request, fd, padding, + (size_t)ntohs(record->len_padding), + MSG_TRUNC); + } + + return true; +} + +static bool discard_unknown_record(struct lwan_request *request, + const struct record *record, + int fd) +{ + char buffer[256]; + size_t to_read = + (size_t)ntohs(record->len_content) + (size_t)ntohs(record->len_padding); + + if (record->type > 11) { + /* Per the spec, 11 is the maximum (unknown type), so anything + * above it is unspecified. */ + lwan_status_warning( + "FastCGI server sent unknown/invalid record type %d", record->type); + return false; + } + + lwan_status_debug("Discarding record of type %d (%zu bytes incl. padding)", + record->type, to_read); + + while (to_read) { + ssize_t r; + + r = lwan_request_async_read_flags( + request, fd, buffer, LWAN_MIN(sizeof(buffer), to_read), MSG_TRUNC); + if (r < 0) + return false; + + to_read -= (size_t)r; + } + + return true; +} + +static bool try_initiating_chunked_response(struct lwan_request *request) +{ + struct lwan_response *response = &request->response; + char *header_start[N_HEADER_START]; + enum lwan_http_status status_code = HTTP_OK; + struct lwan_value buffer = { + .value = lwan_strbuf_get_buffer(response->buffer), + .len = lwan_strbuf_get_length(response->buffer), + }; + struct lwan_request_parser_helper helper = {.buffer = &buffer, + .header_start = header_start}; + + assert(!(request->flags & + (RESPONSE_CHUNKED_ENCODING | RESPONSE_SENT_HEADERS))); + + if (!lwan_request_seems_complete(&helper)) + return true; + + /* FIXME: Maybe split parse_headers() into a function that finds all + * headers and another that looks at the found headers? We don't use + * the second part of this function here. */ + if (!lwan_parse_headers(&helper, buffer.value)) + return false; + + /* FIXME: Maybe use a lwan_key_value_array here? */ + struct lwan_key_value *additional_headers = + calloc(helper.n_header_start + 1, sizeof(struct lwan_key_value)); + if (!additional_headers) + return false; + + struct coro_defer *free_additional_headers = + coro_defer(request->conn->coro, free, additional_headers); + + for (size_t i = 0, j = 0; i < helper.n_header_start; i++) { + char *begin = helper.header_start[i]; + char *end = helper.header_start[i + 1]; + char *p; + + p = strchr(begin, ':'); + if (!p) /* Shouldn't happen, but... */ + continue; + *p = '\0'; + + *(end - 2) = '\0'; + + char *key = begin; + char *value = p + 2; + + if (!strcasecmp(key, "X-Powered-By")) { + /* This is set by PHP-FPM. Do not advertise this for privacy + * reasons. */ + } else if (!strcasecmp(key, "Content-Type")) { + response->mime_type = coro_strdup(request->conn->coro, value); + } else if (!strcasecmp(key, "Status")) { + if (strlen(value) < 4) { + status_code = HTTP_INTERNAL_ERROR; + continue; + } + + value[3] = '\0'; + int status_as_int = parse_int(value, -1); + + if (status_as_int < 100 || status_as_int >= 600) + status_code = HTTP_INTERNAL_ERROR; + else + status_code = (enum lwan_http_status)status_as_int; + } else { + additional_headers[j++] = + (struct lwan_key_value){.key = key, .value = value}; + } + } + + if (!lwan_response_set_chunked_full(request, status_code, + additional_headers)) { + return false; + } + + coro_defer_fire_and_disarm(request->conn->coro, free_additional_headers); + + char *chunk_start = header_start[helper.n_header_start]; + size_t chunk_len = buffer.len - (size_t)(chunk_start - buffer.value); + + if (chunk_len) { + struct lwan_strbuf first_chunk; + + lwan_strbuf_init(&first_chunk); + lwan_strbuf_set_static(&first_chunk, chunk_start, chunk_len); + lwan_response_send_chunk_full(request, &first_chunk); + + /* We sent the chunk using a temporary strbuf, so reset the + * actual buffer that had the headers+first chunk */ + lwan_strbuf_reset(response->buffer); + } + + return true; +} + +static enum lwan_http_status +fastcgi_handle_request(struct lwan_request *request, + struct lwan_response *response, + void *instance) +{ + struct private_data *pd = instance; + int remaining_tries_for_chunked = 10; + int fcgi_fd; + + fcgi_fd = socket(pd->addr_family, SOCK_STREAM, 0); + if (fcgi_fd < 0) + return HTTP_INTERNAL_ERROR; + + coro_defer(request->conn->coro, close_fd, (void *)(intptr_t)fcgi_fd); + + if (connect(fcgi_fd, (struct sockaddr *)&pd->sock_addr, pd->addr_size) < 0) + return HTTP_UNAVAILABLE; + + lwan_strbuf_reset(response->buffer); + if (!add_params(pd, request, response)) + return HTTP_BAD_REQUEST; + if (!add_script_paths(pd, request, response)) + return HTTP_NOT_FOUND; + + struct request_header request_header = { + .begin_request = {.version = 1, + .type = FASTCGI_TYPE_BEGIN_REQUEST, + .id = htons(1), + .len_content = htons( + (uint16_t)sizeof(struct begin_request_body))}, + .begin_request_body = {.role = htons(FASTCGI_ROLE_RESPONDER)}, + .begin_params = {.version = 1, + .type = FASTCGI_TYPE_PARAMS, + .id = htons(1), + .len_content = htons((uint16_t)lwan_strbuf_get_length( + response->buffer))}, + }; + struct request_footer request_footer = { + .end_params = {.version = 1, + .type = FASTCGI_TYPE_PARAMS, + .id = htons(1)}, + /* FIXME: do we need a STDIN record if there's no request body is empty? + */ + .empty_stdin = {.version = 1, + .type = FASTCGI_TYPE_STDIN, + .id = htons(1)}, + }; + + struct iovec vec[] = { + {.iov_base = &request_header, .iov_len = sizeof(request_header)}, + {.iov_base = lwan_strbuf_get_buffer(response->buffer), + .iov_len = lwan_strbuf_get_length(response->buffer)}, + {.iov_base = &request_footer, .iov_len = sizeof(request_footer)}, + }; + lwan_request_async_writev(request, fcgi_fd, vec, N_ELEMENTS(vec)); + + lwan_strbuf_reset(response->buffer); + + /* FIXME: the header parser starts at the \r from an usual + * HTTP request with the verb line, etc. */ + lwan_strbuf_append_char(response->buffer, '\r'); + + while (true) { + struct record record; + ssize_t r; + + r = lwan_request_async_read(request, fcgi_fd, &record, sizeof(record)); + if (r < 0) + return HTTP_UNAVAILABLE; + if (r != (ssize_t)sizeof(record)) + return HTTP_INTERNAL_ERROR; + + switch (record.type) { + case FASTCGI_TYPE_STDOUT: + if (!handle_stdout(request, &record, fcgi_fd)) + return HTTP_INTERNAL_ERROR; + + /* Fallthrough */ + + case FASTCGI_TYPE_END_REQUEST: + if (request->flags & RESPONSE_CHUNKED_ENCODING) { + if (lwan_strbuf_get_length(response->buffer) != 0) { + /* Avoid buffering all the records from the FastCGI + * server and send chunks as we get them if we know we're + * already responding with chunked encoding. */ + lwan_response_send_chunk(request); + } + } else { + /* See if we can parse the headers at this point; if we can, + * then we can also send the first chunk with the additional + * headers we just parsed from FastCGI. */ + remaining_tries_for_chunked--; + if (!remaining_tries_for_chunked) + return HTTP_UNAVAILABLE; + + if (!try_initiating_chunked_response(request)) + return HTTP_BAD_REQUEST; + } + + if (record.type == FASTCGI_TYPE_END_REQUEST) + return HTTP_OK; + + break; + + case FASTCGI_TYPE_STDERR: + if (!handle_stderr(request, &record, fcgi_fd)) + return HTTP_INTERNAL_ERROR; + break; + + default: + if (!discard_unknown_record(request, &record, fcgi_fd)) + return HTTP_INTERNAL_ERROR; + break; + } + } + + __builtin_unreachable(); +} + +static void *fastcgi_create(const char *prefix __attribute__((unused)), + void *user_settings) +{ + struct lwan_fastcgi_settings *settings = user_settings; + struct private_data *pd; + + if (!settings->address) { + lwan_status_error("FastCGI: `address` not specified"); + return NULL; + } + if (!settings->script_path) { + lwan_status_error("FastCGI: `script_path` not specified"); + return NULL; + } + + if (!settings->default_index) + settings->default_index = "index.php"; + + pd = malloc(sizeof(*pd)); + if (!pd) { + lwan_status_perror("FastCGI: Could not allocate memory for module"); + return NULL; + } + + pd->script_name_cache = + cache_create(create_script_name, destroy_script_name, pd, 60); + if (!pd->script_name_cache) { + lwan_status_error("FastCGI: could not create cache for script_name"); + goto free_pd; + } + + pd->default_index = (struct lwan_value){ + .value = strdup(settings->default_index), + .len = strlen(settings->default_index), + }; + if (!pd->default_index.value) { + lwan_status_error("FastCGI: could not copy default_address for module"); + goto destroy_cache; + } + + pd->script_path = realpath(settings->script_path, NULL); + if (!pd->script_path) { + lwan_status_perror("FastCGI: `script_path` of '%s' is invalid", + pd->script_path); + goto free_default_index; + } + + pd->script_path_fd = open(pd->script_path, O_PATH | O_DIRECTORY); + if (pd->script_path_fd < 0) { + lwan_status_perror("FastCGI: Could not open `script_path` at '%s'", + pd->script_path); + goto free_script_path; + } + + if (*settings->address == '/') { + struct stat st; + + if (stat(settings->address, &st) < 0) { + lwan_status_perror("FastCGI: `address` not found: %s", + settings->address); + goto close_script_path_fd; + } + + if (!(st.st_mode & S_IFSOCK)) { + lwan_status_error("FastCGI: `address` is not a socket: %s", + settings->address); + goto close_script_path_fd; + } + + if (strlen(settings->address) >= sizeof(pd->un_addr.sun_path)) { + lwan_status_error( + "FastCGI: `address` is too long for a sockaddr_un: %s", + settings->address); + goto close_script_path_fd; + } + + pd->addr_family = AF_UNIX; + pd->un_addr = (struct sockaddr_un){.sun_family = AF_UNIX}; + pd->addr_size = sizeof(pd->un_addr); + memcpy(pd->un_addr.sun_path, settings->address, + strlen(settings->address) + 1); + return pd; + } + + char *node, *port, *address_copy; + + address_copy = strdup(settings->address); + if (!address_copy) + goto free_address_copy; + + pd->addr_family = lwan_socket_parse_address(address_copy, &node, &port); + + int int_port = parse_int(port, -1); + if (int_port < 0 || int_port > 0xffff) { + lwan_status_error("FastCGI: Port %d is not in valid range [0-65535]", + int_port); + goto free_address_copy; + } + + switch (pd->addr_family) { + case AF_MAX: + lwan_status_error("FastCGI: Could not parse '%s' as 'address:port'", + settings->address); + goto free_address_copy; + + case AF_INET: { + struct in_addr in_addr; + + if (inet_pton(AF_INET, node, &in_addr) < 0) { + lwan_status_perror("FastCGI: Could not parse IPv4 address '%s'", + node); + goto free_address_copy; + } + + pd->in_addr = + (struct sockaddr_in){.sin_family = AF_INET, + .sin_addr = in_addr, + .sin_port = htons((uint16_t)int_port)}; + pd->addr_size = sizeof(in_addr); + return pd; + } + + case AF_INET6: { + struct in6_addr in6_addr; + + if (inet_pton(AF_INET6, node, &in6_addr) < 0) { + lwan_status_perror("FastCGI: Could not parse IPv6 address '%s'", + node); + goto free_address_copy; + } + + pd->in6_addr = + (struct sockaddr_in6){.sin6_family = AF_INET6, + .sin6_addr = in6_addr, + .sin6_port = htons((uint16_t)int_port)}; + pd->addr_size = sizeof(in6_addr); + return pd; + } + } + + return pd; + +free_address_copy: + free(address_copy); +close_script_path_fd: + close(pd->script_path_fd); +free_script_path: + free(pd->script_path); +free_default_index: + free(pd->default_index.value); +destroy_cache: + cache_destroy(pd->script_name_cache); +free_pd: + free(pd); + return NULL; +} + +static void fastcgi_destroy(void *instance) +{ + struct private_data *pd = instance; + + cache_destroy(pd->script_name_cache); + free(pd->default_index.value); + close(pd->script_path_fd); + free(pd->script_path); + free(pd); +} + +static void *fastcgi_create_from_hash(const char *prefix, + const struct hash *hash) +{ + struct lwan_fastcgi_settings settings = { + .address = hash_find(hash, "address"), + .script_path = hash_find(hash, "script_path"), + .default_index = hash_find(hash, "default_index"), + }; + return fastcgi_create(prefix, &settings); +} + +static const struct lwan_module module = { + .create = fastcgi_create, + .create_from_hash = fastcgi_create_from_hash, + .destroy = fastcgi_destroy, + .handle_request = fastcgi_handle_request, +}; + +LWAN_REGISTER_MODULE(fastcgi, &module); diff --git a/src/lib/lwan-mod-fastcgi.h b/src/lib/lwan-mod-fastcgi.h new file mode 100644 index 000000000..76006001d --- /dev/null +++ b/src/lib/lwan-mod-fastcgi.h @@ -0,0 +1,48 @@ +/* + * lwan - simple web server + * Copyright (c) 2022 L. A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#pragma once + +#if defined(__cplusplus) +extern "C" { +#endif + +#include "lwan.h" + +struct lwan_fastcgi_settings { + const char *address; + const char *script_path; + const char *default_index; +}; + +LWAN_MODULE_FORWARD_DECL(fastcgi); + +#define FASTCGI(socket_path_, script_path_, default_index_) \ + .module = LWAN_MODULE_REF(fastcgi), \ + .args = ((struct lwan_fastcgi_settings[]){{ \ + .socket_path = socket_path_, \ + .script_path = script_path_, \ + .default_index = default_index_, \ + }}), \ + .flags = (enum lwan_handler_flags)0 + +#if defined(__cplusplus) +} +#endif From 4c2b91be88091b4e277302a32c261c84c1d8bec6 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 9 Apr 2022 21:23:00 -0700 Subject: [PATCH 1925/2505] config_fuzzer does not need private APIs --- src/bin/fuzz/config_fuzzer.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/src/bin/fuzz/config_fuzzer.cc b/src/bin/fuzz/config_fuzzer.cc index 761c557de..730d3b8f2 100644 --- a/src/bin/fuzz/config_fuzzer.cc +++ b/src/bin/fuzz/config_fuzzer.cc @@ -4,7 +4,6 @@ extern "C" { #include "lwan-config.h" -#include "lwan-private.h" } static bool dump(struct config *config, int indent_level) From 2c95c480646baa2eb230da33c82a2ed87e61bee8 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 9 Apr 2022 21:28:25 -0700 Subject: [PATCH 1926/2505] Copy LWAN_NO_DISCARD macro to config_fuzzer.cc --- src/bin/fuzz/config_fuzzer.cc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/bin/fuzz/config_fuzzer.cc b/src/bin/fuzz/config_fuzzer.cc index 730d3b8f2..55c89bd03 100644 --- a/src/bin/fuzz/config_fuzzer.cc +++ b/src/bin/fuzz/config_fuzzer.cc @@ -6,6 +6,12 @@ extern "C" { #include "lwan-config.h" } +#define LWAN_NO_DISCARD(...) \ + do { \ + __typeof__(__VA_ARGS__) no_discard_ = __VA_ARGS__; \ + __asm__ __volatile__("" ::"g"(no_discard_) : "memory"); \ + } while (0) + static bool dump(struct config *config, int indent_level) { const struct config_line *line; From 4272b886241688021ddc5f938d21e4830b447bed Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 9 Apr 2022 21:29:57 -0700 Subject: [PATCH 1927/2505] Ensure lwan.h can be compiled from C++ --- src/lib/lwan.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index b79712226..5da1f889f 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -554,7 +554,7 @@ lwan_request_get_remote_address(struct lwan_request *request, const char * lwan_request_get_remote_address_and_port(struct lwan_request *request, - char buffer[static INET6_ADDRSTRLEN], + char buffer LWAN_ARRAY_PARAM(INET6_ADDRSTRLEN), uint16_t *port) __attribute__((warn_unused_result)); From 44283f701359c7b0bcf5e0b5466591c7442d081a Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 9 Apr 2022 22:02:06 -0700 Subject: [PATCH 1928/2505] Address out-of-bounds read in huffman decoder for HTTP/2 Since this code isn't being used at the moment, this is perfectly fine. It just means that the fuzzer is doing its job. This still needs to be optimized later, but let's not worry about that for now. Let's just leave the fuzzer do its thing and find all the corner cases we'll need to think about when it's actually time to use this. Credits to oss-fuzz: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=46506 --- ...stcase-minimized-h2_huffman_fuzzer-4703583657918464 | 1 + src/lib/lwan-h2-huffman.c | 10 ++++++++-- src/scripts/gentables.py | 6 +++++- 3 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 fuzz/regression/clusterfuzz-testcase-minimized-h2_huffman_fuzzer-4703583657918464 diff --git a/fuzz/regression/clusterfuzz-testcase-minimized-h2_huffman_fuzzer-4703583657918464 b/fuzz/regression/clusterfuzz-testcase-minimized-h2_huffman_fuzzer-4703583657918464 new file mode 100644 index 000000000..790cc272b --- /dev/null +++ b/fuzz/regression/clusterfuzz-testcase-minimized-h2_huffman_fuzzer-4703583657918464 @@ -0,0 +1 @@ + � \ No newline at end of file diff --git a/src/lib/lwan-h2-huffman.c b/src/lib/lwan-h2-huffman.c index 70cf23d63..c2c33848d 100644 --- a/src/lib/lwan-h2-huffman.c +++ b/src/lib/lwan-h2-huffman.c @@ -301,8 +301,14 @@ static inline bool consume(struct bit_reader *reader, int count) assert(count > 0); reader->bitbuf <<= count; reader->bitcount -= count; - return !__builtin_sub_overflow(reader->total_bitcount, count, - &reader->total_bitcount); + if (__builtin_sub_overflow(reader->total_bitcount, count, + &reader->total_bitcount)) { + return false; + } + if (reader->total_bitcount == 0) { + return false; + } + return true; } static inline size_t output_size(size_t input_size) diff --git a/src/scripts/gentables.py b/src/scripts/gentables.py index e957f3673..ad5387a6f 100755 --- a/src/scripts/gentables.py +++ b/src/scripts/gentables.py @@ -187,7 +187,11 @@ def generate_level(level, next_table): assert(count > 0); reader->bitbuf <<= count; reader->bitcount -= count; - return !__builtin_sub_overflow(reader->total_bitcount, count, &reader->total_bitcount); + if (__builtin_sub_overflow(reader->total_bitcount, count, &reader->total_bitcount)) + return false; + if (reader->total_bitcount == 0) + return false; + return true; } """) From dbf5da9246013d8bebecfacb5c62660d562826b2 Mon Sep 17 00:00:00 2001 From: Sam Stuewe Date: Sun, 3 Apr 2022 21:22:59 -0500 Subject: [PATCH 1929/2505] Re-expose lwan_straitjacket_enforce() 7d4a98f re-exposed straitjackets to the C API; however, the pattern added to /src/lib/liblwan.sym accidentally excluded `lwan_straitjacket_enforce()`. Signed-off-by: Sam Stuewe --- src/lib/liblwan.sym | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/liblwan.sym b/src/lib/liblwan.sym index 4c8088401..55b7ee83e 100644 --- a/src/lib/liblwan.sym +++ b/src/lib/liblwan.sym @@ -74,7 +74,7 @@ global: lwan_request_await_*; lwan_request_async_*; - lwan_straitjacket_enforce_*; + lwan_straitjacket_enforce*; local: *; From 859ffd69a1e921fbe39227530e04c74841b3985b Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 9 Apr 2022 22:30:27 -0700 Subject: [PATCH 1930/2505] FastCGI: Simplify how script_filename is allocated --- src/lib/lwan-mod-fastcgi.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index 0cf7776b1..eba48ff30 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -173,7 +173,6 @@ static struct cache_entry *create_script_name(const char *key, void *context) struct script_name_cache_entry *entry; struct lwan_value url; char temp[PATH_MAX]; - char rp_buf[PATH_MAX]; int r; entry = malloc(sizeof(*entry)); @@ -201,15 +200,12 @@ static struct cache_entry *create_script_name(const char *key, void *context) if (r < 0 || r >= (int)sizeof(temp)) goto free_script_name; - char *rp = realpathat(pd->script_path_fd, pd->script_path, temp, rp_buf); - if (!rp) - goto free_script_name; - - if (strncmp(rp_buf, pd->script_path, strlen(pd->script_path))) + entry->script_filename = + realpathat(pd->script_path_fd, pd->script_path, temp, NULL); + if (!entry->script_filename) goto free_script_name; - entry->script_filename = strdup(rp); - if (!entry->script_filename) + if (strncmp(entry->script_filename, pd->script_path, strlen(pd->script_path))) goto free_script_filename; return &entry->base; From 0491fa62226e217c541ba2667cc633d5f55cb70c Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 9 Apr 2022 23:08:30 -0700 Subject: [PATCH 1931/2505] Fix print of NULL pointer when initializing FastCGI module --- src/lib/lwan-mod-fastcgi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index eba48ff30..0475c684e 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -717,7 +717,7 @@ static void *fastcgi_create(const char *prefix __attribute__((unused)), pd->script_path = realpath(settings->script_path, NULL); if (!pd->script_path) { lwan_status_perror("FastCGI: `script_path` of '%s' is invalid", - pd->script_path); + settings->script_path); goto free_default_index; } From dd040f32812dc13f0f108ca814cfd10930a49e44 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 10 Apr 2022 08:20:54 -0700 Subject: [PATCH 1932/2505] Set the right bit when encoding FastCGI parameters larger than 127 bytes --- src/lib/lwan-mod-fastcgi.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index 0475c684e..860a66f1d 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -127,8 +127,6 @@ static void add_param_len(struct lwan_strbuf *strbuf, const char *value, size_t len_value) { - const uint32_t large_mask = htonl(0x80000000u); - /* FIXME: these should be enabled for release builds, too! */ assert(len_key <= INT_MAX); assert(len_value <= INT_MAX); @@ -136,14 +134,14 @@ static void add_param_len(struct lwan_strbuf *strbuf, if (len_key <= 127) { lwan_strbuf_append_char(strbuf, (char)len_key); } else { - uint32_t len_net = htonl((uint32_t)len_key) | large_mask; + uint32_t len_net = htonl((uint32_t)len_key) | 1u << 31; lwan_strbuf_append_str(strbuf, (char *)&len_net, 4); } if (len_value <= 127) { lwan_strbuf_append_char(strbuf, (char)len_value); } else { - uint32_t len_net = htonl((uint32_t)len_value) | large_mask; + uint32_t len_net = htonl((uint32_t)len_value) | 1u << 31; lwan_strbuf_append_str(strbuf, (char *)&len_net, 4); } From 3dc44e608a06464ff73271964bf4d26072b08e2b Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 10 Apr 2022 08:40:13 -0700 Subject: [PATCH 1933/2505] Move PARAMS block size check to fastcgi_handle_request() Since adding parameters were split in two functions, better check this in the callsite. --- src/lib/lwan-mod-fastcgi.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index 860a66f1d..fec7f205f 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -346,16 +346,6 @@ static bool add_params(const struct private_data *pd, colon + 2, value_len); } - if (UNLIKELY(lwan_strbuf_get_length(strbuf) > 0xffffu)) { - /* Should not happen because DEFAULT_BUFFER_SIZE is a lot smaller - * than 65535, but check anyway. (If anything, we could send multiple - * PARAMS records, but that's very unlikely to happen anyway until - * we change how request headers are read.) */ - static_assert(DEFAULT_BUFFER_SIZE <= 0xffffu, - "only needs one PARAMS record"); - return false; - } - return true; } @@ -576,6 +566,15 @@ fastcgi_handle_request(struct lwan_request *request, return HTTP_BAD_REQUEST; if (!add_script_paths(pd, request, response)) return HTTP_NOT_FOUND; + if (UNLIKELY(lwan_strbuf_get_length(response->buffer) > 0xffffu)) { + /* Should not happen because DEFAULT_BUFFER_SIZE is a lot smaller + * than 65535, but check anyway. (If anything, we could send multiple + * PARAMS records, but that's very unlikely to happen anyway until + * we change how request headers are read.) */ + static_assert(DEFAULT_BUFFER_SIZE <= 0xffffu, + "only needs one PARAMS record"); + return HTTP_BAD_REQUEST; + } struct request_header request_header = { .begin_request = {.version = 1, From 467b45394cfbe5e3d5f005b3309d64c87862d3c8 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 10 Apr 2022 08:41:16 -0700 Subject: [PATCH 1934/2505] Plug memory leak in the FastCGI module initialization --- src/lib/lwan-mod-fastcgi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index fec7f205f..61aef83a0 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -790,6 +790,7 @@ static void *fastcgi_create(const char *prefix __attribute__((unused)), .sin_addr = in_addr, .sin_port = htons((uint16_t)int_port)}; pd->addr_size = sizeof(in_addr); + free(address_copy); return pd; } @@ -807,6 +808,7 @@ static void *fastcgi_create(const char *prefix __attribute__((unused)), .sin6_addr = in6_addr, .sin6_port = htons((uint16_t)int_port)}; pd->addr_size = sizeof(in6_addr); + free(address_copy); return pd; } } From 8acf2a4a25635b566f138fa7ca623484f387eab6 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 10 Apr 2022 12:43:10 -0700 Subject: [PATCH 1935/2505] Bubble awaited socket hangup event to request handler The way async/await works in Lwan is that, when registering the awaited fd with epoll, the data pointer still points to the HTTP client connection conn. This ensures that the same mechanism that's used for all connections is used to resume them. This also has the effect that, if the peer of an awaited fd closes its connection, we get a hangup event that will close the HTTP client connection as well. The behavior as is is wrong, since there are two file descriptors registered in epoll with the same conn pointer, and we would call close on both of them, potentially causing a race condition that could cause an unrelated fd to be closed. This patch checks if a conn is suspended by looking at two flags: CONN_SUSPENDED and CONN_HAS_REMOVE_SLEEP_DEFER. If both are set, then it means that we're not unsuspending a coro due to async/await, but rather because it was sleeping with a timer. By checking if the former is set but not the latter, we're sure that this is an awaited fd, so we can skip its expiration and let the request handler handle the situation. Request handlers are supposed to defer a callback that closes the fds it's watching, so this all works out. Found by running: while true; do curl http://localhost:8080/php/index.php || break done While running a simple PHP script. The loop would eventually be broken. With this fix, this doesn't happen anymore. --- src/lib/lwan-thread.c | 12 ++---------- src/lib/lwan.h | 1 + 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index b9c286c36..50b8dd711 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -936,14 +936,11 @@ static void *thread_io_loop(void *data) break; } - bool expire = false; if (UNLIKELY(event->events & (EPOLLRDHUP | EPOLLHUP))) { - if (!(event->events & EPOLLIN)) { + if ((conn->flags & CONN_AWAITED_FD) != CONN_SUSPENDED) { timeout_queue_expire(&tq, conn); continue; } - - expire = true; } if (!conn->coro) { @@ -956,12 +953,7 @@ static void *thread_io_loop(void *data) } resume_coro(&tq, conn, epoll_fd); - - if (expire) { - timeout_queue_expire(&tq, conn); - } else { - timeout_queue_move_to_last(&tq, conn); - } + timeout_queue_move_to_last(&tq, conn); } if (created_coros) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 5da1f889f..a89462705 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -273,6 +273,7 @@ enum lwan_connection_flags { * the connection coro ends. */ CONN_SUSPENDED = 1 << 5, CONN_HAS_REMOVE_SLEEP_DEFER = 1 << 6, + CONN_AWAITED_FD = CONN_SUSPENDED | CONN_HAS_REMOVE_SLEEP_DEFER, CONN_CORK = 1 << 7, From 3b659d99f3f298184457f5af6a2ebe6404ef408e Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 10 Apr 2022 13:37:26 -0700 Subject: [PATCH 1936/2505] Do not send Expires headers in FastCGI responses --- src/lib/lwan-mod-fastcgi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index 61aef83a0..5f0b9bbb7 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -614,6 +614,8 @@ fastcgi_handle_request(struct lwan_request *request, * HTTP request with the verb line, etc. */ lwan_strbuf_append_char(response->buffer, '\r'); + request->flags |= RESPONSE_NO_EXPIRES; + while (true) { struct record record; ssize_t r; From a5037c9f2431814ed9a20b9ecceaaf0773f73370 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 10 Apr 2022 14:10:38 -0700 Subject: [PATCH 1937/2505] Fix heading level of FastCGI section in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6e6c76498..df2825034 100644 --- a/README.md +++ b/README.md @@ -713,7 +713,7 @@ a `404 Not Found` error will be sent instead. |--------|------|---------|-------------| | `code` | `int` | `999` | A HTTP response code | -### FastCGI +#### FastCGI The `fastcgi` proxies requests between the HTTP client connecting to Lwan and a FastCGI server accessible by Lwan. This is useful, for instance, From 1cbb096ac9f1ec9f96813041b5b11516bb0a95a7 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 10 Apr 2022 18:27:22 -0700 Subject: [PATCH 1938/2505] FastCGI section had a word missing --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index df2825034..df5464d53 100644 --- a/README.md +++ b/README.md @@ -715,8 +715,8 @@ a `404 Not Found` error will be sent instead. #### FastCGI -The `fastcgi` proxies requests between the HTTP client connecting to Lwan -and a FastCGI server accessible by Lwan. This is useful, for instance, +The `fastcgi` module proxies requests between the HTTP client connecting to +Lwan and a FastCGI server accessible by Lwan. This is useful, for instance, to serve pages from a scripting language such as PHP. > :bulb: **Note:** This is a preliminary version of this module, and From da4d3f70f7a114a2bb89098ca2b35146fbdfa4fa Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 10 Apr 2022 18:28:02 -0700 Subject: [PATCH 1939/2505] Use non-blocking sockets for FastCGI connection (Also set CLOEXEC while we're at it.) --- src/lib/lwan-mod-fastcgi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index 5f0b9bbb7..b874e8b39 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -552,7 +552,8 @@ fastcgi_handle_request(struct lwan_request *request, int remaining_tries_for_chunked = 10; int fcgi_fd; - fcgi_fd = socket(pd->addr_family, SOCK_STREAM, 0); + fcgi_fd = + socket(pd->addr_family, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); if (fcgi_fd < 0) return HTTP_INTERNAL_ERROR; From 830ef686f1e8ee779a97b0cfddb077f9ebacd3d9 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 10 Apr 2022 18:34:51 -0700 Subject: [PATCH 1940/2505] Cleanup making of FastCGI requests --- src/lib/lwan-mod-fastcgi.c | 78 +++++++++++++++++++++++--------------- 1 file changed, 48 insertions(+), 30 deletions(-) diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index b874e8b39..741543b36 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -228,9 +228,9 @@ static void destroy_script_name(struct cache_entry *entry, void *context) free(snce); } -static bool add_script_paths(const struct private_data *pd, - struct lwan_request *request, - struct lwan_response *response) +static enum lwan_http_status add_script_paths(const struct private_data *pd, + struct lwan_request *request, + struct lwan_response *response) { struct script_name_cache_entry *snce = (struct script_name_cache_entry *)cache_coro_get_and_ref_entry( @@ -239,15 +239,15 @@ static bool add_script_paths(const struct private_data *pd, if (snce) { add_param(response->buffer, "SCRIPT_NAME", snce->script_name); add_param(response->buffer, "SCRIPT_FILENAME", snce->script_filename); - return true; + return HTTP_OK; } - return false; + return HTTP_NOT_FOUND; } -static bool add_params(const struct private_data *pd, - struct lwan_request *request, - struct lwan_response *response) +static enum lwan_http_status add_params(const struct private_data *pd, + struct lwan_request *request, + struct lwan_response *response) { const struct lwan_request_parser_helper *request_helper = request->helper; struct lwan_strbuf *strbuf = response->buffer; @@ -299,7 +299,7 @@ static bool add_params(const struct private_data *pd, * forward a va_arg to strbuf_append_printf() instead? */ if (asprintf(&temp, "%s?%s", request->original_url.value, query_string) < 0) { - return false; + return HTTP_INTERNAL_ERROR; } add_param(strbuf, "QUERY_STRING", query_string ? query_string : ""); @@ -346,7 +346,7 @@ static bool add_params(const struct private_data *pd, colon + 2, value_len); } - return true; + return HTTP_OK; } static bool @@ -543,30 +543,21 @@ static bool try_initiating_chunked_response(struct lwan_request *request) return true; } -static enum lwan_http_status -fastcgi_handle_request(struct lwan_request *request, - struct lwan_response *response, - void *instance) +static enum lwan_http_status send_request(struct private_data *pd, + struct lwan_request *request, + struct lwan_response *response, + int fcgi_fd) { - struct private_data *pd = instance; - int remaining_tries_for_chunked = 10; - int fcgi_fd; + enum lwan_http_status status; - fcgi_fd = - socket(pd->addr_family, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); - if (fcgi_fd < 0) - return HTTP_INTERNAL_ERROR; + status = add_params(pd, request, response); + if (status != HTTP_OK) + return status; - coro_defer(request->conn->coro, close_fd, (void *)(intptr_t)fcgi_fd); + status = add_script_paths(pd, request, response); + if (status != HTTP_OK) + return status; - if (connect(fcgi_fd, (struct sockaddr *)&pd->sock_addr, pd->addr_size) < 0) - return HTTP_UNAVAILABLE; - - lwan_strbuf_reset(response->buffer); - if (!add_params(pd, request, response)) - return HTTP_BAD_REQUEST; - if (!add_script_paths(pd, request, response)) - return HTTP_NOT_FOUND; if (UNLIKELY(lwan_strbuf_get_length(response->buffer) > 0xffffu)) { /* Should not happen because DEFAULT_BUFFER_SIZE is a lot smaller * than 65535, but check anyway. (If anything, we could send multiple @@ -611,6 +602,33 @@ fastcgi_handle_request(struct lwan_request *request, lwan_strbuf_reset(response->buffer); + return HTTP_OK; +} + +static enum lwan_http_status +fastcgi_handle_request(struct lwan_request *request, + struct lwan_response *response, + void *instance) +{ + struct private_data *pd = instance; + enum lwan_http_status status; + int remaining_tries_for_chunked = 10; + int fcgi_fd; + + fcgi_fd = + socket(pd->addr_family, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); + if (fcgi_fd < 0) + return HTTP_INTERNAL_ERROR; + + coro_defer(request->conn->coro, close_fd, (void *)(intptr_t)fcgi_fd); + + if (connect(fcgi_fd, (struct sockaddr *)&pd->sock_addr, pd->addr_size) < 0) + return HTTP_UNAVAILABLE; + + status = send_request(pd, request, response, fcgi_fd); + if (status != HTTP_OK) + return status; + /* FIXME: the header parser starts at the \r from an usual * HTTP request with the verb line, etc. */ lwan_strbuf_append_char(response->buffer, '\r'); From 08aa12a1e4f0588f8e15837d8a39ff55ab7201a5 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 10 Apr 2022 18:50:13 -0700 Subject: [PATCH 1941/2505] Cleanup HTTP request header parsing Split between finding the headers and looking at which header do in two different functions, so one of them can be exported privately to the FastCGI module without requiring a request_parser_helper. --- src/lib/lwan-mod-fastcgi.c | 21 +++++++-------- src/lib/lwan-private.h | 5 ++-- src/lib/lwan-request.c | 53 ++++++++++++++++++++++---------------- 3 files changed, 42 insertions(+), 37 deletions(-) diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index 741543b36..03fc0b6eb 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -450,38 +450,35 @@ static bool try_initiating_chunked_response(struct lwan_request *request) { struct lwan_response *response = &request->response; char *header_start[N_HEADER_START]; + char *next_request; enum lwan_http_status status_code = HTTP_OK; struct lwan_value buffer = { .value = lwan_strbuf_get_buffer(response->buffer), .len = lwan_strbuf_get_length(response->buffer), }; - struct lwan_request_parser_helper helper = {.buffer = &buffer, - .header_start = header_start}; assert(!(request->flags & (RESPONSE_CHUNKED_ENCODING | RESPONSE_SENT_HEADERS))); - if (!lwan_request_seems_complete(&helper)) + if (!memmem(buffer.value, buffer.len, "\r\n\r\n", 4)) return true; - /* FIXME: Maybe split parse_headers() into a function that finds all - * headers and another that looks at the found headers? We don't use - * the second part of this function here. */ - if (!lwan_parse_headers(&helper, buffer.value)) + ssize_t n_headers = lwan_find_headers(header_start, &buffer, &next_request); + if (n_headers < 0) return false; /* FIXME: Maybe use a lwan_key_value_array here? */ struct lwan_key_value *additional_headers = - calloc(helper.n_header_start + 1, sizeof(struct lwan_key_value)); + calloc((size_t)n_headers + 1, sizeof(struct lwan_key_value)); if (!additional_headers) return false; struct coro_defer *free_additional_headers = coro_defer(request->conn->coro, free, additional_headers); - for (size_t i = 0, j = 0; i < helper.n_header_start; i++) { - char *begin = helper.header_start[i]; - char *end = helper.header_start[i + 1]; + for (ssize_t i = 0, j = 0; i < n_headers; i++) { + char *begin = header_start[i]; + char *end = header_start[i + 1]; char *p; p = strchr(begin, ':'); @@ -525,7 +522,7 @@ static bool try_initiating_chunked_response(struct lwan_request *request) coro_defer_fire_and_disarm(request->conn->coro, free_additional_headers); - char *chunk_start = header_start[helper.n_header_start]; + char *chunk_start = header_start[n_headers]; size_t chunk_len = buffer.len - (size_t)(chunk_start - buffer.value); if (chunk_len) { diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index e708057e3..95d6f8d0e 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -269,10 +269,9 @@ void lwan_straitjacket_enforce_from_config(struct config *c); uint64_t lwan_request_get_id(struct lwan_request *request); -bool lwan_parse_headers(struct lwan_request_parser_helper *helper, - char *buffer); +ssize_t lwan_find_headers(char **header_start, struct lwan_value *buffer, + char **next_request); const char *lwan_request_get_header_from_helper(struct lwan_request_parser_helper *helper, const char *header); -bool lwan_request_seems_complete(struct lwan_request_parser_helper *helper); sa_family_t lwan_socket_parse_address(char *listener, char **node, char **port); diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 3b3018122..a85391958 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -497,28 +497,29 @@ __attribute__((noinline)) static void set_header_value( set_header_value(&(helper->dest), end, p, header_len); \ } while (0) -static bool parse_headers(struct lwan_request_parser_helper *helper, - char *buffer) +static ALWAYS_INLINE ssize_t find_headers(char **header_start, + struct lwan_value *request_buffer, + char **next_request) { - char *buffer_end = helper->buffer->value + helper->buffer->len; - char **header_start = helper->header_start; - size_t n_headers = 0; + char *buffer = request_buffer->value; + char *buffer_end = buffer + request_buffer->len; + ssize_t n_headers = 0; char *next_header; for (char *next_chr = buffer + 1;;) { next_header = memchr(next_chr, '\r', (size_t)(buffer_end - next_chr)); if (UNLIKELY(!next_header)) - return false; + return -1; if (next_chr == next_header) { if (buffer_end - next_chr >= (ptrdiff_t)HEADER_TERMINATOR_LEN) { STRING_SWITCH_SMALL (next_header) { case STR2_INT('\r', '\n'): - helper->next_request = next_header + HEADER_TERMINATOR_LEN; + *next_request = next_header + HEADER_TERMINATOR_LEN; } } - break; + goto out; } /* Is there at least a space for a minimal (H)eader and a (V)alue? */ @@ -526,20 +527,34 @@ static bool parse_headers(struct lwan_request_parser_helper *helper, header_start[n_headers++] = next_chr; if (UNLIKELY(n_headers >= N_HEADER_START - 1)) - return false; + return -1; } else { /* Better to abort early if there's no space. */ - return false; + return -1; } next_chr = next_header + HEADER_TERMINATOR_LEN; if (UNLIKELY(next_chr >= buffer_end)) - return false; + return -1; } +out: header_start[n_headers] = next_header; + return n_headers; +} - for (size_t i = 0; i < n_headers; i++) { +static bool parse_headers(struct lwan_request_parser_helper *helper, + char *buffer) +{ + char **header_start = helper->header_start; + ssize_t n_headers = 0; + + n_headers = find_headers(header_start, helper->buffer, + &helper->next_request); + if (n_headers < 0) + return false; + + for (ssize_t i = 0; i < n_headers; i++) { char *p = header_start[i]; char *end = header_start[i + 1] - HEADER_TERMINATOR_LEN; @@ -580,16 +595,16 @@ static bool parse_headers(struct lwan_request_parser_helper *helper, } } - helper->n_header_start = n_headers; + helper->n_header_start = (size_t)n_headers; return true; } #undef HEADER_LENGTH #undef SET_HEADER_VALUE -bool lwan_parse_headers(struct lwan_request_parser_helper *helper, - char *buffer) +ssize_t lwan_find_headers(char **header_start, struct lwan_value *buffer, + char **next_request) { - return parse_headers(helper, buffer); + return find_headers(header_start, buffer, next_request); } static void parse_if_modified_since(struct lwan_request_parser_helper *helper) @@ -1841,12 +1856,6 @@ lwan_request_get_accept_encoding(struct lwan_request *request) return request->flags & REQUEST_ACCEPT_MASK; } -bool lwan_request_seems_complete(struct lwan_request_parser_helper *helper) -{ - return read_request_finalizer_from_helper(helper->buffer, helper, 1, - false) == FINALIZER_DONE; -} - #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION static int useless_coro_for_fuzzing(struct coro *c __attribute__((unused)), void *data __attribute__((unused))) From 01f1388695ee5cf021e43d879043e18bf9cfb60b Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 13 Apr 2022 08:28:22 -0700 Subject: [PATCH 1942/2505] Build fix for request_fuzzer Closes https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=46656 --- src/lib/lwan-request.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index a85391958..df53b8aa0 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1863,6 +1863,12 @@ static int useless_coro_for_fuzzing(struct coro *c __attribute__((unused)), return 0; } +static bool request_seems_complete(struct lwan_request_parser_helper *helper) +{ + return read_request_finalizer_from_helper(helper->buffer, helper, 1, + false) == FINALIZER_DONE; +} + __attribute__((used)) int fuzz_parse_http_request(const uint8_t *data, size_t length) { @@ -1898,7 +1904,7 @@ __attribute__((used)) int fuzz_parse_http_request(const uint8_t *data, /* If the finalizer isn't happy with a request, there's no point in * going any further with parsing it. */ - if (!lwan_request_seems_complete(&request)) + if (!request_seems_complete(&request)) return 0; /* client_read() NUL-terminates the string */ From 33b57dc4a05d087c08b784c54f5fc71006bd63d6 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 13 Apr 2022 08:29:47 -0700 Subject: [PATCH 1943/2505] Minor cleanups in create_script_name() --- src/lib/lwan-mod-fastcgi.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index 03fc0b6eb..ab7fc8f27 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -170,7 +170,6 @@ static struct cache_entry *create_script_name(const char *key, void *context) struct private_data *pd = context; struct script_name_cache_entry *entry; struct lwan_value url; - char temp[PATH_MAX]; int r; entry = malloc(sizeof(*entry)); @@ -184,15 +183,12 @@ static struct cache_entry *create_script_name(const char *key, void *context) } /* SCRIPT_NAME */ - r = snprintf(temp, sizeof(temp), "/%.*s", (int)url.len, url.value); - if (r < 0 || r >= (int)sizeof(temp)) - goto free_entry; - - entry->script_name = strdup(temp); - if (!entry->script_name) + r = asprintf(&entry->script_name, "/%.*s", (int)url.len, url.value); + if (r < 0) goto free_entry; /* SCRIPT_FILENAME */ + char temp[PATH_MAX]; r = snprintf(temp, sizeof(temp), "%s/%.*s", pd->script_path, (int)url.len, url.value); if (r < 0 || r >= (int)sizeof(temp)) From 3985a20a1a871038205f79b365245444923afb42 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 13 Apr 2022 08:30:02 -0700 Subject: [PATCH 1944/2505] Remove outdated FIXME --- src/lib/lwan-mod-fastcgi.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index ab7fc8f27..bd26eb43e 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -578,8 +578,6 @@ static enum lwan_http_status send_request(struct private_data *pd, .end_params = {.version = 1, .type = FASTCGI_TYPE_PARAMS, .id = htons(1)}, - /* FIXME: do we need a STDIN record if there's no request body is empty? - */ .empty_stdin = {.version = 1, .type = FASTCGI_TYPE_STDIN, .id = htons(1)}, From dd0d7f7df55629ee9dec01cdd1fc6748ff009127 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 13 Apr 2022 08:30:25 -0700 Subject: [PATCH 1945/2505] Better document some macros and enumerations --- src/lib/lwan.h | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index a89462705..dd47e9230 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -42,10 +42,9 @@ extern "C" { #if defined(__cplusplus) #define ZERO_IF_IS_ARRAY(array) 0 #else -/* This macro expands to 0 if its parameter is an array, and generates a +/* This macro expands to 0 if its parameter is an array, and causes a * compilation error otherwise. This is used by the N_ELEMENTS() macro to catch - * invalid usages of this macro (e.g. when using arrays decayed to pointers in - * function parameters). */ + * invalid usages of this macro (e.g. when using arrays decayed to pointers) */ #define ZERO_IF_IS_ARRAY(array) \ (!sizeof(char[1 - 2 * __builtin_types_compatible_p( \ __typeof__(array), __typeof__(&(array)[0]))])) @@ -266,15 +265,29 @@ enum lwan_connection_flags { CONN_EVENTS_MASK = 1 << 0 | 1 << 1, CONN_IS_KEEP_ALIVE = 1 << 2, + + /* WebSockets-related flags. */ CONN_IS_UPGRADE = 1 << 3, CONN_IS_WEBSOCKET = 1 << 4, - /* This is only used to determine if timeout_del() is necessary when - * the connection coro ends. */ + /* These are used for a few different things: + * - Avoid re-deferring callbacks to remove request from the timeout wheel + * after it has slept previously and is requesting to sleep again. (The + * timeout defer is disarmed right after resuming, and is only there because + * connections may be closed when they're suspended.) + * - Distinguish file descriptor in event loop between the connection and + * an awaited file descriptor. (This is set in the connection that's awaiting + * since the pointer to the connection is used as user_data in both cases. + * This is required to be able to resume the connection coroutine after the + * await is completed, and to bubble up errors in awaited file descriptors to + * request handlers rather than abruptly closing the connection.) */ CONN_SUSPENDED = 1 << 5, CONN_HAS_REMOVE_SLEEP_DEFER = 1 << 6, CONN_AWAITED_FD = CONN_SUSPENDED | CONN_HAS_REMOVE_SLEEP_DEFER, + /* Used when HTTP pipelining has been detected. This enables usage of the + * MSG_MORE flags when sending responses to batch as many short responses + * as possible in a single TCP fragment. */ CONN_CORK = 1 << 7, /* Set only on file descriptors being watched by async/await to determine @@ -294,14 +307,25 @@ enum lwan_connection_flags { }; enum lwan_connection_coro_yield { + /* Returns to the event loop and terminates the coroutine, freeing + * all resources associated with it, including calling deferred + * callback, and the coroutine itself. */ CONN_CORO_ABORT, + /* Return to the event loop without changing the epoll event mask + * or any other flag in this coroutine. */ CONN_CORO_YIELD, + /* Returns to the event loop, and optionally change the epoll event + * mask (if it's not already the expected one.) */ CONN_CORO_WANT_READ, CONN_CORO_WANT_WRITE, CONN_CORO_WANT_READ_WRITE, + /* If a connection coroutine yields with CONN_CORO_SUSPEND, then + * it'll be resumed using CONN_CORO_RESUME from the event loop. + * CONN_CORO_RESUME should never be used from within connections + * themselves, and should be considered a private API. */ CONN_CORO_SUSPEND, CONN_CORO_RESUME, @@ -313,6 +337,8 @@ enum lwan_connection_coro_yield { CONN_CORO_MAX, + /* Private API used by the async/await mechanism. Shouldn't be used + * by handlers. */ CONN_CORO_ASYNC = CONN_CORO_ASYNC_AWAIT_READ, }; From 35c95904258700d24b606f5a10d4c9658a027620 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 13 Apr 2022 21:34:22 -0700 Subject: [PATCH 1946/2505] FastCGI: Reduce allocations while trying to initiate chunked encoding Also return better error messages back to the client depending on which kind of error happened. --- src/lib/lwan-mod-fastcgi.c | 54 ++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index bd26eb43e..38831e60a 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -442,7 +442,16 @@ static bool discard_unknown_record(struct lwan_request *request, return true; } -static bool try_initiating_chunked_response(struct lwan_request *request) +DEFINE_ARRAY_TYPE_INLINEFIRST(header_array, struct lwan_key_value) + +static void reset_additional_header(void *data) +{ + struct header_array *array = data; + header_array_reset(array); +} + +static enum lwan_http_status +try_initiating_chunked_response(struct lwan_request *request) { struct lwan_response *response = &request->response; char *header_start[N_HEADER_START]; @@ -457,22 +466,19 @@ static bool try_initiating_chunked_response(struct lwan_request *request) (RESPONSE_CHUNKED_ENCODING | RESPONSE_SENT_HEADERS))); if (!memmem(buffer.value, buffer.len, "\r\n\r\n", 4)) - return true; + return HTTP_OK; ssize_t n_headers = lwan_find_headers(header_start, &buffer, &next_request); if (n_headers < 0) - return false; + return HTTP_BAD_REQUEST; - /* FIXME: Maybe use a lwan_key_value_array here? */ - struct lwan_key_value *additional_headers = - calloc((size_t)n_headers + 1, sizeof(struct lwan_key_value)); - if (!additional_headers) - return false; + struct header_array additional_headers; - struct coro_defer *free_additional_headers = - coro_defer(request->conn->coro, free, additional_headers); + header_array_init(&additional_headers); + struct coro_defer *additional_headers_reset = coro_defer(request->conn->coro, + reset_additional_header, &additional_headers); - for (ssize_t i = 0, j = 0; i < n_headers; i++) { + for (ssize_t i = 0; i < n_headers; i++) { char *begin = header_start[i]; char *end = header_start[i + 1]; char *p; @@ -506,17 +512,26 @@ static bool try_initiating_chunked_response(struct lwan_request *request) else status_code = (enum lwan_http_status)status_as_int; } else { - additional_headers[j++] = - (struct lwan_key_value){.key = key, .value = value}; + struct lwan_key_value *header = header_array_append(&additional_headers); + + if (!header) + return HTTP_INTERNAL_ERROR; + + *header = (struct lwan_key_value){.key = key, .value = value}; } } + struct lwan_key_value *header = header_array_append(&additional_headers); + if (!header) + return HTTP_INTERNAL_ERROR; + *header = (struct lwan_key_value){}; + if (!lwan_response_set_chunked_full(request, status_code, - additional_headers)) { - return false; + header_array_get_array(&additional_headers))) { + return HTTP_INTERNAL_ERROR; } - coro_defer_fire_and_disarm(request->conn->coro, free_additional_headers); + coro_defer_fire_and_disarm(request->conn->coro, additional_headers_reset); char *chunk_start = header_start[n_headers]; size_t chunk_len = buffer.len - (size_t)(chunk_start - buffer.value); @@ -533,7 +548,7 @@ static bool try_initiating_chunked_response(struct lwan_request *request) lwan_strbuf_reset(response->buffer); } - return true; + return HTTP_OK; } static enum lwan_http_status send_request(struct private_data *pd, @@ -659,8 +674,9 @@ fastcgi_handle_request(struct lwan_request *request, if (!remaining_tries_for_chunked) return HTTP_UNAVAILABLE; - if (!try_initiating_chunked_response(request)) - return HTTP_BAD_REQUEST; + status = try_initiating_chunked_response(request); + if (status != HTTP_OK) + return status; } if (record.type == FASTCGI_TYPE_END_REQUEST) From f3dac16697e185f1e6030b9b715c208f3a8e3e3f Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 13 Apr 2022 21:35:20 -0700 Subject: [PATCH 1947/2505] FastCGI: Implement STDIN record support Untested. --- README.md | 4 +-- src/lib/lwan-mod-fastcgi.c | 63 +++++++++++++++++++++++++++++--------- 2 files changed, 49 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index df5464d53..6322db604 100644 --- a/README.md +++ b/README.md @@ -720,9 +720,7 @@ Lwan and a FastCGI server accessible by Lwan. This is useful, for instance, to serve pages from a scripting language such as PHP. > :bulb: **Note:** This is a preliminary version of this module, and -> as such, it's not well optimized and some features are missing. Of -> note, requests that send a body (e.g. POST requests) won't work because -> Lwan does not implement the STDIN record yet. +> as such, it's not well optimized and some features are missing. | Option | Type | Default | Description | |--------|------|---------|-------------| diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index 38831e60a..5cc273cb0 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -103,11 +103,6 @@ struct request_header { struct record begin_params; } __attribute__((packed)); -struct request_footer { - struct record end_params; - struct record empty_stdin; -} __attribute__((packed)); - struct script_name_cache_entry { struct cache_entry base; char *script_name; @@ -589,23 +584,61 @@ static enum lwan_http_status send_request(struct private_data *pd, .len_content = htons((uint16_t)lwan_strbuf_get_length( response->buffer))}, }; - struct request_footer request_footer = { - .end_params = {.version = 1, - .type = FASTCGI_TYPE_PARAMS, - .id = htons(1)}, - .empty_stdin = {.version = 1, - .type = FASTCGI_TYPE_STDIN, - .id = htons(1)}, + struct record end_params_block = { + .version = 1, + .type = FASTCGI_TYPE_PARAMS, + .id = htons(1), + }; + struct record end_stdin_block = { + .version = 1, + .type = FASTCGI_TYPE_STDIN, + .id = htons(1), }; - struct iovec vec[] = { {.iov_base = &request_header, .iov_len = sizeof(request_header)}, {.iov_base = lwan_strbuf_get_buffer(response->buffer), .iov_len = lwan_strbuf_get_length(response->buffer)}, - {.iov_base = &request_footer, .iov_len = sizeof(request_footer)}, + {.iov_base = &end_params_block, .iov_len = sizeof(end_params_block)}, + {.iov_base = &end_stdin_block, .iov_len = sizeof(end_stdin_block)}, }; - lwan_request_async_writev(request, fcgi_fd, vec, N_ELEMENTS(vec)); + const struct lwan_value *body_data = lwan_request_get_request_body(request); + if (!body_data) { + lwan_request_async_writev(request, fcgi_fd, vec, N_ELEMENTS(vec)); + goto done; + } + + /* If we have body data, don't send the last element just yet: we need + * to send each stdin segment until we don't have anything else there + * anymore before sending one with len_content = 0. */ + lwan_request_async_writev(request, fcgi_fd, vec, N_ELEMENTS(vec) - 1); + + size_t to_send = body_data->len; + char *buffer = body_data->value; + while (to_send) { + size_t block_size = LWAN_MIN(0xffffull, to_send); + struct record stdin_header = { + .version = 1, + .type = FASTCGI_TYPE_STDIN, + .id = htons(1), + .len_content = htons((uint16_t)block_size), + }; + struct iovec chunk_vec[] = { + {.iov_base = &stdin_header, .iov_len = sizeof(stdin_header)}, + {.iov_base = buffer, .iov_len = block_size}, + }; + + lwan_request_async_writev(request, fcgi_fd, chunk_vec, + N_ELEMENTS(chunk_vec)); + + to_send -= block_size; + buffer += block_size; + } + + lwan_request_async_write(request, fcgi_fd, &end_stdin_block, + sizeof(end_stdin_block)); + +done: lwan_strbuf_reset(response->buffer); return HTTP_OK; From b73e4f968bd9405d35c4a47e84333dfdf3e914f5 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 17 Apr 2022 10:53:51 -0700 Subject: [PATCH 1948/2505] Assert async_await fd events are handled by the suspended HTTP connection --- src/lib/lwan-thread.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 50b8dd711..e53a23c75 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -928,6 +928,8 @@ static void *thread_io_loop(void *data) for (struct epoll_event *event = events; n_fds--; event++) { struct lwan_connection *conn = event->data.ptr; + assert(!(conn->flags & CONN_ASYNC_AWAIT)); + if (conn->flags & (CONN_LISTENER_HTTP | CONN_LISTENER_HTTPS)) { if (LIKELY(accept_waiting_clients(t, conn))) continue; From 939dbf6356174af94cc8dff096d809480ad7c219 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 17 Apr 2022 10:55:26 -0700 Subject: [PATCH 1949/2505] Send a Unavailable response if we couldn't create coro --- src/lib/lwan-thread.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index e53a23c75..0a238f4ea 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -947,7 +947,7 @@ static void *thread_io_loop(void *data) if (!conn->coro) { if (UNLIKELY(!spawn_coro(conn, &switcher, &tq))) { - send_last_response_without_coro(t->lwan, conn, HTTP_INTERNAL_ERROR); + send_last_response_without_coro(t->lwan, conn, HTTP_UNAVAILABLE); continue; } From 6b0d0c8b0f9dae6789cf36b2493c58dfbc9c5caa Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 21 Apr 2022 19:16:57 -0700 Subject: [PATCH 1950/2505] Build fix for request_fuzzer --- src/lib/lwan-request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index df53b8aa0..2415bc08c 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1904,7 +1904,7 @@ __attribute__((used)) int fuzz_parse_http_request(const uint8_t *data, /* If the finalizer isn't happy with a request, there's no point in * going any further with parsing it. */ - if (!request_seems_complete(&request)) + if (!request_seems_complete(&helper)) return 0; /* client_read() NUL-terminates the string */ From f572581513a2dc760ffbae9c00b63856f12d0c84 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 21 Apr 2022 19:17:24 -0700 Subject: [PATCH 1951/2505] Do not resucitate connections that were aborted We were calling timeout_queue_move_to_last() even if we had just expired a connection. This would effectively close a connection twice when the keep-alive timeout expired, causing a race condition. Fix by only calling either timeout_queue_expire() or timeout_queue_move_to_last() inside resume_coro() to avoid a branch in the event loop to conditionally push the connection to the end of the list. Fixes #340. --- src/lib/lwan-thread.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 0a238f4ea..80583cdb0 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -575,14 +575,18 @@ static ALWAYS_INLINE void resume_coro(struct timeout_queue *tq, int64_t from_coro = coro_resume(conn->coro); enum lwan_connection_coro_yield yield_result = from_coro & 0xffffffff; - if (UNLIKELY(yield_result >= CONN_CORO_ASYNC)) - yield_result = resume_async(tq, yield_result, from_coro, conn, epoll_fd); - - if (UNLIKELY(yield_result == CONN_CORO_ABORT)) - return timeout_queue_expire(tq, conn); + if (UNLIKELY(yield_result >= CONN_CORO_ASYNC)) { + yield_result = + resume_async(tq, yield_result, from_coro, conn, epoll_fd); + } - return update_epoll_flags(lwan_connection_get_fd(tq->lwan, conn), conn, - epoll_fd, yield_result); + if (UNLIKELY(yield_result == CONN_CORO_ABORT)) { + timeout_queue_expire(tq, conn); + } else { + update_epoll_flags(lwan_connection_get_fd(tq->lwan, conn), conn, + epoll_fd, yield_result); + timeout_queue_move_to_last(tq, conn); + } } static void update_date_cache(struct lwan_thread *thread) @@ -955,7 +959,6 @@ static void *thread_io_loop(void *data) } resume_coro(&tq, conn, epoll_fd); - timeout_queue_move_to_last(&tq, conn); } if (created_coros) From 7a8a0eb7023122ad9605627c45af03b78fca44c4 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 21 Apr 2022 19:22:07 -0700 Subject: [PATCH 1952/2505] Remove redundant assignments when removing connection from TQ These were there because I was never able to figure out why connections would sometimes be ressucitated. Since that's been fixed, let's remove these useless assignments. --- src/lib/lwan-tq.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib/lwan-tq.c b/src/lib/lwan-tq.c index 096e56b19..8fa980eb2 100644 --- a/src/lib/lwan-tq.c +++ b/src/lib/lwan-tq.c @@ -52,8 +52,6 @@ static inline void timeout_queue_remove(struct timeout_queue *tq, next->prev = node->prev; prev->next = node->next; - - node->next = node->prev = -1; } bool timeout_queue_empty(struct timeout_queue *tq) { return tq->head.next < 0; } From 15e802fbdb78a9e98f68211e8768609b6c961ab3 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 21 Apr 2022 19:23:12 -0700 Subject: [PATCH 1953/2505] Allocate temp memory in FastCGI::handle_stderr() with malloc Instead of using coro_malloc() that doesn't allow us to free the memory, use plain old malloc and schedule a deferred free once we don't need the buffer anymore. --- src/lib/lwan-mod-fastcgi.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index 5cc273cb0..6f4a252da 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -371,16 +371,15 @@ handle_stdout(struct lwan_request *request, const struct record *record, int fd) static bool handle_stderr(struct lwan_request *request, const struct record *record, int fd) { - /* Even though we only use buffer within this function, we need to use - * coro_malloc() to allocate this buffer. Otherwise, if - * lwan_request_async_read() yields the coroutine, the main loop might - * terminate the coroutine if either connection is dropped. */ size_t to_read = (size_t)ntohs(record->len_content); - char *buffer = coro_malloc(request->conn->coro, to_read); + char *buffer = malloc(to_read); if (!buffer) return false; + struct coro_defer *buffer_free_defer = + coro_defer(request->conn->coro, free, buffer); + for (char *p = buffer; to_read;) { ssize_t r = lwan_request_async_read(request, fd, p, to_read); @@ -394,6 +393,8 @@ handle_stderr(struct lwan_request *request, const struct record *record, int fd) lwan_status_error("FastCGI stderr output: %.*s", (int)ntohs(record->len_content), buffer); + coro_defer_fire_and_disarm(request->conn->coro, buffer_free_defer); + if (record->len_padding) { char padding[256]; lwan_request_async_read_flags(request, fd, padding, From 677977cf2e27cf7b89e390e851ee37f75ef898d9 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 21 Apr 2022 19:24:32 -0700 Subject: [PATCH 1954/2505] FastCGI: Ensure there's a space after the error code in the Status: header --- src/lib/lwan-mod-fastcgi.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index 6f4a252da..b8d2fe765 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -499,6 +499,10 @@ try_initiating_chunked_response(struct lwan_request *request) status_code = HTTP_INTERNAL_ERROR; continue; } + if (value[3] != ' ') { + status_code = HTTP_INTERNAL_ERROR; + continue; + } value[3] = '\0'; int status_as_int = parse_int(value, -1); From 6de28511b5770154ee53feda1b7c6d56a315b810 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 21 Apr 2022 19:25:32 -0700 Subject: [PATCH 1955/2505] Clean up handling of FastCGI STDIN records --- src/lib/lwan-mod-fastcgi.c | 69 ++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 32 deletions(-) diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index b8d2fe765..c68a7a61e 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -551,6 +551,37 @@ try_initiating_chunked_response(struct lwan_request *request) return HTTP_OK; } +static void send_stdin_records(struct lwan_request *request, + int fcgi_fd, + const struct lwan_value *body_data, + const struct record *end_stdin_block) +{ + size_t to_send = body_data->len; + char *buffer = body_data->value; + + while (to_send) { + size_t block_size = LWAN_MIN(0xffffull, to_send); + struct record stdin_header = { + .version = 1, + .type = FASTCGI_TYPE_STDIN, + .id = htons(1), + .len_content = htons((uint16_t)block_size), + }; + struct iovec vec[] = { + {.iov_base = &stdin_header, .iov_len = sizeof(stdin_header)}, + {.iov_base = buffer, .iov_len = block_size}, + }; + + lwan_request_async_writev(request, fcgi_fd, vec, N_ELEMENTS(vec)); + + to_send -= block_size; + buffer += block_size; + } + + lwan_request_async_write(request, fcgi_fd, end_stdin_block, + sizeof(*end_stdin_block)); +} + static enum lwan_http_status send_request(struct private_data *pd, struct lwan_request *request, struct lwan_response *response, @@ -610,40 +641,14 @@ static enum lwan_http_status send_request(struct private_data *pd, const struct lwan_value *body_data = lwan_request_get_request_body(request); if (!body_data) { lwan_request_async_writev(request, fcgi_fd, vec, N_ELEMENTS(vec)); - goto done; - } - - /* If we have body data, don't send the last element just yet: we need - * to send each stdin segment until we don't have anything else there - * anymore before sending one with len_content = 0. */ - lwan_request_async_writev(request, fcgi_fd, vec, N_ELEMENTS(vec) - 1); - - size_t to_send = body_data->len; - char *buffer = body_data->value; - while (to_send) { - size_t block_size = LWAN_MIN(0xffffull, to_send); - struct record stdin_header = { - .version = 1, - .type = FASTCGI_TYPE_STDIN, - .id = htons(1), - .len_content = htons((uint16_t)block_size), - }; - struct iovec chunk_vec[] = { - {.iov_base = &stdin_header, .iov_len = sizeof(stdin_header)}, - {.iov_base = buffer, .iov_len = block_size}, - }; - - lwan_request_async_writev(request, fcgi_fd, chunk_vec, - N_ELEMENTS(chunk_vec)); - - to_send -= block_size; - buffer += block_size; + } else { + /* If we have body data, don't send the last element just yet: we need + * to send each stdin segment until we don't have anything else there + * anymore before sending one with len_content = 0. */ + lwan_request_async_writev(request, fcgi_fd, vec, N_ELEMENTS(vec) - 1); + send_stdin_records(request, fcgi_fd, body_data, &end_stdin_block); } - lwan_request_async_write(request, fcgi_fd, &end_stdin_block, - sizeof(end_stdin_block)); - -done: lwan_strbuf_reset(response->buffer); return HTTP_OK; From 7a7baf683f9ffd844ef97e535d5f22571596200a Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 21 Apr 2022 19:28:19 -0700 Subject: [PATCH 1956/2505] FastCGI: Yield 413 if received header buffer exceeds DEFAULT_BUFFER_SIZE --- src/lib/lwan-mod-fastcgi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index c68a7a61e..2ed976d0c 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -604,7 +604,7 @@ static enum lwan_http_status send_request(struct private_data *pd, * we change how request headers are read.) */ static_assert(DEFAULT_BUFFER_SIZE <= 0xffffu, "only needs one PARAMS record"); - return HTTP_BAD_REQUEST; + return HTTP_TOO_LARGE; } struct request_header request_header = { From 58e0a81ff56de322346043d24d6f872ceb2d0c8f Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 23 Apr 2022 09:41:41 -0700 Subject: [PATCH 1957/2505] Open FastCGI script_path directory with O_CLOEXEC --- src/lib/lwan-mod-fastcgi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index 2ed976d0c..5e0b9f20f 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -789,7 +789,7 @@ static void *fastcgi_create(const char *prefix __attribute__((unused)), goto free_default_index; } - pd->script_path_fd = open(pd->script_path, O_PATH | O_DIRECTORY); + pd->script_path_fd = open(pd->script_path, O_PATH | O_DIRECTORY | O_CLOEXEC); if (pd->script_path_fd < 0) { lwan_status_perror("FastCGI: Could not open `script_path` at '%s'", pd->script_path); From 9f7f7089720a3d8298b4bc41ec612264287825b3 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 23 Apr 2022 09:42:05 -0700 Subject: [PATCH 1958/2505] Ensure FastCGI address is either a UDS, IPv4, or IPv6 address --- src/lib/lwan-mod-fastcgi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index 5e0b9f20f..020f219a1 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -884,7 +884,8 @@ static void *fastcgi_create(const char *prefix __attribute__((unused)), } } - return pd; + lwan_status_error("FastCGI: Address '%s' isn't a valid Unix Domain Socket, IPv4, or IPv6 address", + settings->address); free_address_copy: free(address_copy); From 8cc7f6b4679ad67eaa88f3ceaa4adbf99da62ee1 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 23 Apr 2022 09:42:45 -0700 Subject: [PATCH 1959/2505] When updating epoll flags, call get_fd() only if needed This isn't expensive but if we can avoid it, well, avoid it :) --- src/lib/lwan-thread.c | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 80583cdb0..04b55be46 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -457,7 +457,7 @@ conn_flags_to_epoll_events(enum lwan_connection_flags flags) return map[flags & CONN_EVENTS_MASK]; } -static void update_epoll_flags(int fd, +static void update_epoll_flags(const struct timeout_queue *tq, struct lwan_connection *conn, int epoll_fd, enum lwan_connection_coro_yield yield_result) @@ -505,10 +505,9 @@ static void update_epoll_flags(int fd, if (conn->flags == prev_flags) return; - struct epoll_event event = { - .events = conn_flags_to_epoll_events(conn->flags), - .data.ptr = conn, - }; + struct epoll_event event = {.events = conn_flags_to_epoll_events(conn->flags), + .data.ptr = conn}; + int fd = lwan_connection_get_fd(tq->lwan, conn); if (UNLIKELY(epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &event) < 0)) lwan_status_perror("epoll_ctl"); @@ -522,7 +521,7 @@ static void clear_async_await_flag(void *data) } static enum lwan_connection_coro_yield -resume_async(struct timeout_queue *tq, +resume_async(const struct timeout_queue *tq, enum lwan_connection_coro_yield yield_result, int64_t from_coro, struct lwan_connection *conn, @@ -583,8 +582,7 @@ static ALWAYS_INLINE void resume_coro(struct timeout_queue *tq, if (UNLIKELY(yield_result == CONN_CORO_ABORT)) { timeout_queue_expire(tq, conn); } else { - update_epoll_flags(lwan_connection_get_fd(tq->lwan, conn), conn, - epoll_fd, yield_result); + update_epoll_flags(tq, conn, epoll_fd, yield_result); timeout_queue_move_to_last(tq, conn); } } @@ -732,9 +730,7 @@ static bool process_pending_timers(struct timeout_queue *tq, } request = container_of(timeout, struct lwan_request, timeout); - - update_epoll_flags(request->fd, request->conn, epoll_fd, - CONN_CORO_RESUME); + update_epoll_flags(tq, request->conn, epoll_fd, CONN_CORO_RESUME); } if (should_expire_timers) { From 2bb076b06ec75e9efecabdd8464f2692a82cb921 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 24 Apr 2022 10:35:05 -0700 Subject: [PATCH 1960/2505] Fix dump_request_id test --- src/bin/testrunner/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/testrunner/main.c b/src/bin/testrunner/main.c index 9aa60f0bd..022b984f6 100644 --- a/src/bin/testrunner/main.c +++ b/src/bin/testrunner/main.c @@ -205,7 +205,7 @@ LWAN_HANDLER(hello_world) const char *dump_request_id = lwan_request_get_query_param(request, "dump_request_id"); if (dump_request_id && streq(dump_request_id, "1")) { - lwan_strbuf_append_printf(response->buffer, "\nRequest ID: <<%0lx>>", + lwan_strbuf_append_printf(response->buffer, "\nRequest ID: <<%016lx>>", lwan_request_get_id(request)); } From f9a6d2f11ebab4db7bbd46944a4ef40e9046acb5 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 24 Apr 2022 10:35:19 -0700 Subject: [PATCH 1961/2505] Consider prefix when building redir location string In the file serving module, if one requests, say, "/blog/foo", where "foo" is a directory, we were redirecting to "blog/foo/" instead, which would in fact request "/blog/blog/foo/", which isn't what we want. Consider the prefix (from the configuration file) when building this string, so that, for instance, if the prefix is "/", we redirect to "/blog/foo/", which is what we want. --- src/lib/lwan-mod-serve-files.c | 3 ++- src/scripts/testsuite.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 471fffebe..2eacacfa1 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -755,7 +755,8 @@ static bool redir_init(struct file_cache_entry *ce, { struct redir_cache_data *rd = &ce->redir_cache_data; - return asprintf(&rd->redir_to, "%s/", get_rel_path(full_path, priv)) >= 0; + return asprintf(&rd->redir_to, "%s%s/", priv->prefix, + get_rel_path(full_path, priv)) >= 0; } static const struct cache_funcs *get_funcs(struct serve_files_priv *priv, diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index 07b2c8a32..d36eca8cb 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -431,7 +431,7 @@ def test_directory_without_trailing_slash_redirects(self): self.assertResponsePlain(r, 301) self.assertTrue('location' in r.headers) - self.assertEqual(r.headers['location'], 'icons/') + self.assertEqual(r.headers['location'], '/icons/') class TestRedirect(LwanTest): def test_redirect_default(self): From 8b0c6e8e07245416d98697ca7d29a6845311d5ee Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 24 Apr 2022 10:37:34 -0700 Subject: [PATCH 1962/2505] Fix TestFuzzRegression tests --- src/scripts/testsuite.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index d36eca8cb..5a4902e3c 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -1007,11 +1007,15 @@ def run_test_wrapped(self): return self.run_test(contents) return run_test_wrapped +def only_request_fuzzer_regression(): + for path in os.listdir("fuzz/regression"): + if not "request_fuzzer" in path: + continue + if path.startswith(("clusterfuzz-", "crash-")): + yield path + TestFuzzRegression = type('TestFuzzRegression', (TestFuzzRegressionBase,), { - "test_" + name.replace("-", "_"): TestFuzzRegressionBase.wrap(name) - for name in ( - cf for cf in os.listdir("fuzz/regression") if cf.startswith(("clusterfuzz-", "crash-") and "request_fuzzer" in cf) - ) + "test_" + name.replace("-", "_"): TestFuzzRegressionBase.wrap(name) for name in only_request_fuzzer_regression() }) if __name__ == '__main__': From 7b85c8ce33a3045b9a6a9fe288b54aca2517d121 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 24 Apr 2022 10:38:02 -0700 Subject: [PATCH 1963/2505] Mark some timeout queue functions as inline --- src/lib/lwan-tq.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-tq.c b/src/lib/lwan-tq.c index 8fa980eb2..102443c19 100644 --- a/src/lib/lwan-tq.c +++ b/src/lib/lwan-tq.c @@ -54,10 +54,13 @@ static inline void timeout_queue_remove(struct timeout_queue *tq, prev->next = node->next; } -bool timeout_queue_empty(struct timeout_queue *tq) { return tq->head.next < 0; } +inline bool timeout_queue_empty(struct timeout_queue *tq) +{ + return tq->head.next < 0; +} -void timeout_queue_move_to_last(struct timeout_queue *tq, - struct lwan_connection *conn) +inline void timeout_queue_move_to_last(struct timeout_queue *tq, + struct lwan_connection *conn) { /* CONN_IS_KEEP_ALIVE isn't checked here because non-keep-alive connections * are closed in the request processing coroutine after they have been From f19a90e9bec90befdfb4620523c8ae727cd4bd6b Mon Sep 17 00:00:00 2001 From: pontscho Date: Mon, 2 May 2022 13:20:22 +0200 Subject: [PATCH 1964/2505] WebSocket: handling masked ping on proper way --- src/lib/lwan-websocket.c | 49 ++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index 3ed194725..e1cba4103 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -108,22 +108,6 @@ void lwan_response_websocket_write_binary(struct lwan_request *request) lwan_response_websocket_write(request, WS_OPCODE_BINARY); } -static void send_websocket_pong(struct lwan_request *request, size_t len) -{ - char temp[128]; - - if (UNLIKELY(len > 125)) { - lwan_status_debug("Received PING opcode with length %zu." - "Max is 125. Aborting connection.", - len); - coro_yield(request->conn->coro, CONN_CORO_ABORT); - __builtin_unreachable(); - } - - lwan_recv(request, temp, len, 0); - write_websocket_frame(request, WS_OPCODE_PONG, temp, len); -} - static size_t get_frame_length(struct lwan_request *request, uint16_t header) { uint64_t len; @@ -209,6 +193,37 @@ static void unmask(char *msg, size_t msg_len, char mask[static 4]) } } +static inline void send_websocket_pong(struct lwan_request *request, size_t header) +{ + char mask[4]; + char msg[128]; + + const size_t len = header & 0x7f; + const int masked = header >> 7; + + if (UNLIKELY(len > 125)) { + lwan_status_debug("Received PING opcode with length %zu." + "Max is 125. Aborting connection.", + len); + coro_yield(request->conn->coro, CONN_CORO_ABORT); + __builtin_unreachable(); + } + + if (masked) { + struct iovec vec[] = { + {.iov_base = mask, .iov_len = sizeof(mask)}, + {.iov_base = msg, .iov_len = len}, + }; + lwan_readv(request, vec, N_ELEMENTS(vec)); + unmask(msg, len, mask); + + } else { + lwan_recv(request, msg, len, 0); + } + + write_websocket_frame(request, 0x80 | WS_OPCODE_PONG, msg, len); +} + int lwan_response_websocket_read_hint(struct lwan_request *request, size_t size_hint) { enum ws_opcode opcode = WS_OPCODE_INVALID; @@ -262,7 +277,7 @@ int lwan_response_websocket_read_hint(struct lwan_request *request, size_t size_ break; case WS_OPCODE_PING: - send_websocket_pong(request, header & 0x7f); + send_websocket_pong(request, header); goto next_frame; case WS_OPCODE_PONG: From 7c3ff6653d9265e988f76d819cf18e1c08a0d65e Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 3 May 2022 18:24:51 -0700 Subject: [PATCH 1965/2505] WebSocket: ping frames are always masked We check if header & 0x80 before handling any opcode, so remove the redundant check in send_websocket_pong(). Also, re-indent the function with clang-format. --- src/lib/lwan-websocket.c | 42 ++++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index e1cba4103..c9157d360 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -193,35 +193,31 @@ static void unmask(char *msg, size_t msg_len, char mask[static 4]) } } -static inline void send_websocket_pong(struct lwan_request *request, size_t header) +static void send_websocket_pong(struct lwan_request *request, uint16_t header) { - char mask[4]; - char msg[128]; + const size_t len = header & 0x7f; + char msg[128]; + char mask[4]; - const size_t len = header & 0x7f; - const int masked = header >> 7; + assert(!(header & 0x80)); - if (UNLIKELY(len > 125)) { + if (UNLIKELY(len > 125)) { lwan_status_debug("Received PING opcode with length %zu." "Max is 125. Aborting connection.", len); - coro_yield(request->conn->coro, CONN_CORO_ABORT); - __builtin_unreachable(); - } - - if (masked) { - struct iovec vec[] = { - {.iov_base = mask, .iov_len = sizeof(mask)}, - {.iov_base = msg, .iov_len = len}, - }; - lwan_readv(request, vec, N_ELEMENTS(vec)); - unmask(msg, len, mask); - - } else { - lwan_recv(request, msg, len, 0); - } - - write_websocket_frame(request, 0x80 | WS_OPCODE_PONG, msg, len); + coro_yield(request->conn->coro, CONN_CORO_ABORT); + __builtin_unreachable(); + } + + struct iovec vec[] = { + {.iov_base = mask, .iov_len = sizeof(mask)}, + {.iov_base = msg, .iov_len = len}, + }; + + lwan_readv(request, vec, N_ELEMENTS(vec)); + unmask(msg, len, mask); + + write_websocket_frame(request, 0x80 | WS_OPCODE_PONG, msg, len); } int lwan_response_websocket_read_hint(struct lwan_request *request, size_t size_hint) From c8d59086e7ce2da8986911273db1d88e52fb013d Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 4 May 2022 21:02:29 -0700 Subject: [PATCH 1966/2505] Fix assertion in send_websocket_pong() --- src/lib/lwan-websocket.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index c9157d360..9e2fc49eb 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -199,7 +199,7 @@ static void send_websocket_pong(struct lwan_request *request, uint16_t header) char msg[128]; char mask[4]; - assert(!(header & 0x80)); + assert(header & 0x80); if (UNLIKELY(len > 125)) { lwan_status_debug("Received PING opcode with length %zu." From b53cd3257a117911f21eb478c280631c578f291a Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 16 May 2022 19:13:30 -0700 Subject: [PATCH 1967/2505] Fix heap buffer overflow in prototype h2 huffman decoder Thanks to oss-fuzz: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=46990 --- src/lib/lwan-h2-huffman.c | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/lib/lwan-h2-huffman.c b/src/lib/lwan-h2-huffman.c index c2c33848d..0a7e2d41b 100644 --- a/src/lib/lwan-h2-huffman.c +++ b/src/lib/lwan-h2-huffman.c @@ -269,7 +269,7 @@ static inline const struct h2_huffman_code *next_level2(uint8_t peeked_byte) struct bit_reader { const uint8_t *bitptr; uint64_t bitbuf; - uint64_t total_bitcount; + int64_t total_bitcount; int bitcount; }; @@ -301,14 +301,8 @@ static inline bool consume(struct bit_reader *reader, int count) assert(count > 0); reader->bitbuf <<= count; reader->bitcount -= count; - if (__builtin_sub_overflow(reader->total_bitcount, count, - &reader->total_bitcount)) { - return false; - } - if (reader->total_bitcount == 0) { - return false; - } - return true; + reader->total_bitcount -= count; + return reader->total_bitcount > 0; } static inline size_t output_size(size_t input_size) @@ -324,13 +318,14 @@ uint8_t *lwan_h2_huffman_decode_for_fuzzing(const uint8_t *input, uint8_t *output = malloc(output_size(input_len)); uint8_t *ret = output; struct bit_reader bit_reader = {.bitptr = input, - .total_bitcount = input_len * 8}; + .total_bitcount = (int64_t)input_len * 8}; - while ((int64_t)bit_reader.total_bitcount > 7) { + while (bit_reader.total_bitcount > 7) { uint8_t peeked_byte = peek_byte(&bit_reader); if (LIKELY(level0[peeked_byte].num_bits)) { *output++ = level0[peeked_byte].symbol; consume(&bit_reader, level0[peeked_byte].num_bits); + assert(bit_reader.total_bitcount >= 0); continue; } @@ -341,7 +336,8 @@ uint8_t *lwan_h2_huffman_decode_for_fuzzing(const uint8_t *input, peeked_byte = peek_byte(&bit_reader); if (level1[peeked_byte].num_bits) { *output++ = level1[peeked_byte].symbol; - consume(&bit_reader, level1[peeked_byte].num_bits); + if (!consume(&bit_reader, level1[peeked_byte].num_bits)) + goto fail; continue; } @@ -352,7 +348,8 @@ uint8_t *lwan_h2_huffman_decode_for_fuzzing(const uint8_t *input, peeked_byte = peek_byte(&bit_reader); if (level2[peeked_byte].num_bits) { *output++ = level2[peeked_byte].symbol; - consume(&bit_reader, level2[peeked_byte].num_bits); + if (!consume(&bit_reader, level2[peeked_byte].num_bits)) + goto fail; continue; } @@ -368,7 +365,8 @@ uint8_t *lwan_h2_huffman_decode_for_fuzzing(const uint8_t *input, } if (LIKELY(level3[peeked_byte].num_bits)) { *output++ = level3[peeked_byte].symbol; - consume(&bit_reader, level3[peeked_byte].num_bits); + if (!consume(&bit_reader, level3[peeked_byte].num_bits)) + goto fail; continue; } } From 98f102edb78c24edd3bbcf8731a4e2b584d2f7c2 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 20 May 2022 06:54:00 -0700 Subject: [PATCH 1968/2505] Reduce number of syscalls when sending FastCGI records Before, for a GET request (no STDIN records): writev(24, [{iov_base="\1\1\0\1\0\10\0\0\0\1\0\0\0\0\0\0\1\4\0\1\1\265\0\0", iov_len=24}, {iov_base="\21\7GATEWAY_INTERFACECGI/1.1\v\tREMO"..., iov_len=437}, {iov_base="\1\4\0\1\0\0\0\0", iov_len=8}], 3) = 469 sendto(24, "\1\5\0\1\0\0\0\0", 8, MSG_DONTWAIT|MSG_NOSIGNAL, NULL, 0) = 8 After, for the same request: writev(24, [{iov_base="\1\1\0\1\0\10\0\0\0\1\0\0\0\0\0\0\1\4\0\1\1\265\0\0", iov_len=24}, {iov_base="\21\7GATEWAY_INTERFACECGI/1.1\v\tREMO"..., iov_len=437}, {iov_base="\1\4\0\1\0\0\0\0", iov_len=8}, {iov_base="\1\5\0\1\0\0\0\0", iov_len=8}], 4) = 477 Gains are a bit better when sending STDIN records, as most of them will be sent with a single writev() syscall. We flush every 16 iovs to avoid inlinefirst array reallocation and keep allocations in the stack as much as we can. (There is a possibility that the last STDIN record after multiple STDIN records might trigger reallocation/copy, but the memory will be freed anyway. This can happen if build_stdin_records() managed to put 16 elements in the iovec_array.) --- src/lib/lwan-mod-fastcgi.c | 148 +++++++++++++++++++++++++------------ 1 file changed, 99 insertions(+), 49 deletions(-) diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index 020f219a1..aae2228e1 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -551,35 +551,59 @@ try_initiating_chunked_response(struct lwan_request *request) return HTTP_OK; } -static void send_stdin_records(struct lwan_request *request, - int fcgi_fd, - const struct lwan_value *body_data, - const struct record *end_stdin_block) +DEFINE_ARRAY_TYPE_INLINEFIRST(iovec_array, struct iovec) +DEFINE_ARRAY_TYPE_INLINEFIRST(record_array, struct record) + +static bool build_stdin_records(struct lwan_request *request, + struct iovec_array *iovec_array, + struct record_array *record_array, + int fcgi_fd, + const struct lwan_value *body_data) { + if (!body_data) + return true; + size_t to_send = body_data->len; char *buffer = body_data->value; while (to_send) { + struct record *record; + struct iovec *iovec; size_t block_size = LWAN_MIN(0xffffull, to_send); - struct record stdin_header = { + + record = record_array_append(record_array); + if (UNLIKELY(!record)) + return false; + *record = (struct record){ .version = 1, .type = FASTCGI_TYPE_STDIN, .id = htons(1), .len_content = htons((uint16_t)block_size), }; - struct iovec vec[] = { - {.iov_base = &stdin_header, .iov_len = sizeof(stdin_header)}, - {.iov_base = buffer, .iov_len = block_size}, - }; - lwan_request_async_writev(request, fcgi_fd, vec, N_ELEMENTS(vec)); + iovec = iovec_array_append(iovec_array); + if (UNLIKELY(!iovec)) + return false; + *iovec = (struct iovec){.iov_base = record, .iov_len = sizeof(*record)}; + + iovec = iovec_array_append(iovec_array); + if (UNLIKELY(!iovec)) + return false; + *iovec = (struct iovec){.iov_base = buffer, .iov_len = block_size}; + + if (iovec_array_len(iovec_array) == LWAN_ARRAY_INCREMENT) { + lwan_request_async_writev(request, fcgi_fd, + iovec_array_get_array(iovec_array), + (int)iovec_array_len(iovec_array)); + iovec_array_reset(iovec_array); + record_array_reset(record_array); + } to_send -= block_size; buffer += block_size; } - lwan_request_async_write(request, fcgi_fd, end_stdin_block, - sizeof(*end_stdin_block)); + return true; } static enum lwan_http_status send_request(struct private_data *pd, @@ -607,48 +631,74 @@ static enum lwan_http_status send_request(struct private_data *pd, return HTTP_TOO_LARGE; } - struct request_header request_header = { - .begin_request = {.version = 1, - .type = FASTCGI_TYPE_BEGIN_REQUEST, - .id = htons(1), - .len_content = htons( - (uint16_t)sizeof(struct begin_request_body))}, - .begin_request_body = {.role = htons(FASTCGI_ROLE_RESPONDER)}, - .begin_params = {.version = 1, - .type = FASTCGI_TYPE_PARAMS, - .id = htons(1), - .len_content = htons((uint16_t)lwan_strbuf_get_length( - response->buffer))}, - }; - struct record end_params_block = { - .version = 1, - .type = FASTCGI_TYPE_PARAMS, - .id = htons(1), - }; - struct record end_stdin_block = { - .version = 1, - .type = FASTCGI_TYPE_STDIN, - .id = htons(1), + struct iovec_array iovec_array; + struct record_array record_array; + struct iovec *iovec; + + /* These arrays should never go beyond the inlinefirst threshold, so they + * shouldn't leak -- thus requiring no defer to reset them. */ + record_array_init(&record_array); + iovec_array_init(&iovec_array); + + iovec = iovec_array_append(&iovec_array); + if (UNLIKELY(!iovec)) + return HTTP_INTERNAL_ERROR; + *iovec = (struct iovec){ + .iov_base = + &(struct request_header){ + .begin_request = {.version = 1, + .type = FASTCGI_TYPE_BEGIN_REQUEST, + .id = htons(1), + .len_content = htons( + (uint16_t)sizeof(struct begin_request_body))}, + .begin_request_body = {.role = htons(FASTCGI_ROLE_RESPONDER)}, + .begin_params = {.version = 1, + .type = FASTCGI_TYPE_PARAMS, + .id = htons(1), + .len_content = + htons((uint16_t)lwan_strbuf_get_length( + response->buffer))}}, + .iov_len = sizeof(struct request_header), }; - struct iovec vec[] = { - {.iov_base = &request_header, .iov_len = sizeof(request_header)}, - {.iov_base = lwan_strbuf_get_buffer(response->buffer), - .iov_len = lwan_strbuf_get_length(response->buffer)}, - {.iov_base = &end_params_block, .iov_len = sizeof(end_params_block)}, - {.iov_base = &end_stdin_block, .iov_len = sizeof(end_stdin_block)}, + + iovec = iovec_array_append(&iovec_array); + if (UNLIKELY(!iovec)) + return HTTP_INTERNAL_ERROR; + *iovec = + (struct iovec){.iov_base = lwan_strbuf_get_buffer(response->buffer), + .iov_len = lwan_strbuf_get_length(response->buffer)}; + + iovec = iovec_array_append(&iovec_array); + if (UNLIKELY(!iovec)) + return HTTP_INTERNAL_ERROR; + *iovec = (struct iovec){ + .iov_base = &(struct record){.version = 1, + .type = FASTCGI_TYPE_PARAMS, + .id = htons(1)}, + .iov_len = sizeof(struct record), }; - const struct lwan_value *body_data = lwan_request_get_request_body(request); - if (!body_data) { - lwan_request_async_writev(request, fcgi_fd, vec, N_ELEMENTS(vec)); - } else { - /* If we have body data, don't send the last element just yet: we need - * to send each stdin segment until we don't have anything else there - * anymore before sending one with len_content = 0. */ - lwan_request_async_writev(request, fcgi_fd, vec, N_ELEMENTS(vec) - 1); - send_stdin_records(request, fcgi_fd, body_data, &end_stdin_block); + if (!build_stdin_records(request, &iovec_array, &record_array, fcgi_fd, + lwan_request_get_request_body(request))) { + return HTTP_INTERNAL_ERROR; } + iovec = iovec_array_append(&iovec_array); + if (UNLIKELY(!iovec)) + return HTTP_INTERNAL_ERROR; + *iovec = (struct iovec){ + .iov_base = &(struct record){.version = 1, + .type = FASTCGI_TYPE_STDIN, + .id = htons(1)}, + .iov_len = sizeof(struct record), + }; + + lwan_request_async_writev(request, fcgi_fd, + iovec_array_get_array(&iovec_array), + (int)iovec_array_len(&iovec_array)); + iovec_array_reset(&iovec_array); + record_array_reset(&record_array); + lwan_strbuf_reset(response->buffer); return HTTP_OK; From 066b11ee0a8754abd37166977146a7dd116056bf Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 28 May 2022 08:36:22 -0700 Subject: [PATCH 1969/2505] Do not build containers with -mach=native/-mtune=native In some cases, the machine building the containers will have instruction sets that are not supported by the machines running the containers, causing SIGILL. --- CMakeLists.txt | 7 +++++-- Containerfile | 2 +- README.md | 4 ++++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 163c967f3..c058801df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -260,8 +260,11 @@ else () message(STATUS "Valgrind headers not found -- disabling valgrind support") endif() -enable_c_flag_if_avail(-mtune=native C_FLAGS_REL LWAN_HAVE_MTUNE_NATIVE) -enable_c_flag_if_avail(-march=native C_FLAGS_REL LWAN_HAVE_MARCH_NATIVE) +option(MTUNE_NATIVE "Build with -mtune=native/-march=native" "ON") +if (MTUNE_NATIVE) + enable_c_flag_if_avail(-mtune=native C_FLAGS_REL LWAN_HAVE_MTUNE_NATIVE) + enable_c_flag_if_avail(-march=native C_FLAGS_REL LWAN_HAVE_MARCH_NATIVE) +endif () enable_c_flag_if_avail(-fstack-protector-explicit CMAKE_C_FLAGS LWAN_HAVE_STACK_PROTECTOR_EXPLICIT) diff --git a/Containerfile b/Containerfile index 077c7c119..c5092d4b4 100644 --- a/Containerfile +++ b/Containerfile @@ -3,7 +3,7 @@ RUN apk add --no-cache gcc make musl-dev cmake pkgconfig linux-headers \ luajit-dev sqlite-dev zlib-dev brotli-dev zstd-dev COPY . /lwan WORKDIR /lwan/build -RUN cmake .. -DCMAKE_BUILD_TYPE=Release +RUN cmake .. -DCMAKE_BUILD_TYPE=Release -DMTUNE_NATIVE=OFF RUN make -j FROM docker.io/library/alpine:3.14.2 diff --git a/README.md b/README.md index 6322db604..ba5055d1b 100644 --- a/README.md +++ b/README.md @@ -159,6 +159,10 @@ names provided in the "Optional dependencies" section. The `-DUSE_SYSLOG=ON` option can be passed to CMake to also log to the system log in addition to the standard output. +If you're building Lwan for a distribution, it might be wise to use the +`-DMTUNE_NATIVE=OFF` option, otherwise the generated binary may fail to +run on some computers. + ### Tests ~/lwan/build$ make testsuite From e62f08f2ec2f797b426c1101174ac4b6a8117d14 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 4 Jun 2022 17:03:10 -0700 Subject: [PATCH 1970/2505] Wait for FastCGI socket connection to be established Since the FastCGI socket is created as a non-blocking socket, connect(2) might not immediately connect, and can return errors such as EAGAIN or EINPROGRESS. Instead of failing the connection and closing the HTTP client connection due to this, async await until we can figure out the connection state by calling getsockopt(2), as suggested by the connect(2) man page. --- src/lib/lwan-mod-fastcgi.c | 52 +++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index aae2228e1..1479bf272 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -28,6 +28,7 @@ #define _GNU_SOURCE #include #include +#include #include #include #include @@ -704,6 +705,53 @@ static enum lwan_http_status send_request(struct private_data *pd, return HTTP_OK; } +static bool try_connect(struct lwan_request *request, + int sock_fd, + struct sockaddr *sockaddr, + socklen_t socklen) +{ + if (LIKELY(!connect(sock_fd, sockaddr, socklen))) + return true; + + /* Since socket has been created in non-blocking mode, connection + * might not be completed immediately. Depending on the socket type, + * connect() might return EAGAIN or EINPROGRESS. */ + if (errno != EAGAIN && errno != EINPROGRESS) + return false; + + /* If we get any of the above errors, try checking for socket error + * codes and loop until we get no errors. We await for writing here + * because that's what the Linux man page for connect(2) says we should + * do in this case. */ + for (int try = 0; try < 10; try++) { + socklen_t sockerrnolen = (socklen_t)sizeof(int); + int sockerrno; + + lwan_request_await_write(request, sock_fd); + + if (getsockopt(sock_fd, SOL_SOCKET, SO_ERROR, &sockerrno, + &sockerrnolen) < 0) { + break; + } + + switch (sockerrno) { + case EISCONN: + case 0: + return true; + + case EAGAIN: + case EINPROGRESS: + case EINTR: + continue; + + default: + return false; + } + } + + return false; +} + static enum lwan_http_status fastcgi_handle_request(struct lwan_request *request, struct lwan_response *response, @@ -721,8 +769,10 @@ fastcgi_handle_request(struct lwan_request *request, coro_defer(request->conn->coro, close_fd, (void *)(intptr_t)fcgi_fd); - if (connect(fcgi_fd, (struct sockaddr *)&pd->sock_addr, pd->addr_size) < 0) + if (!try_connect(request, fcgi_fd, (struct sockaddr *)&pd->sock_addr, + pd->addr_size)) { return HTTP_UNAVAILABLE; + } status = send_request(pd, request, response, fcgi_fd); if (status != HTTP_OK) From 47bd26e9fbe1444be2f6f1b5dfc7dc4984631278 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 4 Jun 2022 17:40:51 -0700 Subject: [PATCH 1971/2505] Add new Lua method: ws_write() This sends data using text records if sending ASCII-only strings, and with binary records otherwise. --- README.md | 1 + src/lib/lwan-lua.c | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/README.md b/README.md index ba5055d1b..ef8aa4a4d 100644 --- a/README.md +++ b/README.md @@ -521,6 +521,7 @@ information from the request, or to set the response, as seen below: - `req:ws_upgrade()` returns `1` if the connection could be upgraded to a WebSocket; `0` otherwise - `req:ws_write_text(str)` sends `str` through the WebSocket-upgraded connection as text frame - `req:ws_write_binary(str)` sends `str` through the WebSocket-upgraded connection as binary frame + - `req:ws_write(str)` sends `str` through the WebSocket-upgraded connection as text or binary frame, depending on content containing only ASCII characters or not - `req:ws_read()` returns a string with the contents of the last WebSocket frame, or a number indicating an status (ENOTCONN/107 on Linux if it has been disconnected; EAGAIN/11 on Linux if nothing was available; ENOMSG/42 on Linux otherwise). The return value here might change in the future for something more Lua-like. - `req:remote_address()` returns a string with the remote IP address. - `req:path()` returns a string with the request path. diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index 4161a3ec2..7f1cdfc26 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -196,6 +196,24 @@ LWAN_LUA_METHOD(ws_write_binary) return 0; } +LWAN_LUA_METHOD(ws_write) +{ + size_t data_len; + const char *data_str = lua_tolstring(L, -1, &data_len); + + lwan_strbuf_set_static(request->response.buffer, data_str, data_len); + + for (size_t i = 0; i < data_len; i++) { + if ((signed char)data_str[i] < 0) { + lwan_response_websocket_write_binary(request); + return 0; + } + } + + lwan_response_websocket_write_text(request); + return 0; +} + LWAN_LUA_METHOD(ws_read) { int r; From 573fe5891879b4c1372d191755bf9fe6d0533516 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 4 Jun 2022 17:43:10 -0700 Subject: [PATCH 1972/2505] Add locale-neutral strcasecmp() implementation --- src/lib/missing.c | 36 ++++++++++++++++++++++++++++++++++++ src/lib/missing/string.h | 2 ++ 2 files changed, 38 insertions(+) diff --git a/src/lib/missing.c b/src/lib/missing.c index 311e8745b..f68e0d671 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -656,3 +656,39 @@ long int lwan_getentropy(void *buffer, size_t buffer_len, int flags) return lwan_getentropy_fallback(buffer, buffer_len); } #endif + +static char tolower_neutral_table[256]; + +__attribute__((constructor)) +static void build_tolower_neutral_table(void) +{ + for (int i = 0; i < 256; i++) { + char c = (char)i; + + if (c >= 'A' && c <= 'Z') + c |= 0x20; + + tolower_neutral_table[i] = c; + } +} + +static inline char tolower_neutral(char c) +{ + return tolower_neutral_table[(unsigned char)c]; +} + +int strcasecmp_neutral(const char *a, const char *b) +{ + if (a == b) + return 0; + + for (;;) { + const char ca = *a++; + const char cb = *b++; + + if (ca == '\0' || tolower_neutral(ca) != tolower_neutral(cb)) + return (ca > cb) - (ca < cb); + } + + return 0; +} diff --git a/src/lib/missing/string.h b/src/lib/missing/string.h index 290a19c25..1c8b62557 100644 --- a/src/lib/missing/string.h +++ b/src/lib/missing/string.h @@ -70,4 +70,6 @@ static inline void *mempmove(void *dest, const void *src, size_t len) return d + len; } +int strcasecmp_neutral(const char *a, const char *b); + #endif /* MISSING_STRING_H */ From 0f7c1c95bf3a9d9b5026a3cd9aaabb59b2e8c3cf Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 4 Jun 2022 17:44:56 -0700 Subject: [PATCH 1973/2505] Use strcasecmp_neutral() where strcasecmp() was previously used --- src/lib/lwan-lua.c | 2 +- src/lib/lwan-mod-fastcgi.c | 6 +++--- src/lib/lwan-mod-rewrite.c | 4 ++-- src/lib/lwan.c | 14 +++++++------- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index 7f1cdfc26..7a654d43d 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -249,7 +249,7 @@ static bool append_key_value(struct lwan_request *request, const char *lua_value = lua_tolstring(L, value_index, &len); char *value = coro_memdup(coro, lua_value, len + 1); - if (!strcasecmp(key, "Content-Type")) { + if (!strcasecmp_neutral(key, "Content-Type")) { request->response.mime_type = value; } else { struct lwan_key_value *kv; diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index 1479bf272..47248384b 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -490,12 +490,12 @@ try_initiating_chunked_response(struct lwan_request *request) char *key = begin; char *value = p + 2; - if (!strcasecmp(key, "X-Powered-By")) { + if (!strcasecmp_neutral(key, "X-Powered-By")) { /* This is set by PHP-FPM. Do not advertise this for privacy * reasons. */ - } else if (!strcasecmp(key, "Content-Type")) { + } else if (!strcasecmp_neutral(key, "Content-Type")) { response->mime_type = coro_strdup(request->conn->coro, value); - } else if (!strcasecmp(key, "Status")) { + } else if (!strcasecmp_neutral(key, "Status")) { if (strlen(value) < 4) { status_code = HTTP_INTERNAL_ERROR; continue; diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index 7ce67c957..cca17adbe 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -719,8 +719,8 @@ static void parse_condition_accept_encoding(struct pattern *pattern, static bool get_method_from_string(struct pattern *pattern, const char *string) { -#define GENERATE_CMP(upper, lower, mask, constant, probability) \ - if (!strcasecmp(string, #upper)) { \ +#define GENERATE_CMP(upper, lower, mask, constant, probability) \ + if (!strcasecmp_neutral(string, #upper)) { \ pattern->condition.request_flags |= (mask); \ return true; \ } diff --git a/src/lib/lwan.c b/src/lib/lwan.c index d099ae1ad..5cd50ead0 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -144,17 +144,17 @@ static bool can_override_header(const char *name) /* NOTE: Update lwan_prepare_response_header_full() in lwan-response.c * if new headers are added here. */ - if (!strcasecmp(name, "Date")) + if (!strcasecmp_neutral(name, "Date")) return false; - if (!strcasecmp(name, "Expires")) + if (!strcasecmp_neutral(name, "Expires")) return false; - if (!strcasecmp(name, "WWW-Authenticate")) + if (!strcasecmp_neutral(name, "WWW-Authenticate")) return false; - if (!strcasecmp(name, "Connection")) + if (!strcasecmp_neutral(name, "Connection")) return false; - if (!strcasecmp(name, "Content-Type")) + if (!strcasecmp_neutral(name, "Content-Type")) return false; - if (!strcasecmp(name, "Transfer-Encoding")) + if (!strcasecmp_neutral(name, "Transfer-Encoding")) return false; if (!strncasecmp(name, "Access-Control-Allow-", sizeof("Access-Control-Allow-") - 1)) @@ -177,7 +177,7 @@ static void build_response_headers(struct lwan *l, if (!can_override_header(kv->key)) { lwan_status_warning("Cannot override header '%s'", kv->key); } else { - if (!strcasecmp(kv->key, "Server")) + if (!strcasecmp_neutral(kv->key, "Server")) set_server = true; lwan_strbuf_append_printf(&strbuf, "\r\n%s: %s", kv->key, From ceaa8a4ab311bc669658420d446ff88048857db1 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 4 Jun 2022 17:45:34 -0700 Subject: [PATCH 1974/2505] Simplify strbuf reset-trimming On reset-trim, instead of replacing the buffer with a freshly allocated one the size of the trim threshold, simply free the old buffer and reset the strbuf as usual. --- src/lib/lwan-strbuf.c | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/lib/lwan-strbuf.c b/src/lib/lwan-strbuf.c index a71c2ac34..db72340e7 100644 --- a/src/lib/lwan-strbuf.c +++ b/src/lib/lwan-strbuf.c @@ -313,16 +313,8 @@ void lwan_strbuf_reset(struct lwan_strbuf *s) void lwan_strbuf_reset_trim(struct lwan_strbuf *s, size_t trim_thresh) { if (s->flags & BUFFER_MALLOCD && s->capacity > trim_thresh) { - /* Not using realloc() here because we don't care about the contents - * of this buffer after reset is called, but we want to maintain a - * buffer already allocated of up to trim_thresh bytes. */ - void *tmp = malloc(trim_thresh); - - if (tmp) { - free(s->buffer); - s->buffer = tmp; - s->capacity = trim_thresh; - } + free(s->buffer); + s->flags &= ~BUFFER_MALLOCD; } return lwan_strbuf_reset(s); From 8b135646d47d590ca85d70543c21ce1c1f482ee5 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 4 Jun 2022 17:48:14 -0700 Subject: [PATCH 1975/2505] Use lwan_random_uint64() instead of rand() in TWFB benchmark --- src/samples/techempower/techempower.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index 3e9c699c5..d6b2ae08a 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -213,7 +213,8 @@ static bool db_query_key(struct db_stmt *stmt, struct db_json *out, int key) static inline bool db_query(struct db_stmt *stmt, struct db_json *out) { - return db_query_key(stmt, out, rand() % 10000); + uint64_t random_num = lwan_random_uint64() % 10000ull; + return db_query_key(stmt, out, (int)random_num); } LWAN_HANDLER(db) @@ -485,8 +486,6 @@ int main(void) lwan_init(&l); - srand((unsigned int)time(NULL)); - if (getenv("USE_MYSQL")) { db_connection_params = (struct db_connection_params){ .type = DB_CONN_MYSQL, From 84f2a275b6b30aa743b6979d66414eabb5909282 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 4 Jun 2022 17:48:45 -0700 Subject: [PATCH 1976/2505] When parsing headers, find_headers() failing is very unlikely --- src/lib/lwan-request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 2415bc08c..d9932210d 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -551,7 +551,7 @@ static bool parse_headers(struct lwan_request_parser_helper *helper, n_headers = find_headers(header_start, helper->buffer, &helper->next_request); - if (n_headers < 0) + if (UNLIKELY(n_headers < 0)) return false; for (ssize_t i = 0; i < n_headers; i++) { From 7d0ed25d40eddaba233f19b93a3a669103e28ead Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 4 Jun 2022 17:53:47 -0700 Subject: [PATCH 1977/2505] Do not require code description for FastCGI Status headers --- src/lib/lwan-mod-fastcgi.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index 47248384b..fbe99a080 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -496,11 +496,7 @@ try_initiating_chunked_response(struct lwan_request *request) } else if (!strcasecmp_neutral(key, "Content-Type")) { response->mime_type = coro_strdup(request->conn->coro, value); } else if (!strcasecmp_neutral(key, "Status")) { - if (strlen(value) < 4) { - status_code = HTTP_INTERNAL_ERROR; - continue; - } - if (value[3] != ' ') { + if (strlen(value) < 3) { status_code = HTTP_INTERNAL_ERROR; continue; } From 2174689a58e302122a863faaa7b33777a1f9064f Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 19 Jun 2022 12:32:50 -0700 Subject: [PATCH 1978/2505] Ensure additional_headers_sent array is freed on error --- src/lib/lwan-mod-fastcgi.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index fbe99a080..2c2d01898 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -512,7 +512,7 @@ try_initiating_chunked_response(struct lwan_request *request) struct lwan_key_value *header = header_array_append(&additional_headers); if (!header) - return HTTP_INTERNAL_ERROR; + goto free_array_and_disarm; *header = (struct lwan_key_value){.key = key, .value = value}; } @@ -520,12 +520,12 @@ try_initiating_chunked_response(struct lwan_request *request) struct lwan_key_value *header = header_array_append(&additional_headers); if (!header) - return HTTP_INTERNAL_ERROR; + goto free_array_and_disarm; *header = (struct lwan_key_value){}; if (!lwan_response_set_chunked_full(request, status_code, header_array_get_array(&additional_headers))) { - return HTTP_INTERNAL_ERROR; + goto free_array_and_disarm; } coro_defer_fire_and_disarm(request->conn->coro, additional_headers_reset); @@ -546,6 +546,10 @@ try_initiating_chunked_response(struct lwan_request *request) } return HTTP_OK; + +free_array_and_disarm: + coro_defer_fire_and_disarm(request->conn->coro, additional_headers_reset); + return HTTP_INTERNAL_ERROR; } DEFINE_ARRAY_TYPE_INLINEFIRST(iovec_array, struct iovec) From 4e0b91710e2804ff39b92692f75514bc59fc5f5e Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 19 Jun 2022 13:36:39 -0700 Subject: [PATCH 1979/2505] Update gentables.py with the code found in the h2-huffman decoder --- src/scripts/gentables.py | 47 +++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/src/scripts/gentables.py b/src/scripts/gentables.py index ad5387a6f..2642bfb1c 100755 --- a/src/scripts/gentables.py +++ b/src/scripts/gentables.py @@ -127,14 +127,22 @@ def generate_level(level, next_table): #include #include #include + #define LIKELY(x) x #define UNLIKELY(x) x + static inline uint64_t read64be(const void *ptr) { uint64_t v; memcpy(&v, ptr, 8); return htobe64(v); } +static inline uint32_t read32be(const void *ptr) { + uint32_t v; + memcpy(&v, ptr, 4); + return htobe32(v); +} + """) symbols = {} @@ -167,17 +175,26 @@ def generate_level(level, next_table): print("""struct bit_reader { const uint8_t *bitptr; uint64_t bitbuf; - uint64_t total_bitcount; + int64_t total_bitcount; int bitcount; }; static inline uint8_t peek_byte(struct bit_reader *reader) { if (reader->bitcount < 8) { - // FIXME: need to use shorter reads depending on total_bitcount! - reader->bitbuf |= read64be(reader->bitptr) >> reader->bitcount; - reader->bitptr += (63 - reader->bitcount + (reader->bitcount & 1)) >> 3; - reader->bitcount |= 56; + if (reader->total_bitcount >= 64) { + reader->bitbuf |= read64be(reader->bitptr) >> reader->bitcount; + reader->bitptr += (63 - reader->bitcount + (reader->bitcount & 1)) >> 3; + reader->bitcount |= 56; + } else if (reader->total_bitcount >= 32) { + reader->bitbuf |= read32be(reader->bitptr) >> reader->bitcount; + reader->bitptr += (31 - reader->bitcount + (reader->bitcount & 1)) >> 3; + reader->bitcount |= 24; + } else { + reader->bitbuf |= *reader->bitptr >> reader->bitcount; + reader->bitptr += (7 - reader->bitcount + (reader->bitcount & 1)) >> 3; + reader->bitcount |= 8; + } } return reader->bitbuf >> 56; } @@ -187,11 +204,8 @@ def generate_level(level, next_table): assert(count > 0); reader->bitbuf <<= count; reader->bitcount -= count; - if (__builtin_sub_overflow(reader->total_bitcount, count, &reader->total_bitcount)) - return false; - if (reader->total_bitcount == 0) - return false; - return true; + reader->total_bitcount -= count; + return reader->total_bitcount > 0; } """) @@ -206,9 +220,9 @@ def generate_level(level, next_table): uint8_t *output = malloc(output_size(input_len)); uint8_t *ret = output; struct bit_reader bit_reader = {.bitptr = input, - .total_bitcount = input_len * 8}; + .total_bitcount = (int64_t)input_len * 8}; - while ((int64_t)bit_reader.total_bitcount > 7) { + while (bit_reader.total_bitcount > 7) { uint8_t peeked_byte = peek_byte(&bit_reader); if (LIKELY(level0[peeked_byte].num_bits)) { *output++ = level0[peeked_byte].symbol; @@ -223,7 +237,8 @@ def generate_level(level, next_table): peeked_byte = peek_byte(&bit_reader); if (level1[peeked_byte].num_bits) { *output++ = level1[peeked_byte].symbol; - consume(&bit_reader, level1[peeked_byte].num_bits); + if (!consume(&bit_reader, level1[peeked_byte].num_bits)) + goto fail; continue; } @@ -234,7 +249,8 @@ def generate_level(level, next_table): peeked_byte = peek_byte(&bit_reader); if (level2[peeked_byte].num_bits) { *output++ = level2[peeked_byte].symbol; - consume(&bit_reader, level2[peeked_byte].num_bits); + if (!consume(&bit_reader, level2[peeked_byte].num_bits)) + goto fail; continue; } @@ -250,7 +266,8 @@ def generate_level(level, next_table): } if (LIKELY(level3[peeked_byte].num_bits)) { *output++ = level3[peeked_byte].symbol; - consume(&bit_reader, level3[peeked_byte].num_bits); + if (!consume(&bit_reader, level3[peeked_byte].num_bits)) + goto fail; continue; } } From f50a73b50bef66d612c819205f0f3d1c4ed562f4 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 9 Jul 2022 14:51:21 -0700 Subject: [PATCH 1980/2505] strbuf: Avoid memcpy() inside realloc() when growing with used == 0 If a buffer isn't used but is pre-allocated (e.g. was previously allocated, then reset), avoid the memcpy() within realloc() by allocating a brand new buffer and freeing the old one instead of reallocating the buffer. This is common on FastCGI requests: the strbuf is used to build the params record payload, and then reused to receive the standard output records. --- src/lib/lwan-strbuf.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-strbuf.c b/src/lib/lwan-strbuf.c index db72340e7..c3d2adc13 100644 --- a/src/lib/lwan-strbuf.c +++ b/src/lib/lwan-strbuf.c @@ -65,13 +65,28 @@ static bool grow_buffer_if_needed(struct lwan_strbuf *s, size_t size) } if (UNLIKELY(s->capacity < size)) { + char *buffer; const size_t aligned_size = align_size(size + 1); + if (UNLIKELY(!aligned_size)) return false; - char *buffer = realloc(s->buffer, aligned_size); - if (UNLIKELY(!buffer)) - return false; + if (s->used == 0) { + /* Avoid memcpy() inside realloc() if we were not using the + * allocated buffer at this point. */ + buffer = malloc(aligned_size); + + if (UNLIKELY(!buffer)) + return false; + + free(s->buffer); + buffer[0] = '\0'; + } else { + buffer = realloc(s->buffer, aligned_size); + + if (UNLIKELY(!buffer)) + return false; + } s->buffer = buffer; s->capacity = aligned_size; From 2a0ab9d4fe753df881ccb1a0651a05fbb3eb3aca Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 10 Jul 2022 11:43:58 -0700 Subject: [PATCH 1981/2505] Build on compilers without __builtin_expect_with_probability() --- CMakeLists.txt | 1 + src/cmake/lwan-build-config.h.cmake | 1 + src/lib/lwan-request.c | 5 +++++ 3 files changed, 7 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index c058801df..39287f326 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -230,6 +230,7 @@ endif() # Check for GCC builtin functions # check_c_source_compiles("int main(void) { __builtin_cpu_init(); }" LWAN_HAVE_BUILTIN_CPU_INIT) +check_c_source_compiles("int main(void) { __builtin_expect_with_probability(0, 0, 0); }" LWAN_HAVE_BUILTIN_EXPECT_PROBABILITY) check_c_source_compiles("int main(void) { __builtin_clzll(0); }" LWAN_HAVE_BUILTIN_CLZLL) check_c_source_compiles("int main(void) { __builtin_fpclassify(0, 0, 0, 0, 0, 0.0f); }" LWAN_HAVE_BUILTIN_FPCLASSIFY) check_c_source_compiles("int main(void) { unsigned long long p; (void)__builtin_mul_overflow(0, 0, &p); }" LWAN_HAVE_BUILTIN_MUL_OVERFLOW) diff --git a/src/cmake/lwan-build-config.h.cmake b/src/cmake/lwan-build-config.h.cmake index 8977e2d71..5bbfaf904 100644 --- a/src/cmake/lwan-build-config.h.cmake +++ b/src/cmake/lwan-build-config.h.cmake @@ -54,6 +54,7 @@ #cmakedefine LWAN_HAVE_BUILTIN_MUL_OVERFLOW #cmakedefine LWAN_HAVE_BUILTIN_ADD_OVERFLOW #cmakedefine LWAN_HAVE_BUILTIN_FPCLASSIFY +#cmakedefine LWAN_HAVE_BUILTIN_EXPECT_PROBABILITY /* C11 _Static_assert() */ #cmakedefine LWAN_HAVE_STATIC_ASSERT diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index d9932210d..22cfe84c8 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -242,6 +242,11 @@ static char *parse_proxy_protocol_v2(struct lwan_request *request, char *buffer) return buffer + size; } +#if !defined(LWAN_HAVE_BUILTIN_EXPECT_PROBABILITY) +#define __builtin_expect_with_probability(value1, value2, probability) \ + __builtin_expect(value1, value2) +#endif + static ALWAYS_INLINE char *identify_http_method(struct lwan_request *request, char *buffer) { From d0a936dc7f0493b335f1b0ad2073067f1b13fb24 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 10 Jul 2022 11:44:23 -0700 Subject: [PATCH 1982/2505] Prototype for close(2) is in --- src/lib/lwan-mod-fastcgi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index 2c2d01898..3162e0c6c 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -38,6 +38,7 @@ #include #include #include +#include #include "lwan-private.h" From a40ddcad946e3204a97f6616102a830f606d1490 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 10 Jul 2022 11:47:40 -0700 Subject: [PATCH 1983/2505] Show TLS options in `lwan --help` Also remove options to show modules/handlers, and show that information in `--help` directly. --- src/bin/lwan/main.c | 74 ++++++++++++++++++++++++++++++++------------- 1 file changed, 53 insertions(+), 21 deletions(-) diff --git a/src/bin/lwan/main.c b/src/bin/lwan/main.c index d288fa065..37d14a1e1 100644 --- a/src/bin/lwan/main.c +++ b/src/bin/lwan/main.c @@ -36,10 +36,11 @@ static void print_module_info(void) { const struct lwan_module_info *module; - printf("Available modules:\n"); + printf("Built-in modules:"); LWAN_SECTION_FOREACH(lwan_module, module) { - printf(" * %s\n", module->name); + printf(" %s", module->name); } + printf(".\n"); } static void @@ -47,10 +48,11 @@ print_handler_info(void) { const struct lwan_handler_info *handler; - printf("Available handlers:\n"); + printf("Built-in handlers:"); LWAN_SECTION_FOREACH(lwan_handler, handler) { - printf(" * %s\n", handler->name); + printf(" %s", handler->name); } + printf(".\n"); } static void @@ -108,14 +110,25 @@ print_help(const char *argv0, const struct lwan_config *config) printf("\n"); printf("Serve files through HTTP.\n\n"); printf("Options:\n"); - printf(" -r, --root Path to serve files from (default: ./wwwroot).\n"); - printf(" -l, --listen Listener (default: %s).\n", config->listener); - printf(" -c, --config Path to config file path.\n"); - printf(" -u, --user Username to drop privileges to (root required).\n"); - printf(" -C, --chroot Chroot to path passed to --root (root required).\n"); - printf(" -m, --modules Print information about available modules.\n"); - printf(" -H, --handlers Print information about available handlers.\n"); - printf(" -h, --help This.\n"); + printf(" -r, --root Path to serve files from (default: ./wwwroot).\n"); + printf("\n"); + printf(" -l, --listen Listener (default: %s).\n", config->listener); +#ifdef LWAN_HAVE_MBEDTLS + printf(" -L, --tls-listen TLS Listener (default: %s).\n", + config->tls_listener ? + config->tls_listener : "not listening"); +#endif + printf("\n"); + printf(" -c, --config Path to config file path.\n"); + printf(" -u, --user Username to drop privileges to (root required).\n"); + printf(" -C, --chroot Chroot to path passed to --root (root required).\n"); +#ifdef LWAN_HAVE_MBEDTLS + printf("\n"); + printf(" -P, --cert-path Path to TLS certificate.\n"); + printf(" -K, --cert-key Path to TLS key.\n"); +#endif + printf("\n"); + printf(" -h, --help This.\n"); printf("\n"); printf("Examples:\n"); printf(" Serve system-wide documentation:\n"); @@ -126,8 +139,15 @@ print_help(const char *argv0, const struct lwan_config *config) printf(" %s\n", argv0); printf(" Use /etc/%s:\n", config_file); printf(" %s -c /etc/%s\n", argv0, config_file); +#ifdef LWAN_HAVE_MBEDTLS + printf(" Serve system docs with HTTP and HTTPS:\n"); + printf(" %s -P /path/to/cert.pem -K /path/to/cert.key \\\n" + " -l '*:8080' -L '*:8081' -r /usr/share/doc\n", argv0); +#endif printf("\n"); print_build_time_configuration(); + print_module_info(); + print_handler_info(); printf("\n"); printf("Report bugs at .\n"); @@ -145,23 +165,35 @@ parse_args(int argc, char *argv[], struct lwan_config *config, char *root, { .name = "config", .has_arg = 1, .val = 'c' }, { .name = "chroot", .val = 'C' }, { .name = "user", .val = 'u', .has_arg = 1 }, - { .name = "modules", .val = 'm' }, - { .name = "handlers", .val = 'H' }, +#ifdef LWAN_HAVE_MBEDTLS + { .name = "tls-listen", .val = 'L', .has_arg = 1 }, + { .name = "cert-path", .val = 'P', .has_arg = 1 }, + { .name = "cert-key", .val = 'K', .has_arg = 1 }, +#endif { } }; int c, optidx = 0; enum args result = ARGS_USE_CONFIG; - while ((c = getopt_long(argc, argv, "Hmhr:l:c:u:C", opts, &optidx)) != -1) { + while ((c = getopt_long(argc, argv, "L:P:K:hr:l:c:u:C", opts, &optidx)) != -1) { switch (c) { - case 'H': - print_handler_info(); - return ARGS_FAILED; +#ifdef LWAN_HAVE_MBEDTLS + case 'L': + free(config->tls_listener); + config->tls_listener = strdup(optarg); + result = ARGS_SERVE_FILES; + break; - case 'm': - print_module_info(); - return ARGS_FAILED; + case 'P': + free(config->ssl.cert); + config->ssl.cert = strdup(optarg); + break; + case 'K': + free(config->ssl.key); + config->ssl.key = strdup(optarg); + break; +#endif case 'u': free((char *)sj->user_name); sj->user_name = (const char *)strdup(optarg); From bb6e4146f5b434df869cebfaedbc638d82e25374 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 10 Jul 2022 11:49:33 -0700 Subject: [PATCH 1984/2505] FastCGI: Simplify convertion from network-to-host order for len_content --- src/lib/lwan-mod-fastcgi.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index 3162e0c6c..6e2b0b83b 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -345,7 +345,7 @@ static enum lwan_http_status add_params(const struct private_data *pd, static bool handle_stdout(struct lwan_request *request, const struct record *record, int fd) { - size_t to_read = (size_t)ntohs(record->len_content); + size_t to_read = record->len_content; char *buffer = lwan_strbuf_extend_unsafe(request->response.buffer, to_read); if (!buffer) @@ -373,7 +373,7 @@ handle_stdout(struct lwan_request *request, const struct record *record, int fd) static bool handle_stderr(struct lwan_request *request, const struct record *record, int fd) { - size_t to_read = (size_t)ntohs(record->len_content); + size_t to_read = record->len_content; char *buffer = malloc(to_read); if (!buffer) @@ -393,14 +393,14 @@ handle_stderr(struct lwan_request *request, const struct record *record, int fd) } lwan_status_error("FastCGI stderr output: %.*s", - (int)ntohs(record->len_content), buffer); + (int)record->len_content, buffer); coro_defer_fire_and_disarm(request->conn->coro, buffer_free_defer); if (record->len_padding) { char padding[256]; lwan_request_async_read_flags(request, fd, padding, - (size_t)ntohs(record->len_padding), + (size_t)record->len_padding, MSG_TRUNC); } @@ -412,8 +412,7 @@ static bool discard_unknown_record(struct lwan_request *request, int fd) { char buffer[256]; - size_t to_read = - (size_t)ntohs(record->len_content) + (size_t)ntohs(record->len_padding); + size_t to_read = record->len_content + record->len_padding; if (record->type > 11) { /* Per the spec, 11 is the maximum (unknown type), so anything @@ -795,6 +794,9 @@ fastcgi_handle_request(struct lwan_request *request, if (r != (ssize_t)sizeof(record)) return HTTP_INTERNAL_ERROR; + record.len_content = ntohs(record.len_content); + record.id = htons(record.id); + switch (record.type) { case FASTCGI_TYPE_STDOUT: if (!handle_stdout(request, &record, fcgi_fd)) From 0b0dbfdb08f6f4675bdac569fbb8569c2f29fc5d Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 10 Jul 2022 11:50:21 -0700 Subject: [PATCH 1985/2505] Disable -Wstringop-truncation where it doesn't matter Add a comment explaining why. --- src/lib/lwan-tables.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index 0803cec61..5266461cb 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -138,7 +138,15 @@ lwan_determine_mime_type_for_file_name(const char *file_name) uint64_t key; const unsigned char *extension; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstringop-truncation" + /* Data is stored with NULs on strings up to 7 chars, and no NULs + * for 8-char strings, because that's implicit. So truncation is + * intentional here: comparison in compare_mime_entry() uses + * strncmp(..., 8), so even if NUL isn't present, it'll stop at the + * right place. */ strncpy((char *)&key, last_dot + 1, 8); +#pragma GCC diagnostic pop key &= ~0x2020202020202020ull; extension = bsearch(&key, uncompressed_mime_entries, MIME_ENTRIES, 8, From 51fd872a482803e9846662e32d12d666814c4465 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 10 Jul 2022 11:55:21 -0700 Subject: [PATCH 1986/2505] Use a shorter table for case insensitive string equality comparison --- src/lib/lwan-lua.c | 2 +- src/lib/lwan-mod-fastcgi.c | 6 ++-- src/lib/lwan-mod-rewrite.c | 2 +- src/lib/lwan.c | 14 ++++---- src/lib/missing.c | 68 +++++++++++++++++++++++--------------- src/lib/missing/string.h | 4 ++- 6 files changed, 57 insertions(+), 39 deletions(-) diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index 7a654d43d..ca7501349 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -249,7 +249,7 @@ static bool append_key_value(struct lwan_request *request, const char *lua_value = lua_tolstring(L, value_index, &len); char *value = coro_memdup(coro, lua_value, len + 1); - if (!strcasecmp_neutral(key, "Content-Type")) { + if (!strcaseequal_neutral(key, "Content-Type")) { request->response.mime_type = value; } else { struct lwan_key_value *kv; diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index 6e2b0b83b..1294ad08c 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -490,12 +490,12 @@ try_initiating_chunked_response(struct lwan_request *request) char *key = begin; char *value = p + 2; - if (!strcasecmp_neutral(key, "X-Powered-By")) { + if (!strcaseequal_neutral(key, "X-Powered-By")) { /* This is set by PHP-FPM. Do not advertise this for privacy * reasons. */ - } else if (!strcasecmp_neutral(key, "Content-Type")) { + } else if (!strcaseequal_neutral(key, "Content-Type")) { response->mime_type = coro_strdup(request->conn->coro, value); - } else if (!strcasecmp_neutral(key, "Status")) { + } else if (!strcaseequal_neutral(key, "Status")) { if (strlen(value) < 3) { status_code = HTTP_INTERNAL_ERROR; continue; diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index cca17adbe..c0efbeda6 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -720,7 +720,7 @@ static void parse_condition_accept_encoding(struct pattern *pattern, static bool get_method_from_string(struct pattern *pattern, const char *string) { #define GENERATE_CMP(upper, lower, mask, constant, probability) \ - if (!strcasecmp_neutral(string, #upper)) { \ + if (!strcaseequal_neutral(string, #upper)) { \ pattern->condition.request_flags |= (mask); \ return true; \ } diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 5cd50ead0..7c751ea51 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -144,17 +144,17 @@ static bool can_override_header(const char *name) /* NOTE: Update lwan_prepare_response_header_full() in lwan-response.c * if new headers are added here. */ - if (!strcasecmp_neutral(name, "Date")) + if (!strcaseequal_neutral(name, "Date")) return false; - if (!strcasecmp_neutral(name, "Expires")) + if (!strcaseequal_neutral(name, "Expires")) return false; - if (!strcasecmp_neutral(name, "WWW-Authenticate")) + if (!strcaseequal_neutral(name, "WWW-Authenticate")) return false; - if (!strcasecmp_neutral(name, "Connection")) + if (!strcaseequal_neutral(name, "Connection")) return false; - if (!strcasecmp_neutral(name, "Content-Type")) + if (!strcaseequal_neutral(name, "Content-Type")) return false; - if (!strcasecmp_neutral(name, "Transfer-Encoding")) + if (!strcaseequal_neutral(name, "Transfer-Encoding")) return false; if (!strncasecmp(name, "Access-Control-Allow-", sizeof("Access-Control-Allow-") - 1)) @@ -177,7 +177,7 @@ static void build_response_headers(struct lwan *l, if (!can_override_header(kv->key)) { lwan_status_warning("Cannot override header '%s'", kv->key); } else { - if (!strcasecmp_neutral(kv->key, "Server")) + if (!strcaseequal_neutral(kv->key, "Server")) set_server = true; lwan_strbuf_append_printf(&strbuf, "\r\n%s: %s", kv->key, diff --git a/src/lib/missing.c b/src/lib/missing.c index f68e0d671..9cf4e5412 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -657,38 +658,53 @@ long int lwan_getentropy(void *buffer, size_t buffer_len, int flags) } #endif -static char tolower_neutral_table[256]; - -__attribute__((constructor)) -static void build_tolower_neutral_table(void) +static inline int isalpha_neutral(char c) { - for (int i = 0; i < 256; i++) { - char c = (char)i; - - if (c >= 'A' && c <= 'Z') - c |= 0x20; - - tolower_neutral_table[i] = c; - } + /* Use this instead of isalpha() from ctype.h because they consider + * the current locale. This assumes CHAR_BIT == 8. */ + static const unsigned char upper_table[32] = { + 0, 0, 0, 0, 0, 0, 0, 0, 254, 255, 255, 7, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + unsigned char uc = (unsigned char)c; + static_assert(CHAR_BIT == 8, "sane CHAR_BIT value"); + return upper_table[uc >> 3] & 1 << (uc & 7); } -static inline char tolower_neutral(char c) +bool strcaseequal_neutral(const char *a, const char *b) { - return tolower_neutral_table[(unsigned char)c]; + for (;;) { + char ca = *a++; + char cb = *b++; + + /* See which bits are different in either character */ + switch (ca ^ cb) { + case 0: /* ca and cb are the same: advance */ + if (ca == '\0') { + /* If `ca` is 0 here, then cb must be 0 too, so we don't + * need to check both. */ + return true; + } + continue; + case 32: /* Only 5th bit is set: advance if either are uppercase + * ASCII characters, but differ in case only */ + /* If either is an uppercase ASCII character, then move on */ + if (isalpha_neutral(ca) || isalpha_neutral(cb)) + continue; + /* Fallthrough */ + default: + return false; + } + } } -int strcasecmp_neutral(const char *a, const char *b) +__attribute__((constructor)) static void test_strcaseequal_neutral(void) { - if (a == b) - return 0; - - for (;;) { - const char ca = *a++; - const char cb = *b++; + assert(strcaseequal_neutral("LWAN", "lwan") == true); + assert(strcaseequal_neutral("LwAn", "lWaN") == true); + assert(strcaseequal_neutral("SomE-HeaDer", "some-header") == true); - if (ca == '\0' || tolower_neutral(ca) != tolower_neutral(cb)) - return (ca > cb) - (ca < cb); - } - - return 0; + assert(strcaseequal_neutral("SomE-HeaDeP", "some-header") == false); + assert(strcaseequal_neutral("LwAN", "lwam") == false); + assert(strcaseequal_neutral("LwAn", "lWaM") == false); } diff --git a/src/lib/missing/string.h b/src/lib/missing/string.h index 1c8b62557..07adb5b4b 100644 --- a/src/lib/missing/string.h +++ b/src/lib/missing/string.h @@ -23,6 +23,8 @@ #ifndef MISSING_STRING_H #define MISSING_STRING_H +#include + #define strndupa_impl(s, l) \ ({ \ char *strndupa_tmp_s = alloca(l + 1); \ @@ -70,6 +72,6 @@ static inline void *mempmove(void *dest, const void *src, size_t len) return d + len; } -int strcasecmp_neutral(const char *a, const char *b); +bool strcaseequal_neutral(const char *a, const char *b); #endif /* MISSING_STRING_H */ From a7674500906c07a9ea2862fd55c21422eccd400b Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 21 Jul 2022 14:48:25 -0700 Subject: [PATCH 1987/2505] Fix build on platforms that require libucontext --- src/lib/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index 31cc9bcc4..63b0ec72a 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -113,7 +113,7 @@ if (NOT HAVE_BUILTIN_FPCLASSIFY) set(ADDITIONAL_LIBRARIES ${ADDITIONAL_LIBRARIES} -lm PARENT_SCOPE) endif () -if (HAVE_LIBUCONTEXT) +if (LWAN_HAVE_LIBUCONTEXT) message(STATUS "Using libucontext/${CMAKE_SYSTEM_PROCESSOR} for coroutine context switching") include(ExternalProject) From fa67fd86bf28f19a6ffc5706046f07a3bc7621e5 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 23 Jul 2022 23:00:02 -0700 Subject: [PATCH 1988/2505] Add a sample pastebin --- src/samples/CMakeLists.txt | 1 + src/samples/pastebin/CMakeLists.txt | 8 ++ src/samples/pastebin/main.c | 202 ++++++++++++++++++++++++++++ 3 files changed, 211 insertions(+) create mode 100644 src/samples/pastebin/CMakeLists.txt create mode 100644 src/samples/pastebin/main.c diff --git a/src/samples/CMakeLists.txt b/src/samples/CMakeLists.txt index 2a4a69766..d7e703ab5 100644 --- a/src/samples/CMakeLists.txt +++ b/src/samples/CMakeLists.txt @@ -5,6 +5,7 @@ if (NOT ${CMAKE_BUILD_TYPE} MATCHES "Coverage") add_subdirectory(clock) add_subdirectory(websocket) add_subdirectory(asyncawait) + add_subdirectory(pastebin) endif() add_subdirectory(techempower) diff --git a/src/samples/pastebin/CMakeLists.txt b/src/samples/pastebin/CMakeLists.txt new file mode 100644 index 000000000..8b5c121f9 --- /dev/null +++ b/src/samples/pastebin/CMakeLists.txt @@ -0,0 +1,8 @@ +add_executable(pastebin + main.c +) + +target_link_libraries(pastebin + ${LWAN_COMMON_LIBS} + ${ADDITIONAL_LIBRARIES} +) diff --git a/src/samples/pastebin/main.c b/src/samples/pastebin/main.c new file mode 100644 index 000000000..ccd259308 --- /dev/null +++ b/src/samples/pastebin/main.c @@ -0,0 +1,202 @@ +/* + * lwan - simple web server + * Copyright (c) 2022 L. A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include + +#include "hash.h" +#include "int-to-str.h" +#include "lwan.h" +#include "lwan-cache.h" +#include "lwan-private.h" + +#define SERVER_NAME "paste.example.com" +#define SERVER_PORT 8080 +#define CACHE_FOR_HOURS 2 + +static struct cache *pastes; + +struct paste { + struct cache_entry entry; + struct lwan_value paste; + char value[]; +}; + +static struct hash *pending_pastes(void) +{ + /* This is kind of a hack: we can't have just a single thread-local + * for the current thread's pending paste because a coroutine might + * yield while trying to obtain an item from the pastes cache, which + * would override that value. Store these in a thread-local hash + * table instead, which can be consulted by the create_paste() function. + * Items are removed from this table in a defer handler. */ + static __thread struct hash *pending_pastes; + + if (!pending_pastes) { + pending_pastes = hash_str_new(NULL, NULL); + if (!pending_pastes) { + lwan_status_critical( + "Could not allocate pending pastes hash table"); + } + } + + return pending_pastes; +} + +static struct cache_entry *create_paste(const char *key, void *context) +{ + const struct lwan_value *body = hash_find(pending_pastes(), key); + size_t alloc_size; + + if (!body) + return NULL; + + if (__builtin_add_overflow(sizeof(struct paste), body->len, &alloc_size)) + return NULL; + + struct paste *paste = malloc(alloc_size); + if (paste) { + paste->paste = (struct lwan_value){ + .value = memcpy(paste->value, body->value, body->len), + .len = body->len, + }; + } + + return &paste->entry; +} + +static void destroy_paste(struct cache_entry *entry, void *context) +{ + free(entry); +} + +static void remove_from_pending(void *data) +{ + const char *key = data; + + hash_del(pending_pastes(), key); +} + +static enum lwan_http_status post_paste(struct lwan_request *request, + struct lwan_response *response) +{ + const struct lwan_value *body = lwan_request_get_request_body(request); + char key_buf[3 * sizeof(uint64_t)]; + size_t key_len; + + if (!body) + return HTTP_BAD_REQUEST; + + for (int try = 0; try < 10; try++) { + /* Copy so that the key is valid even when this handler function + * ends. */ + char *key = coro_strdup( + request->conn->coro, + uint_to_string(lwan_random_uint64(), key_buf, &key_len)); + + if (!hash_add_unique(pending_pastes(), key, body)) { + coro_defer(request->conn->coro, remove_from_pending, key); + + struct cache_entry *paste = + cache_coro_get_and_ref_entry(pastes, request->conn->coro, key); + + if (paste) { + response->mime_type = "text/plain"; + lwan_strbuf_printf(response->buffer, "http://%s:%d/p/%s", + SERVER_NAME, SERVER_PORT, key); + return HTTP_OK; + } + } + } + + return HTTP_UNAVAILABLE; +} + +static enum lwan_http_status doc(struct lwan_request *request, + struct lwan_response *response) +{ + response->mime_type = "text/plain"; + + lwan_strbuf_printf( + response->buffer, + "Simple Paste Bin\n" + "================\n" + "\n" + "To post a file: curl -X POST --data-binary @/path/to/filename http://%s:%d/\n" + "To post clipboard: curl -X POST --data-binary @- http://%s:%d/ | xsel -o\n" + "To view: Access the URL given as a response\n" + "\n" + "Items are cached for %d hours and are not stored on disk", + SERVER_NAME, SERVER_PORT, SERVER_NAME, SERVER_PORT, CACHE_FOR_HOURS); + + return HTTP_OK; +} + +LWAN_HANDLER(view_root) +{ + switch (lwan_request_get_method(request)) { + case REQUEST_METHOD_POST: + return post_paste(request, response); + case REQUEST_METHOD_GET: + return doc(request, response); + default: + return HTTP_NOT_ALLOWED; + } +} + +LWAN_HANDLER(view_paste) +{ + struct paste *paste = (struct paste *)cache_coro_get_and_ref_entry( + pastes, request->conn->coro, request->url.value); + + if (!paste) + return HTTP_NOT_FOUND; + + response->mime_type = "text/plain"; + lwan_strbuf_set_static(response->buffer, paste->paste.value, + paste->paste.len); + + return HTTP_NOT_FOUND; +} + +int main(void) +{ + const struct lwan_url_map default_map[] = { + {.prefix = "/", .handler = LWAN_HANDLER_REF(view_root)}, + {.prefix = "/p/", .handler = LWAN_HANDLER_REF(view_paste)}, + {.prefix = NULL}, + }; + struct lwan l; + + lwan_init(&l); + + pastes = cache_create(create_paste, destroy_paste, NULL, + CACHE_FOR_HOURS * 60 * 60); + if (!pastes) + lwan_status_critical("Could not create paste cache"); + + lwan_set_url_map(&l, default_map); + lwan_main_loop(&l); + + lwan_shutdown(&l); + + cache_destroy(pastes); + + return 0; +} From 6ddaf9d502f7e2f7472a320616a7d6c2a924e53c Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 23 Jul 2022 23:03:35 -0700 Subject: [PATCH 1989/2505] pastebin: Return correct error code if a paste exists --- src/samples/pastebin/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/samples/pastebin/main.c b/src/samples/pastebin/main.c index ccd259308..52036a5e5 100644 --- a/src/samples/pastebin/main.c +++ b/src/samples/pastebin/main.c @@ -172,7 +172,7 @@ LWAN_HANDLER(view_paste) lwan_strbuf_set_static(response->buffer, paste->paste.value, paste->paste.len); - return HTTP_NOT_FOUND; + return HTTP_OK; } int main(void) From f7b2d18569df4cced176ac57d0d4ca214150e93f Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 24 Jul 2022 09:31:50 -0700 Subject: [PATCH 1990/2505] Fix return value of create_paste() if malloc() fails --- src/samples/pastebin/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/samples/pastebin/main.c b/src/samples/pastebin/main.c index 52036a5e5..0d4b95a6c 100644 --- a/src/samples/pastebin/main.c +++ b/src/samples/pastebin/main.c @@ -78,7 +78,7 @@ static struct cache_entry *create_paste(const char *key, void *context) }; } - return &paste->entry; + return (struct cache_entry *)paste; } static void destroy_paste(struct cache_entry *entry, void *context) From 991e9020c62ed0ae903780efa0c1cfeb075ceffd Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 24 Jul 2022 09:44:45 -0700 Subject: [PATCH 1991/2505] pastebin: Allow changing of content-type by requesting a particular extension --- src/samples/pastebin/main.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/samples/pastebin/main.c b/src/samples/pastebin/main.c index 0d4b95a6c..3f9bdc90b 100644 --- a/src/samples/pastebin/main.c +++ b/src/samples/pastebin/main.c @@ -18,7 +18,9 @@ * USA. */ +#define _GNU_SOURCE #include +#include #include "hash.h" #include "int-to-str.h" @@ -140,7 +142,8 @@ static enum lwan_http_status doc(struct lwan_request *request, "\n" "To post a file: curl -X POST --data-binary @/path/to/filename http://%s:%d/\n" "To post clipboard: curl -X POST --data-binary @- http://%s:%d/ | xsel -o\n" - "To view: Access the URL given as a response\n" + "To view: Access the URL given as a response.\n" + " Extension suffixes may be used to provide response with different MIME-type.\n" "\n" "Items are cached for %d hours and are not stored on disk", SERVER_NAME, SERVER_PORT, SERVER_NAME, SERVER_PORT, CACHE_FOR_HOURS); @@ -162,13 +165,21 @@ LWAN_HANDLER(view_root) LWAN_HANDLER(view_paste) { + char *dot = memrchr(request->url.value, '.', request->url.len); + + if (dot) { + response->mime_type = lwan_determine_mime_type_for_file_name(dot); + *dot = '\0'; + } else { + response->mime_type = "text/plain"; + } + struct paste *paste = (struct paste *)cache_coro_get_and_ref_entry( pastes, request->conn->coro, request->url.value); if (!paste) return HTTP_NOT_FOUND; - response->mime_type = "text/plain"; lwan_strbuf_set_static(response->buffer, paste->paste.value, paste->paste.len); From bde91d01cec892da448f218940fd709593bef121 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 24 Jul 2022 10:03:33 -0700 Subject: [PATCH 1992/2505] pastebin: Fix command line to post from clipboard --- src/samples/pastebin/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/samples/pastebin/main.c b/src/samples/pastebin/main.c index 3f9bdc90b..bbcc36749 100644 --- a/src/samples/pastebin/main.c +++ b/src/samples/pastebin/main.c @@ -141,7 +141,7 @@ static enum lwan_http_status doc(struct lwan_request *request, "================\n" "\n" "To post a file: curl -X POST --data-binary @/path/to/filename http://%s:%d/\n" - "To post clipboard: curl -X POST --data-binary @- http://%s:%d/ | xsel -o\n" + "To post clipboard: xsel -o | curl -X POST --data-binary @- http://%s:%d/\n" "To view: Access the URL given as a response.\n" " Extension suffixes may be used to provide response with different MIME-type.\n" "\n" From 58a0fd7d62c3d16d970693f6efda74484f5f0568 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 24 Jul 2022 11:57:42 -0700 Subject: [PATCH 1993/2505] =?UTF-8?q?pastebin:=20Return=20proper=20404=20e?= =?UTF-8?q?rror=20message=20when=20paste=20isn=C2=B4t=20found?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/samples/pastebin/main.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/samples/pastebin/main.c b/src/samples/pastebin/main.c index bbcc36749..6f27f1edb 100644 --- a/src/samples/pastebin/main.c +++ b/src/samples/pastebin/main.c @@ -166,12 +166,13 @@ LWAN_HANDLER(view_root) LWAN_HANDLER(view_paste) { char *dot = memrchr(request->url.value, '.', request->url.len); + const char *mime_type; if (dot) { - response->mime_type = lwan_determine_mime_type_for_file_name(dot); + mime_type = lwan_determine_mime_type_for_file_name(dot); *dot = '\0'; } else { - response->mime_type = "text/plain"; + mime_type = "text/plain"; } struct paste *paste = (struct paste *)cache_coro_get_and_ref_entry( @@ -180,6 +181,7 @@ LWAN_HANDLER(view_paste) if (!paste) return HTTP_NOT_FOUND; + response->mime_type = mime_type; lwan_strbuf_set_static(response->buffer, paste->paste.value, paste->paste.len); From 1a931e7a2ab12834af91bce9aae9b90877aaf4bf Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 14 Aug 2022 16:15:59 -0700 Subject: [PATCH 1994/2505] Add "backref" condition to rewrite module --- README.md | 1 + src/bin/testrunner/testrunner.conf | 4 ++ src/lib/lwan-mod-rewrite.c | 88 +++++++++++++++++++++++++++--- src/scripts/testsuite.py | 9 +++ 4 files changed, 95 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index ef8aa4a4d..5a9870dd3 100644 --- a/README.md +++ b/README.md @@ -629,6 +629,7 @@ for that condition to be evaluated: |`has_query_string` | No | No | Boolean | Checks if request has a query string (even if empty) | |`method` | No | No | Method name | Checks if HTTP method is the one specified | |`lua` | No | No | String | Runs Lua function `matches(req)` inside String and checks if it returns `true` or `false` | +|`backref` | No | Yes | A single `backref index` = `value` | Checks if the backref number matches the provided value | *Can use subst. syntax* refers to the ability to reference the matched pattern using the same substitution syntax used for the `rewrite as` or diff --git a/src/bin/testrunner/testrunner.conf b/src/bin/testrunner/testrunner.conf index 57a254667..c3351a45f 100644 --- a/src/bin/testrunner/testrunner.conf +++ b/src/bin/testrunner/testrunner.conf @@ -112,6 +112,10 @@ site { end''' } rewrite /pattern { + pattern (%d+)/backref { + 0 = 42 + rewrite as = /hello?name=fourtytwo + } pattern foo/(%d+)(%a)(%d+) { redirect to = /hello?name=pre%2middle%3othermiddle%1post } diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index c0efbeda6..a44a758c1 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -62,6 +62,7 @@ enum pattern_flag { PATTERN_COND_HTTP10 = 1 << 14, PATTERN_COND_HAS_QUERY_STRING = 1 << 15, PATTERN_COND_HTTPS = 1 << 16, + PATTERN_COND_BACKREF = 1 << 17, PATTERN_COND_MASK = PATTERN_COND_COOKIE | PATTERN_COND_ENV_VAR | PATTERN_COND_STAT | PATTERN_COND_QUERY_VAR | PATTERN_COND_POST_VAR | PATTERN_COND_HEADER | @@ -69,19 +70,19 @@ enum pattern_flag { PATTERN_COND_ACCEPT_ENCODING | PATTERN_COND_PROXIED | PATTERN_COND_HTTP10 | PATTERN_COND_HAS_QUERY_STRING | - PATTERN_COND_HTTPS, + PATTERN_COND_HTTPS | PATTERN_COND_BACKREF, - PATTERN_COND_STAT__HAS_IS_FILE = 1 << 17, - PATTERN_COND_STAT__HAS_IS_DIR = 1 << 18, - PATTERN_COND_STAT__IS_FILE = 1 << 19, - PATTERN_COND_STAT__IS_DIR = 1 << 20, + PATTERN_COND_STAT__HAS_IS_FILE = 1 << 18, + PATTERN_COND_STAT__HAS_IS_DIR = 1 << 19, + PATTERN_COND_STAT__IS_FILE = 1 << 20, + PATTERN_COND_STAT__IS_DIR = 1 << 21, PATTERN_COND_STAT__FILE_CHECK = PATTERN_COND_STAT__HAS_IS_FILE | PATTERN_COND_STAT__IS_FILE, PATTERN_COND_STAT__DIR_CHECK = PATTERN_COND_STAT__HAS_IS_DIR | PATTERN_COND_STAT__IS_DIR, - PATTERN_COND_HTTPS__IS_HTTPS = 1 << 21, + PATTERN_COND_HTTPS__IS_HTTPS = 1 << 22, }; struct pattern { @@ -99,6 +100,10 @@ struct pattern { struct { char *script; } lua; + struct { + int index; + char *str; + } backref; enum lwan_request_flags request_flags; /* FIXME: Use pahole to find alignment holes? */ } condition; @@ -296,6 +301,23 @@ static bool condition_matches(struct lwan_request *request, return false; } + if (p->flags & PATTERN_COND_BACKREF) { + assert(p->condition.backref.index > 0); + assert(p->condition.backref.str); + + if (captures > p->condition.backref.index) + return false; + + const struct str_find *s = &sf[p->condition.backref.index]; + const size_t len = strlen(p->condition.backref.str); + + if ((s->sm_eo - s->sm_so) != len) + return false; + + if (memcmp(s->sm_so, p->condition.backref.str, len) != 0) + return false; + } + if (p->flags & PATTERN_COND_HTTPS) { bool is_tls = request->conn->flags & CONN_TLS; if (p->flags & PATTERN_COND_HTTPS__IS_HTTPS) { @@ -546,6 +568,9 @@ static void rewrite_destroy(void *instance) free(iter->condition.lua.script); } #endif + if (iter->flags & PATTERN_COND_BACKREF) { + free(iter->condition.backref.str); + } } pattern_array_reset(&pd->patterns); @@ -717,10 +742,56 @@ static void parse_condition_accept_encoding(struct pattern *pattern, } } +static void parse_condition_backref(struct pattern *pattern, + struct config *config) +{ + const struct config_line *line; + int index = -1; + char *str = NULL; + + while ((line = config_read_line(config))) { + switch (line->type) { + case CONFIG_LINE_TYPE_SECTION: + config_error(config, "Unexpected section: %s", line->key); + goto out; + + case CONFIG_LINE_TYPE_SECTION_END: + if (index < 0) { + config_error(config, "Backref index not provided"); + goto out; + } + + pattern->flags |= PATTERN_COND_BACKREF; + pattern->condition.backref.index = index; + pattern->condition.backref.str = str; + return; + + case CONFIG_LINE_TYPE_LINE: + index = parse_int(line->key, -1); + if (index < 0) { + config_error("Expecting backref index, got ``%s''", line->key); + goto out; + } + + free(str); + str = strdup(line->value); + if (!str) { + lwan_status_critical( + "Couldn't allocate memory for backref key"); + } + + break; + } + } + +out: + free(str); +} + static bool get_method_from_string(struct pattern *pattern, const char *string) { #define GENERATE_CMP(upper, lower, mask, constant, probability) \ - if (!strcaseequal_neutral(string, #upper)) { \ + if (!strcaseequal_neutral(string, #upper)) { \ pattern->condition.request_flags |= (mask); \ return true; \ } @@ -762,6 +833,9 @@ static void parse_condition(struct pattern *pattern, if (streq(line->value, "encoding")) { return parse_condition_accept_encoding(pattern, config); } + if (streq(line->value, "backref")) { + return parse_condition_backref(pattern, config); + } config_error(config, "Condition `%s' not supported", line->value); } diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index 5a4902e3c..4d8274c5e 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -467,6 +467,15 @@ def test_conditional_rewrite_without_cookie(self): self.assertResponsePlain(r, 200) self.assertEqual(r.text, 'Hello, light!') + def test_conditional_rewrite_backref(self): + r = requests.get('http://localhost:8080/pattern/42/backref') + + self.assertResponsePlain(r, 200) + self.assertEqual(r.text, 'Hello, fourtytwo!') + + r = requests.get('http://localhost:8080/pattern/420/backref') + self.assertNotEqual(r.text, 'Hello, fourtytwo!') + def test_pattern_redirect_to(self): r = requests.get('http://127.0.0.1:8080/pattern/foo/1234x5678', allow_redirects=False) From 84c50f3d5fad9906313e4d0cc848a3504abd6513 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 14 Aug 2022 16:16:33 -0700 Subject: [PATCH 1995/2505] Clean up dependencies for configdump --- src/bin/tools/CMakeLists.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/bin/tools/CMakeLists.txt b/src/bin/tools/CMakeLists.txt index 87c32f75e..c045780e7 100644 --- a/src/bin/tools/CMakeLists.txt +++ b/src/bin/tools/CMakeLists.txt @@ -35,9 +35,6 @@ else () ${CMAKE_SOURCE_DIR}/src/lib/lwan-config.c ${CMAKE_SOURCE_DIR}/src/lib/lwan-status.c ${CMAKE_SOURCE_DIR}/src/lib/lwan-strbuf.c - ${CMAKE_SOURCE_DIR}/src/lib/hash.c - ${CMAKE_SOURCE_DIR}/src/lib/murmur3.c - ${CMAKE_SOURCE_DIR}/src/lib/missing.c ) add_executable(weighttp weighttp.c) From e1fee0dba9abc06b8cf3837da35135e05bfecee7 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 14 Aug 2022 16:16:55 -0700 Subject: [PATCH 1996/2505] Allow hash tables with 64-bit integer keys --- src/lib/hash.c | 38 ++++++++++++++++++++++++++++++++++++++ src/lib/hash.h | 2 ++ 2 files changed, 40 insertions(+) diff --git a/src/lib/hash.c b/src/lib/hash.c index bc01fef8a..865bf031c 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -80,10 +80,12 @@ static_assert((MIN_BUCKETS & (MIN_BUCKETS - 1)) == 0, "Bucket size is power of 2"); static inline unsigned int hash_int_shift_mult(const void *keyptr); +static inline unsigned int hash_int64_shift_mult(const void *keyptr); static unsigned int odd_constant = DEFAULT_ODD_CONSTANT; static unsigned (*hash_str)(const void *key) = murmur3_simple; static unsigned (*hash_int)(const void *key) = hash_int_shift_mult; +static unsigned (*hash_int64)(const void *key) = hash_int64_shift_mult; static bool resize_bucket(struct hash_bucket *bucket, unsigned int new_size) { @@ -125,6 +127,16 @@ static inline unsigned int hash_int_shift_mult(const void *keyptr) return key; } +static inline unsigned int hash_int64_shift_mult(const void *keyptr) +{ + const uint64_t key = (uint64_t)(uintptr_t)keyptr; + uint32_t key_low = (uint32_t)(key & 0xffffffff); + uint32_t key_high = (uint32_t)(key >> 32); + + return hash_int_shift_mult((void *)(uintptr_t)key_low) ^ + hash_int_shift_mult((void *)(uintptr_t)key_high); +} + #if defined(LWAN_HAVE_BUILTIN_CPU_INIT) && defined(LWAN_HAVE_BUILTIN_IA32_CRC32) static inline unsigned int hash_str_crc32(const void *keyptr) { @@ -167,6 +179,22 @@ static inline unsigned int hash_int_crc32(const void *keyptr) return __builtin_ia32_crc32si(odd_constant, (unsigned int)(uintptr_t)keyptr); } + +static inline unsigned int hash_int64_crc32(const void *keyptr) +{ +#ifdef __x86_64__ + return __builtin_ia32_crc32di(odd_constant, (uint64_t)(uintptr_t)keyptr); +#else + const uint64_t key = (uint64_t)(uintptr_t)keyptr; + uint32_t crc; + + crc = __builtin_ia32_crc32si(odd_constant, (uint32_t)(key & 0xffffffff)); + crc = __builtin_ia32_crc32si(crc, (uint32_t)(key >> 32)); + + return crc; +#endif +} + #endif __attribute__((constructor(65535))) static void initialize_odd_constant(void) @@ -181,8 +209,10 @@ __attribute__((constructor(65535))) static void initialize_odd_constant(void) #if defined(LWAN_HAVE_BUILTIN_CPU_INIT) && defined(LWAN_HAVE_BUILTIN_IA32_CRC32) __builtin_cpu_init(); if (__builtin_cpu_supports("sse4.2")) { + lwan_status_debug("Using CRC32 instructions to calculate hashes"); hash_str = hash_str_crc32; hash_int = hash_int_crc32; + hash_int64 = hash_int64_crc32; } #endif } @@ -231,6 +261,14 @@ struct hash *hash_int_new(void (*free_key)(void *value), free_value ? free_value : no_op); } +struct hash *hash_int64_new(void (*free_key)(void *value), + void (*free_value)(void *value)) +{ + return hash_internal_new(hash_int64, hash_int_key_equal, + free_key ? free_key : no_op, + free_value ? free_value : no_op); +} + struct hash *hash_str_new(void (*free_key)(void *value), void (*free_value)(void *value)) { diff --git a/src/lib/hash.h b/src/lib/hash.h index 49837e9ba..e035fcfa0 100644 --- a/src/lib/hash.h +++ b/src/lib/hash.h @@ -13,6 +13,8 @@ struct hash_iter { struct hash *hash_int_new(void (*free_key)(void *value), void (*free_value)(void *value)); +struct hash *hash_int64_new(void (*free_key)(void *value), + void (*free_value)(void *value)); struct hash *hash_str_new(void (*free_key)(void *value), void (*free_value)(void *value)); void hash_free(struct hash *hash); From 612321dd17e0563ab5dd993079cb72caf3b28740 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 14 Aug 2022 16:17:58 -0700 Subject: [PATCH 1997/2505] Allow caches to use arbitrary-type keys --- src/lib/lwan-cache.c | 20 +++++++++++++++----- src/lib/lwan-cache.h | 5 +++++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/lib/lwan-cache.c b/src/lib/lwan-cache.c index fa8179f4f..1058b03f0 100644 --- a/src/lib/lwan-cache.c +++ b/src/lib/lwan-cache.c @@ -76,10 +76,11 @@ struct cache { static bool cache_pruner_job(void *data); -struct cache *cache_create(cache_create_entry_cb create_entry_cb, - cache_destroy_entry_cb destroy_entry_cb, - void *cb_context, - time_t time_to_live) +struct cache *cache_create_full(cache_create_entry_cb create_entry_cb, + cache_destroy_entry_cb destroy_entry_cb, + void *cb_context, + time_t time_to_live, + struct hash *(*hash_create_func)(void (*)(void *), void (*)(void *))) { struct cache *cache; @@ -91,7 +92,7 @@ struct cache *cache_create(cache_create_entry_cb create_entry_cb, if (!cache) return NULL; - cache->hash.table = hash_str_new(free, NULL); + cache->hash.table = hash_create_func(free, NULL); if (!cache->hash.table) goto error_no_hash; @@ -122,6 +123,15 @@ struct cache *cache_create(cache_create_entry_cb create_entry_cb, return NULL; } +struct cache *cache_create(cache_create_entry_cb create_entry_cb, + cache_destroy_entry_cb destroy_entry_cb, + void *cb_context, + time_t time_to_live) +{ + return cache_create_full(create_entry_cb, destroy_entry_cb, cb_context, + time_to_live, hash_str_new); +} + void cache_destroy(struct cache *cache) { assert(cache); diff --git a/src/lib/lwan-cache.h b/src/lib/lwan-cache.h index dc0369d22..a9904c725 100644 --- a/src/lib/lwan-cache.h +++ b/src/lib/lwan-cache.h @@ -43,6 +43,11 @@ struct cache *cache_create(cache_create_entry_cb create_entry_cb, cache_destroy_entry_cb destroy_entry_cb, void *cb_context, time_t time_to_live); +struct cache *cache_create_full(cache_create_entry_cb create_entry_cb, + cache_destroy_entry_cb destroy_entry_cb, + void *cb_context, + time_t time_to_live, + struct hash *(*hash_create_func)(void (*)(void *), void (*)(void *))); void cache_destroy(struct cache *cache); struct cache_entry *cache_get_and_ref_entry(struct cache *cache, From 23ea3286ab1ecbc3d3ea9ef130d0307135b7a349 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 14 Aug 2022 16:20:44 -0700 Subject: [PATCH 1998/2505] Reduce allocations in pastebin sample by using int64 keys --- src/samples/pastebin/main.c | 38 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/src/samples/pastebin/main.c b/src/samples/pastebin/main.c index 6f27f1edb..2adc06ebf 100644 --- a/src/samples/pastebin/main.c +++ b/src/samples/pastebin/main.c @@ -51,7 +51,7 @@ static struct hash *pending_pastes(void) static __thread struct hash *pending_pastes; if (!pending_pastes) { - pending_pastes = hash_str_new(NULL, NULL); + pending_pastes = hash_int64_new(NULL, NULL); if (!pending_pastes) { lwan_status_critical( "Could not allocate pending pastes hash table"); @@ -63,7 +63,8 @@ static struct hash *pending_pastes(void) static struct cache_entry *create_paste(const char *key, void *context) { - const struct lwan_value *body = hash_find(pending_pastes(), key); + const struct lwan_value *body = + hash_find(pending_pastes(), (const void *)key); size_t alloc_size; if (!body) @@ -90,38 +91,31 @@ static void destroy_paste(struct cache_entry *entry, void *context) static void remove_from_pending(void *data) { - const char *key = data; - - hash_del(pending_pastes(), key); + hash_del(pending_pastes(), data); } static enum lwan_http_status post_paste(struct lwan_request *request, struct lwan_response *response) { const struct lwan_value *body = lwan_request_get_request_body(request); - char key_buf[3 * sizeof(uint64_t)]; - size_t key_len; if (!body) return HTTP_BAD_REQUEST; for (int try = 0; try < 10; try++) { - /* Copy so that the key is valid even when this handler function - * ends. */ - char *key = coro_strdup( - request->conn->coro, - uint_to_string(lwan_random_uint64(), key_buf, &key_len)); + uint64_t key = lwan_random_uint64(); - if (!hash_add_unique(pending_pastes(), key, body)) { - coro_defer(request->conn->coro, remove_from_pending, key); + if (!hash_add_unique(pending_pastes(), &key, body)) { + coro_defer(request->conn->coro, remove_from_pending, + (void *)(uintptr_t)key); - struct cache_entry *paste = - cache_coro_get_and_ref_entry(pastes, request->conn->coro, key); + struct cache_entry *paste = cache_coro_get_and_ref_entry( + pastes, request->conn->coro, (void *)(uintptr_t)key); if (paste) { response->mime_type = "text/plain"; - lwan_strbuf_printf(response->buffer, "http://%s:%d/p/%s", - SERVER_NAME, SERVER_PORT, key); + lwan_strbuf_printf(response->buffer, "https://%s/p/%zu\n\n", + SERVER_NAME, key); return HTTP_OK; } } @@ -175,8 +169,9 @@ LWAN_HANDLER(view_paste) mime_type = "text/plain"; } + const int64_t key = parse_long_long(request->url.value, 0); struct paste *paste = (struct paste *)cache_coro_get_and_ref_entry( - pastes, request->conn->coro, request->url.value); + pastes, request->conn->coro, (void *)(uintptr_t)key); if (!paste) return HTTP_NOT_FOUND; @@ -199,8 +194,9 @@ int main(void) lwan_init(&l); - pastes = cache_create(create_paste, destroy_paste, NULL, - CACHE_FOR_HOURS * 60 * 60); + pastes = cache_create_full(create_paste, destroy_paste, NULL, + CACHE_FOR_HOURS * 60 * 60, + hash_int64_new); if (!pastes) lwan_status_critical("Could not create paste cache"); From 37f1836ade5e2505e88a97227bf02e653ab2508b Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 14 Aug 2022 16:22:48 -0700 Subject: [PATCH 1999/2505] Use integer keys in techempower cached queries benchmark --- src/samples/techempower/techempower.c | 35 ++++++++++++++------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index d6b2ae08a..0a27c3817 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -282,10 +282,11 @@ struct db_json_cached { struct db_json db_json; }; -static struct cache_entry *cached_queries_new(const char *key, void *context) +static struct cache_entry *cached_queries_new(const char *keyptr, void *context) { struct db_json_cached *entry; struct db_stmt *stmt; + int key = (int)(uintptr_t)keyptr; entry = malloc(sizeof(*entry)); if (UNLIKELY(!entry)) @@ -298,7 +299,7 @@ static struct cache_entry *cached_queries_new(const char *key, void *context) return NULL; } - if (!db_query_key(stmt, &entry->db_json, atoi(key))) { + if (!db_query_key(stmt, &entry->db_json, key)) { free(entry); entry = NULL; } @@ -313,14 +314,14 @@ static void cached_queries_free(struct cache_entry *entry, void *context) free(entry); } -static struct cache_entry *my_cache_coro_get_and_ref_entry(struct cache *cache, - struct lwan_request *request, - const char *key) +static struct cache_entry *my_cache_coro_get_and_ref_entry( + struct cache *cache, struct lwan_request *request, int key) { /* Using this function instead of cache_coro_get_and_ref_entry() will avoid - * calling coro_defer(), which, in cases where the number of cached queries is - * too high, will trigger reallocations of the coro_defer array (and the "demotion" - * from the storage inlined in the coro struct to somewhere in the heap). + * calling coro_defer(), which, in cases where the number of cached queries + * is too high, will trigger reallocations of the coro_defer array (and the + * "demotion" from the storage inlined in the coro struct to somewhere in + * the heap). * * For large number of cached elements, too, this will reduce the number of * indirect calls that are performed every time a request is serviced. @@ -328,7 +329,8 @@ static struct cache_entry *my_cache_coro_get_and_ref_entry(struct cache *cache, for (int tries = 64; tries; tries--) { int error; - struct cache_entry *ce = cache_get_and_ref_entry(cache, key, &error); + struct cache_entry *ce = + cache_get_and_ref_entry(cache, (void *)(uintptr_t)key, &error); if (LIKELY(ce)) return ce; @@ -356,13 +358,11 @@ LWAN_HANDLER(cached_queries) struct queries_json qj = {.queries_len = (size_t)queries}; for (long i = 0; i < queries; i++) { - char key_buf[INT_TO_STR_BUFFER_SIZE]; struct db_json_cached *jc; - size_t discard; + int key = (int)lwan_random_uint64() % 10000; jc = (struct db_json_cached *)my_cache_coro_get_and_ref_entry( - cached_queries_cache, request, - int_to_string(rand() % 10000, key_buf, &discard)); + cached_queries_cache, request, key); if (UNLIKELY(!jc)) return HTTP_INTERNAL_ERROR; @@ -519,10 +519,11 @@ int main(void) if (!fortune_tpl) lwan_status_critical("Could not compile fortune templates"); - cached_queries_cache = cache_create(cached_queries_new, - cached_queries_free, - NULL, - 3600 /* 1 hour */); + cached_queries_cache = cache_create_full(cached_queries_new, + cached_queries_free, + NULL, + 3600 /* 1 hour */, + hash_int_new); if (!cached_queries_cache) lwan_status_critical("Could not create cached queries cache"); From 624ece1122b9b32af0ea98ffe2976670aa64d313 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 14 Aug 2022 16:23:04 -0700 Subject: [PATCH 2000/2505] Move header -> fastcgi param computation to its own function --- src/lib/lwan-mod-fastcgi.c | 77 +++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 34 deletions(-) diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index 1294ad08c..9f1b401e0 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -238,6 +238,46 @@ static enum lwan_http_status add_script_paths(const struct private_data *pd, return HTTP_NOT_FOUND; } +static void add_headers(struct lwan_strbuf *strbuf, + char **header_start, + size_t n_header_start) +{ + for (size_t i = 0; i < n_header_start; i++) { + const char *header = header_start[i]; + const char *next_header = header_start[i + 1]; + const char *colon = memchr(header, ':', 127 - sizeof("HTTP_: ") - 1); + char header_name[128]; + int r; + + if (!colon) + continue; + + const size_t header_len = (size_t)(colon - header); + const size_t value_len = (size_t)(next_header - colon - 4); + + r = snprintf(header_name, sizeof(header_name), "HTTP_%.*s", + (int)header_len, header); + if (r < 0 || r >= (int)sizeof(header_name)) + continue; + + /* FIXME: RFC7230/RFC3875 compliance */ + for (char *p = header_name; *p; p++) { + if (isalpha(*p)) + *p &= ~0x20; + else if (!isdigit(*p)) + *p = '_'; + } + + if (streq(header_name, "HTTP_PROXY")) { + /* Mitigation for https://httpoxy.org */ + continue; + } + + add_param_len(strbuf, header_name, header_len + sizeof("HTTP_") - 1, + colon + 2, value_len); + } +} + static enum lwan_http_status add_params(const struct private_data *pd, struct lwan_request *request, struct lwan_response *response) @@ -304,40 +344,9 @@ static enum lwan_http_status add_params(const struct private_data *pd, add_param(strbuf, "REQUEST_URI", request->original_url.value); } - for (size_t i = 0; i < request_helper->n_header_start; i++) { - const char *header = request_helper->header_start[i]; - const char *next_header = request_helper->header_start[i + 1]; - const char *colon = memchr(header, ':', 127 - sizeof("HTTP_: ") - 1); - char header_name[128]; - int r; - - if (!colon) - continue; - - const size_t header_len = (size_t)(colon - header); - const size_t value_len = (size_t)(next_header - colon - 4); - - r = snprintf(header_name, sizeof(header_name), "HTTP_%.*s", - (int)header_len, header); - if (r < 0 || r >= (int)sizeof(header_name)) - continue; - - /* FIXME: RFC7230/RFC3875 compliance */ - for (char *p = header_name; *p; p++) { - if (isalpha(*p)) - *p &= ~0x20; - else if (!isdigit(*p)) - *p = '_'; - } - - if (streq(header_name, "HTTP_PROXY")) { - /* Mitigation for https://httpoxy.org */ - continue; - } - - add_param_len(strbuf, header_name, header_len + sizeof("HTTP_") - 1, - colon + 2, value_len); - } + add_headers(strbuf, + request_helper->header_start, + request_helper->n_header_start); return HTTP_OK; } From d983204c40bcd498686c88ce22a0029778cc7c86 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 14 Aug 2022 16:23:51 -0700 Subject: [PATCH 2001/2505] Cleanup struct paste in pastebin --- src/samples/pastebin/main.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/samples/pastebin/main.c b/src/samples/pastebin/main.c index 2adc06ebf..34d6f60ff 100644 --- a/src/samples/pastebin/main.c +++ b/src/samples/pastebin/main.c @@ -36,7 +36,7 @@ static struct cache *pastes; struct paste { struct cache_entry entry; - struct lwan_value paste; + size_t len; char value[]; }; @@ -75,10 +75,8 @@ static struct cache_entry *create_paste(const char *key, void *context) struct paste *paste = malloc(alloc_size); if (paste) { - paste->paste = (struct lwan_value){ - .value = memcpy(paste->value, body->value, body->len), - .len = body->len, - }; + paste->len = body->len; + memcpy(paste->value, body->value, body->len); } return (struct cache_entry *)paste; @@ -177,8 +175,7 @@ LWAN_HANDLER(view_paste) return HTTP_NOT_FOUND; response->mime_type = mime_type; - lwan_strbuf_set_static(response->buffer, paste->paste.value, - paste->paste.len); + lwan_strbuf_set_static(response->buffer, paste->value, paste->len); return HTTP_OK; } From 8e91373a5fb3300f536d23da9b00096ec24539cb Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 14 Aug 2022 16:24:35 -0700 Subject: [PATCH 2002/2505] Add a FIXME for a per-site authorization section that might be useful --- src/lib/lwan.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 7c751ea51..346c77742 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -532,6 +532,8 @@ parse_site(struct config *c, const struct config_line *l, struct lwan *lwan) config_error(c, "Expecting prefix section"); return; case CONFIG_LINE_TYPE_SECTION: + /* FIXME: per-site authorization? */ + if (l->key[0] == '&') { void *handler = find_handler(l->key + 1); if (handler) { From 51b38b9ddbfc13411bbd7c92355d19864c97c091 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 14 Aug 2022 16:25:07 -0700 Subject: [PATCH 2003/2505] Hardcode stuff in the pastebin example to make deployment easier These really need to be environment variables or something :P --- src/samples/pastebin/main.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/samples/pastebin/main.c b/src/samples/pastebin/main.c index 34d6f60ff..f68b85e02 100644 --- a/src/samples/pastebin/main.c +++ b/src/samples/pastebin/main.c @@ -28,8 +28,8 @@ #include "lwan-cache.h" #include "lwan-private.h" -#define SERVER_NAME "paste.example.com" -#define SERVER_PORT 8080 +#define SERVER_NAME "paste.lwan.ws" +#define SERVER_PORT 443 #define CACHE_FOR_HOURS 2 static struct cache *pastes; @@ -132,13 +132,16 @@ static enum lwan_http_status doc(struct lwan_request *request, "Simple Paste Bin\n" "================\n" "\n" - "To post a file: curl -X POST --data-binary @/path/to/filename http://%s:%d/\n" - "To post clipboard: xsel -o | curl -X POST --data-binary @- http://%s:%d/\n" + "To post a file: curl -X POST --data-binary @/path/to/filename " + "https://%s/\n" + "To post clipboard: xsel -o | curl -X POST --data-binary @- " + "https://%s/\n" "To view: Access the URL given as a response.\n" - " Extension suffixes may be used to provide response with different MIME-type.\n" + " Extension suffixes may be used to provide " + "response with different MIME-type.\n" "\n" "Items are cached for %d hours and are not stored on disk", - SERVER_NAME, SERVER_PORT, SERVER_NAME, SERVER_PORT, CACHE_FOR_HOURS); + SERVER_NAME, SERVER_NAME, CACHE_FOR_HOURS); return HTTP_OK; } From 6baa0a3284b26ca9fcc9f2193b47456c09f95864 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 14 Aug 2022 16:35:14 -0700 Subject: [PATCH 2004/2505] Fix build warnings introduced with recent changes --- src/lib/hash.c | 3 ++- src/lib/lwan-mod-rewrite.c | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/lib/hash.c b/src/lib/hash.c index 865bf031c..e3d2d64ec 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -183,7 +183,8 @@ static inline unsigned int hash_int_crc32(const void *keyptr) static inline unsigned int hash_int64_crc32(const void *keyptr) { #ifdef __x86_64__ - return __builtin_ia32_crc32di(odd_constant, (uint64_t)(uintptr_t)keyptr); + return (unsigned int)__builtin_ia32_crc32di(odd_constant, + (uint64_t)(uintptr_t)keyptr); #else const uint64_t key = (uint64_t)(uintptr_t)keyptr; uint32_t crc; diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index a44a758c1..f445bb5c0 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -311,10 +311,10 @@ static bool condition_matches(struct lwan_request *request, const struct str_find *s = &sf[p->condition.backref.index]; const size_t len = strlen(p->condition.backref.str); - if ((s->sm_eo - s->sm_so) != len) + if ((size_t)(s->sm_eo - s->sm_so) != len) return false; - if (memcmp(s->sm_so, p->condition.backref.str, len) != 0) + if (!memcmp(request->url.value + s->sm_so, p->condition.backref.str, len)) return false; } @@ -769,7 +769,7 @@ static void parse_condition_backref(struct pattern *pattern, case CONFIG_LINE_TYPE_LINE: index = parse_int(line->key, -1); if (index < 0) { - config_error("Expecting backref index, got ``%s''", line->key); + config_error(config, "Expecting backref index, got ``%s''", line->key); goto out; } From 525c5dd0e8dee4f74ebe6e7121e96f2c5ef85bac Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 14 Aug 2022 16:40:27 -0700 Subject: [PATCH 2005/2505] Fix invocations of strcaseequal_neutral() These probably were calls to strcasecmp() that were being compared to 0 (which is "both strings are equal"). The new function returns 1 if they're equal. --- src/lib/lwan-lua.c | 2 +- src/lib/lwan-mod-fastcgi.c | 6 +++--- src/lib/lwan-mod-rewrite.c | 2 +- src/lib/lwan.c | 14 +++++++------- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index ca7501349..864c25072 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -249,7 +249,7 @@ static bool append_key_value(struct lwan_request *request, const char *lua_value = lua_tolstring(L, value_index, &len); char *value = coro_memdup(coro, lua_value, len + 1); - if (!strcaseequal_neutral(key, "Content-Type")) { + if (strcaseequal_neutral(key, "Content-Type")) { request->response.mime_type = value; } else { struct lwan_key_value *kv; diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index 9f1b401e0..567e29d6f 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -499,12 +499,12 @@ try_initiating_chunked_response(struct lwan_request *request) char *key = begin; char *value = p + 2; - if (!strcaseequal_neutral(key, "X-Powered-By")) { + if (strcaseequal_neutral(key, "X-Powered-By")) { /* This is set by PHP-FPM. Do not advertise this for privacy * reasons. */ - } else if (!strcaseequal_neutral(key, "Content-Type")) { + } else if (strcaseequal_neutral(key, "Content-Type")) { response->mime_type = coro_strdup(request->conn->coro, value); - } else if (!strcaseequal_neutral(key, "Status")) { + } else if (strcaseequal_neutral(key, "Status")) { if (strlen(value) < 3) { status_code = HTTP_INTERNAL_ERROR; continue; diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index f445bb5c0..83d62782c 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -791,7 +791,7 @@ static void parse_condition_backref(struct pattern *pattern, static bool get_method_from_string(struct pattern *pattern, const char *string) { #define GENERATE_CMP(upper, lower, mask, constant, probability) \ - if (!strcaseequal_neutral(string, #upper)) { \ + if (strcaseequal_neutral(string, #upper)) { \ pattern->condition.request_flags |= (mask); \ return true; \ } diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 346c77742..89877949a 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -144,17 +144,17 @@ static bool can_override_header(const char *name) /* NOTE: Update lwan_prepare_response_header_full() in lwan-response.c * if new headers are added here. */ - if (!strcaseequal_neutral(name, "Date")) + if (strcaseequal_neutral(name, "Date")) return false; - if (!strcaseequal_neutral(name, "Expires")) + if (strcaseequal_neutral(name, "Expires")) return false; - if (!strcaseequal_neutral(name, "WWW-Authenticate")) + if (strcaseequal_neutral(name, "WWW-Authenticate")) return false; - if (!strcaseequal_neutral(name, "Connection")) + if (strcaseequal_neutral(name, "Connection")) return false; - if (!strcaseequal_neutral(name, "Content-Type")) + if (strcaseequal_neutral(name, "Content-Type")) return false; - if (!strcaseequal_neutral(name, "Transfer-Encoding")) + if (strcaseequal_neutral(name, "Transfer-Encoding")) return false; if (!strncasecmp(name, "Access-Control-Allow-", sizeof("Access-Control-Allow-") - 1)) @@ -177,7 +177,7 @@ static void build_response_headers(struct lwan *l, if (!can_override_header(kv->key)) { lwan_status_warning("Cannot override header '%s'", kv->key); } else { - if (!strcaseequal_neutral(kv->key, "Server")) + if (strcaseequal_neutral(kv->key, "Server")) set_server = true; lwan_strbuf_append_printf(&strbuf, "\r\n%s: %s", kv->key, From 98a4689064d2bc76e07d0e5f5ee82e50652775b2 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 14 Aug 2022 16:42:25 -0700 Subject: [PATCH 2006/2505] Initialize backref condition properly in the config file --- src/bin/testrunner/testrunner.conf | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/bin/testrunner/testrunner.conf b/src/bin/testrunner/testrunner.conf index c3351a45f..ec4e4476c 100644 --- a/src/bin/testrunner/testrunner.conf +++ b/src/bin/testrunner/testrunner.conf @@ -113,8 +113,10 @@ site { } rewrite /pattern { pattern (%d+)/backref { - 0 = 42 - rewrite as = /hello?name=fourtytwo + condition backref { + 0 = 42 + } + rewrite as = /hello?name=fourtytwo } pattern foo/(%d+)(%a)(%d+) { redirect to = /hello?name=pre%2middle%3othermiddle%1post From e8acdc6f868aafee944b99c1505d33032f357e33 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 14 Aug 2022 16:42:54 -0700 Subject: [PATCH 2007/2505] s//<8 spaces>/ in testrunner.conf --- src/bin/testrunner/testrunner.conf | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/bin/testrunner/testrunner.conf b/src/bin/testrunner/testrunner.conf index ec4e4476c..c0d5ef4ea 100644 --- a/src/bin/testrunner/testrunner.conf +++ b/src/bin/testrunner/testrunner.conf @@ -52,17 +52,17 @@ site { redirect /elsewhere { to = http://lwan.ws } redirect /redirect307 { - to = http://lwan.ws - code = 307 + to = http://lwan.ws + code = 307 } rewrite /read-env { - pattern user { rewrite as = /hello?name=${USER} } + pattern user { rewrite as = /hello?name=${USER} } } rewrite /css/ { pattern test.css { - condition cookie { style = dark } + condition cookie { style = dark } rewrite as = /hello?name=dark } pattern test.css { @@ -86,16 +86,16 @@ site { &hello_world /admin { authorization basic { - realm = Administration Page + realm = Administration Page password file = htpasswd - } + } } lua /inline { default type = text/html cache period = 30s script = '''function handle_get_root(req) - req:say('Hello') - end''' + req:say('Hello') + end''' } lua /lua { default type = text/html @@ -103,12 +103,12 @@ site { cache period = 30s } lua /luawait { - script='''function handle_get_root(req) + script='''function handle_get_root(req) local ms = req:query_param[[ms]] - if not ms then ms = 1234 end + if not ms then ms = 1234 end req:say("sleeping "..ms.."ms") - req:sleep(ms) - req:say("slept") + req:sleep(ms) + req:say("slept") end''' } rewrite /pattern { @@ -128,7 +128,7 @@ site { expand_with_lua = true redirect to = ''' function handle_rewrite(req, captures) - local r = captures[1] * captures[2] + local r = captures[1] * captures[2] return '/hello?name=redirected' .. r end ''' @@ -136,7 +136,7 @@ site { pattern lua/rewrite/(%d+)x(%d+) { expand_with_lua = true rewrite as = """function handle_rewrite(req, captures) - local r = captures[1] * captures[2] + local r = captures[1] * captures[2] return '/hello?name=rewritten' .. r end""" } From c7a134ba9cc14092d3699ace55ffe64ca457a22c Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 20 Aug 2022 16:06:38 -0700 Subject: [PATCH 2008/2505] Properly free cache keys if using non-string keys --- src/lib/lwan-cache.c | 60 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 11 deletions(-) diff --git a/src/lib/lwan-cache.c b/src/lib/lwan-cache.c index 1058b03f0..ede3a4d9b 100644 --- a/src/lib/lwan-cache.c +++ b/src/lib/lwan-cache.c @@ -59,6 +59,13 @@ struct cache { void *context; } cb; + /* FIXME: I'm not really a fan of how these are set in cache_create(), + * but this has to do for now. */ + struct { + void *(*copy)(const void *); + void (*free)(void *); + } key; + struct { time_t time_to_live; } settings; @@ -76,11 +83,31 @@ struct cache { static bool cache_pruner_job(void *data); +static ALWAYS_INLINE void *identity_key_copy(const void *key) +{ + return (void *)key; +} + +static void identity_key_free(void *key) +{ +} + +static ALWAYS_INLINE void *str_key_copy(const void *key) +{ + return strdup(key); +} + +static ALWAYS_INLINE void str_key_free(void *key) +{ + free(key); +} + struct cache *cache_create_full(cache_create_entry_cb create_entry_cb, - cache_destroy_entry_cb destroy_entry_cb, - void *cb_context, - time_t time_to_live, - struct hash *(*hash_create_func)(void (*)(void *), void (*)(void *))) + cache_destroy_entry_cb destroy_entry_cb, + hash_create_func_cb hash_create_func, + key_copy_cb copy_cb, + void *cb_context, + time_t time_to_live) { struct cache *cache; @@ -103,8 +130,17 @@ struct cache *cache_create_full(cache_create_entry_cb create_entry_cb, cache->cb.create_entry = create_entry_cb; cache->cb.destroy_entry = destroy_entry_cb; + cache->cb.key_copy_cb = copy_cb ? copy_cb : strdup_key_copy_cb; cache->cb.context = cb_context; + if (hash_create_func == hash_str_new) { + cache->key.copy = str_key_copy; + cache->key.free = str_key_free; + } else { + cache->key.copy = identity_key_copy; + cache->key.free = identity_key_free; + } + cache->settings.time_to_live = time_to_live; list_head_init(&cache->queue.list); @@ -128,8 +164,8 @@ struct cache *cache_create(cache_create_entry_cb create_entry_cb, void *cb_context, time_t time_to_live) { - return cache_create_full(create_entry_cb, destroy_entry_cb, cb_context, - time_to_live, hash_str_new); + return cache_create_full(create_entry_cb, destroy_entry_cb, hash_str_new, + strdup_key_copy_cb, cb_context, time_to_live); } void cache_destroy(struct cache *cache) @@ -187,16 +223,18 @@ struct cache_entry *cache_get_and_ref_entry(struct cache *cache, ATOMIC_INC(cache->stats.misses); #endif - key_copy = strdup(key); + key_copy = cache->key.copy(key); if (UNLIKELY(!key_copy)) { - *error = ENOMEM; - return NULL; + if (cache->key.copy != identity_key_copy) { + *error = ENOMEM; + return NULL; + } } entry = cache->cb.create_entry(key, cache->cb.context); if (UNLIKELY(!entry)) { *error = ECANCELED; - free(key_copy); + cache->key.free(key_copy); return NULL; } @@ -260,7 +298,7 @@ void cache_entry_unref(struct cache *cache, struct cache_entry *entry) /* FREE_KEY_ON_DESTROY is set on elements that never got into the * hash table, so their keys are never destroyed automatically. */ if (entry->flags & FREE_KEY_ON_DESTROY) - free(entry->key); + cache->key.free(entry->key); return cache->cb.destroy_entry(entry, cache->cb.context); } From bb9a3c04212497acf13e062ed1521ba5aba5d69d Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 20 Aug 2022 16:09:26 -0700 Subject: [PATCH 2009/2505] Update uses of cache infra to use new prototypes --- src/lib/lwan-cache.c | 4 ++-- src/lib/lwan-cache.h | 13 +++++------ src/lib/lwan-http-authorize.c | 4 ++-- src/lib/lwan-mod-fastcgi.c | 3 ++- src/lib/lwan-mod-lua.c | 2 +- src/lib/lwan-mod-serve-files.c | 2 +- src/samples/freegeoip/freegeoip.c | 4 ++-- src/samples/pastebin/main.c | 31 ++++++++++++++++----------- src/samples/techempower/techempower.c | 2 +- 9 files changed, 36 insertions(+), 29 deletions(-) diff --git a/src/lib/lwan-cache.c b/src/lib/lwan-cache.c index ede3a4d9b..ad62e0148 100644 --- a/src/lib/lwan-cache.c +++ b/src/lib/lwan-cache.c @@ -188,7 +188,7 @@ void cache_destroy(struct cache *cache) } struct cache_entry *cache_get_and_ref_entry(struct cache *cache, - const char *key, int *error) + const void *key, int *error) { struct cache_entry *entry; char *key_copy; @@ -414,7 +414,7 @@ static void cache_entry_unref_defer(void *data1, void *data2) struct cache_entry *cache_coro_get_and_ref_entry(struct cache *cache, struct coro *coro, - const char *key) + const void *key) { for (int tries = GET_AND_REF_TRIES; tries; tries--) { int error; diff --git a/src/lib/lwan-cache.h b/src/lib/lwan-cache.h index a9904c725..7a6b4943d 100644 --- a/src/lib/lwan-cache.h +++ b/src/lib/lwan-cache.h @@ -32,10 +32,11 @@ struct cache_entry { time_t time_to_expire; }; -typedef struct cache_entry *(*cache_create_entry_cb)( - const char *key, void *context); -typedef void (*cache_destroy_entry_cb)( - struct cache_entry *entry, void *context); +typedef struct cache_entry *(*cache_create_entry_cb)(const void *key, + void *context); +typedef void (*cache_destroy_entry_cb)(struct cache_entry *entry, + void *context); +typedef struct hash *(*hash_create_func_cb)(void (*)(void *), void (*)(void *)); struct cache; @@ -51,7 +52,7 @@ struct cache *cache_create_full(cache_create_entry_cb create_entry_cb, void cache_destroy(struct cache *cache); struct cache_entry *cache_get_and_ref_entry(struct cache *cache, - const char *key, int *error); + const void *key, int *error); void cache_entry_unref(struct cache *cache, struct cache_entry *entry); struct cache_entry *cache_coro_get_and_ref_entry(struct cache *cache, - struct coro *coro, const char *key); + struct coro *coro, const void *key); diff --git a/src/lib/lwan-http-authorize.c b/src/lib/lwan-http-authorize.c index 883ed2a27..7df35512a 100644 --- a/src/lib/lwan-http-authorize.c +++ b/src/lib/lwan-http-authorize.c @@ -45,7 +45,7 @@ static void zero_and_free(void *str) } static struct cache_entry * -create_realm_file(const char *key, void *context __attribute__((unused))) +create_realm_file(const void *key, void *context __attribute__((unused))) { struct realm_password_file_t *rpf = malloc(sizeof(*rpf)); const struct config_line *l; @@ -105,7 +105,7 @@ create_realm_file(const char *key, void *context __attribute__((unused))) } if (config_last_error(f)) { - lwan_status_error("Error on password file \"%s\", line %d: %s", key, + lwan_status_error("Error on password file \"%s\", line %d: %s", (char *)key, config_cur_line(f), config_last_error(f)); goto error; } diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index 567e29d6f..6b70b198b 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -162,11 +162,12 @@ add_int_param(struct lwan_strbuf *strbuf, const char *key, ssize_t value) return add_param_len(strbuf, key, strlen(key), p, len); } -static struct cache_entry *create_script_name(const char *key, void *context) +static struct cache_entry *create_script_name(const void *keyptr, void *context) { struct private_data *pd = context; struct script_name_cache_entry *entry; struct lwan_value url; + const char *key = keyptr; int r; entry = malloc(sizeof(*entry)); diff --git a/src/lib/lwan-mod-lua.c b/src/lib/lwan-mod-lua.c index b8503a36b..9236bac5b 100644 --- a/src/lib/lwan-mod-lua.c +++ b/src/lib/lwan-mod-lua.c @@ -47,7 +47,7 @@ struct lwan_lua_state { lua_State *L; }; -static struct cache_entry *state_create(const char *key __attribute__((unused)), +static struct cache_entry *state_create(const void *key __attribute__((unused)), void *context) { struct lwan_lua_priv *priv = context; diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 2eacacfa1..d0ff6f93f 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -864,7 +864,7 @@ static void destroy_cache_entry(struct cache_entry *entry, free(fce); } -static struct cache_entry *create_cache_entry(const char *key, void *context) +static struct cache_entry *create_cache_entry(const void *key, void *context) { struct serve_files_priv *priv = context; struct file_cache_entry *fce; diff --git a/src/samples/freegeoip/freegeoip.c b/src/samples/freegeoip/freegeoip.c index b5f4c10c0..a85fd58f8 100644 --- a/src/samples/freegeoip/freegeoip.c +++ b/src/samples/freegeoip/freegeoip.c @@ -233,7 +233,7 @@ static ALWAYS_INLINE char *text_column_helper(sqlite3_stmt *stmt, int ind) return value ? strdup((char *)value) : NULL; } -static struct cache_entry *create_ipinfo(const char *key, +static struct cache_entry *create_ipinfo(const void *key, void *context __attribute__((unused))) { sqlite3_stmt *stmt; @@ -291,7 +291,7 @@ static struct cache_entry *create_ipinfo(const char *key, } #if QUERIES_PER_HOUR != 0 -static struct cache_entry *create_query_limit(const char *key +static struct cache_entry *create_query_limit(const void *key __attribute__((unused)), void *context __attribute__((unused))) diff --git a/src/samples/pastebin/main.c b/src/samples/pastebin/main.c index f68b85e02..f56789d0a 100644 --- a/src/samples/pastebin/main.c +++ b/src/samples/pastebin/main.c @@ -61,7 +61,7 @@ static struct hash *pending_pastes(void) return pending_pastes; } -static struct cache_entry *create_paste(const char *key, void *context) +static struct cache_entry *create_paste(const void *key, void *context) { const struct lwan_value *body = hash_find(pending_pastes(), (const void *)key); @@ -101,21 +101,26 @@ static enum lwan_http_status post_paste(struct lwan_request *request, return HTTP_BAD_REQUEST; for (int try = 0; try < 10; try++) { - uint64_t key = lwan_random_uint64(); + const void *key; - if (!hash_add_unique(pending_pastes(), &key, body)) { - coro_defer(request->conn->coro, remove_from_pending, - (void *)(uintptr_t)key); + do { + key = (const void *)(uintptr_t)lwan_random_uint64(); + } while (!key); - struct cache_entry *paste = cache_coro_get_and_ref_entry( - pastes, request->conn->coro, (void *)(uintptr_t)key); + if (hash_add_unique(pending_pastes(), key, body) < 0) + continue; - if (paste) { - response->mime_type = "text/plain"; - lwan_strbuf_printf(response->buffer, "https://%s/p/%zu\n\n", - SERVER_NAME, key); - return HTTP_OK; - } + coro_defer(request->conn->coro, remove_from_pending, + (void *)(uintptr_t)key); + + struct cache_entry *paste = + cache_coro_get_and_ref_entry(pastes, request->conn->coro, key); + + if (paste) { + response->mime_type = "text/plain"; + lwan_strbuf_printf(response->buffer, "https://%s/p/%zu\n\n", + SERVER_NAME, key); + return HTTP_OK; } } diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index 0a27c3817..c9c7b46d3 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -282,7 +282,7 @@ struct db_json_cached { struct db_json db_json; }; -static struct cache_entry *cached_queries_new(const char *keyptr, void *context) +static struct cache_entry *cached_queries_new(const void *keyptr, void *context) { struct db_json_cached *entry; struct db_stmt *stmt; From 2bb9ccc6db23cb2c2b664c1afe826af5d7b2e99a Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 20 Aug 2022 16:09:48 -0700 Subject: [PATCH 2010/2505] Properly parse unsigned int IDs in pastebin sample --- src/samples/pastebin/main.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/samples/pastebin/main.c b/src/samples/pastebin/main.c index f56789d0a..33a1a4cef 100644 --- a/src/samples/pastebin/main.c +++ b/src/samples/pastebin/main.c @@ -19,6 +19,7 @@ */ #define _GNU_SOURCE +#include #include #include @@ -163,6 +164,25 @@ LWAN_HANDLER(view_root) } } +static bool parse_uint64(const char *s, uint64_t *out) +{ + char *endptr; + + if (!*s) + return false; + + errno = 0; + *ret = strtoull(s, &endptr, 10); + + if (errno != 0) + return false; + + if (*endptr != '\0' || s == endptr) + return false; + + return true; +} + LWAN_HANDLER(view_paste) { char *dot = memrchr(request->url.value, '.', request->url.len); @@ -175,7 +195,11 @@ LWAN_HANDLER(view_paste) mime_type = "text/plain"; } - const int64_t key = parse_long_long(request->url.value, 0); + uint64_t key; + + if (!parse_uint64(request->url.value, &key)) + return HTTP_BAD_REQUEST; + struct paste *paste = (struct paste *)cache_coro_get_and_ref_entry( pastes, request->conn->coro, (void *)(uintptr_t)key); From f035b82557c9aa6b4552f0e6083ab1a696257498 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 20 Aug 2022 16:14:44 -0700 Subject: [PATCH 2011/2505] Fix build errors after changing some prototypes --- src/lib/lwan-cache.c | 4 +--- src/lib/lwan-cache.h | 4 ++-- src/samples/pastebin/main.c | 12 +++++++----- src/samples/techempower/techempower.c | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/lib/lwan-cache.c b/src/lib/lwan-cache.c index ad62e0148..3caab78c7 100644 --- a/src/lib/lwan-cache.c +++ b/src/lib/lwan-cache.c @@ -105,7 +105,6 @@ static ALWAYS_INLINE void str_key_free(void *key) struct cache *cache_create_full(cache_create_entry_cb create_entry_cb, cache_destroy_entry_cb destroy_entry_cb, hash_create_func_cb hash_create_func, - key_copy_cb copy_cb, void *cb_context, time_t time_to_live) { @@ -130,7 +129,6 @@ struct cache *cache_create_full(cache_create_entry_cb create_entry_cb, cache->cb.create_entry = create_entry_cb; cache->cb.destroy_entry = destroy_entry_cb; - cache->cb.key_copy_cb = copy_cb ? copy_cb : strdup_key_copy_cb; cache->cb.context = cb_context; if (hash_create_func == hash_str_new) { @@ -165,7 +163,7 @@ struct cache *cache_create(cache_create_entry_cb create_entry_cb, time_t time_to_live) { return cache_create_full(create_entry_cb, destroy_entry_cb, hash_str_new, - strdup_key_copy_cb, cb_context, time_to_live); + cb_context, time_to_live); } void cache_destroy(struct cache *cache) diff --git a/src/lib/lwan-cache.h b/src/lib/lwan-cache.h index 7a6b4943d..4ae4f2724 100644 --- a/src/lib/lwan-cache.h +++ b/src/lib/lwan-cache.h @@ -46,9 +46,9 @@ struct cache *cache_create(cache_create_entry_cb create_entry_cb, time_t time_to_live); struct cache *cache_create_full(cache_create_entry_cb create_entry_cb, cache_destroy_entry_cb destroy_entry_cb, + struct hash *(*hash_create_func)(void (*)(void *), void (*)(void *)), void *cb_context, - time_t time_to_live, - struct hash *(*hash_create_func)(void (*)(void *), void (*)(void *))); + time_t time_to_live); void cache_destroy(struct cache *cache); struct cache_entry *cache_get_and_ref_entry(struct cache *cache, diff --git a/src/samples/pastebin/main.c b/src/samples/pastebin/main.c index 33a1a4cef..6bcb9f963 100644 --- a/src/samples/pastebin/main.c +++ b/src/samples/pastebin/main.c @@ -120,7 +120,7 @@ static enum lwan_http_status post_paste(struct lwan_request *request, if (paste) { response->mime_type = "text/plain"; lwan_strbuf_printf(response->buffer, "https://%s/p/%zu\n\n", - SERVER_NAME, key); + SERVER_NAME, (uint64_t)(uintptr_t)key); return HTTP_OK; } } @@ -172,7 +172,7 @@ static bool parse_uint64(const char *s, uint64_t *out) return false; errno = 0; - *ret = strtoull(s, &endptr, 10); + *out = strtoull(s, &endptr, 10); if (errno != 0) return false; @@ -223,9 +223,11 @@ int main(void) lwan_init(&l); - pastes = cache_create_full(create_paste, destroy_paste, NULL, - CACHE_FOR_HOURS * 60 * 60, - hash_int64_new); + pastes = cache_create_full(create_paste, + destroy_paste, + hash_int64_new, + NULL, + CACHE_FOR_HOURS * 60 * 60); if (!pastes) lwan_status_critical("Could not create paste cache"); diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index c9c7b46d3..eed3158c9 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -521,9 +521,9 @@ int main(void) cached_queries_cache = cache_create_full(cached_queries_new, cached_queries_free, + hash_int_new, NULL, - 3600 /* 1 hour */, - hash_int_new); + 3600 /* 1 hour */); if (!cached_queries_cache) lwan_status_critical("Could not create cached queries cache"); From 1fd768b5c1488f2a2afa940e4215deaa55bdda31 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 26 Aug 2022 21:39:14 -0700 Subject: [PATCH 2012/2505] Fix backref condition in mod-rewrite Tests were not passing so I ended up not testing this throghouly --- src/bin/testrunner/testrunner.conf | 2 +- src/lib/lwan-mod-rewrite.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bin/testrunner/testrunner.conf b/src/bin/testrunner/testrunner.conf index c0d5ef4ea..e1e27dc53 100644 --- a/src/bin/testrunner/testrunner.conf +++ b/src/bin/testrunner/testrunner.conf @@ -114,7 +114,7 @@ site { rewrite /pattern { pattern (%d+)/backref { condition backref { - 0 = 42 + 1 = 42 } rewrite as = /hello?name=fourtytwo } diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index 83d62782c..e66afa90a 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -302,10 +302,10 @@ static bool condition_matches(struct lwan_request *request, } if (p->flags & PATTERN_COND_BACKREF) { - assert(p->condition.backref.index > 0); + assert(p->condition.backref.index >= 0); assert(p->condition.backref.str); - if (captures > p->condition.backref.index) + if (p->condition.backref.index > captures) return false; const struct str_find *s = &sf[p->condition.backref.index]; @@ -314,7 +314,7 @@ static bool condition_matches(struct lwan_request *request, if ((size_t)(s->sm_eo - s->sm_so) != len) return false; - if (!memcmp(request->url.value + s->sm_so, p->condition.backref.str, len)) + if (memcmp(request->url.value + s->sm_so, p->condition.backref.str, len)) return false; } From d56ad7e290abbd69acc5a04e7e9dab9e81ec4264 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 26 Aug 2022 22:05:45 -0700 Subject: [PATCH 2013/2505] Tentative fix for minimal request tests --- src/lib/lwan-request.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 22cfe84c8..60076ff2d 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -554,7 +554,12 @@ static bool parse_headers(struct lwan_request_parser_helper *helper, char **header_start = helper->header_start; ssize_t n_headers = 0; - n_headers = find_headers(header_start, helper->buffer, + /* FIXME: is there a better way to do this? */ + struct lwan_value header_start_buffer = { + .value = buffer, + .len = helper->buffer->len - (size_t)(buffer - helper->buffer->value) + }; + n_headers = find_headers(header_start, &header_start_buffer, &helper->next_request); if (UNLIKELY(n_headers < 0)) return false; From 7a4c4dbe5b1431c3d37a19b2831ab1d56187c43d Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 6 Sep 2022 18:18:51 -0700 Subject: [PATCH 2014/2505] Do not try to free integer keys in caches (This whole string/integer key thing is a mess right now and needs to be cleaned up. This is just a crutch so that the pastebin sample doesn't crash after 2h when pruning cache entries.) --- src/lib/lwan-cache.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-cache.c b/src/lib/lwan-cache.c index 3caab78c7..8d6478351 100644 --- a/src/lib/lwan-cache.c +++ b/src/lib/lwan-cache.c @@ -118,7 +118,11 @@ struct cache *cache_create_full(cache_create_entry_cb create_entry_cb, if (!cache) return NULL; - cache->hash.table = hash_create_func(free, NULL); + if (hash_create_func == hash_str_new) { + cache->hash.table = hash_create_func(free, NULL); + } else { + cache->hash.table = hash_create_func(NULL, NULL); + } if (!cache->hash.table) goto error_no_hash; From b82bce2cec7a64453843fa9f2e6e5fe401312b40 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 7 Sep 2022 19:44:44 -0700 Subject: [PATCH 2015/2505] Update .js MIME type to text/javascript per RFC 9239 --- src/bin/tools/mime.types | 3 +-- src/bin/tools/mimegen.c | 2 +- src/lib/lwan-tables.c | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/bin/tools/mime.types b/src/bin/tools/mime.types index 15b09b11c..adf47992b 100644 --- a/src/bin/tools/mime.types +++ b/src/bin/tools/mime.types @@ -142,7 +142,6 @@ application/ipfix ipfix application/java-archive jar application/java-serialized-object ser application/java-vm class -application/javascript js # application/jose # application/jose+json # application/jrd+json @@ -1687,7 +1686,7 @@ text/csv csv # text/fwdred # text/grammar-ref-list text/html html htm -# text/javascript +text/javascript js mjs # text/jcr-cnd # text/markdown # text/mizar diff --git a/src/bin/tools/mimegen.c b/src/bin/tools/mimegen.c index b7c88c269..58c3f6b1a 100644 --- a/src/bin/tools/mimegen.c +++ b/src/bin/tools/mimegen.c @@ -177,7 +177,7 @@ static bool is_builtin_mime_type(const char *mime) * a bsearch(). application/octet-stream is the fallback. */ if (streq(mime, "application/octet-stream")) return true; - if (streq(mime, "application/javascript")) + if (streq(mime, "text/javascript")) return true; if (streq(mime, "image/jpeg")) return true; diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index 5266461cb..f1a359e07 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -98,7 +98,7 @@ void lwan_tables_init(void) assert(streq(lwan_determine_mime_type_for_file_name(".gif"), "image/gif")); assert(streq(lwan_determine_mime_type_for_file_name(".JS"), - "application/javascript")); + "text/javascript")); assert(streq(lwan_determine_mime_type_for_file_name(".BZ2"), "application/x-bzip2")); } @@ -129,7 +129,7 @@ lwan_determine_mime_type_for_file_name(const char *file_name) case STR4_INT_L('.','g','i','f'): return "image/gif"; case STR4_INT_L('.','h','t','m'): return "text/html"; case STR4_INT_L('.','j','p','g'): return "image/jpeg"; - case STR4_INT_L('.','j','s',' '): return "application/javascript"; + case STR4_INT_L('.','j','s',' '): return "text/javascript"; case STR4_INT_L('.','p','n','g'): return "image/png"; case STR4_INT_L('.','t','x','t'): return "text/plain"; } From 34d0a1a104eb6513be07b907b2ff256eb1bc22f6 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 9 Sep 2022 21:10:52 -0700 Subject: [PATCH 2016/2505] When gracefully closing connections, truncate reads We don't care about the contents of the buffer at this point. --- src/lib/lwan-thread.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 04b55be46..797acf980 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -83,7 +83,7 @@ static void graceful_close(struct lwan *l, } for (int tries = 0; tries < 20; tries++) { - ssize_t r = recv(fd, buffer, DEFAULT_BUFFER_SIZE, 0); + ssize_t r = recv(fd, buffer, DEFAULT_BUFFER_SIZE, MSG_TRUNC); if (!r) break; From 0ba61f82b7a177558712704ce20c072987bf0682 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 9 Sep 2022 21:13:10 -0700 Subject: [PATCH 2017/2505] Do not retry adding paste to hash table on hash table insertion error --- src/samples/pastebin/main.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/samples/pastebin/main.c b/src/samples/pastebin/main.c index 6bcb9f963..7f092838a 100644 --- a/src/samples/pastebin/main.c +++ b/src/samples/pastebin/main.c @@ -102,17 +102,22 @@ static enum lwan_http_status post_paste(struct lwan_request *request, return HTTP_BAD_REQUEST; for (int try = 0; try < 10; try++) { - const void *key; + void *key; do { - key = (const void *)(uintptr_t)lwan_random_uint64(); + key = (void *)(uintptr_t)lwan_random_uint64(); } while (!key); - if (hash_add_unique(pending_pastes(), key, body) < 0) + switch (hash_add_unique(pending_pastes(), key, body)) { + case -EEXIST: continue; + case 0: + break; + default: + return HTTP_UNAVAILABLE; + } - coro_defer(request->conn->coro, remove_from_pending, - (void *)(uintptr_t)key); + coro_defer(request->conn->coro, remove_from_pending, key); struct cache_entry *paste = cache_coro_get_and_ref_entry(pastes, request->conn->coro, key); From ab0c13bc6b1592b35ba2ec83dc88a56312ce119c Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 17 Sep 2022 13:04:44 -0700 Subject: [PATCH 2018/2505] lwan_socket_get_backlog_size() should be static --- src/lib/lwan-private.h | 2 -- src/lib/lwan-socket.c | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 95d6f8d0e..3108a25b8 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -82,8 +82,6 @@ struct lwan_request_parser_helper { #define LWAN_MAX(a_, b_) LWAN_MIN_MAX_DETAIL(a_, b_, LWAN_TMP_ID, LWAN_TMP_ID, <) -int lwan_socket_get_backlog_size(void); - void lwan_set_thread_name(const char *name); void lwan_response_init(struct lwan *l); diff --git a/src/lib/lwan-socket.c b/src/lib/lwan-socket.c index 0bda4e7c3..27f2ac8d7 100644 --- a/src/lib/lwan-socket.c +++ b/src/lib/lwan-socket.c @@ -85,7 +85,7 @@ static void init_backlog_size(void) backlog_size = SOMAXCONN; } -int lwan_socket_get_backlog_size(void) +static int get_backlog_size(void) { static pthread_once_t backlog_size_once = PTHREAD_ONCE_INIT; pthread_once(&backlog_size_once, init_backlog_size); @@ -187,7 +187,7 @@ static int listen_addrinfo(int fd, bool print_listening_msg, bool is_https) { - if (listen(fd, lwan_socket_get_backlog_size()) < 0) + if (listen(fd, get_backlog_size()) < 0) lwan_status_critical_perror("listen"); if (print_listening_msg) { From a9849580b7cb61967270fca713b2ba7b9d8f1cde Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 17 Sep 2022 13:06:44 -0700 Subject: [PATCH 2019/2505] Allow registering handlers with fixed routes By declaring a handler with LWAN_HANDLER_ROUTE(handler_name, route), and specifying route with the same syntax as the prefix in a struct lwan_url_map, one can register a handler to respond to that route almost automatically: instead of calling lwan_set_url_map(), one calls lwan_detect_url_map() instead. --- src/lib/lwan.c | 51 +++++++++++++++++++++++++------------ src/lib/lwan.h | 28 +++++++++++++++++--- src/samples/hello/main.c | 8 ++---- src/samples/pastebin/main.c | 11 +++----- 4 files changed, 65 insertions(+), 33 deletions(-) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 89877949a..9fd66e683 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -66,7 +66,7 @@ static const struct lwan_config default_config = { .allow_put_temp_file = false, }; -LWAN_HANDLER(brew_coffee) +LWAN_HANDLER_ROUTE(brew_coffee, "/brew-coffee") { /* Placeholder handler so that __start_lwan_handler and __stop_lwan_handler * symbols will get defined. @@ -404,30 +404,49 @@ static void parse_listener_prefix(struct config *c, config_close(isolated); } +static void register_url_map(struct lwan *l, const struct lwan_url_map *map) +{ + struct lwan_url_map *copy = add_url_map(&l->url_map_trie, NULL, map); + + if (copy->module && copy->module->create) { + lwan_status_debug("Initializing module %s from struct", + get_module_name(copy->module)); + + copy->data = copy->module->create(map->prefix, copy->args); + if (!copy->data) { + lwan_status_critical("Could not initialize module %s", + get_module_name(copy->module)); + } + + copy->flags = copy->module->flags; + copy->handler = copy->module->handle_request; + } else { + copy->flags = HANDLER_PARSE_MASK; + } +} + void lwan_set_url_map(struct lwan *l, const struct lwan_url_map *map) { lwan_trie_destroy(&l->url_map_trie); if (UNLIKELY(!lwan_trie_init(&l->url_map_trie, destroy_urlmap))) lwan_status_critical_perror("Could not initialize trie"); - for (; map->prefix; map++) { - struct lwan_url_map *copy = add_url_map(&l->url_map_trie, NULL, map); + for (; map->prefix; map++) + register_url_map(l, map); +} - if (copy->module && copy->module->create) { - lwan_status_debug("Initializing module %s from struct", - get_module_name(copy->module)); +void lwan_detect_url_map(struct lwan *l) +{ + const struct lwan_url_map_route_info *iter; - copy->data = copy->module->create(map->prefix, copy->args); - if (!copy->data) { - lwan_status_critical("Could not initialize module %s", - get_module_name(copy->module)); - } + lwan_trie_destroy(&l->url_map_trie); + if (UNLIKELY(!lwan_trie_init(&l->url_map_trie, destroy_urlmap))) + lwan_status_critical_perror("Could not initialize trie"); - copy->flags = copy->module->flags; - copy->handler = copy->module->handle_request; - } else { - copy->flags = HANDLER_PARSE_MASK; - } + LWAN_SECTION_FOREACH(lwan_url_map, iter) { + struct lwan_url_map map = {.prefix = iter->route, + .handler = iter->handler}; + register_url_map(l, &map); } } diff --git a/src/lib/lwan.h b/src/lib/lwan.h index dd47e9230..8aa7d2ad5 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -69,18 +69,32 @@ extern "C" { lwan_module_info_##name_ = {.name = #name_, .module = module_} #define LWAN_HANDLER_REF(name_) lwan_handler_##name_ -#define LWAN_HANDLER(name_) \ + +#define _LWAN_HANDLER_PROTO(name_) \ static enum lwan_http_status lwan_handler_##name_( \ struct lwan_request *, struct lwan_response *, void *); \ static const struct lwan_handler_info \ __attribute__((used, section(LWAN_SECTION_NAME(lwan_handler)))) \ - lwan_handler_info_##name_ = {.name = #name_, \ - .handler = lwan_handler_##name_}; \ + lwan_handler_info_##name_ = {.name = #name_, \ + .handler = lwan_handler_##name_}; +#define _LWAN_HANDLER_FUNC(name_) \ static enum lwan_http_status lwan_handler_##name_( \ struct lwan_request *request __attribute__((unused)), \ struct lwan_response *response __attribute__((unused)), \ void *data __attribute__((unused))) +#define LWAN_HANDLER(name_) \ + _LWAN_HANDLER_PROTO(name_) \ + _LWAN_HANDLER_FUNC(name_) + +#define LWAN_HANDLER_ROUTE(name_, route_) \ + _LWAN_HANDLER_PROTO(name_) \ + static const struct lwan_url_map_route_info \ + __attribute__((used, section(LWAN_SECTION_NAME(lwan_url_map)))) \ + lwan_url_map_route_info_##name_ = {.route = route_, \ + .handler = lwan_handler_##name_}; \ + __attribute__((used)) _LWAN_HANDLER_FUNC(name_) + #define ALWAYS_INLINE inline __attribute__((always_inline)) #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ @@ -427,6 +441,13 @@ struct lwan_module_info { const struct lwan_module *module; }; +struct lwan_url_map_route_info { + const char *route; + enum lwan_http_status (*handler)(struct lwan_request *request, + struct lwan_response *response, + void *data); +}; + struct lwan_handler_info { const char *name; enum lwan_http_status (*handler)(struct lwan_request *request, @@ -527,6 +548,7 @@ struct lwan { }; void lwan_set_url_map(struct lwan *l, const struct lwan_url_map *map); +void lwan_detect_url_map(struct lwan *l); void lwan_main_loop(struct lwan *l); size_t lwan_prepare_response_header(struct lwan_request *request, diff --git a/src/samples/hello/main.c b/src/samples/hello/main.c index ea37df0d5..16e68bea0 100644 --- a/src/samples/hello/main.c +++ b/src/samples/hello/main.c @@ -19,7 +19,7 @@ #include "lwan.h" -LWAN_HANDLER(hello_world) +LWAN_HANDLER_ROUTE(hello_world, "/") { static const char message[] = "Hello, World!"; @@ -32,15 +32,11 @@ LWAN_HANDLER(hello_world) int main(void) { - const struct lwan_url_map default_map[] = { - { .prefix = "/", .handler = LWAN_HANDLER_REF(hello_world) }, - { .prefix = NULL } - }; struct lwan l; lwan_init(&l); - lwan_set_url_map(&l, default_map); + lwan_detect_url_map(&l); lwan_main_loop(&l); lwan_shutdown(&l); diff --git a/src/samples/pastebin/main.c b/src/samples/pastebin/main.c index 7f092838a..97f84249e 100644 --- a/src/samples/pastebin/main.c +++ b/src/samples/pastebin/main.c @@ -157,7 +157,7 @@ static enum lwan_http_status doc(struct lwan_request *request, return HTTP_OK; } -LWAN_HANDLER(view_root) +LWAN_HANDLER_ROUTE(view_root, "/") { switch (lwan_request_get_method(request)) { case REQUEST_METHOD_POST: @@ -188,7 +188,7 @@ static bool parse_uint64(const char *s, uint64_t *out) return true; } -LWAN_HANDLER(view_paste) +LWAN_HANDLER_ROUTE(view_paste, "/p/") { char *dot = memrchr(request->url.value, '.', request->url.len); const char *mime_type; @@ -219,11 +219,6 @@ LWAN_HANDLER(view_paste) int main(void) { - const struct lwan_url_map default_map[] = { - {.prefix = "/", .handler = LWAN_HANDLER_REF(view_root)}, - {.prefix = "/p/", .handler = LWAN_HANDLER_REF(view_paste)}, - {.prefix = NULL}, - }; struct lwan l; lwan_init(&l); @@ -236,7 +231,7 @@ int main(void) if (!pastes) lwan_status_critical("Could not create paste cache"); - lwan_set_url_map(&l, default_map); + lwan_detect_url_map(&l); lwan_main_loop(&l); lwan_shutdown(&l); From 32bc7a9bbe0b73b730c0c0ac70e111a158b1e534 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 17 Sep 2022 13:09:26 -0700 Subject: [PATCH 2020/2505] Move assertions from isalpha_neutral() to test function Also only build the test function on debug builds --- src/lib/missing.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/lib/missing.c b/src/lib/missing.c index 9cf4e5412..e2c0f88a1 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -662,13 +662,12 @@ static inline int isalpha_neutral(char c) { /* Use this instead of isalpha() from ctype.h because they consider * the current locale. This assumes CHAR_BIT == 8. */ - static const unsigned char upper_table[32] = { + static const unsigned char table[32] = { 0, 0, 0, 0, 0, 0, 0, 0, 254, 255, 255, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; unsigned char uc = (unsigned char)c; - static_assert(CHAR_BIT == 8, "sane CHAR_BIT value"); - return upper_table[uc >> 3] & 1 << (uc & 7); + return table[uc >> 3] & 1 << (uc & 7); } bool strcaseequal_neutral(const char *a, const char *b) @@ -698,6 +697,7 @@ bool strcaseequal_neutral(const char *a, const char *b) } } +#ifndef NDEBUG __attribute__((constructor)) static void test_strcaseequal_neutral(void) { assert(strcaseequal_neutral("LWAN", "lwan") == true); @@ -707,4 +707,10 @@ __attribute__((constructor)) static void test_strcaseequal_neutral(void) assert(strcaseequal_neutral("SomE-HeaDeP", "some-header") == false); assert(strcaseequal_neutral("LwAN", "lwam") == false); assert(strcaseequal_neutral("LwAn", "lWaM") == false); + + static_assert(CHAR_BIT == 8, "sane CHAR_BIT value"); + static_assert('*' == 42, "ASCII character set"); + static_assert('0' == 48, "ASCII character set"); + static_assert('a' == 97, "ASCII character set"); } +#endif From 8eb8816d929e4de495600b6629e7bc96f2e35822 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 10 Oct 2022 23:23:55 -0700 Subject: [PATCH 2021/2505] Document template variable names for auto index and errors --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index 5a9870dd3..63654f6d1 100644 --- a/README.md +++ b/README.md @@ -304,6 +304,14 @@ can be decided automatically, so some configuration options are provided. | `max_post_data_size` | `int` | `40960` | Sets the maximum number of data size for POST requests, in bytes | | `max_put_data_size` | `int` | `40960` | Sets the maximum number of data size for PUT requests, in bytes | | `allow_temp_files` | `str` | `""` | Use temporary files; set to `post` for POST requests, `put` for PUT requests, or `all` (equivalent to setting to `post put`) for both.| +| `error_template` | `str` | Default error template | Template for error codes. See variables below. | + +#### Variables for `error_template` + +| Variable | Type | Description | +|----------|------|-------------| +| `short_message` | `str` | Short error message (e.g. `Not found`) | +| `long_message` | `str` | Long error message (e.g. `The requested resource could not be found on this server`) | ### Straitjacket @@ -475,6 +483,20 @@ best to serve files in the fastest way possible according to some heuristics. | `read_ahead` | `int` | `131702` | Maximum amount of bytes to read ahead when caching open files. A value of `0` disables readahead. Readahead is performed by a low priority thread to not block the I/O threads while file extents are being read from the filesystem. | | `cache_for` | `time` | `5s` | Time to keep file metadata (size, compressed contents, open file descriptor, etc.) in cache | +##### Variables for `directory_list_template` + +| Variable | Type | Description | +|----------|------|-------------| +| `rel_path` | `str` | Path relative to the root directory real path | +| `readme` | `str` | Contents of first readme file found (`readme`, `readme.txt`, `read.me`, `README.TXT`, `README`) | +| `file_list` | iterator | Iterates on file list | +| `file_list.zebra_class` | `str` | `odd` for odd items, or `even` or even items | +| `file_list.icon` | `str` | Path to the icon for the file type | +| `file_list.name` | `str` | File name (escaped) | +| `file_list.type` | `str` | File type (directory or regular file) | +| `file_list.size` | `int` | File size | +| `file_list.unit` | `str` | Unit for `file_size` | + #### Lua The `lua` module will allow requests to be serviced by scripts written in From e047aef728075444e1af5727dec9b4c0056a124e Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 10 Oct 2022 23:24:25 -0700 Subject: [PATCH 2022/2505] Remove test for Python libraries WSGI support is not yet in the tree, there's no need to check for it. --- CMakeLists.txt | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 39287f326..116cefec1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,16 +39,6 @@ if (NOT ZLIB_INCLUDES STREQUAL "") include_directories(${ZLIB_INCLUDES}) endif () -find_package(Python3 3.9 COMPONENTS Development) -if (NOT Python3_FOUND) - message(STATUS "Disabling WSGI support") -else () - message(STATUS "Building with WSGI support using ${PYTHON_LIBRARIES}") - list(APPEND ADDITIONAL_LIBRARIES "${Python3_LIBRARIES}") - include_directories(${Python3_INCLUDE_DIRS}) - set(LWAN_HAVE_PYTHON 1) -endif () - foreach (pc_file luajit lua lua51 lua5.1 lua-5.1) if (${pc_file} STREQUAL "luajit") pkg_check_modules(LUA luajit>=2.0 luajit<2.2) From ebab03608c33850e0a8416efbd7da5ec4537471e Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 10 Oct 2022 23:24:54 -0700 Subject: [PATCH 2023/2505] Build with GNU11 instead of GNU99 --- CMakeLists.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 116cefec1..04d2590cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -208,11 +208,11 @@ endif () # -# Ensure compiler is compatible with GNU99 standard +# Ensure compiler is compatible with GNU11 standard # -check_c_compiler_flag(-std=gnu99 LWAN_HAVE_STD_GNU99) -if (NOT LWAN_HAVE_STD_GNU99) - message(FATAL_ERROR "Compiler does not support -std=gnu99. Consider using a newer compiler") +check_c_compiler_flag(-std=gnu11 LWAN_HAVE_STD_GNU11) +if (NOT LWAN_HAVE_STD_GNU11) + message(FATAL_ERROR "Compiler does not support -std=gnu11. Consider using a newer compiler") endif() @@ -322,7 +322,7 @@ enable_warning_if_supported(-Wunsequenced) # While a useful warning, this is giving false positives. enable_warning_if_supported(-Wno-free-nonheap-object) -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wshadow -Wconversion -std=gnu99") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wshadow -Wconversion -std=gnu11") set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${C_FLAGS_REL}") set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} ${C_FLAGS_REL}") set(CMAKE_C_FLAGS_MINSIZEREL "${CMAKE_C_FLAGS_MINSIZEREL} ${C_FLAGS_REL}") From ed3784e1ddac8566b319ff82533efc328af43db4 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 10 Oct 2022 23:26:52 -0700 Subject: [PATCH 2024/2505] License header: s/simple //g --- src/bin/lwan/main.c | 2 +- src/bin/testrunner/main.c | 2 +- src/bin/tools/configdump.c | 2 +- src/bin/tools/mimegen.c | 2 +- src/cmake/lwan-build-config.h.cmake | 2 +- src/lib/int-to-str.c | 2 +- src/lib/int-to-str.h | 2 +- src/lib/lwan-array.c | 2 +- src/lib/lwan-array.h | 2 +- src/lib/lwan-cache.c | 2 +- src/lib/lwan-cache.h | 2 +- src/lib/lwan-config.c | 2 +- src/lib/lwan-config.h | 2 +- src/lib/lwan-coro.c | 2 +- src/lib/lwan-coro.h | 2 +- src/lib/lwan-h2-huffman.c | 2 +- src/lib/lwan-http-authorize.c | 2 +- src/lib/lwan-http-authorize.h | 2 +- src/lib/lwan-io-wrappers.c | 2 +- src/lib/lwan-io-wrappers.h | 2 +- src/lib/lwan-job.c | 2 +- src/lib/lwan-lua.c | 2 +- src/lib/lwan-lua.h | 2 +- src/lib/lwan-mod-fastcgi.c | 2 +- src/lib/lwan-mod-fastcgi.h | 2 +- src/lib/lwan-mod-lua.c | 2 +- src/lib/lwan-mod-lua.h | 2 +- src/lib/lwan-mod-redirect.c | 2 +- src/lib/lwan-mod-redirect.h | 2 +- src/lib/lwan-mod-response.c | 2 +- src/lib/lwan-mod-response.h | 2 +- src/lib/lwan-mod-rewrite.c | 2 +- src/lib/lwan-mod-rewrite.h | 2 +- src/lib/lwan-mod-serve-files.c | 2 +- src/lib/lwan-mod-serve-files.h | 2 +- src/lib/lwan-private.h | 2 +- src/lib/lwan-pubsub.c | 2 +- src/lib/lwan-pubsub.h | 2 +- src/lib/lwan-readahead.c | 2 +- src/lib/lwan-request.c | 2 +- src/lib/lwan-response.c | 2 +- src/lib/lwan-socket.c | 2 +- src/lib/lwan-status.c | 2 +- src/lib/lwan-status.h | 2 +- src/lib/lwan-straitjacket.c | 2 +- src/lib/lwan-strbuf.c | 2 +- src/lib/lwan-strbuf.h | 2 +- src/lib/lwan-tables.c | 2 +- src/lib/lwan-template.c | 2 +- src/lib/lwan-template.h | 2 +- src/lib/lwan-thread.c | 2 +- src/lib/lwan-time.c | 2 +- src/lib/lwan-tq.c | 2 +- src/lib/lwan-tq.h | 2 +- src/lib/lwan-trie.c | 2 +- src/lib/lwan-trie.h | 2 +- src/lib/lwan-websocket.c | 2 +- src/lib/lwan.c | 2 +- src/lib/lwan.h | 2 +- src/lib/missing-pthread.c | 2 +- src/lib/missing.c | 2 +- src/lib/missing/assert.h | 2 +- src/lib/missing/fcntl.h | 2 +- src/lib/missing/ioprio.h | 2 +- src/lib/missing/libproc.h | 2 +- src/lib/missing/limits.h | 2 +- src/lib/missing/linux/capability.h | 2 +- src/lib/missing/pthread.h | 2 +- src/lib/missing/pwd.h | 2 +- src/lib/missing/stdio.h | 2 +- src/lib/missing/stdlib.h | 2 +- src/lib/missing/string.h | 2 +- src/lib/missing/sys/epoll.h | 2 +- src/lib/missing/sys/ioctl.h | 2 +- src/lib/missing/sys/mman.h | 2 +- src/lib/missing/sys/sendfile.h | 2 +- src/lib/missing/sys/socket.h | 2 +- src/lib/missing/sys/syscall.h | 2 +- src/lib/missing/sys/types.h | 2 +- src/lib/missing/sys/vfs.h | 2 +- src/lib/missing/time.h | 2 +- src/lib/missing/unistd.h | 2 +- src/lib/ringbuffer.h | 2 +- src/samples/asyncawait/main.c | 2 +- src/samples/chatr/main.c | 2 +- src/samples/clock/main.c | 2 +- src/samples/freegeoip/freegeoip.c | 2 +- src/samples/hello-no-meta/main.c | 2 +- src/samples/hello/main.c | 2 +- src/samples/pastebin/main.c | 2 +- src/samples/techempower/database.c | 2 +- src/samples/techempower/database.h | 2 +- src/samples/techempower/techempower.c | 2 +- src/samples/websocket/main.c | 2 +- 94 files changed, 94 insertions(+), 94 deletions(-) diff --git a/src/bin/lwan/main.c b/src/bin/lwan/main.c index 37d14a1e1..0e0b2a9b6 100644 --- a/src/bin/lwan/main.c +++ b/src/bin/lwan/main.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/bin/testrunner/main.c b/src/bin/testrunner/main.c index 022b984f6..56a921102 100644 --- a/src/bin/testrunner/main.c +++ b/src/bin/testrunner/main.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/bin/tools/configdump.c b/src/bin/tools/configdump.c index ba0a3c2b7..d1893d4d1 100644 --- a/src/bin/tools/configdump.c +++ b/src/bin/tools/configdump.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2019 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/bin/tools/mimegen.c b/src/bin/tools/mimegen.c index 58c3f6b1a..69c2b18d8 100644 --- a/src/bin/tools/mimegen.c +++ b/src/bin/tools/mimegen.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2016 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/cmake/lwan-build-config.h.cmake b/src/cmake/lwan-build-config.h.cmake index 5bbfaf904..efcc919c3 100644 --- a/src/cmake/lwan-build-config.h.cmake +++ b/src/cmake/lwan-build-config.h.cmake @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2016 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/int-to-str.c b/src/lib/int-to-str.c index 168505b67..d5a553f18 100644 --- a/src/lib/int-to-str.c +++ b/src/lib/int-to-str.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/int-to-str.h b/src/lib/int-to-str.h index 4c35fb407..7aa7ecedf 100644 --- a/src/lib/int-to-str.h +++ b/src/lib/int-to-str.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-array.c b/src/lib/lwan-array.c index 1fdd7ce4f..54237bb86 100644 --- a/src/lib/lwan-array.c +++ b/src/lib/lwan-array.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2017 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-array.h b/src/lib/lwan-array.h index 66df734ef..4c8eb6f02 100644 --- a/src/lib/lwan-array.h +++ b/src/lib/lwan-array.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2017 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-cache.c b/src/lib/lwan-cache.c index 8d6478351..36bd90e6b 100644 --- a/src/lib/lwan-cache.c +++ b/src/lib/lwan-cache.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2013 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-cache.h b/src/lib/lwan-cache.h index 4ae4f2724..c146fd4f5 100644 --- a/src/lib/lwan-cache.h +++ b/src/lib/lwan-cache.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2013 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index eef966ea6..bbc08a6a4 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2017 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-config.h b/src/lib/lwan-config.h index 35065027e..8a6584beb 100644 --- a/src/lib/lwan-config.h +++ b/src/lib/lwan-config.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2013 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 9628a1874..41ad6740a 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-coro.h b/src/lib/lwan-coro.h index e97ee2f03..b5605ecfa 100644 --- a/src/lib/lwan-coro.h +++ b/src/lib/lwan-coro.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-h2-huffman.c b/src/lib/lwan-h2-huffman.c index 0a7e2d41b..81816edf7 100644 --- a/src/lib/lwan-h2-huffman.c +++ b/src/lib/lwan-h2-huffman.c @@ -5,7 +5,7 @@ * This is being included here so we can fuzz-test the decoder in * oss-fuzz before we go forward with the HTTP/2 implementation. * - * lwan - simple web server + * lwan - web server * Copyright (c) 2022 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-http-authorize.c b/src/lib/lwan-http-authorize.c index 7df35512a..3d0b13121 100644 --- a/src/lib/lwan-http-authorize.c +++ b/src/lib/lwan-http-authorize.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2014 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-http-authorize.h b/src/lib/lwan-http-authorize.h index 7c6ad9279..3bc719418 100644 --- a/src/lib/lwan-http-authorize.h +++ b/src/lib/lwan-http-authorize.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2014 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index fb0c2ba2c..5745b3a65 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2013 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-io-wrappers.h b/src/lib/lwan-io-wrappers.h index e2bd316d8..d86f23e96 100644 --- a/src/lib/lwan-io-wrappers.h +++ b/src/lib/lwan-io-wrappers.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2013 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-job.c b/src/lib/lwan-job.c index 695a4b27e..427f46aac 100644 --- a/src/lib/lwan-job.c +++ b/src/lib/lwan-job.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012, 2013 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index 864c25072..f6a95e5ba 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2014 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-lua.h b/src/lib/lwan-lua.h index 75612c435..dde1a0880 100644 --- a/src/lib/lwan-lua.h +++ b/src/lib/lwan-lua.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2014 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index 6b70b198b..2793a15fc 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2022 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-mod-fastcgi.h b/src/lib/lwan-mod-fastcgi.h index 76006001d..2954f7d37 100644 --- a/src/lib/lwan-mod-fastcgi.h +++ b/src/lib/lwan-mod-fastcgi.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2022 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-mod-lua.c b/src/lib/lwan-mod-lua.c index 9236bac5b..e9778a804 100644 --- a/src/lib/lwan-mod-lua.c +++ b/src/lib/lwan-mod-lua.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2017 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-mod-lua.h b/src/lib/lwan-mod-lua.h index 139b456d0..9c45bced9 100644 --- a/src/lib/lwan-mod-lua.h +++ b/src/lib/lwan-mod-lua.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2017 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-mod-redirect.c b/src/lib/lwan-mod-redirect.c index db8a64ad6..d586054e5 100644 --- a/src/lib/lwan-mod-redirect.c +++ b/src/lib/lwan-mod-redirect.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2014 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-mod-redirect.h b/src/lib/lwan-mod-redirect.h index 3b670495c..aa203edaa 100644 --- a/src/lib/lwan-mod-redirect.h +++ b/src/lib/lwan-mod-redirect.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2014 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-mod-response.c b/src/lib/lwan-mod-response.c index 2d35bf3f5..159325ca2 100644 --- a/src/lib/lwan-mod-response.c +++ b/src/lib/lwan-mod-response.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2017 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-mod-response.h b/src/lib/lwan-mod-response.h index a65352a7c..e57b766c2 100644 --- a/src/lib/lwan-mod-response.h +++ b/src/lib/lwan-mod-response.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2017 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index e66afa90a..b3fb7d9fc 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2015 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-mod-rewrite.h b/src/lib/lwan-mod-rewrite.h index 82318b761..0ce48e689 100644 --- a/src/lib/lwan-mod-rewrite.h +++ b/src/lib/lwan-mod-rewrite.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2014 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index d0ff6f93f..f3cb823aa 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-mod-serve-files.h b/src/lib/lwan-mod-serve-files.h index 879fc7ab7..f4413bed8 100644 --- a/src/lib/lwan-mod-serve-files.h +++ b/src/lib/lwan-mod-serve-files.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 3108a25b8..52effc718 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-pubsub.c b/src/lib/lwan-pubsub.c index bd82764ec..a8b46e269 100644 --- a/src/lib/lwan-pubsub.c +++ b/src/lib/lwan-pubsub.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2020 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-pubsub.h b/src/lib/lwan-pubsub.h index 21acbdfb8..cb33f7724 100644 --- a/src/lib/lwan-pubsub.h +++ b/src/lib/lwan-pubsub.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2020 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-readahead.c b/src/lib/lwan-readahead.c index b18d31bd3..b26ce771e 100644 --- a/src/lib/lwan-readahead.c +++ b/src/lib/lwan-readahead.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2018 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 60076ff2d..9f98b3e75 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012-2014 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 8cd0fa162..35f067e50 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-socket.c b/src/lib/lwan-socket.c index 27f2ac8d7..996ffd493 100644 --- a/src/lib/lwan-socket.c +++ b/src/lib/lwan-socket.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012, 2013 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-status.c b/src/lib/lwan-status.c index c1ea8c9a3..f8783f8dd 100644 --- a/src/lib/lwan-status.c +++ b/src/lib/lwan-status.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2021 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-status.h b/src/lib/lwan-status.h index 534286072..51b6eaa16 100644 --- a/src/lib/lwan-status.h +++ b/src/lib/lwan-status.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2013 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-straitjacket.c b/src/lib/lwan-straitjacket.c index 1bec15b6b..cffa5c964 100644 --- a/src/lib/lwan-straitjacket.c +++ b/src/lib/lwan-straitjacket.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2015 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-strbuf.c b/src/lib/lwan-strbuf.c index c3d2adc13..af87e4f69 100644 --- a/src/lib/lwan-strbuf.c +++ b/src/lib/lwan-strbuf.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-strbuf.h b/src/lib/lwan-strbuf.h index 9c43cf68a..1ee77779e 100644 --- a/src/lib/lwan-strbuf.h +++ b/src/lib/lwan-strbuf.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index f1a359e07..3710ee890 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012, 2013 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index 71070740b..1ce8d0df9 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-template.h b/src/lib/lwan-template.h index ae26c2367..f3841a830 100644 --- a/src/lib/lwan-template.h +++ b/src/lib/lwan-template.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 797acf980..6f1ec0d0c 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012, 2013 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-time.c b/src/lib/lwan-time.c index 2a9b03f93..8f5b163bc 100644 --- a/src/lib/lwan-time.c +++ b/src/lib/lwan-time.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2017 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-tq.c b/src/lib/lwan-tq.c index 102443c19..a00a4a5e8 100644 --- a/src/lib/lwan-tq.c +++ b/src/lib/lwan-tq.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2019 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-tq.h b/src/lib/lwan-tq.h index 4c25971cd..bc42363d6 100644 --- a/src/lib/lwan-tq.h +++ b/src/lib/lwan-tq.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2019 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-trie.c b/src/lib/lwan-trie.c index 2fccf7ae8..d26630ab6 100644 --- a/src/lib/lwan-trie.c +++ b/src/lib/lwan-trie.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-trie.h b/src/lib/lwan-trie.h index e9ea1e792..cd822bb52 100644 --- a/src/lib/lwan-trie.h +++ b/src/lib/lwan-trie.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index 9e2fc49eb..b9ca7bc4c 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2019 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 9fd66e683..e81bf6df2 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012, 2013 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 8aa7d2ad5..db9660df6 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/missing-pthread.c b/src/lib/missing-pthread.c index 4d5180a12..fdbeacbeb 100644 --- a/src/lib/missing-pthread.c +++ b/src/lib/missing-pthread.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2018 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/missing.c b/src/lib/missing.c index e2c0f88a1..c901fbee6 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/missing/assert.h b/src/lib/missing/assert.h index 8523dd9e9..ab8415549 100644 --- a/src/lib/missing/assert.h +++ b/src/lib/missing/assert.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/missing/fcntl.h b/src/lib/missing/fcntl.h index f64221f30..3c36adb3e 100644 --- a/src/lib/missing/fcntl.h +++ b/src/lib/missing/fcntl.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/missing/ioprio.h b/src/lib/missing/ioprio.h index 78fdb11ed..a2f20a464 100644 --- a/src/lib/missing/ioprio.h +++ b/src/lib/missing/ioprio.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2018 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/missing/libproc.h b/src/lib/missing/libproc.h index beaed31e5..8ab3bdfea 100644 --- a/src/lib/missing/libproc.h +++ b/src/lib/missing/libproc.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/missing/limits.h b/src/lib/missing/limits.h index 555a0d3c5..5c9a85520 100644 --- a/src/lib/missing/limits.h +++ b/src/lib/missing/limits.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/missing/linux/capability.h b/src/lib/missing/linux/capability.h index 17ee9519c..53befa170 100644 --- a/src/lib/missing/linux/capability.h +++ b/src/lib/missing/linux/capability.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2018 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/missing/pthread.h b/src/lib/missing/pthread.h index 86476ac10..505fe8ef4 100644 --- a/src/lib/missing/pthread.h +++ b/src/lib/missing/pthread.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/missing/pwd.h b/src/lib/missing/pwd.h index 436e4c360..e93467788 100644 --- a/src/lib/missing/pwd.h +++ b/src/lib/missing/pwd.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2019 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/missing/stdio.h b/src/lib/missing/stdio.h index 84cd2ffa1..ecc5b5d5a 100644 --- a/src/lib/missing/stdio.h +++ b/src/lib/missing/stdio.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2019 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/missing/stdlib.h b/src/lib/missing/stdlib.h index 727a6756c..5a191df8e 100644 --- a/src/lib/missing/stdlib.h +++ b/src/lib/missing/stdlib.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2017 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/missing/string.h b/src/lib/missing/string.h index 07adb5b4b..01ae01554 100644 --- a/src/lib/missing/string.h +++ b/src/lib/missing/string.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/missing/sys/epoll.h b/src/lib/missing/sys/epoll.h index 449ade775..92d81a4a0 100644 --- a/src/lib/missing/sys/epoll.h +++ b/src/lib/missing/sys/epoll.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/missing/sys/ioctl.h b/src/lib/missing/sys/ioctl.h index 3f6dc4676..2f2cee344 100644 --- a/src/lib/missing/sys/ioctl.h +++ b/src/lib/missing/sys/ioctl.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2019 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/missing/sys/mman.h b/src/lib/missing/sys/mman.h index 798d72516..c1da95403 100644 --- a/src/lib/missing/sys/mman.h +++ b/src/lib/missing/sys/mman.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/missing/sys/sendfile.h b/src/lib/missing/sys/sendfile.h index 5c564c590..35ac211a3 100644 --- a/src/lib/missing/sys/sendfile.h +++ b/src/lib/missing/sys/sendfile.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/missing/sys/socket.h b/src/lib/missing/sys/socket.h index b34d3b140..3df3ea62b 100644 --- a/src/lib/missing/sys/socket.h +++ b/src/lib/missing/sys/socket.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/missing/sys/syscall.h b/src/lib/missing/sys/syscall.h index 5d6d0ec05..6384a1e72 100644 --- a/src/lib/missing/sys/syscall.h +++ b/src/lib/missing/sys/syscall.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/missing/sys/types.h b/src/lib/missing/sys/types.h index 4f75f32ea..3d06bec38 100644 --- a/src/lib/missing/sys/types.h +++ b/src/lib/missing/sys/types.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/missing/sys/vfs.h b/src/lib/missing/sys/vfs.h index abba29d50..a7e8f431c 100644 --- a/src/lib/missing/sys/vfs.h +++ b/src/lib/missing/sys/vfs.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2020 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/missing/time.h b/src/lib/missing/time.h index af1c60404..3deb4d769 100644 --- a/src/lib/missing/time.h +++ b/src/lib/missing/time.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/missing/unistd.h b/src/lib/missing/unistd.h index 8d6f018da..8257b2ee5 100644 --- a/src/lib/missing/unistd.h +++ b/src/lib/missing/unistd.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/lib/ringbuffer.h b/src/lib/ringbuffer.h index 978d59603..1bece7c5e 100644 --- a/src/lib/ringbuffer.h +++ b/src/lib/ringbuffer.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2018 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/samples/asyncawait/main.c b/src/samples/asyncawait/main.c index f7ab0941f..6d3fff5bc 100644 --- a/src/samples/asyncawait/main.c +++ b/src/samples/asyncawait/main.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2020 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/samples/chatr/main.c b/src/samples/chatr/main.c index 45f7cb448..fd2385e56 100644 --- a/src/samples/chatr/main.c +++ b/src/samples/chatr/main.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2020 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/samples/clock/main.c b/src/samples/clock/main.c index 81f8f359f..5bf4795c1 100644 --- a/src/samples/clock/main.c +++ b/src/samples/clock/main.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2018 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/samples/freegeoip/freegeoip.c b/src/samples/freegeoip/freegeoip.c index a85fd58f8..5bd96330e 100644 --- a/src/samples/freegeoip/freegeoip.c +++ b/src/samples/freegeoip/freegeoip.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2012 L. A. F. Pereira * * SQL query is copied from freegeoip.go diff --git a/src/samples/hello-no-meta/main.c b/src/samples/hello-no-meta/main.c index 92ec86227..6d7f394ad 100644 --- a/src/samples/hello-no-meta/main.c +++ b/src/samples/hello-no-meta/main.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2018 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/samples/hello/main.c b/src/samples/hello/main.c index 16e68bea0..515ffe3c4 100644 --- a/src/samples/hello/main.c +++ b/src/samples/hello/main.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2018 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/samples/pastebin/main.c b/src/samples/pastebin/main.c index 97f84249e..9ef64bfef 100644 --- a/src/samples/pastebin/main.c +++ b/src/samples/pastebin/main.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2022 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/samples/techempower/database.c b/src/samples/techempower/database.c index bab768abf..a6473f78c 100644 --- a/src/samples/techempower/database.c +++ b/src/samples/techempower/database.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2014 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/samples/techempower/database.h b/src/samples/techempower/database.h index f3506c6e7..e9aadfe2a 100644 --- a/src/samples/techempower/database.h +++ b/src/samples/techempower/database.h @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2014 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index eed3158c9..d907525e3 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2014 L. A. F. Pereira * * This program is free software; you can redistribute it and/or diff --git a/src/samples/websocket/main.c b/src/samples/websocket/main.c index be89fb69c..863432dd4 100644 --- a/src/samples/websocket/main.c +++ b/src/samples/websocket/main.c @@ -1,5 +1,5 @@ /* - * lwan - simple web server + * lwan - web server * Copyright (c) 2018 L. A. F. Pereira * * This program is free software; you can redistribute it and/or From ae5f1c212b05afddb3e9c63fa379aa9f5f1852b3 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 10 Oct 2022 23:27:41 -0700 Subject: [PATCH 2025/2505] Export lwan_handler_info_* to shared library --- src/lib/liblwan.sym | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/liblwan.sym b/src/lib/liblwan.sym index 55b7ee83e..5b5227d6d 100644 --- a/src/lib/liblwan.sym +++ b/src/lib/liblwan.sym @@ -70,6 +70,7 @@ global: lwan_strbuf_set_staticz; lwan_module_info_*; + lwan_handler_info_*; lwan_request_await_*; lwan_request_async_*; From 38511a82bf8fb3183b0778b316c04f0bce4eb412 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 10 Oct 2022 23:28:01 -0700 Subject: [PATCH 2026/2505] Use zero_and_free() if user/password existed in realm hash table --- src/lib/lwan-http-authorize.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-http-authorize.c b/src/lib/lwan-http-authorize.c index 3d0b13121..007b73580 100644 --- a/src/lib/lwan-http-authorize.c +++ b/src/lib/lwan-http-authorize.c @@ -87,8 +87,8 @@ create_realm_file(const void *key, void *context __attribute__((unused))) if (LIKELY(!err)) continue; - free(username); - free(password); + zero_and_free(username); + zero_and_free(password); if (err == -EEXIST) { lwan_status_warning( From 956822a85731c72eb2170422dee7b33c69608f66 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 10 Oct 2022 23:28:30 -0700 Subject: [PATCH 2027/2505] Close connection gracefully if reading the request fails --- src/lib/lwan-request.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 9f98b3e75..6bf2cdf17 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1562,8 +1562,8 @@ void lwan_process_request(struct lwan *l, struct lwan_request *request) * in the pipeline, this seems like the safer thing to do. */ request->conn->flags &= ~CONN_IS_KEEP_ALIVE; lwan_default_response(request, status); - coro_yield(request->conn->coro, CONN_CORO_ABORT); - __builtin_unreachable(); + /* Let process_request_coro() gracefully close the connection. */ + return; } status = parse_http_request(request); From ea96e6926c48243bc5c23ee136da22597ccd6917 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 10 Oct 2022 23:29:44 -0700 Subject: [PATCH 2028/2505] Better OOM handling while parsing listener prefix --- src/lib/lwan.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index e81bf6df2..060682ab4 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -337,9 +337,18 @@ static void parse_listener_prefix(struct config *c, while ((l = config_read_line(c))) { switch (l->type) { - case CONFIG_LINE_TYPE_LINE: - hash_add(hash, strdup(l->key), strdup(l->value)); + case CONFIG_LINE_TYPE_LINE: { + char *key_copy = strdup(l->key); + char *value_copy = strdup(l->value); + + if (!key_copy) + lwan_status_critical("Could not copy key from config file"); + if (!value_copy) + lwan_status_critical("Could not copy value from config file"); + + hash_add(hash, key_copy, value_copy); break; + } case CONFIG_LINE_TYPE_SECTION: if (streq(l->key, "authorization")) { From cf529dcc5c92979fd1729a5c264ff054954ad597 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 10 Oct 2022 23:30:54 -0700 Subject: [PATCH 2029/2505] Store route in lwan_handler struct No need to have another similar struct for essentially the same thing --- src/lib/lwan.c | 14 +++++++++----- src/lib/lwan.h | 15 ++++++--------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 060682ab4..cf6ead2c7 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -66,7 +66,7 @@ static const struct lwan_config default_config = { .allow_put_temp_file = false, }; -LWAN_HANDLER_ROUTE(brew_coffee, "/brew-coffee") +LWAN_HANDLER_ROUTE(brew_coffee, NULL /* do not autodetect this route */) { /* Placeholder handler so that __start_lwan_handler and __stop_lwan_handler * symbols will get defined. @@ -444,17 +444,21 @@ void lwan_set_url_map(struct lwan *l, const struct lwan_url_map *map) register_url_map(l, map); } +__attribute__((no_sanitize_address)) void lwan_detect_url_map(struct lwan *l) { - const struct lwan_url_map_route_info *iter; + const struct lwan_handler_info *iter; lwan_trie_destroy(&l->url_map_trie); if (UNLIKELY(!lwan_trie_init(&l->url_map_trie, destroy_urlmap))) lwan_status_critical_perror("Could not initialize trie"); - LWAN_SECTION_FOREACH(lwan_url_map, iter) { - struct lwan_url_map map = {.prefix = iter->route, - .handler = iter->handler}; + LWAN_SECTION_FOREACH(lwan_handler, iter) { + if (!iter->route) + continue; + + const struct lwan_url_map map = {.prefix = iter->route, + .handler = iter->handler}; register_url_map(l, &map); } } diff --git a/src/lib/lwan.h b/src/lib/lwan.h index db9660df6..ac44975ef 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -70,13 +70,13 @@ extern "C" { #define LWAN_HANDLER_REF(name_) lwan_handler_##name_ -#define _LWAN_HANDLER_PROTO(name_) \ +#define _LWAN_HANDLER_PROTO(name_, route_) \ static enum lwan_http_status lwan_handler_##name_( \ struct lwan_request *, struct lwan_response *, void *); \ static const struct lwan_handler_info \ __attribute__((used, section(LWAN_SECTION_NAME(lwan_handler)))) \ - lwan_handler_info_##name_ = {.name = #name_, \ - .handler = lwan_handler_##name_}; + lwan_handler_info_##name_ = { \ + .name = #name_, .handler = lwan_handler_##name_, .route = route_}; #define _LWAN_HANDLER_FUNC(name_) \ static enum lwan_http_status lwan_handler_##name_( \ struct lwan_request *request __attribute__((unused)), \ @@ -84,15 +84,11 @@ extern "C" { void *data __attribute__((unused))) #define LWAN_HANDLER(name_) \ - _LWAN_HANDLER_PROTO(name_) \ + _LWAN_HANDLER_PROTO(name_, NULL) \ _LWAN_HANDLER_FUNC(name_) #define LWAN_HANDLER_ROUTE(name_, route_) \ - _LWAN_HANDLER_PROTO(name_) \ - static const struct lwan_url_map_route_info \ - __attribute__((used, section(LWAN_SECTION_NAME(lwan_url_map)))) \ - lwan_url_map_route_info_##name_ = {.route = route_, \ - .handler = lwan_handler_##name_}; \ + _LWAN_HANDLER_PROTO(name_, route_) \ __attribute__((used)) _LWAN_HANDLER_FUNC(name_) #define ALWAYS_INLINE inline __attribute__((always_inline)) @@ -453,6 +449,7 @@ struct lwan_handler_info { enum lwan_http_status (*handler)(struct lwan_request *request, struct lwan_response *response, void *data); + const char *route; }; struct lwan_url_map { From 8337bcdfeae55dcb32ca37af7443dbcfefddaf7d Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 10 Oct 2022 23:32:11 -0700 Subject: [PATCH 2030/2505] Better output from bin2hex --- src/bin/tools/bin2hex.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/bin/tools/bin2hex.c b/src/bin/tools/bin2hex.c index 9676846c9..034e3936d 100644 --- a/src/bin/tools/bin2hex.c +++ b/src/bin/tools/bin2hex.c @@ -49,9 +49,20 @@ static int bin2hex(const char *path, const char *identifier) return -errno; printf("static const unsigned char %s[] = {\n", identifier); - - for (i = 0; i < st.st_size; i++) - printf("0x%02x, ", ptr[i] & 0xff); + printf(" "); + + int bytes_in_this_line = 0; + for (i = 0; i < st.st_size; i++) { + printf("0x%02x,", ptr[i] & 0xff); + + bytes_in_this_line++; + if (bytes_in_this_line == 11) { + printf("\n "); + bytes_in_this_line = 0; + } else { + printf(" "); + } + } printf("\n};\n"); From 0b9e8278a7d52f96b54e2b88012c160d6b68e2b9 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 10 Oct 2022 23:32:26 -0700 Subject: [PATCH 2031/2505] Add important FIXME to lwan-coro.c --- src/lib/lwan-coro.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 41ad6740a..8e53be84b 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -364,6 +364,8 @@ static void disarmed_defer(void *data __attribute__((unused))) { } +/* FIXME: this can access unallocated memory if the defer array is + * resized! */ void coro_defer_disarm(struct coro *coro, struct coro_defer *defer) { const size_t num_defers = coro_defer_array_len(&coro->defer); From c9c4ca5dc62847bd11d2aa369f898525391fde8e Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 10 Oct 2022 23:58:35 -0700 Subject: [PATCH 2032/2505] Use lwan_main() in simpler samples --- src/lib/lwan.h | 14 ++++++++++++++ src/samples/asyncawait/main.c | 20 ++------------------ src/samples/hello/main.c | 15 +-------------- src/samples/pastebin/main.c | 9 +-------- src/samples/techempower/techempower.c | 7 +------ src/samples/websocket/main.c | 23 +++++------------------ 6 files changed, 24 insertions(+), 64 deletions(-) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index ac44975ef..4f8945546 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -589,6 +589,20 @@ void lwan_init(struct lwan *l); void lwan_init_with_config(struct lwan *l, const struct lwan_config *config); void lwan_shutdown(struct lwan *l); +static inline int lwan_main(void) +{ + struct lwan l; + + lwan_init(&l); + + lwan_detect_url_map(&l); + lwan_main_loop(&l); + + lwan_shutdown(&l); + + return 0; +} + const struct lwan_config *lwan_get_default_config(void); const char *lwan_request_get_host(struct lwan_request *request); diff --git a/src/samples/asyncawait/main.c b/src/samples/asyncawait/main.c index 6d3fff5bc..65e70a86a 100644 --- a/src/samples/asyncawait/main.c +++ b/src/samples/asyncawait/main.c @@ -29,7 +29,7 @@ static void close_socket(void *data) { close((int)(intptr_t)data); } -LWAN_HANDLER(asyncawait) +LWAN_HANDLER_ROUTE(asyncawait, "/") { int fd; struct sockaddr_in addr; @@ -57,20 +57,4 @@ LWAN_HANDLER(asyncawait) return HTTP_OK; } -int main(void) -{ - const struct lwan_url_map default_map[] = { - {.prefix = "/", .handler = LWAN_HANDLER_REF(asyncawait)}, - {}, - }; - struct lwan l; - - lwan_init(&l); - - lwan_set_url_map(&l, default_map); - lwan_main_loop(&l); - - lwan_shutdown(&l); - - return 0; -} +int main(void) { return lwan_main(); } diff --git a/src/samples/hello/main.c b/src/samples/hello/main.c index 515ffe3c4..2ffd4d146 100644 --- a/src/samples/hello/main.c +++ b/src/samples/hello/main.c @@ -29,17 +29,4 @@ LWAN_HANDLER_ROUTE(hello_world, "/") return HTTP_OK; } -int -main(void) -{ - struct lwan l; - - lwan_init(&l); - - lwan_detect_url_map(&l); - lwan_main_loop(&l); - - lwan_shutdown(&l); - - return 0; -} +int main(void) { return lwan_main(); } diff --git a/src/samples/pastebin/main.c b/src/samples/pastebin/main.c index 9ef64bfef..5e833af1f 100644 --- a/src/samples/pastebin/main.c +++ b/src/samples/pastebin/main.c @@ -219,10 +219,6 @@ LWAN_HANDLER_ROUTE(view_paste, "/p/") int main(void) { - struct lwan l; - - lwan_init(&l); - pastes = cache_create_full(create_paste, destroy_paste, hash_int64_new, @@ -231,10 +227,7 @@ int main(void) if (!pastes) lwan_status_critical("Could not create paste cache"); - lwan_detect_url_map(&l); - lwan_main_loop(&l); - - lwan_shutdown(&l); + lwan_main(); cache_destroy(pastes); diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index d907525e3..ffa388bec 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -482,10 +482,6 @@ LWAN_HANDLER(quit_lwan) int main(void) { - struct lwan l; - - lwan_init(&l); - if (getenv("USE_MYSQL")) { db_connection_params = (struct db_connection_params){ .type = DB_CONN_MYSQL, @@ -527,11 +523,10 @@ int main(void) if (!cached_queries_cache) lwan_status_critical("Could not create cached queries cache"); - lwan_main_loop(&l); + lwan_main(); cache_destroy(cached_queries_cache); lwan_tpl_free(fortune_tpl); - lwan_shutdown(&l); return 0; } diff --git a/src/samples/websocket/main.c b/src/samples/websocket/main.c index 863432dd4..d8b84930c 100644 --- a/src/samples/websocket/main.c +++ b/src/samples/websocket/main.c @@ -28,7 +28,7 @@ static struct lwan_pubsub_topic *chat; /* This is a write-only sample of the API: it just sends random integers * over a WebSockets connection. */ -LWAN_HANDLER(ws_write) +LWAN_HANDLER_ROUTE(ws_write, "/ws-write") { enum lwan_http_status status = lwan_request_websocket_upgrade(request); @@ -52,7 +52,7 @@ static void free_strbuf(void *data) /* This is a slightly more featured echo server that tells how many seconds * passed since the last message has been received, and keeps sending it back * again and again. */ -LWAN_HANDLER(ws_read) +LWAN_HANDLER_ROUTE(ws_read, "/ws-read") { enum lwan_http_status status = lwan_request_websocket_upgrade(request); struct lwan_strbuf *last_msg_recv; @@ -121,7 +121,7 @@ static void pub_depart_message(void *data1, void *data2) lwan_pubsub_publish((struct lwan_pubsub_topic *)data1, buffer, (size_t)r); } -LWAN_HANDLER(ws_chat) +LWAN_HANDLER_ROUTE(ws_chat, "/ws-chat") { struct lwan_pubsub_subscriber *sub; struct lwan_pubsub_msg *msg; @@ -197,7 +197,7 @@ LWAN_HANDLER(ws_chat) __builtin_unreachable(); } -LWAN_HANDLER(index) +LWAN_HANDLER_ROUTE(index, "/") { static const char message[] = "\n" @@ -276,23 +276,10 @@ LWAN_HANDLER(index) int main(void) { - const struct lwan_url_map default_map[] = { - {.prefix = "/ws-write", .handler = LWAN_HANDLER_REF(ws_write)}, - {.prefix = "/ws-read", .handler = LWAN_HANDLER_REF(ws_read)}, - {.prefix = "/ws-chat", .handler = LWAN_HANDLER_REF(ws_chat)}, - {.prefix = "/", .handler = LWAN_HANDLER_REF(index)}, - {}, - }; - struct lwan l; - - lwan_init(&l); - chat = lwan_pubsub_new_topic(); - lwan_set_url_map(&l, default_map); - lwan_main_loop(&l); + lwan_main(); - lwan_shutdown(&l); lwan_pubsub_free_topic(chat); return 0; From f11ca99354238b0d6608b56813b0a8b818335d1a Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 11 Oct 2022 22:15:39 -0700 Subject: [PATCH 2033/2505] Remove potential use of deallocated memory in coro defers Appending to an array might reallocate and invalidate the pointers. Return an index to the deferred array instead. --- src/lib/lwan-coro.c | 33 +++++++++++++++++++++------------ src/lib/lwan-coro.h | 16 +++++++--------- src/lib/lwan-mod-fastcgi.c | 5 ++--- src/lib/lwan-request.c | 6 +++--- src/lib/lwan-thread.c | 2 +- 5 files changed, 34 insertions(+), 28 deletions(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 8e53be84b..618a6cc4a 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -364,9 +364,8 @@ static void disarmed_defer(void *data __attribute__((unused))) { } -/* FIXME: this can access unallocated memory if the defer array is - * resized! */ -void coro_defer_disarm(struct coro *coro, struct coro_defer *defer) +static void coro_defer_disarm_internal(struct coro *coro, + struct coro_defer *defer) { const size_t num_defers = coro_defer_array_len(&coro->defer); @@ -384,20 +383,30 @@ void coro_defer_disarm(struct coro *coro, struct coro_defer *defer) } } -void coro_defer_fire_and_disarm(struct coro *coro, struct coro_defer *defer) +void coro_defer_disarm(struct coro *coro, ssize_t d) { + assert(d > 0); + + return coro_defer_disarm_internal( + coro, coro_defer_array_get_elem(&coro->defer, (size_t)d)); +} + +void coro_defer_fire_and_disarm(struct coro *coro, ssize_t d) +{ + assert(d > 0); + + struct coro_defer *defer = coro_defer_array_get_elem(&coro->defer, (size_t)d); assert(coro); - assert(defer); if (defer->has_two_args) defer->two.func(defer->two.data1, defer->two.data2); else defer->one.func(defer->one.data); - return coro_defer_disarm(coro, defer); + return coro_defer_disarm_internal(coro, defer); } -ALWAYS_INLINE struct coro_defer * +ALWAYS_INLINE ssize_t coro_defer(struct coro *coro, defer1_func func, void *data) { struct coro_defer *defer = coro_defer_array_append(&coro->defer); @@ -405,17 +414,17 @@ coro_defer(struct coro *coro, defer1_func func, void *data) if (UNLIKELY(!defer)) { lwan_status_error("Could not add new deferred function for coro %p", coro); - return NULL; + return -1; } defer->one.func = func; defer->one.data = data; defer->has_two_args = false; - return defer; + return (ssize_t)coro_defer_array_get_elem_index(&coro->defer, defer); } -ALWAYS_INLINE struct coro_defer * +ALWAYS_INLINE ssize_t coro_defer2(struct coro *coro, defer2_func func, void *data1, void *data2) { struct coro_defer *defer = coro_defer_array_append(&coro->defer); @@ -423,7 +432,7 @@ coro_defer2(struct coro *coro, defer2_func func, void *data1, void *data2) if (UNLIKELY(!defer)) { lwan_status_error("Could not add new deferred function for coro %p", coro); - return NULL; + return -1; } defer->two.func = func; @@ -431,7 +440,7 @@ coro_defer2(struct coro *coro, defer2_func func, void *data1, void *data2) defer->two.data2 = data2; defer->has_two_args = true; - return defer; + return (ssize_t)coro_defer_array_get_elem_index(&coro->defer, defer); } void *coro_malloc_full(struct coro *coro, diff --git a/src/lib/lwan-coro.h b/src/lib/lwan-coro.h index b5605ecfa..3e5454fde 100644 --- a/src/lib/lwan-coro.h +++ b/src/lib/lwan-coro.h @@ -33,7 +33,6 @@ typedef libucontext_ucontext_t coro_context; #endif struct coro; -struct coro_defer; typedef int (*coro_function_t)(struct coro *coro, void *data); @@ -51,14 +50,13 @@ int64_t coro_resume(struct coro *coro); int64_t coro_resume_value(struct coro *coro, int64_t value); int64_t coro_yield(struct coro *coro, int64_t value); -struct coro_defer * -coro_defer(struct coro *coro, void (*func)(void *data), void *data); -struct coro_defer *coro_defer2(struct coro *coro, - void (*func)(void *data1, void *data2), - void *data1, - void *data2); -void coro_defer_disarm(struct coro *coro, struct coro_defer *defer); -void coro_defer_fire_and_disarm(struct coro *coro, struct coro_defer *defer); +ssize_t coro_defer(struct coro *coro, void (*func)(void *data), void *data); +ssize_t coro_defer2(struct coro *coro, + void (*func)(void *data1, void *data2), + void *data1, + void *data2); +void coro_defer_disarm(struct coro *coro, ssize_t defer); +void coro_defer_fire_and_disarm(struct coro *coro, ssize_t defer); void coro_deferred_run(struct coro *coro, size_t generation); size_t coro_deferred_get_generation(const struct coro *coro); diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index 2793a15fc..986ff8a5c 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -389,8 +389,7 @@ handle_stderr(struct lwan_request *request, const struct record *record, int fd) if (!buffer) return false; - struct coro_defer *buffer_free_defer = - coro_defer(request->conn->coro, free, buffer); + ssize_t buffer_free_defer = coro_defer(request->conn->coro, free, buffer); for (char *p = buffer; to_read;) { ssize_t r = lwan_request_async_read(request, fd, p, to_read); @@ -482,7 +481,7 @@ try_initiating_chunked_response(struct lwan_request *request) struct header_array additional_headers; header_array_init(&additional_headers); - struct coro_defer *additional_headers_reset = coro_defer(request->conn->coro, + ssize_t additional_headers_reset = coro_defer(request->conn->coro, reset_additional_header, &additional_headers); for (ssize_t i = 0; i < n_headers; i++) { diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 6bf2cdf17..959307f7a 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -327,7 +327,7 @@ static void parse_key_values(struct lwan_request *request, struct lwan_key_value *kv; char *ptr = helper_value->value; const char *end = helper_value->value + helper_value->len; - struct coro_defer *reset_defer; + ssize_t reset_defer; if (!helper_value->len) return; @@ -1749,7 +1749,7 @@ void lwan_request_sleep(struct lwan_request *request, uint64_t ms) struct lwan_connection *conn = request->conn; struct timeouts *wheel = conn->thread->wheel; struct timespec now; - struct coro_defer *defer = NULL; + ssize_t defer = 0; /* We need to update the timer wheel right now because * a request might have requested to sleep a long time @@ -1769,7 +1769,7 @@ void lwan_request_sleep(struct lwan_request *request, uint64_t ms) coro_yield(conn->coro, CONN_CORO_SUSPEND); - if (defer) + if (defer > 0) coro_defer_fire_and_disarm(conn->coro, defer); } diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 6f1ec0d0c..7ae2904d5 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -311,7 +311,7 @@ static bool lwan_setup_tls(const struct lwan *l, struct lwan_connection *conn) * destroy this coro (e.g. on connection hangup) before we have the * opportunity to free the SSL context. Defer this call for these * cases. */ - struct coro_defer *defer = + ssize_t defer = coro_defer(conn->coro, lwan_setup_tls_free_ssl_context, &ssl); if (UNLIKELY(!defer)) { From a665b3491aebc218fa31e033635bfa0720ff0f74 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 11 Oct 2022 22:22:32 -0700 Subject: [PATCH 2034/2505] Fix crash while starting pastebin sample --- src/samples/pastebin/main.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/samples/pastebin/main.c b/src/samples/pastebin/main.c index 5e833af1f..74fbb6671 100644 --- a/src/samples/pastebin/main.c +++ b/src/samples/pastebin/main.c @@ -219,6 +219,12 @@ LWAN_HANDLER_ROUTE(view_paste, "/p/") int main(void) { + struct lwan l; + + lwan_init(&l); + + lwan_detect_url_map(&l); + pastes = cache_create_full(create_paste, destroy_paste, hash_int64_new, @@ -227,9 +233,9 @@ int main(void) if (!pastes) lwan_status_critical("Could not create paste cache"); - lwan_main(); + lwan_main_loop(&l); - cache_destroy(pastes); + lwan_shutdown(&l); return 0; } From 7b985e4e664b1dd1bd88538c846cfedcb028bbf0 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 15 Oct 2022 10:26:30 -0700 Subject: [PATCH 2035/2505] Fix assertion failure on oss-fuzz --- src/lib/lwan-coro.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 618a6cc4a..45538c585 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -385,7 +385,7 @@ static void coro_defer_disarm_internal(struct coro *coro, void coro_defer_disarm(struct coro *coro, ssize_t d) { - assert(d > 0); + assert(d >= 0); return coro_defer_disarm_internal( coro, coro_defer_array_get_elem(&coro->defer, (size_t)d)); @@ -393,7 +393,7 @@ void coro_defer_disarm(struct coro *coro, ssize_t d) void coro_defer_fire_and_disarm(struct coro *coro, ssize_t d) { - assert(d > 0); + assert(d >= 0); struct coro_defer *defer = coro_defer_array_get_elem(&coro->defer, (size_t)d); assert(coro); From c1a9345f1460c853c420c28002dd296d9cee5922 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 15 Oct 2022 10:27:24 -0700 Subject: [PATCH 2036/2505] typedef the deferred handle type --- src/lib/lwan-coro.c | 12 ++++++------ src/lib/lwan-coro.h | 15 ++++++++------- src/lib/lwan-mod-fastcgi.c | 4 ++-- src/lib/lwan-request.c | 4 ++-- src/lib/lwan-thread.c | 2 +- 5 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 45538c585..db51e1768 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -383,7 +383,7 @@ static void coro_defer_disarm_internal(struct coro *coro, } } -void coro_defer_disarm(struct coro *coro, ssize_t d) +void coro_defer_disarm(struct coro *coro, coro_deferred d) { assert(d >= 0); @@ -391,7 +391,7 @@ void coro_defer_disarm(struct coro *coro, ssize_t d) coro, coro_defer_array_get_elem(&coro->defer, (size_t)d)); } -void coro_defer_fire_and_disarm(struct coro *coro, ssize_t d) +void coro_defer_fire_and_disarm(struct coro *coro, coro_deferred d) { assert(d >= 0); @@ -406,7 +406,7 @@ void coro_defer_fire_and_disarm(struct coro *coro, ssize_t d) return coro_defer_disarm_internal(coro, defer); } -ALWAYS_INLINE ssize_t +ALWAYS_INLINE coro_deferred coro_defer(struct coro *coro, defer1_func func, void *data) { struct coro_defer *defer = coro_defer_array_append(&coro->defer); @@ -421,10 +421,10 @@ coro_defer(struct coro *coro, defer1_func func, void *data) defer->one.data = data; defer->has_two_args = false; - return (ssize_t)coro_defer_array_get_elem_index(&coro->defer, defer); + return (coro_deferred)coro_defer_array_get_elem_index(&coro->defer, defer); } -ALWAYS_INLINE ssize_t +ALWAYS_INLINE coro_deferred coro_defer2(struct coro *coro, defer2_func func, void *data1, void *data2) { struct coro_defer *defer = coro_defer_array_append(&coro->defer); @@ -440,7 +440,7 @@ coro_defer2(struct coro *coro, defer2_func func, void *data1, void *data2) defer->two.data2 = data2; defer->has_two_args = true; - return (ssize_t)coro_defer_array_get_elem_index(&coro->defer, defer); + return (coro_deferred)coro_defer_array_get_elem_index(&coro->defer, defer); } void *coro_malloc_full(struct coro *coro, diff --git a/src/lib/lwan-coro.h b/src/lib/lwan-coro.h index 3e5454fde..2d762c033 100644 --- a/src/lib/lwan-coro.h +++ b/src/lib/lwan-coro.h @@ -33,6 +33,7 @@ typedef libucontext_ucontext_t coro_context; #endif struct coro; +typedef ssize_t coro_deferred; typedef int (*coro_function_t)(struct coro *coro, void *data); @@ -50,13 +51,13 @@ int64_t coro_resume(struct coro *coro); int64_t coro_resume_value(struct coro *coro, int64_t value); int64_t coro_yield(struct coro *coro, int64_t value); -ssize_t coro_defer(struct coro *coro, void (*func)(void *data), void *data); -ssize_t coro_defer2(struct coro *coro, - void (*func)(void *data1, void *data2), - void *data1, - void *data2); -void coro_defer_disarm(struct coro *coro, ssize_t defer); -void coro_defer_fire_and_disarm(struct coro *coro, ssize_t defer); +coro_deferred coro_defer(struct coro *coro, void (*func)(void *data), void *data); +coro_deferred coro_defer2(struct coro *coro, + void (*func)(void *data1, void *data2), + void *data1, + void *data2); +void coro_defer_disarm(struct coro *coro, coro_deferred defer); +void coro_defer_fire_and_disarm(struct coro *coro, coro_deferred defer); void coro_deferred_run(struct coro *coro, size_t generation); size_t coro_deferred_get_generation(const struct coro *coro); diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index 986ff8a5c..1711f731c 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -389,7 +389,7 @@ handle_stderr(struct lwan_request *request, const struct record *record, int fd) if (!buffer) return false; - ssize_t buffer_free_defer = coro_defer(request->conn->coro, free, buffer); + coro_deferred buffer_free_defer = coro_defer(request->conn->coro, free, buffer); for (char *p = buffer; to_read;) { ssize_t r = lwan_request_async_read(request, fd, p, to_read); @@ -481,7 +481,7 @@ try_initiating_chunked_response(struct lwan_request *request) struct header_array additional_headers; header_array_init(&additional_headers); - ssize_t additional_headers_reset = coro_defer(request->conn->coro, + coro_deferred additional_headers_reset = coro_defer(request->conn->coro, reset_additional_header, &additional_headers); for (ssize_t i = 0; i < n_headers; i++) { diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 959307f7a..b8afa0e9a 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -327,7 +327,7 @@ static void parse_key_values(struct lwan_request *request, struct lwan_key_value *kv; char *ptr = helper_value->value; const char *end = helper_value->value + helper_value->len; - ssize_t reset_defer; + coro_deferred reset_defer; if (!helper_value->len) return; @@ -1749,7 +1749,7 @@ void lwan_request_sleep(struct lwan_request *request, uint64_t ms) struct lwan_connection *conn = request->conn; struct timeouts *wheel = conn->thread->wheel; struct timespec now; - ssize_t defer = 0; + coro_deferred defer = -1; /* We need to update the timer wheel right now because * a request might have requested to sleep a long time diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 7ae2904d5..4bd3bcb11 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -311,7 +311,7 @@ static bool lwan_setup_tls(const struct lwan *l, struct lwan_connection *conn) * destroy this coro (e.g. on connection hangup) before we have the * opportunity to free the SSL context. Defer this call for these * cases. */ - ssize_t defer = + coro_deferred defer = coro_defer(conn->coro, lwan_setup_tls_free_ssl_context, &ssl); if (UNLIKELY(!defer)) { From 014e9255978858bbf4aa1055f25c27a875b7bbc2 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 15 Oct 2022 10:28:26 -0700 Subject: [PATCH 2037/2505] Force alignment of struct lwan_handler_info For some reason I don't yet know how, if the "route" member is added to this struct, then find_handler() will segfault as there seems to be some kind of discrepancy between the struct size and things in the section. Aligning the struct on an 8-byte boundary works around the issue, although I have no clue why that happens. --- src/lib/lwan.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 4f8945546..ed78f7e3e 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -75,6 +75,7 @@ extern "C" { struct lwan_request *, struct lwan_response *, void *); \ static const struct lwan_handler_info \ __attribute__((used, section(LWAN_SECTION_NAME(lwan_handler)))) \ + __attribute__((aligned(8))) /* FIXME: why is this alignment needed? */ \ lwan_handler_info_##name_ = { \ .name = #name_, .handler = lwan_handler_##name_, .route = route_}; #define _LWAN_HANDLER_FUNC(name_) \ From a4ffb734c12e2836ac6d24c04a8cf953c417d550 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 15 Oct 2022 10:30:17 -0700 Subject: [PATCH 2038/2505] Simplify LWAN_HANDLER() and LWAN_HANDLER_ROUTE() macros --- src/lib/lwan.h | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index ed78f7e3e..84f9fb869 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -70,27 +70,22 @@ extern "C" { #define LWAN_HANDLER_REF(name_) lwan_handler_##name_ -#define _LWAN_HANDLER_PROTO(name_, route_) \ +#define LWAN_HANDLER_ROUTE(name_, route_) \ static enum lwan_http_status lwan_handler_##name_( \ struct lwan_request *, struct lwan_response *, void *); \ static const struct lwan_handler_info \ __attribute__((used, section(LWAN_SECTION_NAME(lwan_handler)))) \ __attribute__((aligned(8))) /* FIXME: why is this alignment needed? */ \ lwan_handler_info_##name_ = { \ - .name = #name_, .handler = lwan_handler_##name_, .route = route_}; -#define _LWAN_HANDLER_FUNC(name_) \ - static enum lwan_http_status lwan_handler_##name_( \ + .name = #name_, \ + .route = route_, \ + .handler = lwan_handler_##name_, \ + }; \ + __attribute__((used)) static enum lwan_http_status lwan_handler_##name_( \ struct lwan_request *request __attribute__((unused)), \ struct lwan_response *response __attribute__((unused)), \ void *data __attribute__((unused))) - -#define LWAN_HANDLER(name_) \ - _LWAN_HANDLER_PROTO(name_, NULL) \ - _LWAN_HANDLER_FUNC(name_) - -#define LWAN_HANDLER_ROUTE(name_, route_) \ - _LWAN_HANDLER_PROTO(name_, route_) \ - __attribute__((used)) _LWAN_HANDLER_FUNC(name_) +#define LWAN_HANDLER(name_) LWAN_HANDLER_ROUTE(name_, NULL) #define ALWAYS_INLINE inline __attribute__((always_inline)) From 106d974d787fcfc0d0fa5d862891b3ef8ae0937e Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 21 Oct 2022 20:40:44 -0700 Subject: [PATCH 2039/2505] Simplify creation of script name slightly No need to call strlen() when we have the length in an lwan_value struct. --- src/lib/lwan-mod-fastcgi.c | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index 1711f731c..b799c7c07 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -166,29 +166,25 @@ static struct cache_entry *create_script_name(const void *keyptr, void *context) { struct private_data *pd = context; struct script_name_cache_entry *entry; - struct lwan_value url; - const char *key = keyptr; + const struct lwan_value *url = keyptr; int r; entry = malloc(sizeof(*entry)); if (!entry) return NULL; - if (*key) { - url = (struct lwan_value){.value = (char *)key, .len = strlen(key)}; - } else { - url = pd->default_index; - } + if (!url->len) + url = &pd->default_index; /* SCRIPT_NAME */ - r = asprintf(&entry->script_name, "/%.*s", (int)url.len, url.value); + r = asprintf(&entry->script_name, "/%.*s", (int)url->len, url->value); if (r < 0) goto free_entry; /* SCRIPT_FILENAME */ char temp[PATH_MAX]; - r = snprintf(temp, sizeof(temp), "%s/%.*s", pd->script_path, (int)url.len, - url.value); + r = snprintf(temp, sizeof(temp), "%s/%.*s", pd->script_path, (int)url->len, + url->value); if (r < 0 || r >= (int)sizeof(temp)) goto free_script_name; @@ -228,7 +224,7 @@ static enum lwan_http_status add_script_paths(const struct private_data *pd, { struct script_name_cache_entry *snce = (struct script_name_cache_entry *)cache_coro_get_and_ref_entry( - pd->script_name_cache, request->conn->coro, request->url.value); + pd->script_name_cache, request->conn->coro, &request->url); if (snce) { add_param(response->buffer, "SCRIPT_NAME", snce->script_name); From f10801ce2c5d62dd35ac43b971c6c9b69aeee31e Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 3 Nov 2022 10:15:07 -0700 Subject: [PATCH 2040/2505] Enable more possible warnings (array/string ops) --- CMakeLists.txt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 04d2590cc..a1ea9cc8d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -309,15 +309,18 @@ endif () # # These warnings are only supported by GCC, and some only in newer versions. # -enable_warning_if_supported(-Wduplicated-cond) +enable_warning_if_supported(-Warray-bounds) +enable_warning_if_supported(-Wdouble-promotion) enable_warning_if_supported(-Wduplicated-branches) +enable_warning_if_supported(-Wduplicated-cond) enable_warning_if_supported(-Wlogical-op) -enable_warning_if_supported(-Wrestrict) -enable_warning_if_supported(-Wdouble-promotion) enable_warning_if_supported(-Wno-unused-parameter) +enable_warning_if_supported(-Wrestrict) +enable_warning_if_supported(-Wstringop-overflow) +enable_warning_if_supported(-Wstringop-overread) enable_warning_if_supported(-Wstringop-truncation) -enable_warning_if_supported(-Wvla) enable_warning_if_supported(-Wunsequenced) +enable_warning_if_supported(-Wvla) # While a useful warning, this is giving false positives. enable_warning_if_supported(-Wno-free-nonheap-object) From 0851cbc8a6e7ea93f4847ed0ed42992963fc171a Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 14 Nov 2022 10:26:57 -0800 Subject: [PATCH 2041/2505] Add APIs to initialize a strbuf from the contents of a file --- src/lib/lwan-strbuf.c | 56 +++++++++++++++++++++++++++++++++++++++++++ src/lib/lwan-strbuf.h | 3 +++ 2 files changed, 59 insertions(+) diff --git a/src/lib/lwan-strbuf.c b/src/lib/lwan-strbuf.c index af87e4f69..b9a23c17b 100644 --- a/src/lib/lwan-strbuf.c +++ b/src/lib/lwan-strbuf.c @@ -346,3 +346,59 @@ char *lwan_strbuf_extend_unsafe(struct lwan_strbuf *s, size_t by) return s->buffer + prev_used; } + +bool lwan_strbuf_init_from_file(struct lwan_strbuf *s, const char *path) +{ + int fd = open(path, O_RDONLY | O_CLOEXEC); + struct stat st; + + if (UNLIKELY(fd < 0)) + return false; + + if (UNLIKELY(fstat(fd, &st) < 0)) + goto error; + + if (UNLIKELY(!lwan_strbuf_init(s))) + goto error; + + if (UNLIKELY(!grow_buffer_if_needed(s, st.st_size))) + goto error; + + s->used = st.st_size; + + for (char *buffer = s->buffer; st.st_size; ) { + ssize_t n_read = read(fd, buffer, st.st_size); + + if (UNLIKELY(n_read < 0)) { + if (errno == EINTR) + continue; + goto error; + } + + buffer += n_read; + st.st_size -= (size_t)n_read; + } + + close(fd); + return true; + +error: + close(fd); + return false; +} + +struct lwan_strbuf *lwan_strbuf_new_from_file(const char *path) +{ + struct lwan_strbuf *strbuf = malloc(sizeof(*strbuf)); + + if (!strbuf) + return NULL; + + if (lwan_strbuf_init_from_file(strbuf, path)) { + strbuf->flags |= STRBUF_MALLOCD; + return strbuf; + } + + free(strbuf); + return NULL; +} diff --git a/src/lib/lwan-strbuf.h b/src/lib/lwan-strbuf.h index 1ee77779e..69d4c929f 100644 --- a/src/lib/lwan-strbuf.h +++ b/src/lib/lwan-strbuf.h @@ -99,3 +99,6 @@ static inline char *lwan_strbuf_get_buffer(const struct lwan_strbuf *s) { return s->buffer; } + +bool lwan_strbuf_init_from_file(struct lwan_strbuf *s, const char *path); +struct lwan_strbuf *lwan_strbuf_new_from_file(const char *path); From 34599193f31c99c8a54846761b4239fcde89c243 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 14 Nov 2022 10:27:26 -0800 Subject: [PATCH 2042/2505] Use the proper APIs to initialize a strbuf from a file --- src/lib/lwan-mod-serve-files.c | 32 ++++---------------------------- 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index f3cb823aa..47ed822bb 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -660,46 +660,22 @@ static const char *dirlist_find_readme(struct lwan_strbuf *readme, { static const char *candidates[] = {"readme", "readme.txt", "read.me", "README.TXT", "README"}; - int fd = -1; if (!(priv->flags & SERVE_FILES_AUTO_INDEX_README)) return NULL; for (size_t i = 0; i < N_ELEMENTS(candidates); i++) { - char buffer[PATH_MAX]; + char readme_path[PATH_MAX]; int r; - r = snprintf(buffer, PATH_MAX, "%s/%s", full_path, candidates[i]); + r = snprintf(readme_path, PATH_MAX, "%s/%s", full_path, candidates[i]); if (r < 0 || r >= PATH_MAX) continue; - fd = open(buffer, O_RDONLY | O_CLOEXEC); - if (fd < 0) - continue; - - while (true) { - ssize_t n = read(fd, buffer, sizeof(buffer)); - - if (n < 0) { - if (errno == EINTR) - continue; - goto error; - } - - if (!lwan_strbuf_append_str(readme, buffer, (size_t)n)) - goto error; - - if ((size_t)n < sizeof(buffer)) - break; - } - - close(fd); - return lwan_strbuf_get_buffer(readme); + if (lwan_strbuf_init_from_file(readme, readme_path)) + return lwan_strbuf_get_buffer(readme); } -error: - if (fd >= 0) - close(fd); return NULL; } From 462c01ce9bc2e862d5e6e8d216ae2f7cb36117f4 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 18 Nov 2022 14:52:49 -0800 Subject: [PATCH 2043/2505] Move CGI header serialization code from FastCGI to common place The rules are the same between FastCGI and Ruby Rack, so move to a common place so they can be reused. --- src/lib/lwan-mod-fastcgi.c | 48 +++++++------------------------------- src/lib/lwan-private.h | 8 +++++++ src/lib/lwan-request.c | 48 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 40 deletions(-) diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index b799c7c07..0d9a4418d 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -235,44 +235,14 @@ static enum lwan_http_status add_script_paths(const struct private_data *pd, return HTTP_NOT_FOUND; } -static void add_headers(struct lwan_strbuf *strbuf, - char **header_start, - size_t n_header_start) +static void add_header_to_strbuf(const char *header, + size_t header_len, + const char *value, + size_t value_len, + void *user_data) { - for (size_t i = 0; i < n_header_start; i++) { - const char *header = header_start[i]; - const char *next_header = header_start[i + 1]; - const char *colon = memchr(header, ':', 127 - sizeof("HTTP_: ") - 1); - char header_name[128]; - int r; - - if (!colon) - continue; - - const size_t header_len = (size_t)(colon - header); - const size_t value_len = (size_t)(next_header - colon - 4); - - r = snprintf(header_name, sizeof(header_name), "HTTP_%.*s", - (int)header_len, header); - if (r < 0 || r >= (int)sizeof(header_name)) - continue; - - /* FIXME: RFC7230/RFC3875 compliance */ - for (char *p = header_name; *p; p++) { - if (isalpha(*p)) - *p &= ~0x20; - else if (!isdigit(*p)) - *p = '_'; - } - - if (streq(header_name, "HTTP_PROXY")) { - /* Mitigation for https://httpoxy.org */ - continue; - } - - add_param_len(strbuf, header_name, header_len + sizeof("HTTP_") - 1, - colon + 2, value_len); - } + struct lwan_strbuf *strbuf = user_data; + return add_param_len(strbuf, header, header_len, value, value_len); } static enum lwan_http_status add_params(const struct private_data *pd, @@ -341,9 +311,7 @@ static enum lwan_http_status add_params(const struct private_data *pd, add_param(strbuf, "REQUEST_URI", request->original_url.value); } - add_headers(strbuf, - request_helper->header_start, - request_helper->n_header_start); + lwan_request_foreach_header_for_cgi(request, add_header_to_strbuf, strbuf); return HTTP_OK; } diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 52effc718..af3162711 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -273,3 +273,11 @@ const char *lwan_request_get_header_from_helper(struct lwan_request_parser_helpe const char *header); sa_family_t lwan_socket_parse_address(char *listener, char **node, char **port); + +void lwan_request_foreach_header_for_cgi(const struct lwan_request *request, + void (*cb)(const char *header_name, + size_t header_len, + const char *value, + size_t value_len, + void *user_data), + void *user_data); diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index b8afa0e9a..0e5748426 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -2107,3 +2107,51 @@ ssize_t lwan_request_async_writev(struct lwan_request *request, coro_yield(request->conn->coro, CONN_CORO_ABORT); __builtin_unreachable(); } + +void lwan_request_foreach_header_for_cgi(const struct lwan_request *request, + void (*cb)(const char *header_name, + size_t header_len, + const char *value, + size_t value_len, + void *user_data), + void *user_data) +{ + const struct lwan_request_helper *helper = request->helper; + const char *header_start = helper->header_start; + const size_T n_header_start = helper->n_header_start; + + for (size_t i = 0; i < n_header_start; i++) { + const char *header = header_start[i]; + const char *next_header = header_start[i + 1]; + const char *colon = memchr(header, ':', 127 - sizeof("HTTP_: ") - 1); + char header_name[128]; + int r; + + if (!colon) + continue; + + const size_t header_len = (size_t)(colon - header); + const size_t value_len = (size_t)(next_header - colon - 4); + + r = snprintf(header_name, sizeof(header_name), "HTTP_%.*s", + (int)header_len, header); + if (r < 0 || r >= (int)sizeof(header_name)) + continue; + + /* FIXME: RFC7230/RFC3875 compliance */ + for (char *p = header_name; *p; p++) { + if (isalpha(*p)) + *p &= ~0x20; + else if (!isdigit(*p)) + *p = '_'; + } + + if (streq(header_name, "HTTP_PROXY")) { + /* Mitigation for https://httpoxy.org */ + continue; + } + + cb(header_name, header_len + sizeof("HTTP_") - 1, colon + 2, value_len, + user_data); + } +} From 8d50e73559ca7f5937b81fb82f051c01745313b8 Mon Sep 17 00:00:00 2001 From: David Korczynski Date: Sun, 20 Nov 2022 10:48:16 -0800 Subject: [PATCH 2044/2505] Add Github action for CIFuzz integration Signed-off-by: David Korczynski --- .github/workflows/cifuzz.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/cifuzz.yml diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml new file mode 100644 index 000000000..4a9bf1a3b --- /dev/null +++ b/.github/workflows/cifuzz.yml @@ -0,0 +1,26 @@ +name: CIFuzz +on: [pull_request] +jobs: + Fuzzing: + runs-on: ubuntu-latest + steps: + - name: Build Fuzzers + id: build + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master + with: + oss-fuzz-project-name: 'lwan' + dry-run: false + language: c++ + - name: Run Fuzzers + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master + with: + oss-fuzz-project-name: 'lwan' + fuzz-seconds: 300 + dry-run: false + language: c++ + - name: Upload Crash + uses: actions/upload-artifact@v3 + if: failure() && steps.build.outcome == 'success' + with: + name: artifacts + path: ./out/artifacts From b6ce7d7bce961b2b367f30285137ff29c0d2db0e Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 21 Nov 2022 22:54:44 -0800 Subject: [PATCH 2045/2505] Fix build errors --- src/lib/lwan-private.h | 2 +- src/lib/lwan-request.c | 9 +++++---- src/lib/lwan-strbuf.c | 12 ++++++++---- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index af3162711..c91e15f8d 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -274,7 +274,7 @@ const char *lwan_request_get_header_from_helper(struct lwan_request_parser_helpe sa_family_t lwan_socket_parse_address(char *listener, char **node, char **port); -void lwan_request_foreach_header_for_cgi(const struct lwan_request *request, +void lwan_request_foreach_header_for_cgi(struct lwan_request *request, void (*cb)(const char *header_name, size_t header_len, const char *value, diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 0e5748426..71cfefcfa 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -20,6 +20,7 @@ #define _GNU_SOURCE #include #include +#include #include #include #include @@ -2108,7 +2109,7 @@ ssize_t lwan_request_async_writev(struct lwan_request *request, __builtin_unreachable(); } -void lwan_request_foreach_header_for_cgi(const struct lwan_request *request, +void lwan_request_foreach_header_for_cgi(struct lwan_request *request, void (*cb)(const char *header_name, size_t header_len, const char *value, @@ -2116,9 +2117,9 @@ void lwan_request_foreach_header_for_cgi(const struct lwan_request *request, void *user_data), void *user_data) { - const struct lwan_request_helper *helper = request->helper; - const char *header_start = helper->header_start; - const size_T n_header_start = helper->n_header_start; + struct lwan_request_parser_helper *helper = request->helper; + char **header_start = helper->header_start; + size_t n_header_start = helper->n_header_start; for (size_t i = 0; i < n_header_start; i++) { const char *header = header_start[i]; diff --git a/src/lib/lwan-strbuf.c b/src/lib/lwan-strbuf.c index b9a23c17b..0951e81d4 100644 --- a/src/lib/lwan-strbuf.c +++ b/src/lib/lwan-strbuf.c @@ -19,10 +19,14 @@ */ #define _GNU_SOURCE +#include +#include #include #include +#include #include #include +#include #include "lwan-private.h" @@ -361,13 +365,13 @@ bool lwan_strbuf_init_from_file(struct lwan_strbuf *s, const char *path) if (UNLIKELY(!lwan_strbuf_init(s))) goto error; - if (UNLIKELY(!grow_buffer_if_needed(s, st.st_size))) + if (UNLIKELY(!grow_buffer_if_needed(s, (size_t)st.st_size))) goto error; - s->used = st.st_size; + s->used = (size_t)st.st_size; for (char *buffer = s->buffer; st.st_size; ) { - ssize_t n_read = read(fd, buffer, st.st_size); + ssize_t n_read = read(fd, buffer, (size_t)st.st_size); if (UNLIKELY(n_read < 0)) { if (errno == EINTR) @@ -376,7 +380,7 @@ bool lwan_strbuf_init_from_file(struct lwan_strbuf *s, const char *path) } buffer += n_read; - st.st_size -= (size_t)n_read; + st.st_size -= (off_t)n_read; } close(fd); From 9c59153efe97f84d2239bd8956a1cb4a4b04d648 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 22 Nov 2022 13:06:20 -0800 Subject: [PATCH 2046/2505] Remove CodeQL analysis jobs These weren't properly configured and I don't have the time to set them up now. --- .github/workflows/codeql-analysis.yml | 71 --------------------------- 1 file changed, 71 deletions(-) delete mode 100644 .github/workflows/codeql-analysis.yml diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index 0b7a974d0..000000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,71 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -name: "CodeQL" - -on: - push: - branches: [master] - pull_request: - # The branches below must be a subset of the branches above - branches: [master] - schedule: - - cron: '0 13 * * 3' - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - # Override automatic language detection by changing the below list - # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] - language: ['cpp', 'python'] - # Learn more... - # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - with: - # We must fetch at least the immediate parents so that if this is - # a pull request then we can checkout the head. - fetch-depth: 2 - - # If this run was triggered by a pull request event, then checkout - # the head of the pull request instead of the merge commit. - - run: git checkout HEAD^2 - if: ${{ github.event_name == 'pull_request' }} - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 - - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 From 1d3f55bfb68c92ccde222e395b51ca9ae221f8f6 Mon Sep 17 00:00:00 2001 From: mayhem-bot Date: Thu, 2 Jun 2022 20:53:23 -0400 Subject: [PATCH 2047/2505] Mayhem support --- .github/workflows/config_fuzzer-mayhem.yml | 55 ++++++++++++++++++++++ mayhem/Dockerfile | 28 +++++++++++ mayhem/Dockerfile.dockerignore | 0 mayhem/build.sh | 43 +++++++++++++++++ mayhem/config_fuzzer.mayhemfile | 4 ++ 5 files changed, 130 insertions(+) create mode 100644 .github/workflows/config_fuzzer-mayhem.yml create mode 100644 mayhem/Dockerfile create mode 100644 mayhem/Dockerfile.dockerignore create mode 100644 mayhem/build.sh create mode 100644 mayhem/config_fuzzer.mayhemfile diff --git a/.github/workflows/config_fuzzer-mayhem.yml b/.github/workflows/config_fuzzer-mayhem.yml new file mode 100644 index 000000000..3c28e7ae9 --- /dev/null +++ b/.github/workflows/config_fuzzer-mayhem.yml @@ -0,0 +1,55 @@ +name: Mayhem +on: + push: + pull_request: + workflow_dispatch: + workflow_call: +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} +jobs: + build: + name: ${{ matrix.os }} shared=${{ matrix.shared }} ${{ matrix.build_type }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + shared: [false] + build_type: [Release] + include: + - os: ubuntu-latest + triplet: x64-linux + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + - name: Log in to the Container registry + uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + - name: Build and push Docker image + uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc + with: + context: . + file: mayhem/Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + - name: Start analysis + uses: forallsecure/mcode-action@v1 + with: + mayhem-token: ${{ secrets.MAYHEM_TOKEN }} + args: --image ${{ steps.meta.outputs.tags }} --cmd /out/config_fuzzer --target + config_fuzzer --file mayhem/config_fuzzer.mayhemfile + sarif-output: sarif + - name: Upload SARIF file(s) + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: sarif diff --git a/mayhem/Dockerfile b/mayhem/Dockerfile new file mode 100644 index 000000000..ea3104450 --- /dev/null +++ b/mayhem/Dockerfile @@ -0,0 +1,28 @@ +# Copyright 2019 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +################################################################################ + +FROM gcr.io/oss-fuzz-base/base-builder +RUN apt-get update +RUN apt-get install -y build-essential cmake git ninja-build zlib1g-dev + +COPY . lwan +RUN rm -rf lwan/mayhem +WORKDIR lwan + +COPY mayhem/build.sh $SRC/ + +ENV FUZZING_LANGUAGE=c++ SANITIZER=address +RUN compile \ No newline at end of file diff --git a/mayhem/Dockerfile.dockerignore b/mayhem/Dockerfile.dockerignore new file mode 100644 index 000000000..e69de29bb diff --git a/mayhem/build.sh b/mayhem/build.sh new file mode 100644 index 000000000..bcdd36887 --- /dev/null +++ b/mayhem/build.sh @@ -0,0 +1,43 @@ +#!/bin/bash -euv +# Copyright 2019 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +################################################################################ + + +mkdir -p $WORK/lwan +cd $WORK/lwan + +cmake -GNinja \ + -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_C_COMPILER="${CC}" \ + -DCMAKE_C_FLAGS="${CFLAGS}" \ + $SRC/lwan/ + +ninja -v liblwan.a + +cp $SRC/lwan/fuzz/*.dict $OUT/ + +for fuzzer in $SRC/lwan/src/bin/fuzz/*_fuzzer.cc; do + executable=$(basename $fuzzer .cc) + corpus_base=$(basename $fuzzer _fuzzer.cc) + + zip -jr $OUT/${executable}_seed_corpus.zip $SRC/lwan/fuzz/corpus/corpus-${corpus_base}-* + + $CXX $CXXFLAGS -std=c++11 \ + -Wl,-whole-archive $WORK/lwan/src/lib/liblwan.a -Wl,-no-whole-archive \ + -I$SRC/lwan/src/lib $fuzzer \ + $LIB_FUZZING_ENGINE -lpthread -lz \ + -o $OUT/$executable +done diff --git a/mayhem/config_fuzzer.mayhemfile b/mayhem/config_fuzzer.mayhemfile new file mode 100644 index 000000000..9957e53cf --- /dev/null +++ b/mayhem/config_fuzzer.mayhemfile @@ -0,0 +1,4 @@ +project: PROJECT +target: config_fuzzer +cmds: +- cmd: /out/config_fuzzer From 488d083fa55505665b18e86314985f17b509eba9 Mon Sep 17 00:00:00 2001 From: xansec <76011430+xansec@users.noreply.github.com> Date: Thu, 24 Nov 2022 23:32:16 -0500 Subject: [PATCH 2048/2505] Update and rename config_fuzzer-mayhem.yml to mayhem.yml --- .github/workflows/config_fuzzer-mayhem.yml | 55 ---------------- .github/workflows/mayhem.yml | 77 ++++++++++++++++++++++ 2 files changed, 77 insertions(+), 55 deletions(-) delete mode 100644 .github/workflows/config_fuzzer-mayhem.yml create mode 100644 .github/workflows/mayhem.yml diff --git a/.github/workflows/config_fuzzer-mayhem.yml b/.github/workflows/config_fuzzer-mayhem.yml deleted file mode 100644 index 3c28e7ae9..000000000 --- a/.github/workflows/config_fuzzer-mayhem.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: Mayhem -on: - push: - pull_request: - workflow_dispatch: - workflow_call: -env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} -jobs: - build: - name: ${{ matrix.os }} shared=${{ matrix.shared }} ${{ matrix.build_type }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest] - shared: [false] - build_type: [Release] - include: - - os: ubuntu-latest - triplet: x64-linux - steps: - - uses: actions/checkout@v2 - with: - submodules: recursive - - name: Log in to the Container registry - uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - - name: Build and push Docker image - uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc - with: - context: . - file: mayhem/Dockerfile - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - - name: Start analysis - uses: forallsecure/mcode-action@v1 - with: - mayhem-token: ${{ secrets.MAYHEM_TOKEN }} - args: --image ${{ steps.meta.outputs.tags }} --cmd /out/config_fuzzer --target - config_fuzzer --file mayhem/config_fuzzer.mayhemfile - sarif-output: sarif - - name: Upload SARIF file(s) - uses: github/codeql-action/upload-sarif@v2 - with: - sarif_file: sarif diff --git a/.github/workflows/mayhem.yml b/.github/workflows/mayhem.yml new file mode 100644 index 000000000..167d095ac --- /dev/null +++ b/.github/workflows/mayhem.yml @@ -0,0 +1,77 @@ +name: Mayhem +on: + push: + pull_request: + workflow_dispatch: + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build: + name: '${{ matrix.os }} shared=${{ matrix.shared }} ${{ matrix.build_type }}' + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + shared: [false] + build_type: [Release] + include: + - os: ubuntu-latest + triplet: x64-linux + + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Log in to the Container registry + uses: docker/login-action@v2.1.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v4.1.1 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + - name: Build and push Docker image + uses: docker/build-push-action@v3.2.0 + with: + context: . + push: true + file: mayhem/Dockerfile + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + outputs: + image: ${{ steps.meta.outputs.tags }} + + mayhem: + needs: build + name: 'fuzz ${{ matrix.mayhemfile }}' + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + mayhemfile: + - mayhem/config_fuzzer.mayhemfile + + steps: + - uses: actions/checkout@v3 + + - name: Start analysis for ${{ matrix.mayhemfile }} + uses: ForAllSecure/mcode-action@v1 + with: + mayhem-token: ${{ secrets.MAYHEM_TOKEN }} + args: --image ${{ needs.build.outputs.image }} --file ${{ matrix.mayhemfile }} --duration 300 + sarif-output: sarif + + - name: Upload SARIF file(s) + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: sarif From 745cfbc021e5ba45e4b7eea298a5025363e0e9d2 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 27 Nov 2022 14:37:52 -0800 Subject: [PATCH 2049/2505] Enable more fuzzers on Mayhem --- .github/workflows/mayhem.yml | 4 ++++ mayhem/h2_huffman_fuzzer.mayhemfile | 4 ++++ mayhem/pattern_fuzzer.mayhemfile | 4 ++++ mayhem/request_fuzzer.mayhemfile | 4 ++++ mayhem/template_fuzzer.mayhemfile | 4 ++++ 5 files changed, 20 insertions(+) create mode 100644 mayhem/h2_huffman_fuzzer.mayhemfile create mode 100644 mayhem/pattern_fuzzer.mayhemfile create mode 100644 mayhem/request_fuzzer.mayhemfile create mode 100644 mayhem/template_fuzzer.mayhemfile diff --git a/.github/workflows/mayhem.yml b/.github/workflows/mayhem.yml index 167d095ac..17018c4dd 100644 --- a/.github/workflows/mayhem.yml +++ b/.github/workflows/mayhem.yml @@ -60,6 +60,10 @@ jobs: matrix: mayhemfile: - mayhem/config_fuzzer.mayhemfile + - mayhem/request_fuzzer.mayhemfile + - mayhem/pattern_fuzzer.mayhemfile + - mayhem/template_fuzzer.mayhemfile + - mayhem/h2_huffman_fuzzer.mayhemfile steps: - uses: actions/checkout@v3 diff --git a/mayhem/h2_huffman_fuzzer.mayhemfile b/mayhem/h2_huffman_fuzzer.mayhemfile new file mode 100644 index 000000000..6da25af45 --- /dev/null +++ b/mayhem/h2_huffman_fuzzer.mayhemfile @@ -0,0 +1,4 @@ +project: PROJECT +target: h2_huffman_fuzzer +cmds: +- cmd: /out/h2_huffman_fuzzer diff --git a/mayhem/pattern_fuzzer.mayhemfile b/mayhem/pattern_fuzzer.mayhemfile new file mode 100644 index 000000000..a3f1bc479 --- /dev/null +++ b/mayhem/pattern_fuzzer.mayhemfile @@ -0,0 +1,4 @@ +project: PROJECT +target: pattern_fuzzer +cmds: +- cmd: /out/pattern_fuzzer diff --git a/mayhem/request_fuzzer.mayhemfile b/mayhem/request_fuzzer.mayhemfile new file mode 100644 index 000000000..9735c85e7 --- /dev/null +++ b/mayhem/request_fuzzer.mayhemfile @@ -0,0 +1,4 @@ +project: PROJECT +target: request_fuzzer +cmds: +- cmd: /out/request_fuzzer diff --git a/mayhem/template_fuzzer.mayhemfile b/mayhem/template_fuzzer.mayhemfile new file mode 100644 index 000000000..f96749df8 --- /dev/null +++ b/mayhem/template_fuzzer.mayhemfile @@ -0,0 +1,4 @@ +project: PROJECT +target: template_fuzzer +cmds: +- cmd: /out/template_fuzzer From c94e6ae68b7e1136949d2f72c0bad8fde9baca50 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 27 Nov 2022 14:39:16 -0800 Subject: [PATCH 2050/2505] Move Mayhem fuzzing stuff to fuzz/ directory --- .github/workflows/mayhem.yml | 12 ++++++------ {mayhem => fuzz/mayhem}/Dockerfile | 0 {mayhem => fuzz/mayhem}/Dockerfile.dockerignore | 0 {mayhem => fuzz/mayhem}/build.sh | 0 {mayhem => fuzz/mayhem}/config_fuzzer.mayhemfile | 0 {mayhem => fuzz/mayhem}/h2_huffman_fuzzer.mayhemfile | 0 {mayhem => fuzz/mayhem}/pattern_fuzzer.mayhemfile | 0 {mayhem => fuzz/mayhem}/request_fuzzer.mayhemfile | 0 {mayhem => fuzz/mayhem}/template_fuzzer.mayhemfile | 0 9 files changed, 6 insertions(+), 6 deletions(-) rename {mayhem => fuzz/mayhem}/Dockerfile (100%) rename {mayhem => fuzz/mayhem}/Dockerfile.dockerignore (100%) rename {mayhem => fuzz/mayhem}/build.sh (100%) rename {mayhem => fuzz/mayhem}/config_fuzzer.mayhemfile (100%) rename {mayhem => fuzz/mayhem}/h2_huffman_fuzzer.mayhemfile (100%) rename {mayhem => fuzz/mayhem}/pattern_fuzzer.mayhemfile (100%) rename {mayhem => fuzz/mayhem}/request_fuzzer.mayhemfile (100%) rename {mayhem => fuzz/mayhem}/template_fuzzer.mayhemfile (100%) diff --git a/.github/workflows/mayhem.yml b/.github/workflows/mayhem.yml index 17018c4dd..d7f72dc34 100644 --- a/.github/workflows/mayhem.yml +++ b/.github/workflows/mayhem.yml @@ -44,7 +44,7 @@ jobs: with: context: . push: true - file: mayhem/Dockerfile + file: fuzz/mayhem/Dockerfile tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} @@ -59,11 +59,11 @@ jobs: fail-fast: false matrix: mayhemfile: - - mayhem/config_fuzzer.mayhemfile - - mayhem/request_fuzzer.mayhemfile - - mayhem/pattern_fuzzer.mayhemfile - - mayhem/template_fuzzer.mayhemfile - - mayhem/h2_huffman_fuzzer.mayhemfile + - fuzz/mayhem/config_fuzzer.mayhemfile + - fuzz/mayhem/request_fuzzer.mayhemfile + - fuzz/mayhem/pattern_fuzzer.mayhemfile + - fuzz/mayhem/template_fuzzer.mayhemfile + - fuzz/mayhem/h2_huffman_fuzzer.mayhemfile steps: - uses: actions/checkout@v3 diff --git a/mayhem/Dockerfile b/fuzz/mayhem/Dockerfile similarity index 100% rename from mayhem/Dockerfile rename to fuzz/mayhem/Dockerfile diff --git a/mayhem/Dockerfile.dockerignore b/fuzz/mayhem/Dockerfile.dockerignore similarity index 100% rename from mayhem/Dockerfile.dockerignore rename to fuzz/mayhem/Dockerfile.dockerignore diff --git a/mayhem/build.sh b/fuzz/mayhem/build.sh similarity index 100% rename from mayhem/build.sh rename to fuzz/mayhem/build.sh diff --git a/mayhem/config_fuzzer.mayhemfile b/fuzz/mayhem/config_fuzzer.mayhemfile similarity index 100% rename from mayhem/config_fuzzer.mayhemfile rename to fuzz/mayhem/config_fuzzer.mayhemfile diff --git a/mayhem/h2_huffman_fuzzer.mayhemfile b/fuzz/mayhem/h2_huffman_fuzzer.mayhemfile similarity index 100% rename from mayhem/h2_huffman_fuzzer.mayhemfile rename to fuzz/mayhem/h2_huffman_fuzzer.mayhemfile diff --git a/mayhem/pattern_fuzzer.mayhemfile b/fuzz/mayhem/pattern_fuzzer.mayhemfile similarity index 100% rename from mayhem/pattern_fuzzer.mayhemfile rename to fuzz/mayhem/pattern_fuzzer.mayhemfile diff --git a/mayhem/request_fuzzer.mayhemfile b/fuzz/mayhem/request_fuzzer.mayhemfile similarity index 100% rename from mayhem/request_fuzzer.mayhemfile rename to fuzz/mayhem/request_fuzzer.mayhemfile diff --git a/mayhem/template_fuzzer.mayhemfile b/fuzz/mayhem/template_fuzzer.mayhemfile similarity index 100% rename from mayhem/template_fuzzer.mayhemfile rename to fuzz/mayhem/template_fuzzer.mayhemfile From 9eb26a4326b2ce55904b70d55decc111859e2b6f Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 27 Nov 2022 14:40:44 -0800 Subject: [PATCH 2051/2505] Fix path to build.sh in Mayhem Dockerfile --- fuzz/mayhem/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fuzz/mayhem/Dockerfile b/fuzz/mayhem/Dockerfile index ea3104450..d28dad6a0 100644 --- a/fuzz/mayhem/Dockerfile +++ b/fuzz/mayhem/Dockerfile @@ -22,7 +22,7 @@ COPY . lwan RUN rm -rf lwan/mayhem WORKDIR lwan -COPY mayhem/build.sh $SRC/ +COPY fuzz/mayhem/build.sh $SRC/ ENV FUZZING_LANGUAGE=c++ SANITIZER=address RUN compile \ No newline at end of file From ffb8bf024e3bd14386d60b7b5f9c64cb415f40fa Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 1 Dec 2022 20:46:54 -0800 Subject: [PATCH 2052/2505] Allocate inlinefirst arrays with coro_malloc properly We were allocating those arrays with sizeof(base) rather than sizeof(derived). Oops. --- src/lib/lwan-array.c | 8 ++++++-- src/lib/lwan-array.h | 14 +++++++++----- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/lib/lwan-array.c b/src/lib/lwan-array.c index 54237bb86..d39ef6158 100644 --- a/src/lib/lwan-array.c +++ b/src/lib/lwan-array.c @@ -125,11 +125,15 @@ static void coro_lwan_array_free_inline(void *data) free(array); } -struct lwan_array *coro_lwan_array_new(struct coro *coro, bool inline_first) +struct lwan_array *coro_lwan_array_new(struct coro *coro, + size_t struct_size, + bool inline_first) { struct lwan_array *array; - array = coro_malloc_full(coro, sizeof(*array), + assert(struct_size >= sizeof(struct lwan_array)); + + array = coro_malloc_full(coro, struct_size, inline_first ? coro_lwan_array_free_inline : coro_lwan_array_free_heap); if (LIKELY(array)) diff --git a/src/lib/lwan-array.h b/src/lib/lwan-array.h index 4c8eb6f02..3da94dd41 100644 --- a/src/lib/lwan-array.h +++ b/src/lib/lwan-array.h @@ -40,7 +40,9 @@ void *lwan_array_append_inline(struct lwan_array *a, void lwan_array_sort(struct lwan_array *a, size_t element_size, int (*cmp)(const void *a, const void *b)); -struct lwan_array *coro_lwan_array_new(struct coro *coro, bool inline_first); +struct lwan_array *coro_lwan_array_new(struct coro *coro, + size_t struct_size, + bool inline_first); #define LWAN_ARRAY_FOREACH(array_, iter_) \ for (iter_ = (array_)->base.base; \ @@ -67,7 +69,8 @@ struct lwan_array *coro_lwan_array_new(struct coro *coro, bool inline_first); __attribute__((unused)) static inline struct array_type_ \ *coro_##array_type_##_new(struct coro *coro) \ { \ - return (struct array_type_ *)coro_lwan_array_new(coro, false); \ + return (struct array_type_ *)coro_lwan_array_new( \ + coro, sizeof(struct array_type_), false); \ } \ DEFINE_ARRAY_TYPE_FUNCS(array_type_, element_type_, NULL) @@ -85,7 +88,8 @@ struct lwan_array *coro_lwan_array_new(struct coro *coro, bool inline_first); __attribute__((unused)) static inline struct array_type_ \ *coro_##array_type_##_new(struct coro *coro) \ { \ - return (struct array_type_ *)coro_lwan_array_new(coro, true); \ + return (struct array_type_ *)coro_lwan_array_new( \ + coro, sizeof(struct array_type_), true); \ } \ DEFINE_ARRAY_TYPE_FUNCS(array_type_, element_type_, &array->storage) @@ -96,8 +100,8 @@ struct lwan_array *coro_lwan_array_new(struct coro *coro, bool inline_first); return (element_type_ *)array->base.base; \ } \ __attribute__((unused)) \ - __attribute__((nonnull(1))) static inline void array_type_##_init( \ - struct array_type_ *array) \ + __attribute__((nonnull(1))) static inline void array_type_##_init( \ + struct array_type_ *array) \ { \ array->base = (struct lwan_array){.base = NULL, .elements = 0}; \ } \ From 01e8e54e5fc4b8e69200e8496c3dfa64332b0bb6 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 1 Dec 2022 20:47:35 -0800 Subject: [PATCH 2053/2505] Fix double-free on OOM recovery in mod-rewrite setup --- src/lib/lwan-mod-rewrite.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/lwan-mod-rewrite.c b/src/lib/lwan-mod-rewrite.c index b3fb7d9fc..d9d304262 100644 --- a/src/lib/lwan-mod-rewrite.c +++ b/src/lib/lwan-mod-rewrite.c @@ -626,7 +626,6 @@ static void parse_condition_key_value(struct pattern *pattern, value = strdup(line->value); if (!value) { - free(key); config_error(config, "Could not copy value while parsing condition"); goto out; From d1884e0c53e806c1d7c64ff79f0e6b9e2188b7e9 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 12 Dec 2022 19:23:12 -0800 Subject: [PATCH 2054/2505] Free strbuf if initializing from file and it fails --- src/lib/lwan-strbuf.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/lwan-strbuf.c b/src/lib/lwan-strbuf.c index 0951e81d4..58133d0ba 100644 --- a/src/lib/lwan-strbuf.c +++ b/src/lib/lwan-strbuf.c @@ -387,6 +387,7 @@ bool lwan_strbuf_init_from_file(struct lwan_strbuf *s, const char *path) return true; error: + lwan_strbuf_free(s); close(fd); return false; } From 57df37fe9a9a2964aaeff5343f74be89d9206859 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 12 Dec 2022 19:23:43 -0800 Subject: [PATCH 2055/2505] Simplify strbuf initialization when reading from file --- src/lib/lwan-strbuf.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/lib/lwan-strbuf.c b/src/lib/lwan-strbuf.c index 58133d0ba..4be6f7146 100644 --- a/src/lib/lwan-strbuf.c +++ b/src/lib/lwan-strbuf.c @@ -362,10 +362,7 @@ bool lwan_strbuf_init_from_file(struct lwan_strbuf *s, const char *path) if (UNLIKELY(fstat(fd, &st) < 0)) goto error; - if (UNLIKELY(!lwan_strbuf_init(s))) - goto error; - - if (UNLIKELY(!grow_buffer_if_needed(s, (size_t)st.st_size))) + if (UNLIKELY(!lwan_strbuf_init_with_size(s, (size_t)st.st_size))) goto error; s->used = (size_t)st.st_size; From f71bb30cc0d0c48821d9e9e029cb12e7ee7747dd Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 12 Dec 2022 19:24:02 -0800 Subject: [PATCH 2056/2505] List of modules/handlers are now printed in --help --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 63654f6d1..642f67330 100644 --- a/README.md +++ b/README.md @@ -460,8 +460,8 @@ section can be present in the declaration of a module instance. Handlers do not take any configuration options, but may include the `authorization` section. -> :magic_wand: **Tip:** A list of built-in modules can be obtained by -> executing Lwan with the `-m` command-line argument. +> :magic_wand: **Tip:** Executing Lwan with the `--help` command-line +> argument will show a list of built-in modules and handlers. The following is some basic documentation for the modules shipped with Lwan. From 090c7cd67bcb193c52b96fb7b2fd628fc2c51abd Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 12 Dec 2022 19:24:15 -0800 Subject: [PATCH 2057/2505] Better explain how compressed files will be picked/cached --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 642f67330..3e0ea82a0 100644 --- a/README.md +++ b/README.md @@ -483,6 +483,21 @@ best to serve files in the fastest way possible according to some heuristics. | `read_ahead` | `int` | `131702` | Maximum amount of bytes to read ahead when caching open files. A value of `0` disables readahead. Readahead is performed by a low priority thread to not block the I/O threads while file extents are being read from the filesystem. | | `cache_for` | `time` | `5s` | Time to keep file metadata (size, compressed contents, open file descriptor, etc.) in cache | +> :bulb: **Note:** Files smaller than 16KiB will be compressed in RAM for +> the duration specified in the `cache_for` setting. Lwan will always try +> to compress with deflate, and will optionally compress with Brotli and +> ZSTD. If the compression isn't worth it (e.g. adding the +> `Content-Encoding` headers would make the resulting response larger than +> the uncompressed file contents), Lwan won't small send compressed files. +> For these files, Lwan tries sending the gzipped version if that's found +> in the filesystem and the client requested this encoding. +> +> For files larger than 16KiB, Lwan will not attempt to compress them. In +> future versions, it might do this and send responses using +> chunked-encoding while the file is being compressed (up to a certain +> limit, of course), but for now, only precompressed files (see +> `serve_precompressed_path` setting in the table above) are considered. + ##### Variables for `directory_list_template` | Variable | Type | Description | From d70029c95304333a0dee413d7b16200ee8f01a13 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 12 Dec 2022 19:24:40 -0800 Subject: [PATCH 2058/2505] Improve --help output --- src/bin/lwan/main.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/bin/lwan/main.c b/src/bin/lwan/main.c index 0e0b2a9b6..b7b202dc5 100644 --- a/src/bin/lwan/main.c +++ b/src/bin/lwan/main.c @@ -108,7 +108,11 @@ print_help(const char *argv0, const struct lwan_config *config) printf("Usage: %s [--root /path/to/root/dir] [--listen addr:port]\n", argv0); printf(" [--config] [--user username] [--chroot] [--modules|--handlers]\n"); printf("\n"); +#ifdef LWAN_HAVE_MBEDTLS + printf("Serve files through HTTP or HTTPS.\n\n"); +#else printf("Serve files through HTTP.\n\n"); +#endif printf("Options:\n"); printf(" -r, --root Path to serve files from (default: ./wwwroot).\n"); printf("\n"); @@ -131,8 +135,10 @@ print_help(const char *argv0, const struct lwan_config *config) printf(" -h, --help This.\n"); printf("\n"); printf("Examples:\n"); - printf(" Serve system-wide documentation:\n"); - printf(" %s -r /usr/share/doc\n", argv0); + if (!access("/usr/share/doc", R_OK)) { + printf(" Serve system-wide documentation:\n"); + printf(" %s -r /usr/share/doc\n", argv0); + } printf(" Serve on a different port:\n"); printf(" %s -l '*:1337'\n", argv0); printf(" Use %s from %s:\n", config_file, current_dir); @@ -150,6 +156,7 @@ print_help(const char *argv0, const struct lwan_config *config) print_handler_info(); printf("\n"); printf("Report bugs at .\n"); + printf("For security-related reports, mail them to .\n"); free(current_dir); } From b20faf94e0317c2ccb3d7dd5b8002c3e20000d09 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 13 Dec 2022 20:35:14 -0800 Subject: [PATCH 2059/2505] Tweak note in rewrite module doc --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3e0ea82a0..5326fc79f 100644 --- a/README.md +++ b/README.md @@ -719,8 +719,8 @@ pattern (%g+) { > :bulb: **Note:** In general, this is not necessary, as the file serving > module will do this automatically and pick the smallest file available for -> the requested encoding, cache it for a while, but this shows it's possible -> to have a similar feature by configuration alone. +> the requested encoding, but this shows it's possible to have a similar +> feature by configuration alone. #### Redirect From 075480c059ee4ea3c4a907cfd47b5c0a894f1991 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 13 Dec 2022 20:36:41 -0800 Subject: [PATCH 2060/2505] Include all extensions that correspond to a MIME type --- src/bin/tools/mimegen.c | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/bin/tools/mimegen.c b/src/bin/tools/mimegen.c index 69c2b18d8..764e76d4a 100644 --- a/src/bin/tools/mimegen.c +++ b/src/bin/tools/mimegen.c @@ -171,25 +171,25 @@ static char *compress_output(const struct output *output, size_t *outlen) return compressed; } -static bool is_builtin_mime_type(const char *mime) +static bool is_builtin_ext(const char *ext) { - /* These are the mime types supported by Lwan without having to perform - * a bsearch(). application/octet-stream is the fallback. */ - if (streq(mime, "application/octet-stream")) + /* STRING_SWITCH_L() is not used here to not bring in lwan.h */ + /* FIXME: maybe use an X-macro to keep in sync with lwan-tables.c? */ + if (strcaseequal_neutral(ext, "css")) return true; - if (streq(mime, "text/javascript")) + if (strcaseequal_neutral(ext, "gif")) return true; - if (streq(mime, "image/jpeg")) + if (strcaseequal_neutral(ext, "htm")) return true; - if (streq(mime, "image/gif")) + if (strcaseequal_neutral(ext, "html")) return true; - if (streq(mime, "image/png")) + if (strcaseequal_neutral(ext, "jpg")) return true; - if (streq(mime, "text/html")) + if (strcaseequal_neutral(ext, "js")) return true; - if (streq(mime, "text/css")) + if (strcaseequal_neutral(ext, "png")) return true; - if (streq(mime, "text/plain")) + if (strcaseequal_neutral(ext, "txt")) return true; return false; } @@ -239,7 +239,10 @@ int main(int argc, char *argv[]) continue; mime_type = start; - if (is_builtin_mime_type(mime_type)) + /* "application/octet-stream" is the fallback, so no need to store + * it in the table. It's just one line, though, so maybe not really + * necessary? */ + if (streq(mime_type, "application/octet-stream")) continue; while (*tab && *tab == '\t') /* Find first extension. */ @@ -254,11 +257,20 @@ int main(int argc, char *argv[]) end = strchr(ext, '\0'); /* If not found, find last extension. */ *end = '\0'; + /* Check if we have empty extensions. Shouldn't happen with the provided + * mime.types file, but check on debug builds if this ever happens. */ + assert(end != ext); + if (end - ext > 8) { /* Truncate extensions over 8 characters. See commit 2050759297. */ ext[8] = '\0'; } + /* Lwan has a fast-path for some common extensions, so don't bundle them + * in this table if not really needed. */ + if (is_builtin_ext(ext)) + continue; + k = strdup(ext); v = strdup(mime_type); From abfbcfaf51318ef4f9f0b81a3f8c98a78af36014 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 13 Dec 2022 20:37:25 -0800 Subject: [PATCH 2061/2505] Avoid reallocations while building mimegen uncompressed data --- src/bin/tools/mimegen.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/bin/tools/mimegen.c b/src/bin/tools/mimegen.c index 764e76d4a..f84c54ad3 100644 --- a/src/bin/tools/mimegen.c +++ b/src/bin/tools/mimegen.c @@ -196,9 +196,11 @@ static bool is_builtin_ext(const char *ext) int main(int argc, char *argv[]) { + /* 32k is sufficient for the provided mime.types, but we can reallocate + * if necessary. This is just to avoid unneccessary reallocs. */ + struct output output = { .capacity = 32768 }; FILE *fp; char buffer[256]; - struct output output = { .capacity = 1024 }; size_t compressed_size; char *compressed, *ext; struct hash *ext_mime; From 261e99753b609af32d6aa2f9a95e79b82121f5a4 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 13 Dec 2022 20:37:51 -0800 Subject: [PATCH 2062/2505] Recomment config_open_for_fuzzing() when not building for fuzzing --- src/lib/lwan-config.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index bbc08a6a4..35224ccbc 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -861,7 +861,7 @@ struct config *config_open(const char *path) return config ? config_init_data(config, data, len) : NULL; } -//#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) +#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) struct config *config_open_for_fuzzing(const uint8_t *data, size_t len) { struct config *config = malloc(sizeof(*config)); @@ -875,7 +875,7 @@ struct config *config_open_for_fuzzing(const uint8_t *data, size_t len) return NULL; } -//#endif +#endif void config_close(struct config *config) { From ec9a5bd42e77028a3ed245537fe45503eaedb14b Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 13 Dec 2022 20:38:22 -0800 Subject: [PATCH 2063/2505] Don't call has_content_length() twice when building responses --- src/lib/lwan-response.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index 35f067e50..dd7c7f04c 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -222,7 +222,7 @@ void lwan_default_response(struct lwan_request *request, APPEND_STRING_LEN((const_str_), sizeof(const_str_) - 1) static ALWAYS_INLINE __attribute__((const)) bool -has_content_length(enum lwan_request_flags v) +flags_has_content_length(enum lwan_request_flags v) { return !(v & (RESPONSE_NO_CONTENT_LENGTH | RESPONSE_STREAM | RESPONSE_CHUNKED_ENCODING)); @@ -335,7 +335,8 @@ size_t lwan_prepare_response_header_full( } } - if (LIKELY(has_content_length(request_flags))) { + const bool has_content_length = flags_has_content_length(request_flags); + if (LIKELY(has_content_length)) { APPEND_CONSTANT("\r\nContent-Length: "); APPEND_UINT(lwan_strbuf_get_length(request->response.buffer)); } @@ -348,8 +349,7 @@ size_t lwan_prepare_response_header_full( "\r\nAccess-Control-Allow-Headers: Origin, Accept, " "Content-Type"); } - if (request_flags & RESPONSE_CHUNKED_ENCODING && - !has_content_length(request_flags)) { + if (!has_content_length && (request_flags & RESPONSE_CHUNKED_ENCODING)) { APPEND_CONSTANT("\r\nTransfer-Encoding: chunked"); } if (request_flags & RESPONSE_INCLUDE_REQUEST_ID) { From a51b3c4ed1f3341c5efcebb67680bc034c9ba75b Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 16 Dec 2022 21:27:31 -0800 Subject: [PATCH 2064/2505] Fix crash on startup with the techempower sample cache_create() can't be called before lwan_init(), so do that first. --- src/samples/techempower/techempower.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index ffa388bec..b64c1a9b7 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -482,6 +482,11 @@ LWAN_HANDLER(quit_lwan) int main(void) { + struct lwan l; + + lwan_init(&l); + lwan_detect_url_map(&l); + if (getenv("USE_MYSQL")) { db_connection_params = (struct db_connection_params){ .type = DB_CONN_MYSQL, @@ -523,10 +528,11 @@ int main(void) if (!cached_queries_cache) lwan_status_critical("Could not create cached queries cache"); - lwan_main(); + lwan_main_loop(&l); cache_destroy(cached_queries_cache); lwan_tpl_free(fortune_tpl); + lwan_shutdown(&l); return 0; } From caf341d25b92abc2d4915b0bfc3186da487cbcdb Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 18 Dec 2022 18:30:27 -0800 Subject: [PATCH 2065/2505] Don't detect URL map in techempower sample Read everything from the configuration file, since there's a Lua script in there. Detecting the URL map will override the maps already found in the configuration file. --- src/samples/techempower/techempower.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index b64c1a9b7..d907525e3 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -485,7 +485,6 @@ int main(void) struct lwan l; lwan_init(&l); - lwan_detect_url_map(&l); if (getenv("USE_MYSQL")) { db_connection_params = (struct db_connection_params){ From a1480d836a3ce76a5f19c5106f64ca573d8b0f1b Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 19 Dec 2022 00:39:04 -0800 Subject: [PATCH 2066/2505] Add mention in Linux-Magazin --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 5326fc79f..1a1f3188e 100644 --- a/README.md +++ b/README.md @@ -997,6 +997,10 @@ Mentions in academic journals: * [A dynamic predictive race detector for C/C++ programs (in English, published 2017)](https://link.springer.com/article/10.1007/s11227-017-1996-8) uses Lwan as a "real world example". * [High-precision Data Race Detection Method for Large Scale Programs (in Chinese, published 2021)](http://www.jos.org.cn/jos/article/abstract/6260) also uses Lwan as one of the case studies. +Mentions in magazines: + +* [Linux-Magazin (Germany) mentions Lwan in their December/2021 issue](https://www.linux-magazin.de/ausgaben/2021/12/tooltipps/) + Some talks mentioning Lwan: * [Talk about Lwan](https://www.youtube.com/watch?v=cttY9FdCzUE) at Polyconf16, given by [@lpereira](https://github.com/lpereira). From a4fd3a6aaa1a733b28013ef9fcc7e320206632b9 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 26 Dec 2022 13:11:31 -0800 Subject: [PATCH 2067/2505] Improve performance of url_decode() This function was previously taking ~9.7% of instructions when handling a request. With this change, it now takes ~1.27%, making it over 7x more efficient in the normal case where the request URL doesn't need to be decoded. The previous version wasn't that efficient because it worked roughly like a branchy bytewise memcpy(), whereas the new version avoids copies unless necessary. Strings are still decoded in-place, but I imagine there's still some opportunities for optimization, especially if vector instructions are used directly rather than using string-handling functions from the C library. I haven't fuzzed this but since this is part of what's fuzzed by our fuzzing infrastructure, I'm not really worried. --- src/lib/lwan-request.c | 55 +++++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 17 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 71cfefcfa..8a3d76893 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -278,31 +278,52 @@ static ALWAYS_INLINE char decode_hex_digit(char ch) return hex_digit_tbl[(unsigned char)ch]; } -static ssize_t url_decode(char *str) +__attribute__((nonnull(1))) static ssize_t url_decode(char *str) { - if (UNLIKELY(!str)) - return -EINVAL; + char *inptr = str; + char *outptr = NULL; - char *ch, *decoded; - for (decoded = ch = str; *ch; ch++) { - if (*ch == '%') { - char tmp = - (char)(decode_hex_digit(ch[1]) << 4 | decode_hex_digit(ch[2])); + for (char *ch = strchr(str, '+'); ch; ch = strchr(ch + 1, '+')) + *ch = ' '; - if (UNLIKELY(!tmp)) - return -EINVAL; + while (true) { + char *pct = strchr(inptr, '%'); + if (!pct) { + if (outptr && inptr > outptr) { + size_t len = strlen(inptr); + memmove(outptr, inptr, len); + outptr += len; + *outptr = '\0'; + } + break; + } - *decoded++ = tmp; - ch += 2; - } else if (*ch == '+') { - *decoded++ = ' '; + if (outptr) { + ptrdiff_t diff = pct - inptr; + if (diff) { + memmove(outptr, inptr, (size_t)diff); + outptr += diff; + } } else { - *decoded++ = *ch; + outptr = pct; } + + char decoded = (char)(decode_hex_digit(pct[1]) << 4); + decoded |= (char)decode_hex_digit(pct[2]); + if (UNLIKELY(!decoded)) { + return -1; + } + + *outptr = decoded; + outptr++; + + inptr = pct + 3; } - *decoded = '\0'; - return (ssize_t)(decoded - str); + if (LIKELY(!outptr)) + return (ssize_t)strlen(str); + + return (ssize_t)(outptr - str); } static int key_value_compare(const void *a, const void *b) From 289aecd961ee65fa9f35c53b0916ae21ded44ba5 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 2 Jan 2023 17:33:57 -0800 Subject: [PATCH 2068/2505] Remove more branches from url_decode() --- CMakeLists.txt | 1 + src/cmake/lwan-build-config.h.cmake | 1 + src/lib/lwan-request.c | 25 +++++++------------------ src/lib/missing.c | 10 ++++++++++ src/lib/missing/string.h | 4 ++++ 5 files changed, 23 insertions(+), 18 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a1ea9cc8d..bf8d91e5a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -195,6 +195,7 @@ check_function_exists(gettid LWAN_HAVE_GETTID) check_function_exists(secure_getenv LWAN_HAVE_SECURE_GETENV) check_function_exists(statfs LWAN_HAVE_STATFS) check_function_exists(syslog LWAN_HAVE_SYSLOG_FUNC) +check_function_exists(stpcpy LWAN_HAVE_STPCPY) # This is available on -ldl in glibc, but some systems (such as OpenBSD) # will bundle these in the C library. This isn't required for glibc anyway, diff --git a/src/cmake/lwan-build-config.h.cmake b/src/cmake/lwan-build-config.h.cmake index efcc919c3..eec754eec 100644 --- a/src/cmake/lwan-build-config.h.cmake +++ b/src/cmake/lwan-build-config.h.cmake @@ -46,6 +46,7 @@ #cmakedefine LWAN_HAVE_SO_ATTACH_REUSEPORT_CBPF #cmakedefine LWAN_HAVE_SO_INCOMING_CPU #cmakedefine LWAN_HAVE_SYSLOG +#cmakedefine LWAN_HAVE_STPCPY /* Compiler builtins for specific CPU instruction support */ #cmakedefine LWAN_HAVE_BUILTIN_CLZLL diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 8a3d76893..11142e023 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -281,7 +281,7 @@ static ALWAYS_INLINE char decode_hex_digit(char ch) __attribute__((nonnull(1))) static ssize_t url_decode(char *str) { char *inptr = str; - char *outptr = NULL; + char *outptr = str; for (char *ch = strchr(str, '+'); ch; ch = strchr(ch + 1, '+')) *ch = ' '; @@ -289,23 +289,15 @@ __attribute__((nonnull(1))) static ssize_t url_decode(char *str) while (true) { char *pct = strchr(inptr, '%'); if (!pct) { - if (outptr && inptr > outptr) { - size_t len = strlen(inptr); - memmove(outptr, inptr, len); - outptr += len; - *outptr = '\0'; - } + if (inptr > outptr) + outptr = stpcpy(outptr, inptr); break; } - if (outptr) { - ptrdiff_t diff = pct - inptr; - if (diff) { - memmove(outptr, inptr, (size_t)diff); - outptr += diff; - } - } else { - outptr = pct; + ptrdiff_t diff = pct - inptr; + if (diff) { + memmove(outptr, inptr, (size_t)diff); + outptr += diff; } char decoded = (char)(decode_hex_digit(pct[1]) << 4); @@ -320,9 +312,6 @@ __attribute__((nonnull(1))) static ssize_t url_decode(char *str) inptr = pct + 3; } - if (LIKELY(!outptr)) - return (ssize_t)strlen(str); - return (ssize_t)(outptr - str); } diff --git a/src/lib/missing.c b/src/lib/missing.c index c901fbee6..6293f60c1 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -714,3 +714,13 @@ __attribute__((constructor)) static void test_strcaseequal_neutral(void) static_assert('a' == 97, "ASCII character set"); } #endif + +#ifndef LWAN_HAVE_STPCPY +char *stpcpy(char *restrict dst, const char *restrict src) +{ + /* Implementation from the Linux stpcpy(3) man page. */ + char *p = mempcpy(dst, src, strlen(src)); + *p = 0; + return p; +} +#endif diff --git a/src/lib/missing/string.h b/src/lib/missing/string.h index 01ae01554..70986537f 100644 --- a/src/lib/missing/string.h +++ b/src/lib/missing/string.h @@ -61,6 +61,10 @@ void *mempcpy(void *dest, const void *src, size_t len); void *memrchr(const void *s, int c, size_t n); #endif +#ifndef LWAN_HAVE_STPCPY +char *stpcpy(char *restrict dst, const char *restrict src); +#endif + static inline int streq(const char *a, const char *b) { return strcmp(a, b) == 0; From fe030373cac05ca01f91fb9945d57f1ecf0d0057 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 2 Jan 2023 20:34:02 -0800 Subject: [PATCH 2069/2505] Simplify url_decode() even further Use stpncpy() to handle the segments between %. Provide implementation in missing.c for platforms where this isn't available. Make other minor tweaks, including using a for-loop instead of a while-loop, and marking inptr and pct as const for legibility. --- src/lib/lwan-request.c | 23 ++++++++--------------- src/lib/missing.c | 9 +++++++-- src/lib/missing/string.h | 1 + 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 11142e023..dac8e713a 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -280,31 +280,21 @@ static ALWAYS_INLINE char decode_hex_digit(char ch) __attribute__((nonnull(1))) static ssize_t url_decode(char *str) { - char *inptr = str; + const char *inptr = str; char *outptr = str; for (char *ch = strchr(str, '+'); ch; ch = strchr(ch + 1, '+')) *ch = ' '; - while (true) { - char *pct = strchr(inptr, '%'); - if (!pct) { - if (inptr > outptr) - outptr = stpcpy(outptr, inptr); - break; - } - + for (const char *pct = strchr(inptr, '%'); pct; pct = strchr(inptr, '%')) { ptrdiff_t diff = pct - inptr; - if (diff) { - memmove(outptr, inptr, (size_t)diff); - outptr += diff; - } + if (diff) + outptr = stpncpy(outptr, inptr, (size_t)diff); char decoded = (char)(decode_hex_digit(pct[1]) << 4); decoded |= (char)decode_hex_digit(pct[2]); - if (UNLIKELY(!decoded)) { + if (UNLIKELY(!decoded)) return -1; - } *outptr = decoded; outptr++; @@ -312,6 +302,9 @@ __attribute__((nonnull(1))) static ssize_t url_decode(char *str) inptr = pct + 3; } + if (inptr > outptr) + outptr = stpcpy(outptr, inptr); + return (ssize_t)(outptr - str); } diff --git a/src/lib/missing.c b/src/lib/missing.c index 6293f60c1..eb1b97397 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -716,11 +716,16 @@ __attribute__((constructor)) static void test_strcaseequal_neutral(void) #endif #ifndef LWAN_HAVE_STPCPY -char *stpcpy(char *restrict dst, const char *restrict src) +char *stpncpy(char *restrict dst, const char *restrict src, size sz) { /* Implementation from the Linux stpcpy(3) man page. */ - char *p = mempcpy(dst, src, strlen(src)); + char *p = mempcpy(dst, src, sz); *p = 0; return p; } + +char *stpcpy(char *restrict dst, const char *restrict src) +{ + return stpncpy(dst, src, strlen(src)); +} #endif diff --git a/src/lib/missing/string.h b/src/lib/missing/string.h index 70986537f..b7303d6a9 100644 --- a/src/lib/missing/string.h +++ b/src/lib/missing/string.h @@ -63,6 +63,7 @@ void *memrchr(const void *s, int c, size_t n); #ifndef LWAN_HAVE_STPCPY char *stpcpy(char *restrict dst, const char *restrict src); +char *stpncpy(char *restrict dst, const char *restrict src, size sz); #endif static inline int streq(const char *a, const char *b) From 0a8eb224fe92689aba70cf07f9c8e34c4a7a0636 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 4 Jan 2023 18:41:42 -0800 Subject: [PATCH 2070/2505] Better validate URL-encoded values Check both if %00 is found in the encoded URL, and if any invalid combination (e.g. %__) is found. --- CMakeLists.txt | 1 + src/lib/lwan-request.c | 63 ++++++++++++++++++++++++++++-------------- 2 files changed, 44 insertions(+), 20 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bf8d91e5a..60769ee40 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -322,6 +322,7 @@ enable_warning_if_supported(-Wstringop-overread) enable_warning_if_supported(-Wstringop-truncation) enable_warning_if_supported(-Wunsequenced) enable_warning_if_supported(-Wvla) +enable_warning_if_supported(-Wno-override-init) # While a useful warning, this is giving false positives. enable_warning_if_supported(-Wno-free-nonheap-object) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index dac8e713a..f9fc93e33 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -77,7 +77,6 @@ struct proxy_header_v2 { } addr; }; -static char decode_hex_digit(char ch) __attribute__((pure)); static char *ignore_leading_whitespace(char *buffer) __attribute__((pure)); @@ -267,19 +266,24 @@ static ALWAYS_INLINE char *identify_http_method(struct lwan_request *request, return NULL; } -static ALWAYS_INLINE char decode_hex_digit(char ch) +__attribute__((nonnull(1))) static ssize_t url_decode(char *str) { - static const char hex_digit_tbl[256] = { - ['0'] = 0, ['1'] = 1, ['2'] = 2, ['3'] = 3, ['4'] = 4, ['5'] = 5, - ['6'] = 6, ['7'] = 7, ['8'] = 8, ['9'] = 9, ['a'] = 10, ['b'] = 11, - ['c'] = 12, ['d'] = 13, ['e'] = 14, ['f'] = 15, ['A'] = 10, ['B'] = 11, - ['C'] = 12, ['D'] = 13, ['E'] = 14, ['F'] = 15, + static const unsigned char tbl1[256] = { + [0 ... 255] = 255, ['0'] = 0 << 4, ['1'] = 1 << 4, ['2'] = 2 << 4, + ['3'] = 3 << 4, ['4'] = 4 << 4, ['5'] = 5 << 4, ['6'] = 6 << 4, + ['7'] = 7 << 4, ['8'] = 8 << 4, ['9'] = 9 << 4, ['a'] = 10 << 4, + ['b'] = 11 << 4, ['c'] = 12 << 4, ['d'] = 13 << 4, ['e'] = 14 << 4, + ['f'] = 15 << 4, ['A'] = 10 << 4, ['B'] = 11 << 4, ['C'] = 12 << 4, + ['D'] = 13 << 4, ['E'] = 14 << 4, ['F'] = 15 << 4, + }; + static const char tbl2[256] = { + [0 ... 255] = -1, ['0'] = 0, ['1'] = 1, ['2'] = 2, ['3'] = 3, + ['4'] = 4, ['5'] = 5, ['6'] = 6, ['7'] = 7, ['8'] = 8, + ['9'] = 9, ['a'] = 10, ['b'] = 11, ['c'] = 12, ['d'] = 13, + ['e'] = 14, ['f'] = 15, ['A'] = 10, ['B'] = 11, ['C'] = 12, + ['D'] = 13, ['E'] = 14, ['F'] = 15, }; - return hex_digit_tbl[(unsigned char)ch]; -} -__attribute__((nonnull(1))) static ssize_t url_decode(char *str) -{ const char *inptr = str; char *outptr = str; @@ -287,25 +291,44 @@ __attribute__((nonnull(1))) static ssize_t url_decode(char *str) *ch = ' '; for (const char *pct = strchr(inptr, '%'); pct; pct = strchr(inptr, '%')) { - ptrdiff_t diff = pct - inptr; + const ptrdiff_t diff = pct - inptr; if (diff) outptr = stpncpy(outptr, inptr, (size_t)diff); - char decoded = (char)(decode_hex_digit(pct[1]) << 4); - decoded |= (char)decode_hex_digit(pct[2]); - if (UNLIKELY(!decoded)) - return -1; + const char first = (char)tbl1[(unsigned char)pct[1]]; + const char second = tbl2[(unsigned char)pct[2]]; + const char decoded = first | second; + if (UNLIKELY(decoded <= 0)) { + /* This shouldn't happen in normal circumstances, but if %00 is + * found in the encoded string, bail here. */ + if (decoded == '\0') + return -1; - *outptr = decoded; - outptr++; + /* OR-ing both lookups will yield a negative number if either + * encoded character is not a valid hex digit; check it here so + * that other valid-but-negative bytes (e.g. 0xff) are still + * written to outptr. */ + if (first == -1) { + /* tbl1 is shifted so a valid nibble might be negative; + * check for all the bits here instead. */ + return -1; + } + if (second < 0) { + /* tbl2 isn't shifted so we can check for the sign bit only. */ + return -1; + } + } + *outptr++ = decoded; inptr = pct + 3; } - if (inptr > outptr) + if (inptr > outptr) { outptr = stpcpy(outptr, inptr); + return (ssize_t)(outptr - str); + } - return (ssize_t)(outptr - str); + return (ssize_t)strlen(str); } static int key_value_compare(const void *a, const void *b) From 689ffee6616aa1ff8cf9fddb5d21f3366c282ac8 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 10 Jan 2023 22:33:49 -0800 Subject: [PATCH 2071/2505] Add more assertions when spawning coros --- src/lib/lwan-thread.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 4bd3bcb11..154e7b173 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -688,6 +688,8 @@ static ALWAYS_INLINE bool spawn_coro(struct lwan_connection *conn, assert(!conn->coro); assert(!(conn->flags & CONN_ASYNC_AWAIT)); + assert(!(conn->flags & CONN_AWAITED_FD)); + assert(!(conn->flags & (CONN_LISTENER_HTTP | CONN_LISTENER_HTTPS))); assert(t); assert((uintptr_t)t >= (uintptr_t)tq->lwan->thread.threads); assert((uintptr_t)t < From dadeef968f2a5570bc550ac110e57c6caf912c7b Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 10 Jan 2023 22:34:13 -0800 Subject: [PATCH 2072/2505] Simplify computation of scheduling tables on x86_64 Linux --- src/lib/lwan-thread.c | 42 +++++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 154e7b173..e6d2352b0 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -1072,15 +1072,18 @@ static bool read_cpu_topology(struct lwan *l, uint32_t siblings[]) static void siblings_to_schedtbl(struct lwan *l, uint32_t siblings[], uint32_t schedtbl[]) { - int *seen = alloca(l->available_cpus * sizeof(int)); + int32_t *seen = calloc(l->available_cpus, sizeof(int32_t)); unsigned int n_schedtbl = 0; + if (!seen) + lwan_status_critical("Could not allocate the seen array"); + for (uint32_t i = 0; i < l->available_cpus; i++) seen[i] = -1; for (uint32_t i = 0; i < l->available_cpus; i++) { if (seen[siblings[i]] < 0) { - seen[siblings[i]] = (int)i; + seen[siblings[i]] = (int32_t)i; } else { schedtbl[n_schedtbl++] = (uint32_t)seen[siblings[i]]; schedtbl[n_schedtbl++] = i; @@ -1089,25 +1092,38 @@ siblings_to_schedtbl(struct lwan *l, uint32_t siblings[], uint32_t schedtbl[]) if (n_schedtbl != l->available_cpus) memcpy(schedtbl, seen, l->available_cpus * sizeof(int)); + + free(seen); } static bool topology_to_schedtbl(struct lwan *l, uint32_t schedtbl[], uint32_t n_threads) { - uint32_t *siblings = alloca(l->available_cpus * sizeof(uint32_t)); + uint32_t *siblings = calloc(l->available_cpus, sizeof(uint32_t)); + + if (!siblings) + lwan_status_critical("Could not allocate siblings array"); if (read_cpu_topology(l, siblings)) { - uint32_t *affinity = alloca(l->available_cpus * sizeof(uint32_t)); + uint32_t *affinity = calloc(l->available_cpus, sizeof(uint32_t)); + + if (!affinity) + lwan_status_critical("Could not allocate affinity array"); siblings_to_schedtbl(l, siblings, affinity); for (uint32_t i = 0; i < n_threads; i++) schedtbl[i] = affinity[i % l->available_cpus]; + + free(affinity); + free(siblings); return true; } for (uint32_t i = 0; i < n_threads; i++) schedtbl[i] = (i / 2) % l->thread.count; + + free(siblings); return false; } @@ -1263,7 +1279,6 @@ void lwan_thread_init(struct lwan *l) lwan_status_critical("Could not allocate memory for threads"); uint32_t *schedtbl; - uint32_t n_threads; bool adj_affinity; #if defined(__x86_64__) && defined(__linux__) @@ -1285,15 +1300,11 @@ void lwan_thread_init(struct lwan *l) * use the CPU topology to group two connections per cache line in such * a way that false sharing is avoided. */ - n_threads = (uint32_t)lwan_nextpow2((size_t)((l->thread.count - 1) * 2)); - schedtbl = alloca(n_threads * sizeof(uint32_t)); - - adj_affinity = topology_to_schedtbl(l, schedtbl, n_threads); - - n_threads--; /* Transform count into mask for AND below */ + schedtbl = calloc(l->thread.count, sizeof(uint32_t)); + adj_affinity = topology_to_schedtbl(l, schedtbl, l->thread.count); for (unsigned int i = 0; i < total_conns; i++) - l->conns[i].thread = &l->thread.threads[schedtbl[i & n_threads]]; + l->conns[i].thread = &l->thread.threads[schedtbl[i % l->thread.count]]; } else #endif /* __x86_64__ && __linux__ */ { @@ -1306,7 +1317,6 @@ void lwan_thread_init(struct lwan *l) schedtbl = NULL; adj_affinity = false; - n_threads = l->thread.count; } for (unsigned int i = 0; i < l->thread.count; i++) { @@ -1319,7 +1329,7 @@ void lwan_thread_init(struct lwan *l) * the incoming connection to the right CPU will use. */ for (uint32_t thread_id = 0; thread_id < l->thread.count; thread_id++) { - if (schedtbl[thread_id & n_threads] == i) { + if (schedtbl[thread_id % l->thread.count] == i) { thread = &l->thread.threads[thread_id]; break; } @@ -1351,7 +1361,7 @@ void lwan_thread_init(struct lwan *l) } if (adj_affinity) { - l->thread.threads[i].cpu = schedtbl[i & n_threads]; + l->thread.threads[i].cpu = schedtbl[i % l->thread.count]; adjust_thread_affinity(thread); } @@ -1359,6 +1369,8 @@ void lwan_thread_init(struct lwan *l) } lwan_status_debug("Worker threads created and ready to serve"); + + free(schedtbl); } void lwan_thread_shutdown(struct lwan *l) From bc3d13bc4c2215920b9a82cba6a2060cf27931b7 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 10 Jan 2023 22:34:50 -0800 Subject: [PATCH 2073/2505] s/size/size_t/ for missing strpcpy() prototypes --- src/lib/missing.c | 2 +- src/lib/missing/string.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/missing.c b/src/lib/missing.c index eb1b97397..ee649ac12 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -716,7 +716,7 @@ __attribute__((constructor)) static void test_strcaseequal_neutral(void) #endif #ifndef LWAN_HAVE_STPCPY -char *stpncpy(char *restrict dst, const char *restrict src, size sz) +char *stpncpy(char *restrict dst, const char *restrict src, size_t sz) { /* Implementation from the Linux stpcpy(3) man page. */ char *p = mempcpy(dst, src, sz); diff --git a/src/lib/missing/string.h b/src/lib/missing/string.h index b7303d6a9..f566fe03b 100644 --- a/src/lib/missing/string.h +++ b/src/lib/missing/string.h @@ -63,7 +63,7 @@ void *memrchr(const void *s, int c, size_t n); #ifndef LWAN_HAVE_STPCPY char *stpcpy(char *restrict dst, const char *restrict src); -char *stpncpy(char *restrict dst, const char *restrict src, size sz); +char *stpncpy(char *restrict dst, const char *restrict src, size_t sz); #endif static inline int streq(const char *a, const char *b) From f3a4785d5a6467ea20b404c86e2d0da30e93f8f8 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 10 Jan 2023 22:35:11 -0800 Subject: [PATCH 2074/2505] Loop through the string only once when URL-decoding Use strpbrk() to loop through the string, looking for both + and % characters. Haven't measured the difference to the previous function, though. --- src/lib/lwan-request.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index f9fc93e33..b66af96f5 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -283,20 +283,22 @@ __attribute__((nonnull(1))) static ssize_t url_decode(char *str) ['e'] = 14, ['f'] = 15, ['A'] = 10, ['B'] = 11, ['C'] = 12, ['D'] = 13, ['E'] = 14, ['F'] = 15, }; - const char *inptr = str; char *outptr = str; - for (char *ch = strchr(str, '+'); ch; ch = strchr(ch + 1, '+')) - *ch = ' '; - - for (const char *pct = strchr(inptr, '%'); pct; pct = strchr(inptr, '%')) { - const ptrdiff_t diff = pct - inptr; + for (char *p = strpbrk(inptr, "+%"); p; p = strpbrk(inptr, "+%")) { + const ptrdiff_t diff = p - inptr; if (diff) outptr = stpncpy(outptr, inptr, (size_t)diff); - const char first = (char)tbl1[(unsigned char)pct[1]]; - const char second = tbl2[(unsigned char)pct[2]]; + if (*p == '+') { + *outptr++ = ' '; + inptr = p + 1; + continue; + } + + const char first = (char)tbl1[(unsigned char)p[1]]; + const char second = tbl2[(unsigned char)p[2]]; const char decoded = first | second; if (UNLIKELY(decoded <= 0)) { /* This shouldn't happen in normal circumstances, but if %00 is @@ -320,7 +322,7 @@ __attribute__((nonnull(1))) static ssize_t url_decode(char *str) } *outptr++ = decoded; - inptr = pct + 3; + inptr = p + 3; } if (inptr > outptr) { From 01bb09b3a25ac36a39dc606deabfd1d6b5c5b570 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 26 Jan 2023 20:26:49 -0800 Subject: [PATCH 2075/2505] Use mempmove() rather than stpncpy() to copy substring --- src/lib/lwan-request.c | 2 +- src/lib/missing/string.h | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index b66af96f5..cf3d6e8e3 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -289,7 +289,7 @@ __attribute__((nonnull(1))) static ssize_t url_decode(char *str) for (char *p = strpbrk(inptr, "+%"); p; p = strpbrk(inptr, "+%")) { const ptrdiff_t diff = p - inptr; if (diff) - outptr = stpncpy(outptr, inptr, (size_t)diff); + outptr = mempmove(outptr, inptr, (size_t)diff); if (*p == '+') { *outptr++ = ' '; diff --git a/src/lib/missing/string.h b/src/lib/missing/string.h index f566fe03b..b33ed6d88 100644 --- a/src/lib/missing/string.h +++ b/src/lib/missing/string.h @@ -73,8 +73,7 @@ static inline int streq(const char *a, const char *b) static inline void *mempmove(void *dest, const void *src, size_t len) { - char *d = (char *)memmove(dest, src, len); - return d + len; + return (char *)memmove(dest, src, len) + len; } bool strcaseequal_neutral(const char *a, const char *b); From 1d9bf718a92467bce7e6f7af844183b6fdc9aaf2 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 26 Jan 2023 20:32:53 -0800 Subject: [PATCH 2076/2505] Fix build on non x86-64 Linux Closes #354. --- src/lib/lwan-thread.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index e6d2352b0..e95613ea2 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -1138,6 +1138,8 @@ adjust_thread_affinity(const struct lwan_thread *thread) if (pthread_setaffinity_np(thread->self, sizeof(set), &set)) lwan_status_warning("Could not set thread affinity"); } +#else +#define adjust_thread_affinity(...) #endif #if defined(LWAN_HAVE_MBEDTLS) From 59ac0d0b71b3a8b876cb243af5df55f60af814be Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 5 Apr 2023 12:32:46 -0700 Subject: [PATCH 2077/2505] Place "cmake_minimum_required()" call before "project()" call Newer CMake is making some noise about this; make it happy. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 60769ee40..f79e5aab5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ -project(lwan C) cmake_minimum_required(VERSION 3.0) +project(lwan C) set(PROJECT_DESCRIPTION "Scalable, high performance, experimental web server") message(STATUS "Running CMake for ${PROJECT_NAME} (${PROJECT_DESCRIPTION})") From 1db7b0721697453832b4dad108593b7a29108eb8 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 17 Apr 2023 22:45:52 -0700 Subject: [PATCH 2078/2505] Add comment about ESCA in README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 1a1f3188e..32cf30d41 100644 --- a/README.md +++ b/README.md @@ -991,6 +991,7 @@ Lwan has been also used as a benchmark: * [Kong](https://getkong.org/about/benchmark/) uses Lwan as the [backend API](https://gist.github.com/montanaflynn/01376991f0a3ad07059c) in its benchmark. * [TechEmpower Framework benchmarks](https://www.techempower.com/benchmarks/#section=data-r10&hw=peak&test=json) feature Lwan since round 10. * [KrakenD](http://www.krakend.io) used Lwan for the REST API in all official [benchmarks](http://www.krakend.io/docs/benchmarks/aws/) +* [Effective System Call Aggregation (ESCA)](https://github.com/eecheng87/ESCA) project uses Lwan as one of the benchmarks; they claim that Lwan throughput improved by about 30% with their system call batching approach. Mentions in academic journals: From b1119340cf43c816e8f9518758a4a9b4515860a8 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 18 Apr 2023 09:14:22 -0700 Subject: [PATCH 2079/2505] Fix link to GHCR --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 32cf30d41..e2560554f 100644 --- a/README.md +++ b/README.md @@ -973,7 +973,7 @@ been seen in the wild. *Help build this list!* Some other distribution channels were made available as well: -* Container images are available from the [ghcr.io/lpereira/lwan](GitHub Container Registry). [More information below](#container-images). +* Container images are available from the [GitHub Container Registry](https://ghcr.io/lpereira/lwan). [More information below](#container-images). * A `Dockerfile` is maintained by [@jaxgeller](https://github.com/jaxgeller), and is [available from the Docker registry](https://hub.docker.com/r/jaxgeller/lwan/). * A buildpack for Heroku is maintained by [@bherrera](https://github.com/bherrera), and is [available from its repo](https://github.com/bherrera/heroku-buildpack-lwan). * Lwan is also available as a package in [Biicode](http://docs.biicode.com/c++/examples/lwan.html). From 9b94ff5eecec1e925103b25a43dacc226a634878 Mon Sep 17 00:00:00 2001 From: SPFishcool Date: Mon, 19 Jun 2023 13:33:39 +0800 Subject: [PATCH 2080/2505] implement native ARM64 assambly coroutine I implemented a native coro for ARM64 architecture using assembly language. --- CMakeLists.txt | 2 +- src/lib/lwan-coro.c | 50 +++++++++++++++++++++++++++++++++++++++++++++ src/lib/lwan-coro.h | 2 ++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f79e5aab5..76fd06295 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -340,7 +340,7 @@ else () set(LWAN_COMMON_LIBS -Wl,-whole-archive lwan-static -Wl,-no-whole-archive) endif () -if (NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^x86_64|amd64") +if (NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^x86_64|amd64|aarch64") set(LWAN_HAVE_LIBUCONTEXT 1) endif () diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index db51e1768..a9b685989 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -181,6 +181,38 @@ asm(".text\n\t" "movq 64(%rsi),%rcx\n\t" "movq 56(%rsi),%rsi\n\t" "jmpq *%rcx\n\t"); +#elif defined(__aarch64__) +void __attribute__((noinline, visibility("internal"))) +coro_swapcontext(coro_context *current, coro_context *other); +asm(".text\n\t" + ".p2align 5\n\t" + ASM_ROUTINE(coro_swapcontext) + "mov x10, sp\n\t" + "mov x11, x30\n\t" + "stp x8, x9, [x0, #(1*16)]\n\t" + "stp x10, x11, [x0, #(2*16)]\n\t" + "stp x12, x13, [x0, #(3*16)]\n\t" + "stp x14, x15, [x0, #(4*16)]\n\t" + "stp x19, x20, [x0, #(5*16)]\n\t" + "stp x21, x22, [x0, #(6*16)]\n\t" + "stp x23, x24, [x0, #(7*16)]\n\t" + "stp x25, x26, [x0, #(8*16)]\n\t" + "stp x27, x28, [x0, #(9*16)]\n\t" + "stp x29, x30, [x0, #(10*16)]\n\t" + "stp x0, x1, [x0, #(0*16)]\n\t" + "ldp x8, x9, [x1, #(1*16)]\n\t" + "ldp x10, x11, [x1, #(2*16)]\n\t" + "ldp x12, x13, [x1, #(3*16)]\n\t" + "ldp x14, x15, [x1, #(4*16)]\n\t" + "ldp x19, x20, [x1, #(5*16)]\n\t" + "ldp x21, x22, [x1, #(6*16)]\n\t" + "ldp x23, x24, [x1, #(7*16)]\n\t" + "ldp x25, x26, [x1, #(8*16)]\n\t" + "ldp x27, x28, [x1, #(9*16)]\n\t" + "ldp x29, x30, [x1, #(10*16)]\n\t" + "ldp x0, x1, [x1, #(0*16)]\n\t" + "mov sp, x10\n\t" + "br x11\n\t"); #elif defined(LWAN_HAVE_LIBUCONTEXT) #define coro_swapcontext(cur, oth) libucontext_swapcontext(cur, oth) #else @@ -205,6 +237,15 @@ asm(".text\n\t" "mov %r15, %rdx\n\t" "jmp " ASM_SYMBOL(coro_entry_point) "\n\t" ); +#elif defined(__aarch64__) +void __attribute__((visibility("internal"))) coro_entry_point_arm64(); + +asm(".text\n\t" + ".p2align 5\n\t" + ASM_ROUTINE(coro_entry_point_arm64) + "mov x2, x28\n\t" + "bl " ASM_SYMBOL(coro_entry_point) "\n\t" +); #endif void coro_deferred_run(struct coro *coro, size_t generation) @@ -256,6 +297,15 @@ void coro_reset(struct coro *coro, coro_function_t func, void *data) #define STACK_PTR 9 coro->context[STACK_PTR] = (rsp & ~0xful) - 0x8ul; +#elif defined(__aarch64__) + coro->context[19/* x28 */] = (uintptr_t)data; + coro->context[0 /* x0 */] = (uintptr_t)coro; + coro->context[1 /* x1 */] = (uintptr_t)func; + coro->context[5 /* lr */] = (uintptr_t)coro_entry_point_arm64; + + uintptr_t rsp = (uintptr_t)stack + CORO_STACK_SIZE; +#define STACK_PTR 4 + coro->context[STACK_PTR] = rsp & ~0xful; #elif defined(LWAN_HAVE_LIBUCONTEXT) libucontext_getcontext(&coro->context); diff --git a/src/lib/lwan-coro.h b/src/lib/lwan-coro.h index 2d762c033..c4b0faa08 100644 --- a/src/lib/lwan-coro.h +++ b/src/lib/lwan-coro.h @@ -25,6 +25,8 @@ #if defined(__x86_64__) typedef uintptr_t coro_context[10]; +#elif defined(__aarch64__) +typedef uintptr_t coro_context[22]; #elif defined(LWAN_HAVE_LIBUCONTEXT) #include typedef libucontext_ucontext_t coro_context; From a81edb60bde03f2e235ea01ca9961095238db672 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 3 Jul 2023 15:35:56 -0700 Subject: [PATCH 2081/2505] Document that aarch64 may not require libucontext anymore --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e2560554f..e1e4a8b84 100644 --- a/README.md +++ b/README.md @@ -63,10 +63,11 @@ The build system will look for these libraries and enable/link if available. - Client libraries for either [MySQL](https://dev.mysql.com) or [MariaDB](https://mariadb.org) - [SQLite 3](http://sqlite.org) -> :bulb: **Note:** On non-x86_64 systems, +> :bulb: **Note:** On some systems, > [libucontext](https://github.com/kaniini/libucontext) will be downloaded > and built alongside Lwan. This will require a network connection, so keep -> this in mind when packaging Lwan for non-x86_64 architectures. +> this in mind when packaging Lwan for non-x86_64 or non-aarch64 +> architectures. ### Common operating system package names From dd6c01ba271f8133a10ca15c4e075e6061556f49 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 3 Jul 2023 15:36:11 -0700 Subject: [PATCH 2082/2505] Add mention to a CTF competition that used Lwan --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e1e4a8b84..17ee42d9a 100644 --- a/README.md +++ b/README.md @@ -971,6 +971,7 @@ been seen in the wild. *Help build this list!* * A more complete C++14 [web framework](https://github.com/matt-42/silicon) by [@matt-42](https://github.com/matt-42) offers Lwan as one of its backends. * A [word ladder sample program](https://github.com/sjnam/lwan-sgb-ladders) by [@sjnam](https://github.com/sjnam). [Demo](http://tbcoe.ddns.net/sgb/ladders?start=chaos&goal=order). * A [Shodan search](https://www.shodan.io/search?query=server%3A+lwan) listing some brave souls that expose Lwan to the public internet. +* This [write-up shows the use of Lwan on a Capture the Flag competition](https://medium.com/feedzaitech/pixels-camp-ctf-challenge-qualifiers-writeup-ac661f4af96a). Some other distribution channels were made available as well: From a97f6040903d4652629bc3c24a43c9f4f1e7e9e0 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 3 Jul 2023 15:36:32 -0700 Subject: [PATCH 2083/2505] Cleanup is_dir_good_for_tmp() --- src/lib/lwan-request.c | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index cf3d6e8e3..9253175e3 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1036,8 +1036,9 @@ body_data_finalizer(const struct lwan_value *buffer, return FINALIZER_TRY_AGAIN; } -static const char *is_dir(const char *v) +static const char *is_dir_good_for_tmp(const char *v) { + struct statfs sb; struct stat st; if (!v) @@ -1059,17 +1060,6 @@ static const char *is_dir(const char *v) v); } - return v; -} - -static const char *is_dir_good_for_tmp(const char *v) -{ - struct statfs sb; - - v = is_dir(v); - if (!v) - return NULL; - if (!statfs(v, &sb) && sb.f_type == TMPFS_MAGIC) { lwan_status_warning("%s is a tmpfs filesystem, " "not considering it", v); From 4d56251156a34c71c64dd9151351c8cb452bb002 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 20 Jul 2023 18:41:56 -0700 Subject: [PATCH 2084/2505] strv_split() should work without a separator in the input string This is similar to the workaround posted in #357, but should work with every string, with and without a separator present. --- src/lib/sd-daemon.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/lib/sd-daemon.c b/src/lib/sd-daemon.c index 9b318ad0a..4e34a6c56 100644 --- a/src/lib/sd-daemon.c +++ b/src/lib/sd-daemon.c @@ -140,11 +140,10 @@ static int strv_extend_n(char ***p, const char *s, int n) { static int strv_split(char ***p, const char *value, const char separator) { char *copy = strdup(value); - int n_split = 0; - if (!copy) return -ENOMEM; + int n_split = 1; for (char *c = copy; *c; ) { char *sep_pos = strchr(c, separator); if (!sep_pos) @@ -154,15 +153,17 @@ static int strv_split(char ***p, const char *value, const char separator) { c = sep_pos + 1; } - if (!n_split) - return 0; - *p = calloc((size_t)(n_split + 1), sizeof(char *)); if (!*p) { free(copy); return -ENOMEM; } + if (n_split == 1) { + *p[0] = copy; + return 1; + } + int i = 0; for (char *c = copy; *c; ) { char *sep_pos = strchr(c, separator); From 99201d35f827f8e73dd4a7cad413e85bcc6e24f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Sj=C3=B6lund?= Date: Tue, 18 Jul 2023 21:41:35 +0200 Subject: [PATCH 2085/2505] Add Podman socket activation example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Erik Sjölund --- README.md | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/README.md b/README.md index 17ee42d9a..cb39fa32d 100644 --- a/README.md +++ b/README.md @@ -1048,6 +1048,78 @@ To bring your own `lwan.conf`, simply mount it at `/lwan.conf`. podman run --rm -p 8080:8080 -v ./lwan.conf:/lwan.conf lwan +### Run image with socket activation on a Linux host with Podman + +Podman supports [socket activation of containers](https://github.com/containers/podman/blob/main/docs/tutorials/socket_activation.md#socket-activation-of-containers). +This example shows how to run lwan with socket activation and Podman on a Linux host. + +Requirements: Podman version 4.5.0 or higher. + +1. Create user _test_ + ``` + sudo useradd test + ``` +2. Start a login shell for the user _test_ + ``` + sudo machinectl shell test@ + ``` +3. Clone the lwan git repository to _~/lwan_ +4. Build the image + ``` + podman build -t lwan ~/lwan + ``` +5. Create directories + ``` + mkdir -p ~/.config/containers/systemd + mkdir -p ~/.config/systemd/user + ``` +6. Create the file _~/lwan.conf_ with the contents + ``` + listener systemd:my.socket + site { + serve_files / { + path = /web + } + } + ``` +7. Create the file _~/.config/systemd/user/my.socket_ with the contents + ``` + [Socket] + ListenStream=8080 + ``` +8. Create the file _~/.config/containers/systemd/my.container_ with the contents + ``` + [Unit] + After=my.socket + Requires=my.socket + + [Container] + Network=none + Image=localhost/lwan + Volume=/home/test/lwan.conf:/lwan.conf:Z + Volume=/home/test/web:/web:Z + ``` + The option `:Z` is needed on SELinux systems. + As __lwan__ only needs to communicate over the socket-activated socket, it's possible to use `Network=none`. See the article [How to limit container privilege with socket activation](https://www.redhat.com/sysadmin/socket-activation-podman). +9. Create the web directory and an example text file + ``` + mkdir ~/web + echo hello > ~/web/file.txt + ``` +10. Reload systemd configuration + ``` + systemctl --user daemon-reload + ``` +11. Start the socket + ``` + systemctl --user start my.socket + ``` +12. Download the example text file from the lwan web server + ``` + $ curl localhost:8080/file.txt + hello + ``` + Lwan quotes ----------- From cadf57e27692f00966cc1d304af560b174d12f07 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 25 Jul 2023 20:28:27 -0700 Subject: [PATCH 2086/2505] Avoid mlocking buffers smaller than 10kB --- src/lib/lwan-readahead.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-readahead.c b/src/lib/lwan-readahead.c index b26ce771e..4d0f68ec8 100644 --- a/src/lib/lwan-readahead.c +++ b/src/lib/lwan-readahead.c @@ -164,7 +164,15 @@ static void *lwan_readahead_loop(void *data __attribute__((unused))) case MADVISE: madvise(cmd[i].madvise.addr, cmd[i].madvise.length, MADV_WILLNEED); - mlock(cmd[i].madvise.addr, cmd[i].madvise.length); + + if (cmd[i].madvise.length >= 10 * 1024) { + /* On Linux, SO_ZEROCOPY is only useful to transmit + * 10kB or more because it uses page pinning (what + * mlock(2) does!), so consider the same threshold + * here. */ + mlock(cmd[i].madvise.addr, cmd[i].madvise.length); + } + break; case SHUTDOWN: goto out; From 00ff2edb0cbc5e685ecb36035755c9c67aa8c4ba Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 29 Jul 2023 13:04:35 -0700 Subject: [PATCH 2087/2505] Get rid of connection_flags -> epoll events table Store the epoll events in the upper 16-bit of the connection flags, making the conversion process a cheap shift instead. This also gets rid of a branch in accept_waiting_clients(). One less place to update when making changes! --- src/lib/lwan-thread.c | 35 +++++++++++++++++++---------------- src/lib/lwan.h | 39 +++++++++++++++++++++------------------ 2 files changed, 40 insertions(+), 34 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index e95613ea2..ec98e8b6e 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -447,14 +447,13 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, static ALWAYS_INLINE uint32_t conn_flags_to_epoll_events(enum lwan_connection_flags flags) { - static const uint32_t map[CONN_EVENTS_MASK + 1] = { - [0 /* Suspended (timer or await) */] = EPOLLRDHUP, - [CONN_EVENTS_WRITE] = EPOLLOUT | EPOLLRDHUP, - [CONN_EVENTS_READ] = EPOLLIN | EPOLLRDHUP, - [CONN_EVENTS_READ_WRITE] = EPOLLIN | EPOLLOUT | EPOLLRDHUP, - }; + uint32_t u32flags = (uint32_t)flags; + + /* No bits in the upper 16 bits can be anything other than + * the epoll events we might be interested in! */ + assert(((u32flags >> 16) & ~(EPOLLIN | EPOLLOUT | EPOLLRDHUP)) == 0); - return map[flags & CONN_EVENTS_MASK]; + return u32flags >> 16; } static void update_epoll_flags(const struct timeout_queue *tq, @@ -499,7 +498,7 @@ static void update_epoll_flags(const struct timeout_queue *tq, conn->flags |= or_mask[yield_result]; conn->flags &= and_mask[yield_result]; - assert(!(conn->flags & (CONN_LISTENER_HTTP | CONN_LISTENER_HTTPS))); + assert(!(conn->flags & CONN_LISTENER_HTTP)); assert((conn->flags & CONN_TLS) == (prev_flags & CONN_TLS)); if (conn->flags == prev_flags) @@ -789,17 +788,18 @@ static bool accept_waiting_clients(const struct lwan_thread *t, const uint32_t read_events = conn_flags_to_epoll_events(CONN_EVENTS_READ); struct lwan_connection *conns = t->lwan->conns; int listen_fd = (int)(intptr_t)(listen_socket - conns); - enum lwan_connection_flags new_conn_flags = 0; + enum lwan_connection_flags new_conn_flags = listen_socket->flags & CONN_TLS; -#if defined(LWAN_HAVE_MBEDTLS) - if (listen_socket->flags & CONN_LISTENER_HTTPS) { +#if !defined(NDEBUG) +# if defined(LWAN_HAVE_MBEDTLS) + if (listen_socket->flags & CONN_TLS) { assert(listen_fd == t->tls_listen_fd); - assert(!(listen_socket->flags & CONN_LISTENER_HTTP)); - new_conn_flags = CONN_TLS; } else { assert(listen_fd == t->listen_fd); - assert(listen_socket->flags & CONN_LISTENER_HTTP); } +# else + assert(!(new_conn_flags & CONN_TLS)); +# endif #endif while (true) { @@ -932,7 +932,10 @@ static void *thread_io_loop(void *data) assert(!(conn->flags & CONN_ASYNC_AWAIT)); - if (conn->flags & (CONN_LISTENER_HTTP | CONN_LISTENER_HTTPS)) { + if (conn->flags & CONN_LISTENER_HTTP) { + /* We can't check for CONN_TLS here because that might be a + * client TLS connection! CONN_LISTENER_HTTPS also sets + * CONN_LISTENER_HTTP, so this is fine. */ if (LIKELY(accept_waiting_clients(t, conn))) continue; close(epoll_fd); @@ -941,7 +944,7 @@ static void *thread_io_loop(void *data) } if (UNLIKELY(event->events & (EPOLLRDHUP | EPOLLHUP))) { - if ((conn->flags & CONN_AWAITED_FD) != CONN_SUSPENDED) { + if ((conn->flags & CONN_AWAITED_FD) != CONN_SUSPENDED_MASK) { timeout_queue_expire(&tq, conn); continue; } diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 84f9fb869..8e7f91100 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -29,6 +29,7 @@ extern "C" { #include #include #include +#include #include "hash.h" #include "timeout.h" @@ -263,10 +264,10 @@ enum lwan_request_flags { enum lwan_connection_flags { CONN_MASK = -1, - /* These flags have smaller numbers so that the table to convert - * them to epoll events is smaller. See conn_flags_to_epoll_events(). */ - CONN_EVENTS_READ = 1 << 0, - CONN_EVENTS_WRITE = 1 << 1, + /* Upper 16-bit of CONN_EVENTS_* store the epoll event interest + * mask for those events. */ + CONN_EVENTS_READ = ((EPOLLIN | EPOLLRDHUP) << 16) | 1 << 0, + CONN_EVENTS_WRITE = ((EPOLLOUT | EPOLLRDHUP) << 16) | 1 << 1, CONN_EVENTS_READ_WRITE = CONN_EVENTS_READ | CONN_EVENTS_WRITE, CONN_EVENTS_MASK = 1 << 0 | 1 << 1, @@ -279,17 +280,19 @@ enum lwan_connection_flags { /* These are used for a few different things: * - Avoid re-deferring callbacks to remove request from the timeout wheel * after it has slept previously and is requesting to sleep again. (The - * timeout defer is disarmed right after resuming, and is only there because - * connections may be closed when they're suspended.) + * timeout defer is disarmed right after resuming, and is only there + * because connections may be closed when they're suspended.) * - Distinguish file descriptor in event loop between the connection and - * an awaited file descriptor. (This is set in the connection that's awaiting - * since the pointer to the connection is used as user_data in both cases. - * This is required to be able to resume the connection coroutine after the - * await is completed, and to bubble up errors in awaited file descriptors to - * request handlers rather than abruptly closing the connection.) */ - CONN_SUSPENDED = 1 << 5, + * an awaited file descriptor. (This is set in the connection that's + * awaiting since the pointer to the connection is used as user_data in both + * cases. This is required to be able to resume the connection coroutine + * after the await is completed, and to bubble up errors in awaited file + * descriptors to request handlers rather than abruptly closing the + * connection.) */ + CONN_SUSPENDED_MASK = 1 << 5, + CONN_SUSPENDED = (EPOLLRDHUP << 16) | CONN_SUSPENDED_MASK, CONN_HAS_REMOVE_SLEEP_DEFER = 1 << 6, - CONN_AWAITED_FD = CONN_SUSPENDED | CONN_HAS_REMOVE_SLEEP_DEFER, + CONN_AWAITED_FD = CONN_SUSPENDED_MASK | CONN_HAS_REMOVE_SLEEP_DEFER, /* Used when HTTP pipelining has been detected. This enables usage of the * MSG_MORE flags when sending responses to batch as many short responses @@ -303,13 +306,13 @@ enum lwan_connection_flags { CONN_SENT_CONNECTION_HEADER = 1 << 9, + /* Is this a TLS connection? */ + CONN_TLS = 1 << 10, + /* Both are used to know if an epoll event pertains to a listener rather * than a client. */ - CONN_LISTENER_HTTP = 1 << 10, - CONN_LISTENER_HTTPS = 1 << 11, - - /* Is this a TLS connection? */ - CONN_TLS = 1 << 12, + CONN_LISTENER_HTTP = 1 << 11, + CONN_LISTENER_HTTPS = CONN_LISTENER_HTTP | CONN_TLS, }; enum lwan_connection_coro_yield { From ca992b99e37a3bd0df6cab0d5ef20d96638e092b Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 13 Aug 2023 09:01:27 -0700 Subject: [PATCH 2088/2505] Remove CONN_LISTENER_HTTPS Since CONN_LISTENER_HTTPS is just CONN_LISTENER_HTTP|CONN_TLS, we can remove CONN_LISTENER_HTTPS and rename CONN_LISTENER_HTTP to CONN_LISTENER. This simplifies things slightly. --- src/lib/lwan-thread.c | 13 +++++-------- src/lib/lwan.h | 3 +-- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index ec98e8b6e..a0e163aa1 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -498,7 +498,7 @@ static void update_epoll_flags(const struct timeout_queue *tq, conn->flags |= or_mask[yield_result]; conn->flags &= and_mask[yield_result]; - assert(!(conn->flags & CONN_LISTENER_HTTP)); + assert(!(conn->flags & CONN_LISTENER)); assert((conn->flags & CONN_TLS) == (prev_flags & CONN_TLS)); if (conn->flags == prev_flags) @@ -688,7 +688,7 @@ static ALWAYS_INLINE bool spawn_coro(struct lwan_connection *conn, assert(!conn->coro); assert(!(conn->flags & CONN_ASYNC_AWAIT)); assert(!(conn->flags & CONN_AWAITED_FD)); - assert(!(conn->flags & (CONN_LISTENER_HTTP | CONN_LISTENER_HTTPS))); + assert(!(conn->flags & CONN_LISTENER)); assert(t); assert((uintptr_t)t >= (uintptr_t)tq->lwan->thread.threads); assert((uintptr_t)t < @@ -932,10 +932,7 @@ static void *thread_io_loop(void *data) assert(!(conn->flags & CONN_ASYNC_AWAIT)); - if (conn->flags & CONN_LISTENER_HTTP) { - /* We can't check for CONN_TLS here because that might be a - * client TLS connection! CONN_LISTENER_HTTPS also sets - * CONN_LISTENER_HTTP, so this is fine. */ + if (conn->flags & CONN_LISTENER) { if (LIKELY(accept_waiting_clients(t, conn))) continue; close(epoll_fd); @@ -1355,12 +1352,12 @@ void lwan_thread_init(struct lwan *l) if ((thread->listen_fd = create_listen_socket(thread, i, false)) < 0) lwan_status_critical_perror("Could not create listening socket"); - l->conns[thread->listen_fd].flags |= CONN_LISTENER_HTTP; + l->conns[thread->listen_fd].flags |= CONN_LISTENER; if (tls_initialized) { if ((thread->tls_listen_fd = create_listen_socket(thread, i, true)) < 0) lwan_status_critical_perror("Could not create TLS listening socket"); - l->conns[thread->tls_listen_fd].flags |= CONN_LISTENER_HTTPS; + l->conns[thread->tls_listen_fd].flags |= CONN_TLS; } else { thread->tls_listen_fd = -1; } diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 8e7f91100..b8b4c35cb 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -311,8 +311,7 @@ enum lwan_connection_flags { /* Both are used to know if an epoll event pertains to a listener rather * than a client. */ - CONN_LISTENER_HTTP = 1 << 11, - CONN_LISTENER_HTTPS = CONN_LISTENER_HTTP | CONN_TLS, + CONN_LISTENER = 1 << 11, }; enum lwan_connection_coro_yield { From 91e834b94c5a139725ee54d0cb9f7d18d5190efa Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 22 Aug 2023 22:40:37 -0700 Subject: [PATCH 2089/2505] Ensure TLS listener has CONN_LISTENER flag set The CONN_LISTENER flag was removed in ca992b99e inadvertently. --- src/lib/lwan-thread.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index a0e163aa1..73faee5e2 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -1357,7 +1357,7 @@ void lwan_thread_init(struct lwan *l) if (tls_initialized) { if ((thread->tls_listen_fd = create_listen_socket(thread, i, true)) < 0) lwan_status_critical_perror("Could not create TLS listening socket"); - l->conns[thread->tls_listen_fd].flags |= CONN_TLS; + l->conns[thread->tls_listen_fd].flags |= CONN_LISTENER | CONN_TLS; } else { thread->tls_listen_fd = -1; } From 3b449c1854bf990bd60bcb2672b8aab2c5b85190 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 22 Aug 2023 22:51:19 -0700 Subject: [PATCH 2090/2505] Ensure handling of EPOLLRDHUP events When removing the connection flags to epoll event conversion table, update_epoll_flags() failed to take into consideration that the epoll event interest mask was shifted into the flags -- this caused two things: 1) made an spurious call to epoll_ctl(EPOLL_CTL_MOD) and, 2) cleared EPOLLRDHUP As a result, connections were being closed after read() would return 0 rather than handling EPOLLRDHUP as it should. --- src/lib/lwan-thread.c | 8 ++++---- src/lib/lwan.h | 9 ++++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 73faee5e2..df5c3c90e 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -451,9 +451,9 @@ conn_flags_to_epoll_events(enum lwan_connection_flags flags) /* No bits in the upper 16 bits can be anything other than * the epoll events we might be interested in! */ - assert(((u32flags >> 16) & ~(EPOLLIN | EPOLLOUT | EPOLLRDHUP)) == 0); + assert(((u32flags >> CONN_EPOLL_EVENT_SHIFT) & ~(EPOLLIN | EPOLLOUT | EPOLLRDHUP)) == 0); - return u32flags >> 16; + return u32flags >> CONN_EPOLL_EVENT_SHIFT; } static void update_epoll_flags(const struct timeout_queue *tq, @@ -493,7 +493,7 @@ static void update_epoll_flags(const struct timeout_queue *tq, [CONN_CORO_SUSPEND] = ~CONN_EVENTS_READ_WRITE, [CONN_CORO_RESUME] = ~CONN_SUSPENDED, }; - enum lwan_connection_flags prev_flags = conn->flags; + enum lwan_connection_flags prev_flags = conn->flags & CONN_EPOLL_EVENT_MASK; conn->flags |= or_mask[yield_result]; conn->flags &= and_mask[yield_result]; @@ -501,7 +501,7 @@ static void update_epoll_flags(const struct timeout_queue *tq, assert(!(conn->flags & CONN_LISTENER)); assert((conn->flags & CONN_TLS) == (prev_flags & CONN_TLS)); - if (conn->flags == prev_flags) + if ((conn->flags & CONN_EPOLL_EVENT_MASK) == prev_flags) return; struct epoll_event event = {.events = conn_flags_to_epoll_events(conn->flags), diff --git a/src/lib/lwan.h b/src/lib/lwan.h index b8b4c35cb..061f21dd9 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -261,13 +261,16 @@ enum lwan_request_flags { #undef SELECT_MASK #undef GENERATE_ENUM_ITEM +#define CONN_EPOLL_EVENT_SHIFT 16 +#define CONN_EPOLL_EVENT_MASK ((1 << CONN_EPOLL_EVENT_SHIFT) - 1) + enum lwan_connection_flags { CONN_MASK = -1, /* Upper 16-bit of CONN_EVENTS_* store the epoll event interest * mask for those events. */ - CONN_EVENTS_READ = ((EPOLLIN | EPOLLRDHUP) << 16) | 1 << 0, - CONN_EVENTS_WRITE = ((EPOLLOUT | EPOLLRDHUP) << 16) | 1 << 1, + CONN_EVENTS_READ = ((EPOLLIN | EPOLLRDHUP) << CONN_EPOLL_EVENT_SHIFT) | 1 << 0, + CONN_EVENTS_WRITE = ((EPOLLOUT | EPOLLRDHUP) << CONN_EPOLL_EVENT_SHIFT) | 1 << 1, CONN_EVENTS_READ_WRITE = CONN_EVENTS_READ | CONN_EVENTS_WRITE, CONN_EVENTS_MASK = 1 << 0 | 1 << 1, @@ -290,7 +293,7 @@ enum lwan_connection_flags { * descriptors to request handlers rather than abruptly closing the * connection.) */ CONN_SUSPENDED_MASK = 1 << 5, - CONN_SUSPENDED = (EPOLLRDHUP << 16) | CONN_SUSPENDED_MASK, + CONN_SUSPENDED = (EPOLLRDHUP << CONN_EPOLL_EVENT_SHIFT) | CONN_SUSPENDED_MASK, CONN_HAS_REMOVE_SLEEP_DEFER = 1 << 6, CONN_AWAITED_FD = CONN_SUSPENDED_MASK | CONN_HAS_REMOVE_SLEEP_DEFER, From 1ffdf385a7ec8906bf037ff91b11c6ca9cb4bc57 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 27 Aug 2023 02:46:18 -0700 Subject: [PATCH 2091/2505] Tweaks to README.md --- README.md | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index cb39fa32d..966a289f8 100644 --- a/README.md +++ b/README.md @@ -487,17 +487,21 @@ best to serve files in the fastest way possible according to some heuristics. > :bulb: **Note:** Files smaller than 16KiB will be compressed in RAM for > the duration specified in the `cache_for` setting. Lwan will always try > to compress with deflate, and will optionally compress with Brotli and -> ZSTD. If the compression isn't worth it (e.g. adding the -> `Content-Encoding` headers would make the resulting response larger than -> the uncompressed file contents), Lwan won't small send compressed files. -> For these files, Lwan tries sending the gzipped version if that's found -> in the filesystem and the client requested this encoding. +> zstd (if Lwan has been built with proper support). +> +> In cases where compression wouldn't be worth the effort (e.g. adding the +> `Content-Encoding` header would result in a larger response than sending +> the uncompressed file, usually the case for very small files), Lwan won't +> spend time compressing a file. > > For files larger than 16KiB, Lwan will not attempt to compress them. In > future versions, it might do this and send responses using > chunked-encoding while the file is being compressed (up to a certain > limit, of course), but for now, only precompressed files (see > `serve_precompressed_path` setting in the table above) are considered. +> +> For all cases, Lwan might try using the gzipped version if that's found in +> the filesystem and the client requested this encoding. ##### Variables for `directory_list_template` @@ -633,7 +637,8 @@ which case, the option `expand_with_lua` must be set to `true`, and, instead of using the simple text substitution syntax as the example above, a function named `handle_rewrite(req, captures)` has to be defined instead. The `req` parameter is documented in the Lua module section; the `captures` -parameter is a table containing all the captures, in order. This function +parameter is a table containing all the captures, in order (i.e. ``captures[2]`` +is equivalent to ``%2`` in the simple text substitition syntax). This function returns the new URL to redirect to. This module has no options by itself. Options are specified in each and @@ -760,11 +765,13 @@ a `404 Not Found` error will be sent instead. #### FastCGI The `fastcgi` module proxies requests between the HTTP client connecting to -Lwan and a FastCGI server accessible by Lwan. This is useful, for instance, -to serve pages from a scripting language such as PHP. +Lwan and a [FastCGI](https://en.wikipedia.org/wiki/FastCGI) server +accessible by Lwan. This is useful, for instance, to serve pages from a +scripting language such as PHP. > :bulb: **Note:** This is a preliminary version of this module, and -> as such, it's not well optimized and some features are missing. +> as such, it's not well optimized, some features are missing, and +> some values provided to the environment are hardcoded. | Option | Type | Default | Description | |--------|------|---------|-------------| From 3ddf09bdba5d5b6787b7e6cc23c3ba8efe93ff4b Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 27 Aug 2023 02:46:43 -0700 Subject: [PATCH 2092/2505] Cleanup handling of conenction flags to epoll events --- src/lib/lwan-thread.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index df5c3c90e..6852214e3 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -444,16 +444,14 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, __builtin_unreachable(); } +#define EPOLL_EVENTS(flags) (((uint32_t)flags) >> CONN_EPOLL_EVENT_SHIFT) +#define LWAN_EVENTS(flags) (((uint32_t)flags) & CONN_EPOLL_EVENT_MASK) + static ALWAYS_INLINE uint32_t conn_flags_to_epoll_events(enum lwan_connection_flags flags) { - uint32_t u32flags = (uint32_t)flags; - - /* No bits in the upper 16 bits can be anything other than - * the epoll events we might be interested in! */ - assert(((u32flags >> CONN_EPOLL_EVENT_SHIFT) & ~(EPOLLIN | EPOLLOUT | EPOLLRDHUP)) == 0); - - return u32flags >> CONN_EPOLL_EVENT_SHIFT; + assert((EPOLL_EVENTS(flags) & ~(EPOLLIN | EPOLLOUT | EPOLLRDHUP)) == 0); + return EPOLL_EVENTS(flags); } static void update_epoll_flags(const struct timeout_queue *tq, @@ -493,7 +491,7 @@ static void update_epoll_flags(const struct timeout_queue *tq, [CONN_CORO_SUSPEND] = ~CONN_EVENTS_READ_WRITE, [CONN_CORO_RESUME] = ~CONN_SUSPENDED, }; - enum lwan_connection_flags prev_flags = conn->flags & CONN_EPOLL_EVENT_MASK; + enum lwan_connection_flags prev_flags = conn->flags; conn->flags |= or_mask[yield_result]; conn->flags &= and_mask[yield_result]; @@ -501,7 +499,7 @@ static void update_epoll_flags(const struct timeout_queue *tq, assert(!(conn->flags & CONN_LISTENER)); assert((conn->flags & CONN_TLS) == (prev_flags & CONN_TLS)); - if ((conn->flags & CONN_EPOLL_EVENT_MASK) == prev_flags) + if (LWAN_EVENTS(conn->flags) == LWAN_EVENTS(prev_flags)) return; struct epoll_event event = {.events = conn_flags_to_epoll_events(conn->flags), From a73f13a029c2822eebfa7b53c27707b809b3dbda Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 27 Aug 2023 16:05:00 -0700 Subject: [PATCH 2093/2505] Add HTTP code 406 to the response code table --- src/lib/lwan.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 061f21dd9..f5b688cb0 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -175,6 +175,7 @@ static ALWAYS_INLINE uint64_t string_as_uint64(const char *s) X(FORBIDDEN, 403, "Forbidden", "Access to this resource has been denied") \ X(NOT_FOUND, 404, "Not found", "The requested resource could not be found on this server") \ X(NOT_ALLOWED, 405, "Not allowed", "The requested method is not allowed by this server") \ + X(NOT_ACCEPTABLE, 406, "Not acceptable", "No suitable accepted-encoding header provided") \ X(TIMEOUT, 408, "Request timeout", "Client did not produce a request within expected timeframe") \ X(TOO_LARGE, 413, "Request too large", "The request entity is too large") \ X(RANGE_UNSATISFIABLE, 416, "Requested range unsatisfiable", "The server can't supply the requested portion of the requested resource") \ From 8727cd9057cc3c8e227c797fc7ad98549f149020 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 26 Aug 2023 20:59:27 -0700 Subject: [PATCH 2094/2505] Add source code for https://smolsite.zip This is a website that hosts websites within the URL: zip, base-64 encode the zip, and Lwan will serve that for you. It's pretty much useless, but it was fun to implement. There are other things that could be cleaned up, including the hardcoded HTML/JS and ZIP files within the C source code. ZIP reading code needs extensive fuzzing too. --- src/lib/lwan-private.h | 2 +- src/samples/CMakeLists.txt | 1 + src/samples/smolsite/CMakeLists.txt | 9 + src/samples/smolsite/junzip.c | 325 ++++++++++++++++++++ src/samples/smolsite/junzip.h | 143 +++++++++ src/samples/smolsite/main.c | 447 ++++++++++++++++++++++++++++ 6 files changed, 926 insertions(+), 1 deletion(-) create mode 100644 src/samples/smolsite/CMakeLists.txt create mode 100644 src/samples/smolsite/junzip.c create mode 100644 src/samples/smolsite/junzip.h create mode 100644 src/samples/smolsite/main.c diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index c91e15f8d..46fd591fb 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -62,7 +62,7 @@ struct lwan_request_parser_helper { int urls_rewritten; /* Times URLs have been rewritten */ }; -#define DEFAULT_BUFFER_SIZE 4096 +#define DEFAULT_BUFFER_SIZE 32768 #define DEFAULT_HEADERS_SIZE 2048 #define N_HEADER_START 64 diff --git a/src/samples/CMakeLists.txt b/src/samples/CMakeLists.txt index d7e703ab5..cb35f91d0 100644 --- a/src/samples/CMakeLists.txt +++ b/src/samples/CMakeLists.txt @@ -6,6 +6,7 @@ if (NOT ${CMAKE_BUILD_TYPE} MATCHES "Coverage") add_subdirectory(websocket) add_subdirectory(asyncawait) add_subdirectory(pastebin) + add_subdirectory(smolsite) endif() add_subdirectory(techempower) diff --git a/src/samples/smolsite/CMakeLists.txt b/src/samples/smolsite/CMakeLists.txt new file mode 100644 index 000000000..188e62907 --- /dev/null +++ b/src/samples/smolsite/CMakeLists.txt @@ -0,0 +1,9 @@ +add_executable(smolsite + main.c + junzip.c +) + +target_link_libraries(smolsite + ${LWAN_COMMON_LIBS} + ${ADDITIONAL_LIBRARIES} +) diff --git a/src/samples/smolsite/junzip.c b/src/samples/smolsite/junzip.c new file mode 100644 index 000000000..8f7f824d0 --- /dev/null +++ b/src/samples/smolsite/junzip.c @@ -0,0 +1,325 @@ +// JUnzip library by Joonas Pihlajamaa. See junzip.h for license and details. + +#include +#include +#include + +#include "junzip.h" + +unsigned char jzBuffer[JZ_BUFFER_SIZE]; // limits maximum zip descriptor size + +// Read ZIP file end record. Will move within file. +int jzReadEndRecord(JZFile *zip, JZEndRecord *endRecord) +{ + size_t fileSize, readBytes, i; + JZEndRecord *er; + + if (zip->seek(zip, 0, SEEK_END)) { + fprintf(stderr, "Couldn't go to end of zip file!"); + return Z_ERRNO; + } + + if ((fileSize = zip->tell(zip)) <= sizeof(JZEndRecord)) { + fprintf(stderr, "Too small file to be a zip!"); + return Z_ERRNO; + } + + readBytes = (fileSize < sizeof(jzBuffer)) ? fileSize : sizeof(jzBuffer); + + if (zip->seek(zip, fileSize - readBytes, SEEK_SET)) { + fprintf(stderr, "Cannot seek in zip file!"); + return Z_ERRNO; + } + + if (zip->read(zip, jzBuffer, readBytes) < readBytes) { + fprintf(stderr, "Couldn't read end of zip file!"); + return Z_ERRNO; + } + + // Naively assume signature can only be found in one place... + for (i = readBytes - sizeof(JZEndRecord); i; i--) { + er = (JZEndRecord *)(jzBuffer + i); + if (er->signature == 0x06054B50) + goto signature_found; + } + + fprintf(stderr, "End record signature not found in zip!"); + return Z_ERRNO; + +signature_found: + memcpy(endRecord, er, sizeof(JZEndRecord)); + + if (endRecord->diskNumber || endRecord->centralDirectoryDiskNumber || + endRecord->numEntries != endRecord->numEntriesThisDisk) { + fprintf(stderr, "Multifile zips not supported!"); + return Z_ERRNO; + } + + return Z_OK; +} + +// Read ZIP file global directory. Will move within file. +int jzReadCentralDirectory(JZFile *zip, + JZEndRecord *endRecord, + JZRecordCallback callback, + void *user_data) +{ + JZGlobalFileHeader fileHeader; + JZFileHeader header; + int i; + + if (zip->seek(zip, endRecord->centralDirectoryOffset, SEEK_SET)) { + fprintf(stderr, "Cannot seek in zip file!"); + return Z_ERRNO; + } + + for (i = 0; i < endRecord->numEntries; i++) { + if (zip->read(zip, &fileHeader, sizeof(JZGlobalFileHeader)) < + sizeof(JZGlobalFileHeader)) { + fprintf(stderr, "Couldn't read file header %d!", i); + return Z_ERRNO; + } + + if (fileHeader.signature != 0x02014B50) { + fprintf(stderr, "Invalid file header signature %d!", i); + return Z_ERRNO; + } + + if (fileHeader.fileNameLength + 1 >= JZ_BUFFER_SIZE) { + fprintf(stderr, "Too long file name %d!", i); + return Z_ERRNO; + } + + if (zip->read(zip, jzBuffer, fileHeader.fileNameLength) < + fileHeader.fileNameLength) { + fprintf(stderr, "Couldn't read filename %d!", i); + return Z_ERRNO; + } + + jzBuffer[fileHeader.fileNameLength] = '\0'; // NULL terminate + + if (zip->seek(zip, fileHeader.extraFieldLength, SEEK_CUR) || + zip->seek(zip, fileHeader.fileCommentLength, SEEK_CUR)) { + fprintf(stderr, "Couldn't skip extra field or file comment %d", i); + return Z_ERRNO; + } + + // Construct JZFileHeader from global file header + memcpy(&header, &fileHeader.compressionMethod, sizeof(header)); + header.offset = fileHeader.relativeOffsetOflocalHeader; + + if (!callback(zip, i, &header, (char *)jzBuffer, user_data)) + break; // end if callback returns zero + } + + return Z_OK; +} + +// Read local ZIP file header. Silent on errors so optimistic reading possible. +int jzReadLocalFileHeaderRaw(JZFile *zip, + JZLocalFileHeader *header, + char *filename, + int len) +{ + + if (zip->read(zip, header, sizeof(JZLocalFileHeader)) < + sizeof(JZLocalFileHeader)) + return Z_ERRNO; + + if (header->signature != 0x04034B50) + return Z_ERRNO; + + if (len) { // read filename + if (header->fileNameLength >= len) + return Z_ERRNO; // filename cannot fit + + if (zip->read(zip, filename, header->fileNameLength) < + header->fileNameLength) + return Z_ERRNO; // read fail + + filename[header->fileNameLength] = '\0'; // NULL terminate + } else { // skip filename + if (zip->seek(zip, header->fileNameLength, SEEK_CUR)) + return Z_ERRNO; + } + + if (header->extraFieldLength) { + if (zip->seek(zip, header->extraFieldLength, SEEK_CUR)) + return Z_ERRNO; + } + + // For now, silently ignore bit flags and hope ZLIB can uncompress + // if(header->generalPurposeBitFlag) + // return Z_ERRNO; // Flags not supported + + if (header->compressionMethod == 0 && + (header->compressedSize != header->uncompressedSize)) + return Z_ERRNO; // Method is "store" but sizes indicate otherwise, abort + + return Z_OK; +} + +int jzReadLocalFileHeader(JZFile *zip, + JZFileHeader *header, + char *filename, + int len) +{ + JZLocalFileHeader localHeader; + + if (jzReadLocalFileHeaderRaw(zip, &localHeader, filename, len) != Z_OK) + return Z_ERRNO; + + memcpy(header, &localHeader.compressionMethod, sizeof(JZFileHeader)); + header->offset = 0; // not used in local context + + return Z_OK; +} + +// Read data from file stream, described by header, to preallocated buffer +int jzReadData(JZFile *zip, JZFileHeader *header, void *buffer) +{ +#ifdef HAVE_ZLIB + unsigned char *bytes = (unsigned char *)buffer; // cast + long compressedLeft, uncompressedLeft; + int ret; + z_stream strm; +#endif + + if (header->compressionMethod == 0) { // Store - just read it + if (zip->read(zip, buffer, header->uncompressedSize) < + header->uncompressedSize || + zip->error(zip)) + return Z_ERRNO; +#ifdef HAVE_ZLIB + } else if (header->compressionMethod == 8) { // Deflate - using zlib + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + + strm.avail_in = 0; + strm.next_in = Z_NULL; + + // Use inflateInit2 with negative window bits to indicate raw data + if ((ret = inflateInit2(&strm, -MAX_WBITS)) != Z_OK) + return ret; // Zlib errors are negative + + // Inflate compressed data + for (compressedLeft = header->compressedSize, + uncompressedLeft = header->uncompressedSize; + compressedLeft && uncompressedLeft && ret != Z_STREAM_END; + compressedLeft -= strm.avail_in) { + // Read next chunk + strm.avail_in = + zip->read(zip, jzBuffer, + (sizeof(jzBuffer) < compressedLeft) ? sizeof(jzBuffer) + : compressedLeft); + + if (strm.avail_in == 0 || zip->error(zip)) { + inflateEnd(&strm); + return Z_ERRNO; + } + + strm.next_in = jzBuffer; + strm.avail_out = uncompressedLeft; + strm.next_out = bytes; + + compressedLeft -= strm.avail_in; // inflate will change avail_in + + ret = inflate(&strm, Z_NO_FLUSH); + + if (ret == Z_STREAM_ERROR) + return ret; // shouldn't happen + + switch (ret) { + case Z_NEED_DICT: + ret = Z_DATA_ERROR; /* and fall through */ + case Z_DATA_ERROR: + case Z_MEM_ERROR: + (void)inflateEnd(&strm); + return ret; + } + + bytes += uncompressedLeft - strm.avail_out; // bytes uncompressed + uncompressedLeft = strm.avail_out; + } + + inflateEnd(&strm); +#else +#ifdef HAVE_PUFF + } else if (header->compressionMethod == 8) { // Deflate - using puff() + unsigned long destlen = header->uncompressedSize, + sourcelen = header->compressedSize; + unsigned char *comp = (unsigned char *)malloc(sourcelen); + if (comp == NULL) + return Z_ERRNO; // couldn't allocate + unsigned long read = zip->read(zip, comp, sourcelen); + if (read != sourcelen) + return Z_ERRNO; // TODO: more robust read loop + int ret = puff((unsigned char *)buffer, &destlen, comp, &sourcelen); + free(comp); + if (ret) + return Z_ERRNO; // something went wrong +#endif // HAVE_PUFF +#endif + } else { + return Z_ERRNO; + } + + return Z_OK; +} + +typedef struct { + JZFile handle; + FILE *fp; +} StdioJZFile; + +static size_t stdio_read_file_handle_read(JZFile *file, void *buf, size_t size) +{ + StdioJZFile *handle = (StdioJZFile *)file; + return fread(buf, 1, size, handle->fp); +} + +static size_t stdio_read_file_handle_tell(JZFile *file) +{ + StdioJZFile *handle = (StdioJZFile *)file; + return (size_t)ftell(handle->fp); +} + +static int stdio_read_file_handle_seek(JZFile *file, size_t offset, int whence) +{ + StdioJZFile *handle = (StdioJZFile *)file; + return fseek(handle->fp, (long)offset, whence); +} + +static int stdio_read_file_handle_error(JZFile *file) +{ + StdioJZFile *handle = (StdioJZFile *)file; + return ferror(handle->fp); +} + +static void stdio_read_file_handle_close(JZFile *file) +{ + StdioJZFile *handle = (StdioJZFile *)file; + fclose(handle->fp); + free(file); +} + +JZFile *jzfile_from_stdio_file(FILE *fp) +{ + StdioJZFile *handle = (StdioJZFile *)malloc(sizeof(StdioJZFile)); + + handle->handle.read = stdio_read_file_handle_read; + handle->handle.tell = stdio_read_file_handle_tell; + handle->handle.seek = stdio_read_file_handle_seek; + handle->handle.error = stdio_read_file_handle_error; + handle->handle.close = stdio_read_file_handle_close; + handle->fp = fp; + + return &(handle->handle); +} + +void jzfile_free(JZFile *f) +{ + if (f->close) + f->close(f); +} diff --git a/src/samples/smolsite/junzip.h b/src/samples/smolsite/junzip.h new file mode 100644 index 000000000..aa696a1ce --- /dev/null +++ b/src/samples/smolsite/junzip.h @@ -0,0 +1,143 @@ +/** + * JUnzip library by Joonas Pihlajamaa (firstname.lastname@iki.fi). + * Released into public domain. https://github.com/jokkebk/JUnzip + */ + +#ifndef __JUNZIP_H +#define __JUNZIP_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#include + +// Enable compiling without Zlib as well +// (no compression support, only "store") +#ifdef HAVE_ZLIB +#include +#else +#define Z_OK 0 +#define Z_ERRNO -1 +#endif + +#ifdef HAVE_PUFF +#include "puff.h" +#endif + +// If you don't have stdint.h, the following two lines should work for most 32/64 bit systems +// typedef unsigned int uint32_t; +// typedef unsigned short uint16_t; + +#define JZHOUR(t) ((t)>>11) +#define JZMINUTE(t) (((t)>>5) & 63) +#define JZSECOND(t) (((t) & 31) * 2) +#define JZTIME(h,m,s) (((h)<<11) + ((m)<<5) + (s)/2) + +#define JZYEAR(t) (((t)>>9) + 1980) +#define JZMONTH(t) (((t)>>5) & 15) +#define JZDAY(t) ((t) & 31) +#define JZDATE(y,m,d) ((((y)-1980)<<9) + ((m)<<5) + (d)) + +typedef struct JZFile JZFile; + +struct JZFile { + size_t (*read)(JZFile *file, void *buf, size_t size); + size_t (*tell)(JZFile *file); + int (*seek)(JZFile *file, size_t offset, int whence); + int (*error)(JZFile *file); + void (*close)(JZFile *file); +}; + +JZFile * +jzfile_from_stdio_file(FILE *fp); + +void jzfile_free(JZFile *f); + +typedef struct __attribute__ ((__packed__)) { + uint32_t signature; // 0x04034B50 + uint16_t versionNeededToExtract; // unsupported + uint16_t generalPurposeBitFlag; // unsupported + uint16_t compressionMethod; + uint16_t lastModFileTime; + uint16_t lastModFileDate; + uint32_t crc32; + uint32_t compressedSize; + uint32_t uncompressedSize; + uint16_t fileNameLength; + uint16_t extraFieldLength; // unsupported +} JZLocalFileHeader; + +typedef struct __attribute__ ((__packed__)) { + uint32_t signature; // 0x02014B50 + uint16_t versionMadeBy; // unsupported + uint16_t versionNeededToExtract; // unsupported + uint16_t generalPurposeBitFlag; // unsupported + uint16_t compressionMethod; + uint16_t lastModFileTime; + uint16_t lastModFileDate; + uint32_t crc32; + uint32_t compressedSize; + uint32_t uncompressedSize; + uint16_t fileNameLength; + uint16_t extraFieldLength; // unsupported + uint16_t fileCommentLength; // unsupported + uint16_t diskNumberStart; // unsupported + uint16_t internalFileAttributes; // unsupported + uint32_t externalFileAttributes; // unsupported + uint32_t relativeOffsetOflocalHeader; +} JZGlobalFileHeader; + +typedef struct __attribute__ ((__packed__)) { + uint16_t compressionMethod; + uint16_t lastModFileTime; + uint16_t lastModFileDate; + uint32_t crc32; + uint32_t compressedSize; + uint32_t uncompressedSize; + uint32_t offset; +} JZFileHeader; + +typedef struct __attribute__ ((__packed__)) { + uint32_t signature; // 0x06054b50 + uint16_t diskNumber; // unsupported + uint16_t centralDirectoryDiskNumber; // unsupported + uint16_t numEntriesThisDisk; // unsupported + uint16_t numEntries; + uint32_t centralDirectorySize; + uint32_t centralDirectoryOffset; + uint16_t zipCommentLength; + // Followed by .ZIP file comment (variable size) +} JZEndRecord; + +// Callback prototype for central and local file record reading functions +typedef int (*JZRecordCallback)(JZFile *zip, int index, JZFileHeader *header, + char *filename, void *user_data); + +#define JZ_BUFFER_SIZE 65536 + +// Read ZIP file end record. Will move within file. +int jzReadEndRecord(JZFile *zip, JZEndRecord *endRecord); + +// Read ZIP file global directory. Will move within file. +// Callback is called for each record, until callback returns zero +int jzReadCentralDirectory(JZFile *zip, JZEndRecord *endRecord, + JZRecordCallback callback, void *user_data); + +// Read local ZIP file header. Silent on errors so optimistic reading possible. +int jzReadLocalFileHeader(JZFile *zip, JZFileHeader *header, + char *filename, int len); + +// Same as above but returns the full raw header +int jzReadLocalFileHeaderRaw(JZFile *zip, JZLocalFileHeader *header, + char *filename, int len); + +// Read data from file stream, described by header, to preallocated buffer +// Return value is zlib coded, e.g. Z_OK, or error code +int jzReadData(JZFile *zip, JZFileHeader *header, void *buffer); + +#ifdef __cplusplus +}; +#endif /* __cplusplus */ + +#endif diff --git a/src/samples/smolsite/main.c b/src/samples/smolsite/main.c new file mode 100644 index 000000000..aa0d538bb --- /dev/null +++ b/src/samples/smolsite/main.c @@ -0,0 +1,447 @@ +/* + * smolsite.zip + * Copyright (c) 2023 L. A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#include "base64.h" +#include "hash.h" +#include "int-to-str.h" +#include "lwan.h" +#include "sha1.h" +#include "lwan-cache.h" +#include "lwan-private.h" + +#include "junzip.h" + +#define CACHE_FOR_MINUTES 15 + +static struct cache *sites; + +struct file { + ptrdiff_t data_offset; + size_t size_compressed; + const char *mime_type; + bool deflated; +}; + +struct site { + struct cache_entry entry; + struct lwan_value zipped; + struct hash *files; +}; + +static struct hash *pending_sites(void) +{ + /* This is kind of a hack: we can't have just a single thread-local + * for the current thread's pending site because a coroutine might + * yield while trying to obtain an item from the sites cache, which + * would override that value. Store these in a thread-local hash + * table instead, which can be consulted by the create_site() function. + * Items are removed from this table in a defer handler. */ + static __thread struct hash *pending_sites; + + if (!pending_sites) { + pending_sites = hash_str_new(free, free); + if (!pending_sites) { + lwan_status_critical("Could not allocate pending sites hash table"); + } + } + + return pending_sites; +} + +static int file_cb( + JZFile *zip, int idx, JZFileHeader *header, char *filename, void *user_data) +{ + struct site *site = user_data; + char filename_buf[1024]; + ptrdiff_t data_offset; + size_t cur_offset = zip->tell(zip); + + if (zip->seek(zip, header->offset, SEEK_SET)) + return 0; + + JZFileHeader local; + if (jzReadLocalFileHeader(zip, &local, filename_buf, sizeof(filename_buf))) + return 0; + if (__builtin_add_overflow(zip->tell(zip), local.offset, &data_offset)) + return 0; + if (data_offset < 0) + return 0; + if ((size_t)data_offset > site->zipped.len) + return 0; + + struct file *file = malloc(sizeof(*file)); + if (!file) + return 0; + + file->data_offset = data_offset; + file->deflated = local.compressionMethod == 8; + file->size_compressed = local.compressedSize; + file->mime_type = lwan_determine_mime_type_for_file_name(filename); + + char *key = strdup(filename); + if (key) { + if (!hash_add_unique(site->files, key, file)) { + zip->seek(zip, cur_offset, SEEK_SET); + return 1; + } + } + + free(key); + free(file); + return 0; +} + +static struct cache_entry *create_site(const void *key, void *context) +{ + const struct lwan_value *zipped = + hash_find(pending_sites(), (const void *)key); + + if (!zipped) + return NULL; + + struct site *site = malloc(sizeof(*site)); + if (!site) + goto no_site; + + site->zipped = *zipped; + + FILE *zip_mem = fmemopen(zipped->value, zipped->len, "rb"); + if (!zip_mem) + goto no_file; + + JZFile *zip = jzfile_from_stdio_file(zip_mem); + if (!zip) { + fclose(zip_mem); + goto no_file; + } + + JZEndRecord end_record; + if (jzReadEndRecord(zip, &end_record)) + goto no_end_record; + + site->files = hash_str_new(free, free); + if (!site->files) + goto no_hash; + + if (jzReadCentralDirectory(zip, &end_record, file_cb, site)) + goto no_central_dir; + + jzfile_free(zip); + return (struct cache_entry *)site; + +no_central_dir: + hash_free(site->files); +no_hash: +no_end_record: + jzfile_free(zip); +no_file: + free(site); +no_site: + free(zipped->value); + return NULL; +} + +static void destroy_site(struct cache_entry *entry, void *context) +{ + struct site *site = (struct site *)entry; + hash_free(site->files); + free(site->zipped.value); + free(site); +} + +static void remove_from_pending_defer(void *data) +{ + char *key = data; + hash_del(pending_sites(), key); +} + +static void calc_hash(const struct lwan_request *request, + char digest_str[static 41]) +{ + /* FIXME: Is SHA-1 overkill? */ + sha1_context ctx; + unsigned char digest[20]; + + sha1_init(&ctx); + sha1_update(&ctx, (const unsigned char *)request->url.value, + request->url.len); + sha1_finalize(&ctx, digest); + + snprintf(digest_str, 41, + "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x" + "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + digest[0], digest[1], digest[2], digest[3], digest[4], digest[5], + digest[6], digest[7], digest[8], digest[9], digest[10], digest[11], + digest[12], digest[13], digest[14], digest[15], digest[16], + digest[17], digest[18], digest[19]); +} + +LWAN_HANDLER_ROUTE(view_root, "/") +{ + char digest_str[41]; + struct site *site; + + if (!request->url.len) { + static const struct lwan_key_value redir_headers[] = { + {"Refresh", + "0; url=/" + "UEsDBBQAAgAIAFsCG1c3MTWu6QMAAAoIAAAKABwAaW5kZXguaHRtbFVUCQAD3vjqZ" + "OD46mR1eAsAAQToAwAABOgDAACtVW1v2zYQ/mz9iou3QA5qybJju/" + "GLDCwvAwJ0WNFl+7BhQGnxLHGjSIGkrahb/" + "vuOkoN6XbsV6CyYpu6Ozx0fPjyvC1dKEDztb/ubYF0g45u1dY3ETbDVvIE/" + "gi3Lfs+" + "N3iseZVpqs4Svdu1ndeoSJctxCXsjB33OHFu2hpE95C8eSzk8v7yhKdSCuyINx5MQ" + "ChR54Wg+D+EgsL7Wj2mYQALjCXgbrVI2DQvnquVoVNd1XF/" + "G2uSjSZIkHjc8v7wj2Iq5AngafjeFeLG4oXE6ncGUximBzSDJ4tls4nHJM/" + "PjYnGYxsnkZg4zcs3acQ70BvMs6oKjcUTBflwsfqLvu3IOV1lC3uncI3s7tN5PgGc" + "J+NjIB5Mtaj0fA7/y6CHshJRpeD655C/" + "5jLPOEOmKZcI1xEs8PZrMXmIa4gGV5jwcdRx4OmjWv1gFO61ctGOlkM0SLFM2smgE" + "nVXJTC7Ukui9qh5XwVOQoXJo6IB7FeNcqPzEVxn0jvfnS4c+SfyzCnrPKniZ+" + "Gd1sn6S+OU9h4/" + "uWSud6SlYjzpVrUetxoK1V5dX3Hhz70ILDGyppRUOzyhkvAmCdbV5KISFutASwXuI" + "AWdBKCs4gisQfnzz6gweTAOFto4qAK0oSBto9N5YlLsh/" + "La3DrhhOWX4+f61JxFJhqQZpmDt3EYojo+xvwbrEb2SLo0voeoq+" + "N4MgUniSTEnDiibIextlzzTZclI+VIoEn61d0CnYhpX+EKEOsk3hGtGi+" + "ZTQJVpql04QlUcHPFLL8B2/" + "iT6XuuWxP7MRPxOVKP+8rkYQ3fyayAbRAuwusTnGHi/" + "BygbqXMdVyqnWMwK3cF+iPo2Y+7vGH/" + "CloqkGqOoNqyC5G2fDq1NGqxtZkTlNoFEB21bSIHrbF+" + "ShOIc3Z1EP71u7vmAGgnJ0AfFJIs7Eqp7JaxDhWYQ+" + "oNoZRcOYbdXmRNaDfDCaw1j63T12pDmc9baCYasVIHHuMUd20vXGrdxq6V4qw0n3l" + "IIL0llVJFzyGEridWQNEcpmLT479VIZAf8n6tJPju7PnwZFZ+" + "TRVdftD2xgwHGvqU/" + "GGonOzSx17SNJaqcrlGaQtJi9phE4wb9W0Il8fugMy+F3hMglQj/" + "BbSB8T9xtJJNd6s/" + "hPORXo6G2klLu8IavqWoN62hrb3XOWNC0YxTjN9+" + "umnXHjfmqC2ii31g84Nj1GRoQ5OukC7B8VqkcBJsiZ3YSpHh8d+" + "OVRW9tVSO6DatukXD/nFzFy1aTfdU1zExH0vdBRPsJ649vDhm9vvoPdHwdLIj//" + "ONvaXU1AM/Susvya8tW6caoSZ8vMg07RowTXzf2AR/" + "AVBLAQIeAxQAAgAIAFsCG1c3MTWu6QMAAAoIAAAKABgAAAAAAAEAAACkgQAAAABpb" + "mRleC5odG1sVVQFAAPe+" + "OpkdXgLAAEE6AMAAAToAwAAUEsFBgAAAAABAAEAUAAAAC0EAAAAAA=="}, + {}, + }; + response->headers = redir_headers; + return HTTP_TEMPORARY_REDIRECT; + } + + /* Lwan gives us a percent-decoded URL, but '+' is part of the Base64 + * alphabet */ + for (char *p = request->url.value; *p; p++) { + if (*p == ' ') + *p = '+'; + } + + calc_hash(request, digest_str); + + site = (struct site *)cache_coro_get_and_ref_entry( + sites, request->conn->coro, digest_str); + if (!site) { + struct lwan_value *zip = malloc(sizeof(*zip)); + if (UNLIKELY(!zip)) + return HTTP_INTERNAL_ERROR; + + char *key = strdup(digest_str); + if (UNLIKELY(hash_add_unique(pending_sites(), key, zip))) { + free(key); + free(zip); + return HTTP_INTERNAL_ERROR; + } + + coro_defer(request->conn->coro, remove_from_pending_defer, key); + + /* This base64 decoding stuff could go to create_site(), but + * then we'd need to allocate a new buffer, copy the encoded + * ZIP into that buffer, and free it inside create_site(). It's + * just more work than just decoding it. + */ + unsigned char *decoded; + size_t decoded_len; + + if (UNLIKELY(!base64_validate((unsigned char *)request->url.value, + request->url.len))) { + return HTTP_BAD_REQUEST; + } + + decoded = base64_decode((unsigned char *)request->url.value, + request->url.len, &decoded_len); + if (UNLIKELY(!decoded)) + return HTTP_BAD_REQUEST; + + zip->value = (char *)decoded; + zip->len = decoded_len; + + site = (struct site *)cache_coro_get_and_ref_entry( + sites, request->conn->coro, digest_str); + if (UNLIKELY(!site)) + return HTTP_INTERNAL_ERROR; + } + + response->mime_type = "text/html; charset=utf-8"; + + /* FIXME: this should really be a template! */ + lwan_strbuf_printf( + response->buffer, + "\n" + "🗜 omg it's a smolsite" + "" + "" + "" + " \n" + "

" + " Hosted in the URL by 🗜️smolsite.zip. Powered by the Lwan web server." + "

" + "" + "\n" + "", + digest_str); + return HTTP_OK; +} + +LWAN_HANDLER_ROUTE(view_site, "/s/") +{ + if (request->url.len < 40) + return HTTP_NOT_FOUND; + + char *slash = memchr(request->url.value, '/', request->url.len); + if (!slash) + return HTTP_NOT_FOUND; + if (slash - request->url.value < 40) + return HTTP_NOT_FOUND; + *slash = '\0'; + for (const char *p = request->url.value; *p; p++) { + if (!isxdigit(*p)) + return HTTP_NOT_FOUND; + } + + const char *file_name = slash + 1; + + struct site *site = (struct site *)cache_coro_get_and_ref_entry( + sites, request->conn->coro, request->url.value); + if (!site) + return HTTP_NOT_FOUND; + + if (*file_name == '\0') + file_name = "index.html"; + + struct file *file = hash_find(site->files, file_name); + if (!file) + return HTTP_NOT_FOUND; + + if (file->deflated) { + enum lwan_request_flags accept = + lwan_request_get_accept_encoding(request); + + if (!(accept & REQUEST_ACCEPT_DEFLATE)) + return HTTP_NOT_ACCEPTABLE; + + static const struct lwan_key_value deflate_headers[] = { + {"Content-Encoding", "deflate"}, + {}, + }; + response->headers = deflate_headers; + } + + lwan_strbuf_set_static(response->buffer, + site->zipped.value + file->data_offset, + file->size_compressed); + response->mime_type = file->mime_type; + + return HTTP_OK; +} + +int main(void) +{ + struct lwan l; + + lwan_init(&l); + lwan_detect_url_map(&l); + + sites = cache_create_full(create_site, destroy_site, hash_str_new, NULL, + CACHE_FOR_MINUTES * 60); + if (!sites) + lwan_status_critical("Could not create site cache"); + + lwan_main_loop(&l); + lwan_shutdown(&l); + + return 0; +} From 7e7de9ce916db24ed05f8b7e2290a5f6dbdb75fa Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 31 Aug 2023 19:35:39 -0700 Subject: [PATCH 2095/2505] Revert DEFAULT_BUFFER_SIZE to 4096 This was causing issues on oss-fuzz. Need to find a better way to support the smolsite sample with a smaller buffer, or finally move to a dynamically-allocated ring buffer. --- src/lib/lwan-private.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 46fd591fb..c91e15f8d 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -62,7 +62,7 @@ struct lwan_request_parser_helper { int urls_rewritten; /* Times URLs have been rewritten */ }; -#define DEFAULT_BUFFER_SIZE 32768 +#define DEFAULT_BUFFER_SIZE 4096 #define DEFAULT_HEADERS_SIZE 2048 #define N_HEADER_START 64 From ef2f5141cd77bb0e5a0d33be9a9a8e5c4bbbf68d Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 31 Aug 2023 19:36:51 -0700 Subject: [PATCH 2096/2505] Disable ccache This caused some issues with miscompilations, so let's disable this for the foreseeable future. Lwan doesn't take that long to compile anyway, and with ninja there's not a whole lot of difference for incremental builds. --- CMakeLists.txt | 7 ------- 1 file changed, 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 76fd06295..df245f6bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,13 +22,6 @@ if (NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Debug") endif () -find_program(CCACHE ccache) -if (CCACHE) - message(STATUS "Using ccache from ${CCACHE}") - set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${CCACHE}) - set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ${CCACHE}) -endif () - # # Find libraries # From ff1252f993bb9f69b0e70d66827c1e089f25288d Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 31 Aug 2023 19:38:40 -0700 Subject: [PATCH 2097/2505] Fix compiler warning in lwan_request_async_writev() The length passed to writev(2) is the number of iovs, as an int, not a size_t! I don't know what I was thinking there. --- src/lib/lwan-request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 9253175e3..6792b6d83 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -2090,7 +2090,7 @@ ssize_t lwan_request_async_writev(struct lwan_request *request, vec->iov_len); } - written = writev(fd, iov + curr_iov, (size_t)remaining_len); + written = writev(fd, iov + curr_iov, remaining_len); if (UNLIKELY(written < 0)) { /* FIXME: Consider short writes as another try as well? */ tries--; From 1ab37e9e2c2c02037b1f0a953628eec62c907aed Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 31 Aug 2023 19:39:45 -0700 Subject: [PATCH 2098/2505] Allow compiling templates from a lwan_value as well This is useful if you have a lwan_value without a NUL terminator. The parser would strlen() the string anyway, so the string implementation can simply forward the old method to the new method and call strlen() itself. --- src/lib/lwan-template.c | 28 ++++++++++++++-------------- src/lib/lwan-template.h | 11 ++++++++++- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index 1ce8d0df9..1f4a734b0 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -525,11 +525,11 @@ static struct lexeme *lex_next(struct lexer *lexer) return lexeme; } -static void lex_init(struct lexer *lexer, const char *input) +static void lex_init(struct lexer *lexer, struct lwan_value input) { lexer->state = lex_text; - lexer->pos = lexer->start = input; - lexer->end = input + strlen(input); + lexer->pos = lexer->start = input.value; + lexer->end = input.value + input.len; lexeme_ring_buffer_init(&lexer->ring_buffer); } @@ -1143,7 +1143,7 @@ static bool post_process_template(struct parser *parser) static bool parser_init(struct parser *parser, const struct lwan_var_descriptor *descriptor, - const char *string) + struct lwan_value value) { if (symtab_push(parser, descriptor) < 0) return false; @@ -1151,7 +1151,7 @@ static bool parser_init(struct parser *parser, chunk_array_init(&parser->chunks); parser->tpl->chunks = parser->chunks; - lex_init(&parser->lexer, string); + lex_init(&parser->lexer, value); list_head_init(&parser->stack); return true; @@ -1217,10 +1217,10 @@ static bool parser_shutdown(struct parser *parser, struct lexeme *lexeme) return success; } -static bool parse_string(struct lwan_tpl *tpl, - const char *string, - const struct lwan_var_descriptor *descriptor, - enum lwan_tpl_flag flags) +static bool parse_value(struct lwan_tpl *tpl, + struct lwan_value value, + const struct lwan_var_descriptor *descriptor, + enum lwan_tpl_flag flags) { struct parser parser = { .tpl = tpl, @@ -1231,7 +1231,7 @@ static bool parse_string(struct lwan_tpl *tpl, void *(*state)(struct parser *parser, struct lexeme *lexeme) = parser_text; struct lexeme *lexeme; - if (!parser_init(&parser, descriptor, string)) + if (!parser_init(&parser, descriptor, value)) return false; while (state) { @@ -1349,15 +1349,15 @@ static void dump_program(const struct lwan_tpl *tpl) #endif struct lwan_tpl * -lwan_tpl_compile_string_full(const char *string, - const struct lwan_var_descriptor *descriptor, - enum lwan_tpl_flag flags) +lwan_tpl_compile_value_full(struct lwan_value value, + const struct lwan_var_descriptor *descriptor, + enum lwan_tpl_flag flags) { struct lwan_tpl *tpl; tpl = calloc(1, sizeof(*tpl)); if (tpl) { - if (parse_string(tpl, string, descriptor, flags)) { + if (parse_value(tpl, value, descriptor, flags)) { #if !defined(NDEBUG) && defined(TEMPLATE_DEBUG) dump_program(tpl); #endif diff --git a/src/lib/lwan-template.h b/src/lib/lwan-template.h index f3841a830..d0d08db3f 100644 --- a/src/lib/lwan-template.h +++ b/src/lib/lwan-template.h @@ -81,9 +81,18 @@ void lwan_append_double_to_strbuf(struct lwan_strbuf *buf, void *ptr); bool lwan_tpl_double_is_empty(void *ptr); struct lwan_tpl * +lwan_tpl_compile_value_full(struct lwan_value value, + const struct lwan_var_descriptor *descriptor, + enum lwan_tpl_flag flags); +static inline struct lwan_tpl * lwan_tpl_compile_string_full(const char *string, const struct lwan_var_descriptor *descriptor, - enum lwan_tpl_flag flags); + enum lwan_tpl_flag flags) +{ + struct lwan_value value = {.value = (char *)string, .len = strlen(string)}; + return lwan_tpl_compile_value_full(value, descriptor, flags); +} + struct lwan_tpl * lwan_tpl_compile_string(const char *string, const struct lwan_var_descriptor *descriptor); From 3400e92d174638fdc8e83993cb729b97f3610aed Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 31 Aug 2023 19:41:17 -0700 Subject: [PATCH 2099/2505] Use a template for the smolsite sample & build ZIP during build This should be slightly more efficient than calling snprintf() to build the main HTML with the iframe. It's also quite a bit easier to update, as one just edits smolfile.html and rebuild. The sample smolsite is also generated during build, so updating it requires editing index.html and rebuilding. --- CMakeLists.txt | 2 + src/lib/CMakeLists.txt | 2 - src/samples/smolsite/CMakeLists.txt | 48 +++++-- src/samples/smolsite/main.c | 199 ++++++++++------------------ 4 files changed, 110 insertions(+), 141 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index df245f6bc..837eb9892 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,8 @@ message(STATUS "Running CMake for ${PROJECT_NAME} (${PROJECT_DESCRIPTION})") set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/src/cmake") +include_directories(${CMAKE_BINARY_DIR}) + include(CheckCCompilerFlag) include(CheckCSourceCompiles) include(CheckFunctionExists) diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index 63b0ec72a..4869c4ea3 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -107,8 +107,6 @@ add_custom_target(generate_auto_index_icons ) add_dependencies(lwan-static generate_auto_index_icons) -include_directories(${CMAKE_BINARY_DIR}) - if (NOT HAVE_BUILTIN_FPCLASSIFY) set(ADDITIONAL_LIBRARIES ${ADDITIONAL_LIBRARIES} -lm PARENT_SCOPE) endif () diff --git a/src/samples/smolsite/CMakeLists.txt b/src/samples/smolsite/CMakeLists.txt index 188e62907..900a12bf7 100644 --- a/src/samples/smolsite/CMakeLists.txt +++ b/src/samples/smolsite/CMakeLists.txt @@ -1,9 +1,39 @@ -add_executable(smolsite - main.c - junzip.c -) - -target_link_libraries(smolsite - ${LWAN_COMMON_LIBS} - ${ADDITIONAL_LIBRARIES} -) + +find_program(ZIP NAMES zip) +if (ZIP) + add_executable(smolsite + main.c + junzip.c + ) + + target_link_libraries(smolsite + ${LWAN_COMMON_LIBS} + ${ADDITIONAL_LIBRARIES} + ) + + add_custom_command( + OUTPUT ${CMAKE_BINARY_DIR}/smolsite.zip + COMMAND ${ZIP} -DXjq9 ${CMAKE_BINARY_DIR}/smolsite.zip ${CMAKE_SOURCE_DIR}/src/samples/smolsite/index.html + DEPENDS ${CMAKE_SOURCE_DIR}/src/samples/smolsite/index.html + COMMENT "Zipping smolsite ZIP" + ) + add_custom_target(generate_smolsite_zip + DEPENDS ${CMAKE_BINARY_DIR}/smolsite.zip + ) + + add_custom_command( + OUTPUT ${CMAKE_BINARY_DIR}/smolsite.h + COMMAND bin2hex + ${CMAKE_SOURCE_DIR}/src/samples/smolsite/smolsite.html smolsite_html + ${CMAKE_BINARY_DIR}/smolsite.zip smolsite_zip > ${CMAKE_BINARY_DIR}/smolsite.h + DEPENDS ${CMAKE_SOURCE_DIR}/src/samples/smolsite/smolsite.html + ${CMAKE_BINARY_DIR}/smolsite.zip + bin2hex + COMMENT "Bundling smolsite template" + ) + add_custom_target(generate_smolsite + DEPENDS ${CMAKE_BINARY_DIR}/smolsite.h + ) + + add_dependencies(smolsite generate_smolsite generate_smolsite_zip) +endif () diff --git a/src/samples/smolsite/main.c b/src/samples/smolsite/main.c index aa0d538bb..f94cc383d 100644 --- a/src/samples/smolsite/main.c +++ b/src/samples/smolsite/main.c @@ -32,9 +32,12 @@ #include "sha1.h" #include "lwan-cache.h" #include "lwan-private.h" +#include "lwan-template.h" #include "junzip.h" +#include "smolsite.h" + #define CACHE_FOR_MINUTES 15 static struct cache *sites; @@ -52,6 +55,40 @@ struct site { struct hash *files; }; +struct iframe_tpl_vars { + const char *digest; +}; + +#undef TPL_STRUCT +#define TPL_STRUCT struct iframe_tpl_vars +static const struct lwan_var_descriptor iframe_tpl_desc[] = { + TPL_VAR_STR(digest), + TPL_VAR_SENTINEL, +}; + +static struct lwan_tpl *iframe_tpl; +static struct lwan_value smolsite_zip_base64; + +static void calc_hash(struct lwan_value value, + char digest_str[static 41]) +{ + /* FIXME: Is SHA-1 overkill? */ + sha1_context ctx; + unsigned char digest[20]; + + sha1_init(&ctx); + sha1_update(&ctx, (const unsigned char *)value.value, value.len); + sha1_finalize(&ctx, digest); + + snprintf(digest_str, 41, + "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x" + "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + digest[0], digest[1], digest[2], digest[3], digest[4], digest[5], + digest[6], digest[7], digest[8], digest[9], digest[10], digest[11], + digest[12], digest[13], digest[14], digest[15], digest[16], + digest[17], digest[18], digest[19]); +} + static struct hash *pending_sites(void) { /* This is kind of a hack: we can't have just a single thread-local @@ -179,26 +216,6 @@ static void remove_from_pending_defer(void *data) hash_del(pending_sites(), key); } -static void calc_hash(const struct lwan_request *request, - char digest_str[static 41]) -{ - /* FIXME: Is SHA-1 overkill? */ - sha1_context ctx; - unsigned char digest[20]; - - sha1_init(&ctx); - sha1_update(&ctx, (const unsigned char *)request->url.value, - request->url.len); - sha1_finalize(&ctx, digest); - - snprintf(digest_str, 41, - "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x" - "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", - digest[0], digest[1], digest[2], digest[3], digest[4], digest[5], - digest[6], digest[7], digest[8], digest[9], digest[10], digest[11], - digest[12], digest[13], digest[14], digest[15], digest[16], - digest[17], digest[18], digest[19]); -} LWAN_HANDLER_ROUTE(view_root, "/") { @@ -206,47 +223,14 @@ LWAN_HANDLER_ROUTE(view_root, "/") struct site *site; if (!request->url.len) { - static const struct lwan_key_value redir_headers[] = { - {"Refresh", - "0; url=/" - "UEsDBBQAAgAIAFsCG1c3MTWu6QMAAAoIAAAKABwAaW5kZXguaHRtbFVUCQAD3vjqZ" - "OD46mR1eAsAAQToAwAABOgDAACtVW1v2zYQ/mz9iou3QA5qybJju/" - "GLDCwvAwJ0WNFl+7BhQGnxLHGjSIGkrahb/" - "vuOkoN6XbsV6CyYpu6Ozx0fPjyvC1dKEDztb/ubYF0g45u1dY3ETbDVvIE/" - "gi3Lfs+" - "N3iseZVpqs4Svdu1ndeoSJctxCXsjB33OHFu2hpE95C8eSzk8v7yhKdSCuyINx5MQ" - "ChR54Wg+D+EgsL7Wj2mYQALjCXgbrVI2DQvnquVoVNd1XF/" - "G2uSjSZIkHjc8v7wj2Iq5AngafjeFeLG4oXE6ncGUximBzSDJ4tls4nHJM/" - "PjYnGYxsnkZg4zcs3acQ70BvMs6oKjcUTBflwsfqLvu3IOV1lC3uncI3s7tN5PgGc" - "J+NjIB5Mtaj0fA7/y6CHshJRpeD655C/" - "5jLPOEOmKZcI1xEs8PZrMXmIa4gGV5jwcdRx4OmjWv1gFO61ctGOlkM0SLFM2smgE" - "nVXJTC7Ukui9qh5XwVOQoXJo6IB7FeNcqPzEVxn0jvfnS4c+SfyzCnrPKniZ+" - "Gd1sn6S+OU9h4/" - "uWSud6SlYjzpVrUetxoK1V5dX3Hhz70ILDGyppRUOzyhkvAmCdbV5KISFutASwXuI" - "AWdBKCs4gisQfnzz6gweTAOFto4qAK0oSBto9N5YlLsh/" - "La3DrhhOWX4+f61JxFJhqQZpmDt3EYojo+xvwbrEb2SLo0voeoq+" - "N4MgUniSTEnDiibIextlzzTZclI+VIoEn61d0CnYhpX+EKEOsk3hGtGi+" - "ZTQJVpql04QlUcHPFLL8B2/" - "iT6XuuWxP7MRPxOVKP+8rkYQ3fyayAbRAuwusTnGHi/" - "BygbqXMdVyqnWMwK3cF+iPo2Y+7vGH/" - "CloqkGqOoNqyC5G2fDq1NGqxtZkTlNoFEB21bSIHrbF+" - "ShOIc3Z1EP71u7vmAGgnJ0AfFJIs7Eqp7JaxDhWYQ+" - "oNoZRcOYbdXmRNaDfDCaw1j63T12pDmc9baCYasVIHHuMUd20vXGrdxq6V4qw0n3l" - "IIL0llVJFzyGEridWQNEcpmLT479VIZAf8n6tJPju7PnwZFZ+" - "TRVdftD2xgwHGvqU/" - "GGonOzSx17SNJaqcrlGaQtJi9phE4wb9W0Il8fugMy+F3hMglQj/" - "BbSB8T9xtJJNd6s/" - "hPORXo6G2klLu8IavqWoN62hrb3XOWNC0YxTjN9+" - "umnXHjfmqC2ii31g84Nj1GRoQ5OukC7B8VqkcBJsiZ3YSpHh8d+" - "OVRW9tVSO6DatukXD/nFzFy1aTfdU1zExH0vdBRPsJ649vDhm9vvoPdHwdLIj//" - "ONvaXU1AM/Susvya8tW6caoSZ8vMg07RowTXzf2AR/" - "AVBLAQIeAxQAAgAIAFsCG1c3MTWu6QMAAAoIAAAKABgAAAAAAAEAAACkgQAAAABpb" - "mRleC5odG1sVVQFAAPe+" - "OpkdXgLAAEE6AMAAAToAwAAUEsFBgAAAAABAAEAUAAAAC0EAAAAAA=="}, + const struct lwan_key_value redir_headers[] = { + {"Location", smolsite_zip_base64.value}, {}, }; - response->headers = redir_headers; - return HTTP_TEMPORARY_REDIRECT; + response->headers = coro_memdup(request->conn->coro, + redir_headers, + sizeof(redir_headers)); + return response->headers ? HTTP_TEMPORARY_REDIRECT : HTTP_INTERNAL_ERROR; } /* Lwan gives us a percent-decoded URL, but '+' is part of the Base64 @@ -256,7 +240,7 @@ LWAN_HANDLER_ROUTE(view_root, "/") *p = '+'; } - calc_hash(request, digest_str); + calc_hash(request->url, digest_str); site = (struct site *)cache_coro_get_and_ref_entry( sites, request->conn->coro, digest_str); @@ -303,76 +287,10 @@ LWAN_HANDLER_ROUTE(view_root, "/") response->mime_type = "text/html; charset=utf-8"; - /* FIXME: this should really be a template! */ - lwan_strbuf_printf( - response->buffer, - "\n" - "🗜 omg it's a smolsite" - "" - "" - "" - " \n" - "

" - " Hosted in the URL by 🗜️smolsite.zip. Powered by the Lwan web server." - "

" - "" - "\n" - "", - digest_str); + struct iframe_tpl_vars vars = {.digest = digest_str}; + if (!lwan_tpl_apply_with_buffer(iframe_tpl, response->buffer, &vars)) + return HTTP_INTERNAL_ERROR; + return HTTP_OK; } @@ -428,6 +346,16 @@ LWAN_HANDLER_ROUTE(view_site, "/s/") return HTTP_OK; } +static struct lwan_value base64_encode_to_value(struct lwan_value input) +{ + size_t len; + unsigned char *encoded = + base64_encode((unsigned char *)input.value, input.len, &len); + if (!encoded) + lwan_status_critical("Could not base64-encode smolsite.zip!"); + return (struct lwan_value){.value = (char *)encoded, .len = len}; +} + int main(void) { struct lwan l; @@ -435,6 +363,14 @@ int main(void) lwan_init(&l); lwan_detect_url_map(&l); + smolsite_zip_base64 = base64_encode_to_value(smolsite_zip_value); + + iframe_tpl = lwan_tpl_compile_value_full(smolsite_html_value, + iframe_tpl_desc, + LWAN_TPL_FLAG_CONST_TEMPLATE); + if (!iframe_tpl) + lwan_status_critical("Could not compile template"); + sites = cache_create_full(create_site, destroy_site, hash_str_new, NULL, CACHE_FOR_MINUTES * 60); if (!sites) @@ -442,6 +378,9 @@ int main(void) lwan_main_loop(&l); lwan_shutdown(&l); + cache_destroy(sites); + lwan_tpl_free(iframe_tpl); + free(smolsite_zip_base64.value); return 0; } From 85955c229e5229fc7d6bcd7d162e7bdd1baabd82 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 31 Aug 2023 19:44:48 -0700 Subject: [PATCH 2100/2505] Add index.html and smolsite.html to the smolsite sample I forgot to add these to the previous commit. D'oh! --- src/samples/smolsite/index.html | 33 ++++++++++++++ src/samples/smolsite/smolsite.html | 72 ++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 src/samples/smolsite/index.html create mode 100644 src/samples/smolsite/smolsite.html diff --git a/src/samples/smolsite/index.html b/src/samples/smolsite/index.html new file mode 100644 index 000000000..2c7cc0a0c --- /dev/null +++ b/src/samples/smolsite/index.html @@ -0,0 +1,33 @@ + + + +

It's a smolsite!

+ +

This whole site fits inside the URL!

+ +

To host yours: use the command-line: put everything in a ZIP file, Base 64 encode it, and tack it after "https://smolsite.zip/":

+ +
+$ zip -DXjq9 somesite.zip index.html mylogo.png
+$ echo "https://smolsite.zip/%60cat%20somesite.zip%20|%20base64%20--wrap%200%60"
+
+ + + + diff --git a/src/samples/smolsite/smolsite.html b/src/samples/smolsite/smolsite.html new file mode 100644 index 000000000..a32e823e0 --- /dev/null +++ b/src/samples/smolsite/smolsite.html @@ -0,0 +1,72 @@ + + + 🗜 omg it's a smolsite + + + + +

Hosted in the URL by 🗜️ + smolsite.zip. Powered by the + Lwan web server. +

+ + + \ No newline at end of file From 2351d9221904a40da42c748aacaac566cf312829 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 31 Aug 2023 19:52:03 -0700 Subject: [PATCH 2101/2505] Send Cache-Control headers from smolsite sample This is to avoid both browsers and proxies from caching the redirect to the smolsite_zip_base64 website. --- src/samples/smolsite/main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/samples/smolsite/main.c b/src/samples/smolsite/main.c index f94cc383d..19976a369 100644 --- a/src/samples/smolsite/main.c +++ b/src/samples/smolsite/main.c @@ -225,6 +225,7 @@ LWAN_HANDLER_ROUTE(view_root, "/") if (!request->url.len) { const struct lwan_key_value redir_headers[] = { {"Location", smolsite_zip_base64.value}, + {"Cache-Control", "no-cache, max-age=0, private, no-transform"}, {}, }; response->headers = coro_memdup(request->conn->coro, From ba653c55ce8b92ffd2a8622a7a3a433203fbf887 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 1 Sep 2023 00:01:30 -0700 Subject: [PATCH 2102/2505] jzBuffer shouldn't be a global Oof, I should've looked more carefully into junzip.c when I imported it: jzBuffer is a global in reentrant code. Ooops. --- src/samples/smolsite/junzip.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/samples/smolsite/junzip.c b/src/samples/smolsite/junzip.c index 8f7f824d0..a7fd2d4e3 100644 --- a/src/samples/smolsite/junzip.c +++ b/src/samples/smolsite/junzip.c @@ -6,11 +6,10 @@ #include "junzip.h" -unsigned char jzBuffer[JZ_BUFFER_SIZE]; // limits maximum zip descriptor size - // Read ZIP file end record. Will move within file. int jzReadEndRecord(JZFile *zip, JZEndRecord *endRecord) { + unsigned char jzBuffer[JZ_BUFFER_SIZE]; size_t fileSize, readBytes, i; JZEndRecord *er; @@ -64,6 +63,7 @@ int jzReadCentralDirectory(JZFile *zip, JZRecordCallback callback, void *user_data) { + unsigned char jzBuffer[JZ_BUFFER_SIZE]; JZGlobalFileHeader fileHeader; JZFileHeader header; int i; @@ -192,6 +192,8 @@ int jzReadData(JZFile *zip, JZFileHeader *header, void *buffer) return Z_ERRNO; #ifdef HAVE_ZLIB } else if (header->compressionMethod == 8) { // Deflate - using zlib + unsigned char jzBuffer[JZ_BUFFER_SIZE]; + strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; From 85e8b1d5941785dfd61b1cdcc4a3a75673036e9a Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 1 Sep 2023 00:02:34 -0700 Subject: [PATCH 2103/2505] Return error 500 if allocating space for digest fails --- src/samples/smolsite/main.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/samples/smolsite/main.c b/src/samples/smolsite/main.c index 19976a369..bcf74bd0b 100644 --- a/src/samples/smolsite/main.c +++ b/src/samples/smolsite/main.c @@ -251,6 +251,8 @@ LWAN_HANDLER_ROUTE(view_root, "/") return HTTP_INTERNAL_ERROR; char *key = strdup(digest_str); + if (UNLIKELY(!key)) + return HTTP_INTERNAL_ERROR; if (UNLIKELY(hash_add_unique(pending_sites(), key, zip))) { free(key); free(zip); From 82a007aa79c73671125a6ba81bacca5a6d22ed68 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 1 Sep 2023 00:16:42 -0700 Subject: [PATCH 2104/2505] Move jzBuffer to struct JZFile Make sure there's as little sharing per JZFile as possible. Uses more heap but it's safer (and temporary!). --- src/samples/smolsite/junzip.c | 22 +++++++++------------- src/samples/smolsite/junzip.h | 4 +++- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/samples/smolsite/junzip.c b/src/samples/smolsite/junzip.c index a7fd2d4e3..813cc80c9 100644 --- a/src/samples/smolsite/junzip.c +++ b/src/samples/smolsite/junzip.c @@ -9,7 +9,6 @@ // Read ZIP file end record. Will move within file. int jzReadEndRecord(JZFile *zip, JZEndRecord *endRecord) { - unsigned char jzBuffer[JZ_BUFFER_SIZE]; size_t fileSize, readBytes, i; JZEndRecord *er; @@ -23,21 +22,21 @@ int jzReadEndRecord(JZFile *zip, JZEndRecord *endRecord) return Z_ERRNO; } - readBytes = (fileSize < sizeof(jzBuffer)) ? fileSize : sizeof(jzBuffer); + readBytes = (fileSize < sizeof(zip->buffer)) ? fileSize : sizeof(zip->buffer); if (zip->seek(zip, fileSize - readBytes, SEEK_SET)) { fprintf(stderr, "Cannot seek in zip file!"); return Z_ERRNO; } - if (zip->read(zip, jzBuffer, readBytes) < readBytes) { + if (zip->read(zip, zip->buffer, readBytes) < readBytes) { fprintf(stderr, "Couldn't read end of zip file!"); return Z_ERRNO; } // Naively assume signature can only be found in one place... for (i = readBytes - sizeof(JZEndRecord); i; i--) { - er = (JZEndRecord *)(jzBuffer + i); + er = (JZEndRecord *)(zip->buffer + i); if (er->signature == 0x06054B50) goto signature_found; } @@ -63,7 +62,6 @@ int jzReadCentralDirectory(JZFile *zip, JZRecordCallback callback, void *user_data) { - unsigned char jzBuffer[JZ_BUFFER_SIZE]; JZGlobalFileHeader fileHeader; JZFileHeader header; int i; @@ -90,13 +88,13 @@ int jzReadCentralDirectory(JZFile *zip, return Z_ERRNO; } - if (zip->read(zip, jzBuffer, fileHeader.fileNameLength) < + if (zip->read(zip, zip->buffer, fileHeader.fileNameLength) < fileHeader.fileNameLength) { fprintf(stderr, "Couldn't read filename %d!", i); return Z_ERRNO; } - jzBuffer[fileHeader.fileNameLength] = '\0'; // NULL terminate + zip->buffer[fileHeader.fileNameLength] = '\0'; // NULL terminate if (zip->seek(zip, fileHeader.extraFieldLength, SEEK_CUR) || zip->seek(zip, fileHeader.fileCommentLength, SEEK_CUR)) { @@ -108,7 +106,7 @@ int jzReadCentralDirectory(JZFile *zip, memcpy(&header, &fileHeader.compressionMethod, sizeof(header)); header.offset = fileHeader.relativeOffsetOflocalHeader; - if (!callback(zip, i, &header, (char *)jzBuffer, user_data)) + if (!callback(zip, i, &header, (char *)zip->buffer, user_data)) break; // end if callback returns zero } @@ -192,8 +190,6 @@ int jzReadData(JZFile *zip, JZFileHeader *header, void *buffer) return Z_ERRNO; #ifdef HAVE_ZLIB } else if (header->compressionMethod == 8) { // Deflate - using zlib - unsigned char jzBuffer[JZ_BUFFER_SIZE]; - strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; @@ -212,8 +208,8 @@ int jzReadData(JZFile *zip, JZFileHeader *header, void *buffer) compressedLeft -= strm.avail_in) { // Read next chunk strm.avail_in = - zip->read(zip, jzBuffer, - (sizeof(jzBuffer) < compressedLeft) ? sizeof(jzBuffer) + zip->read(zip, zip->buffer, + (sizeof(zip->buffer) < compressedLeft) ? sizeof(zip->buffer) : compressedLeft); if (strm.avail_in == 0 || zip->error(zip)) { @@ -221,7 +217,7 @@ int jzReadData(JZFile *zip, JZFileHeader *header, void *buffer) return Z_ERRNO; } - strm.next_in = jzBuffer; + strm.next_in = zip->buffer; strm.avail_out = uncompressedLeft; strm.next_out = bytes; diff --git a/src/samples/smolsite/junzip.h b/src/samples/smolsite/junzip.h index aa696a1ce..4e6b8e313 100644 --- a/src/samples/smolsite/junzip.h +++ b/src/samples/smolsite/junzip.h @@ -41,12 +41,15 @@ extern "C" { typedef struct JZFile JZFile; +#define JZ_BUFFER_SIZE 65536 + struct JZFile { size_t (*read)(JZFile *file, void *buf, size_t size); size_t (*tell)(JZFile *file); int (*seek)(JZFile *file, size_t offset, int whence); int (*error)(JZFile *file); void (*close)(JZFile *file); + unsigned char buffer[JZ_BUFFER_SIZE]; }; JZFile * @@ -114,7 +117,6 @@ typedef struct __attribute__ ((__packed__)) { typedef int (*JZRecordCallback)(JZFile *zip, int index, JZFileHeader *header, char *filename, void *user_data); -#define JZ_BUFFER_SIZE 65536 // Read ZIP file end record. Will move within file. int jzReadEndRecord(JZFile *zip, JZEndRecord *endRecord); From ebbe6f37718167e51a344edef9fd57c9f121c9d2 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 5 Sep 2023 20:09:35 -0700 Subject: [PATCH 2105/2505] Ensure file offset is within ZIP file --- src/samples/smolsite/main.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/samples/smolsite/main.c b/src/samples/smolsite/main.c index bcf74bd0b..9bba2022d 100644 --- a/src/samples/smolsite/main.c +++ b/src/samples/smolsite/main.c @@ -130,6 +130,12 @@ static int file_cb( if ((size_t)data_offset > site->zipped.len) return 0; + uint32_t last_data_offset; + if (__builtin_add_overflow(local.compressedSize, data_offset, &last_data_offset)) + return 0; + if (last_data_offset > site->zipped.len) + return 0; + struct file *file = malloc(sizeof(*file)); if (!file) return 0; From e9dc5d6378b1ca790ec6c71e36556e9937ce2dd2 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 5 Sep 2023 21:30:00 -0700 Subject: [PATCH 2106/2505] Ensure buffer read from file has a terminating NUL --- src/lib/lwan-strbuf.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-strbuf.c b/src/lib/lwan-strbuf.c index 4be6f7146..b945a8494 100644 --- a/src/lib/lwan-strbuf.c +++ b/src/lib/lwan-strbuf.c @@ -362,7 +362,10 @@ bool lwan_strbuf_init_from_file(struct lwan_strbuf *s, const char *path) if (UNLIKELY(fstat(fd, &st) < 0)) goto error; - if (UNLIKELY(!lwan_strbuf_init_with_size(s, (size_t)st.st_size))) + size_t min_buf_size; + if (UNLIKELY(__builtin_add_overflow(st.st_size, 1, &min_buf_size))) + goto error; + if (UNLIKELY(!lwan_strbuf_init_with_size(s, min_buf_size))) goto error; s->used = (size_t)st.st_size; @@ -377,6 +380,7 @@ bool lwan_strbuf_init_from_file(struct lwan_strbuf *s, const char *path) } buffer += n_read; + *buffer = '\0'; st.st_size -= (off_t)n_read; } From d6999b76781ba072d183c19accf9b3006a5cb1af Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 5 Sep 2023 21:31:43 -0700 Subject: [PATCH 2107/2505] Generate more ACTION_APPEND_SMALL instructions While this increases the number of instruction dispatches, this reduces pointer chasing to another strbuf and buffer it points to. Use a simple heuristic (string length <= 4 * sizeof(void *)) to determine which instruction to generate. --- src/lib/lwan-template.c | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index 1f4a734b0..4b11983ef 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -854,17 +854,32 @@ static void *parser_text(struct parser *parser, struct lexeme *lexeme) return parser_meta; if (lexeme->type == LEXEME_TEXT) { - if (lexeme->value.len <= sizeof(void *)) { - uintptr_t tmp = 0; - - memcpy(&tmp, lexeme->value.value, lexeme->value.len); - emit_chunk(parser, ACTION_APPEND_SMALL, 0, (void*)tmp); - } else { + if (lexeme->value.len > 4 * sizeof(void *)) { struct lwan_strbuf *buf = lwan_strbuf_from_lexeme(parser, lexeme); if (!buf) return error_lexeme(lexeme, "Out of memory"); emit_chunk(parser, ACTION_APPEND, 0, buf); + } else { + struct lexeme copy = *lexeme; + + while (copy.value.len) { + const size_t len = LWAN_MIN(sizeof(void *), copy.value.len); + uintptr_t tmp = 0; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstringop-truncation" + /* strnlen(..., sizeof(void*)) is used for APPEND_SMALL, + * so it's fine if no NUL is generated for strings with + * sizeof(void *) characters. */ + strncpy((char *)&tmp, copy.value.value, len); +#pragma GCC diagnostic pop + + emit_chunk(parser, ACTION_APPEND_SMALL, 0, (void *)tmp); + + copy.value.len -= len; + copy.value.value += len; + } } parser->tpl->minimum_size += lexeme->value.len; return parser_text; From 493aa337cd4165f0d075e3bf9efdaa72a7654647 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 10 Sep 2023 09:09:59 -0700 Subject: [PATCH 2108/2505] Support the NO_COLOR environment variable --- src/lib/lwan-status.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/lib/lwan-status.c b/src/lib/lwan-status.c index f8783f8dd..68d0537e7 100644 --- a/src/lib/lwan-status.c +++ b/src/lib/lwan-status.c @@ -63,10 +63,19 @@ void lwan_status_shutdown(struct lwan *l __attribute__((unused))) {} static bool can_use_colors(void) { const char *term; + const char *no_color; if (!isatty(fileno(stdout))) return false; + /* From https://no-color.org: "Command-line software which adds ANSI + * color to its output by default should check for a NO_COLOR + * environment variable that, when present and not an empty string + * (regardless of its value), prevents the addition of ANSI color." */ + no_color = secure_getenv("NO_COLOR"); + if (no_color && no_color[0]) + return false; + term = secure_getenv("TERM"); if (term && streq(term, "dumb")) return false; From 82a807b26658f0b11f613cb0c2fabc67b007c172 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 10 Sep 2023 09:12:31 -0700 Subject: [PATCH 2109/2505] Bundle templates for mod-serve-file and response from HTML files This makes it easier to change the templates without having to worry about escaping, C strings, etc. --- src/lib/CMakeLists.txt | 33 ++++++++++++++++----- src/lib/lwan-mod-serve-files.c | 52 +++------------------------------ src/lib/lwan-response.c | 31 +++----------------- src/lib/response-template.html | 24 +++++++++++++++ src/lib/servefile-template.html | 41 ++++++++++++++++++++++++++ 5 files changed, 98 insertions(+), 83 deletions(-) create mode 100644 src/lib/response-template.html create mode 100644 src/lib/servefile-template.html diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index 4869c4ea3..b023977ca 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -89,23 +89,40 @@ add_custom_target(generate_mime_types_table add_dependencies(lwan-static generate_mime_types_table) add_custom_command( - OUTPUT ${CMAKE_BINARY_DIR}/auto-index-icons.h + OUTPUT ${CMAKE_BINARY_DIR}/servefile-data.h COMMAND bin2hex ${CMAKE_SOURCE_DIR}/wwwroot/icons/back.gif back_gif ${CMAKE_SOURCE_DIR}/wwwroot/icons/file.gif file_gif - ${CMAKE_SOURCE_DIR}/wwwroot/icons/folder.gif - folder_gif > - ${CMAKE_BINARY_DIR}/auto-index-icons.h + ${CMAKE_SOURCE_DIR}/wwwroot/icons/folder.gif folder_gif + ${CMAKE_SOURCE_DIR}/src/lib/servefile-template.html servefile_template + > + ${CMAKE_BINARY_DIR}/servefile-data.h DEPENDS ${CMAKE_SOURCE_DIR}/wwwroot/icons/back.gif ${CMAKE_SOURCE_DIR}/wwwroot/icons/file.gif ${CMAKE_SOURCE_DIR}/wwwroot/icons/folder.gif + ${CMAKE_SOURCE_DIR}/src/lib/servefile-template.html + bin2hex + COMMENT "Bundling data for serve-files module" +) +add_custom_target(bundle_servefile_data + DEPENDS ${CMAKE_BINARY_DIR}/servefile-data.h +) +add_dependencies(lwan-static bundle_servefile_data) + +add_custom_command( + OUTPUT ${CMAKE_BINARY_DIR}/response-data.h + COMMAND bin2hex + ${CMAKE_SOURCE_DIR}/src/lib/response-template.html response_template + > + ${CMAKE_BINARY_DIR}/response-data.h + DEPENDS ${CMAKE_SOURCE_DIR}/src/lib/response-template.html bin2hex - COMMENT "Bundling auto-index icons" + COMMENT "Bundling data for response" ) -add_custom_target(generate_auto_index_icons - DEPENDS ${CMAKE_BINARY_DIR}/auto-index-icons.h +add_custom_target(bundle_response_data + DEPENDS ${CMAKE_BINARY_DIR}/response-data.h ) -add_dependencies(lwan-static generate_auto_index_icons) +add_dependencies(lwan-static bundle_response_data) if (NOT HAVE_BUILTIN_FPCLASSIFY) set(ADDITIONAL_LIBRARIES ${ADDITIONAL_LIBRARIES} -lm PARENT_SCOPE) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 47ed822bb..e0b476d0b 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -40,7 +40,7 @@ #include "lwan-template.h" #include "int-to-str.h" -#include "auto-index-icons.h" +#include "servefile-data.h" #if defined(LWAN_HAVE_BROTLI) #include @@ -253,51 +253,6 @@ static const struct lwan_var_descriptor file_list_desc[] = { TPL_VAR_SENTINEL, }; - -static const char *directory_list_tpl_str = - "\n" - "\n" - "{{rel_path?}} Index of {{rel_path}}{{/rel_path?}}\n" - "{{^rel_path?}} Index of /{{/rel_path?}}\n" - "\n" - "\n" - "\n" - "{{rel_path?}}

Index of {{rel_path}}

\n{{/rel_path?}}" - "{{^rel_path?}}

Index of /

\n{{/rel_path?}}" - "{{readme?}}
{{readme}}
\n{{/readme?}}" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - "{{#file_list}}" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - "{{/file_list}}" - "{{^#file_list}}" - " \n" - " \n" - " \n" - "{{/file_list}}" - "
Parent directory
 File nameTypeSize
{{{file_list.name}}}{{file_list.type}}{{file_list.size}}{{file_list.unit}}
Empty directory.
\n" - "\n" - "\n"; - static int directory_list_generator(struct coro *coro, void *data) { static const char *zebra_classes[] = {"odd", "even"}; @@ -986,8 +941,9 @@ static void *serve_files_create(const char *prefix, void *args) settings->directory_list_template, file_list_desc); } else { priv->directory_list_tpl = - lwan_tpl_compile_string_full(directory_list_tpl_str, file_list_desc, - LWAN_TPL_FLAG_CONST_TEMPLATE); + lwan_tpl_compile_value_full(servefile_template_value, + file_list_desc, + LWAN_TPL_FLAG_CONST_TEMPLATE); } if (!priv->directory_list_tpl) { lwan_status_error("Could not compile directory list template"); diff --git a/src/lib/lwan-response.c b/src/lib/lwan-response.c index dd7c7f04c..5d72ea282 100644 --- a/src/lib/lwan-response.c +++ b/src/lib/lwan-response.c @@ -32,32 +32,9 @@ #include "lwan-io-wrappers.h" #include "lwan-template.h" -static struct lwan_tpl *error_template = NULL; +#include "response-data.h" -static const char *error_template_str = "" \ - "" \ - "" \ - "
" \ - "
" \ - "

{{short_message}}

" \ - "
" \ - "

{{long_message}}

" \ - "
" \ - "
" \ - "
" \ - "" \ - ""; +static struct lwan_tpl *error_template = NULL; struct error_template { const char *short_message; @@ -80,8 +57,8 @@ void lwan_response_init(struct lwan *l) error_template = lwan_tpl_compile_file(l->config.error_template, error_descriptor); } else { - error_template = lwan_tpl_compile_string_full( - error_template_str, error_descriptor, LWAN_TPL_FLAG_CONST_TEMPLATE); + error_template = lwan_tpl_compile_value_full( + response_template_value, error_descriptor, LWAN_TPL_FLAG_CONST_TEMPLATE); } if (UNLIKELY(!error_template)) diff --git a/src/lib/response-template.html b/src/lib/response-template.html new file mode 100644 index 000000000..1490cd191 --- /dev/null +++ b/src/lib/response-template.html @@ -0,0 +1,24 @@ + + + +
+
+

{{short_message}}

+
+

{{long_message}}

+
+
+
+ + diff --git a/src/lib/servefile-template.html b/src/lib/servefile-template.html new file mode 100644 index 000000000..0d8427f4f --- /dev/null +++ b/src/lib/servefile-template.html @@ -0,0 +1,41 @@ + + +{{rel_path?}} Index of {{rel_path}}{{/rel_path?}} +{{^rel_path?}} Index of /{{/rel_path?}} + + + +{{rel_path?}}

Index of {{rel_path}}

\n{{/rel_path?}} +{{^rel_path?}}

Index of /

\n{{/rel_path?}} +{{readme?}}
{{readme}}
\n{{/readme?}} + + + + + + + + + + + +{{#file_list}} + + + + + + +{{/file_list}} +{{^#file_list}} + + + +{{/file_list}} +
Parent directory
 File nameTypeSize
{{file_list.icon_alt}}{{{file_list.name}}}{{file_list.type}}{{file_list.size}}{{file_list.unit}}
Empty directory.
+ + From cc28ebe6cabdc5906257a868014c2f92c9c3ffb5 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 10 Sep 2023 09:14:02 -0700 Subject: [PATCH 2110/2505] Reduce size of template instructions Use an union to store the instruction label address and the action. This should actually be in separate structs, but for now let's do it like this. --- src/lib/lwan-template.c | 48 +++++++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index 4b11983ef..796ebd139 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -97,10 +97,15 @@ static const char *lexeme_type_str[TOTAL_LEXEMES] = { #undef GENERATE_ARRAY_ITEM struct chunk { - const void *instruction; + union { + /* `instruction` is filled when baking computed goto address + * from `action`. This needs to be "unbaked" when freeing + * a template. */ + const void *instruction; + enum action action; + }; void *data; enum flags flags; - enum action action; }; DEFINE_ARRAY_TYPE(chunk_array, struct chunk) @@ -108,7 +113,7 @@ DEFINE_ARRAY_TYPE(chunk_array, struct chunk) struct lwan_tpl { struct chunk_array chunks; size_t minimum_size; - bool dispatch_table_direct; + const void *const *dispatch_table; }; struct symtab { @@ -1007,6 +1012,27 @@ bool lwan_tpl_str_is_empty(void *ptr) return !str || *str == '\0'; } +static void +unbake_direct_addresses(struct lwan_tpl *tpl) +{ + struct chunk *iter; + + assert(tpl->dispatch_table); + + LWAN_ARRAY_FOREACH (&tpl->chunks, iter) { + for (enum action action = ACTION_APPEND; action <= ACTION_LAST; action++) { + if (iter->instruction != tpl->dispatch_table[action]) + continue; + + iter->action = action; + if (action == ACTION_APPLY_TPL) + unbake_direct_addresses(iter->data); + + break; + } + } +} + static void free_chunk(struct chunk *chunk) { if (!chunk) @@ -1049,6 +1075,7 @@ static void free_chunk_array(struct chunk_array *array) void lwan_tpl_free(struct lwan_tpl *tpl) { if (tpl) { + unbake_direct_addresses(tpl); free_chunk_array(&tpl->chunks); free(tpl); } @@ -1437,9 +1464,10 @@ bake_direct_addresses(struct lwan_tpl *tpl, iter->instruction = dispatch_table[iter->action]; } - tpl->dispatch_table_direct = true; + tpl->dispatch_table = dispatch_table; } + static const struct chunk *apply(struct lwan_tpl *tpl, const struct chunk *chunks, struct lwan_strbuf *buf, @@ -1453,7 +1481,7 @@ static const struct chunk *apply(struct lwan_tpl *tpl, if (UNLIKELY(!chunk)) return NULL; - if (!tpl->dispatch_table_direct) { + if (!tpl->dispatch_table) { static const void *const dispatch_table[] = { [ACTION_APPEND] = &&action_append, [ACTION_APPEND_SMALL] = &&action_append_small, @@ -1675,17 +1703,19 @@ int main(int argc, char *argv[]) } printf("*** Compiling template...\n"); + #undef TPL_STRUCT + #define TPL_STRUCT struct test_struct struct lwan_var_descriptor desc[] = { - TPL_VAR_INT(struct test_struct, some_int), - TPL_VAR_STR(struct test_struct, a_string), + TPL_VAR_INT(some_int), + TPL_VAR_STR(a_string), TPL_VAR_SENTINEL }; struct lwan_tpl *tpl = lwan_tpl_compile_file(argv[1], desc); if (!tpl) return 1; - printf("*** Applying template 100000 times...\n"); - for (size_t i = 0; i < 100000; i++) { + printf("*** Applying template 10000000 times...\n"); + for (size_t i = 0; i < 10000000; i++) { struct lwan_strbuf *applied = lwan_tpl_apply(tpl, &(struct test_struct) { .some_int = 42, .a_string = "some string" From 90adafba65d019a0740ea27f29c32dfa3e0686b2 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 10 Sep 2023 09:15:24 -0700 Subject: [PATCH 2111/2505] Experimental feature: use dynamically-allocated buffer for requests This is an experimental feature to support larger request buffer sizes, and can be configured by using the "use_dynamic_buffer" configuration option. --- README.md | 1 + src/lib/lwan-thread.c | 40 +++++++++++++++++++++++++++++----------- src/lib/lwan.c | 4 ++++ src/lib/lwan.h | 3 +++ 4 files changed, 37 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 966a289f8..0dd8e5201 100644 --- a/README.md +++ b/README.md @@ -306,6 +306,7 @@ can be decided automatically, so some configuration options are provided. | `max_put_data_size` | `int` | `40960` | Sets the maximum number of data size for PUT requests, in bytes | | `allow_temp_files` | `str` | `""` | Use temporary files; set to `post` for POST requests, `put` for PUT requests, or `all` (equivalent to setting to `post put`) for both.| | `error_template` | `str` | Default error template | Template for error codes. See variables below. | +| `use_dynamic_buffer` | `bool` | `false` | **Experimental:** use a dynamically-allocated buffer for requests. | #### Variables for `error_template` diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 6852214e3..a2e584988 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -57,8 +57,7 @@ static void lwan_strbuf_free_defer(void *data) } static void graceful_close(struct lwan *l, - struct lwan_connection *conn, - char buffer[static DEFAULT_BUFFER_SIZE]) + struct lwan_connection *conn) { int fd = lwan_connection_get_fd(l, conn); @@ -82,8 +81,9 @@ static void graceful_close(struct lwan *l, return; } + char buffer[128]; for (int tries = 0; tries < 20; tries++) { - ssize_t r = recv(fd, buffer, DEFAULT_BUFFER_SIZE, MSG_TRUNC); + ssize_t r = recv(fd, buffer, sizeof(buffer), MSG_TRUNC); if (!r) break; @@ -371,18 +371,15 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, int fd = lwan_connection_get_fd(lwan, conn); enum lwan_request_flags flags = lwan->config.request_flags; struct lwan_strbuf strbuf = LWAN_STRBUF_STATIC_INIT; - char request_buffer[DEFAULT_BUFFER_SIZE]; - struct lwan_value buffer = {.value = request_buffer, .len = 0}; + struct lwan_value buffer; char *next_request = NULL; char *header_start[N_HEADER_START]; struct lwan_proxy proxy; const int error_when_n_packets = lwan_calculate_n_packets(DEFAULT_BUFFER_SIZE); + size_t init_gen; coro_defer(coro, lwan_strbuf_free_defer, &strbuf); - const size_t init_gen = 1; /* 1 call to coro_defer() */ - assert(init_gen == coro_deferred_get_generation(coro)); - #if defined(LWAN_HAVE_MBEDTLS) if (conn->flags & CONN_TLS) { if (UNLIKELY(!lwan_setup_tls(lwan, conn))) { @@ -394,6 +391,21 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, assert(!(conn->flags & CONN_TLS)); #endif + if (conn->flags & CONN_USE_DYNAMIC_BUFFER) { + const size_t dynamic_buffer_len = DEFAULT_BUFFER_SIZE * 16; + buffer = (struct lwan_value){ + .value = coro_malloc(conn->coro, dynamic_buffer_len), + .len = dynamic_buffer_len, + }; + init_gen = 2; + } else { + buffer = (struct lwan_value){ + .value = alloca(DEFAULT_BUFFER_SIZE), + .len = DEFAULT_BUFFER_SIZE, + }; + init_gen = 1; + } + while (true) { struct lwan_request_parser_helper helper = { .buffer = &buffer, @@ -417,7 +429,7 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, coro_deferred_run(coro, init_gen); if (UNLIKELY(!(conn->flags & CONN_IS_KEEP_ALIVE))) { - graceful_close(lwan, conn, request_buffer); + graceful_close(lwan, conn); break; } @@ -678,9 +690,9 @@ static ALWAYS_INLINE bool spawn_coro(struct lwan_connection *conn, { struct lwan_thread *t = conn->thread; #if defined(LWAN_HAVE_MBEDTLS) - const enum lwan_connection_flags flags_to_keep = conn->flags & CONN_TLS; + const enum lwan_connection_flags flags_to_keep = conn->flags & (CONN_TLS | CONN_USE_DYNAMIC_BUFFER); #else - const enum lwan_connection_flags flags_to_keep = 0; + const enum lwan_connection_flags flags_to_keep = CONN_USE_DYNAMIC_BUFFER; #endif assert(!conn->coro); @@ -1319,6 +1331,12 @@ void lwan_thread_init(struct lwan *l) adj_affinity = false; } + if (l->config.use_dynamic_buffer) { + lwan_status_debug("Using dynamically-allocated buffers"); + for (unsigned int i = 0; i < total_conns; i++) + l->conns[i].flags = CONN_USE_DYNAMIC_BUFFER; + } + for (unsigned int i = 0; i < l->thread.count; i++) { struct lwan_thread *thread = NULL; diff --git a/src/lib/lwan.c b/src/lib/lwan.c index cf6ead2c7..8840f8317 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -64,6 +64,7 @@ static const struct lwan_config default_config = { .allow_post_temp_file = false, .max_put_data_size = 10 * DEFAULT_BUFFER_SIZE, .allow_put_temp_file = false, + .use_dynamic_buffer = false, }; LWAN_HANDLER_ROUTE(brew_coffee, NULL /* do not autodetect this route */) @@ -628,6 +629,9 @@ static bool setup_from_config(struct lwan *lwan, const char *path) } else if (streq(line->key, "allow_cors")) { lwan->config.allow_cors = parse_bool(line->value, default_config.allow_cors); + } else if (streq(line->key, "use_dynamic_buffer")) { + lwan->config.use_dynamic_buffer = + parse_bool(line->value, default_config.use_dynamic_buffer); } else if (streq(line->key, "expires")) { lwan->config.expires = parse_time_period(line->value, default_config.expires); diff --git a/src/lib/lwan.h b/src/lib/lwan.h index f5b688cb0..e03416842 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -316,6 +316,8 @@ enum lwan_connection_flags { /* Both are used to know if an epoll event pertains to a listener rather * than a client. */ CONN_LISTENER = 1 << 11, + + CONN_USE_DYNAMIC_BUFFER = 1 << 12, }; enum lwan_connection_coro_yield { @@ -521,6 +523,7 @@ struct lwan_config { bool allow_cors; bool allow_post_temp_file; bool allow_put_temp_file; + bool use_dynamic_buffer; }; struct lwan { From 4ebf0578c3ea26b6058cf6f384a5b6c03ca630c8 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 10 Sep 2023 09:17:14 -0700 Subject: [PATCH 2112/2505] Don't send Connection: keep-alive header for HTTP/1.1 requests That's implicit. This saves up a few bytes in the response! --- src/lib/lwan-request.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 6792b6d83..2e6364dd3 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -783,6 +783,7 @@ ignore_leading_whitespace(char *buffer) static ALWAYS_INLINE void parse_connection_header(struct lwan_request *request) { struct lwan_request_parser_helper *helper = request->helper; + struct lwan_connection *conn = request->conn; bool has_keep_alive = false; bool has_close = false; @@ -801,7 +802,7 @@ static ALWAYS_INLINE void parse_connection_header(struct lwan_request *request) break; case STR4_INT_L('u','p','g','r'): case STR4_INT_L(' ', 'u','p','g'): - request->conn->flags |= CONN_IS_UPGRADE; + conn->flags |= CONN_IS_UPGRADE; break; } @@ -810,14 +811,16 @@ static ALWAYS_INLINE void parse_connection_header(struct lwan_request *request) } out: - if (LIKELY(!(request->flags & REQUEST_IS_HTTP_1_0))) + if (LIKELY(!(request->flags & REQUEST_IS_HTTP_1_0))) { has_keep_alive = !has_close; + if (has_keep_alive) + conn->flags |= CONN_SENT_CONNECTION_HEADER; + } if (has_keep_alive) { - request->conn->flags |= CONN_IS_KEEP_ALIVE; + conn->flags |= CONN_IS_KEEP_ALIVE; } else { - request->conn->flags &= - ~(CONN_IS_KEEP_ALIVE | CONN_SENT_CONNECTION_HEADER); + conn->flags &= ~(CONN_IS_KEEP_ALIVE | CONN_SENT_CONNECTION_HEADER); } } From 8745220f60b5b74c12e53e076e6bc1084a399416 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 10 Sep 2023 09:18:13 -0700 Subject: [PATCH 2113/2505] Slightly better ways of validating/cleaning strings in smolsite --- src/samples/smolsite/main.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/samples/smolsite/main.c b/src/samples/smolsite/main.c index 9bba2022d..9e9a38504 100644 --- a/src/samples/smolsite/main.c +++ b/src/samples/smolsite/main.c @@ -242,10 +242,8 @@ LWAN_HANDLER_ROUTE(view_root, "/") /* Lwan gives us a percent-decoded URL, but '+' is part of the Base64 * alphabet */ - for (char *p = request->url.value; *p; p++) { - if (*p == ' ') - *p = '+'; - } + for (char *p = strchr(request->url.value, ' '); p; p = strchr(p + 1, ' ')) + *p = '+'; calc_hash(request->url, digest_str); @@ -314,10 +312,8 @@ LWAN_HANDLER_ROUTE(view_site, "/s/") if (slash - request->url.value < 40) return HTTP_NOT_FOUND; *slash = '\0'; - for (const char *p = request->url.value; *p; p++) { - if (!isxdigit(*p)) - return HTTP_NOT_FOUND; - } + if (strcspn(request->url.value, "0123456789abcdefABCDEF")) + return HTTP_NOT_FOUND; const char *file_name = slash + 1; From f7085592f3cb0d60faeee25238b0cc626a46387a Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 16 Sep 2023 10:35:31 -0700 Subject: [PATCH 2114/2505] `--modules` and `--handlers` do not exist anymore Modules and handlers are always printed out when invoking Lwan with `--help`. --- src/bin/lwan/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/lwan/main.c b/src/bin/lwan/main.c index b7b202dc5..635b9dc9f 100644 --- a/src/bin/lwan/main.c +++ b/src/bin/lwan/main.c @@ -106,7 +106,7 @@ print_help(const char *argv0, const struct lwan_config *config) const char *config_file = lwan_get_config_path(path_buf, sizeof(path_buf)); printf("Usage: %s [--root /path/to/root/dir] [--listen addr:port]\n", argv0); - printf(" [--config] [--user username] [--chroot] [--modules|--handlers]\n"); + printf(" [--config] [--user username] [--chroot]\n"); printf("\n"); #ifdef LWAN_HAVE_MBEDTLS printf("Serve files through HTTP or HTTPS.\n\n"); From b234db01be5e62fe926cd52b99b8d35d092b85f8 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 16 Sep 2023 10:42:01 -0700 Subject: [PATCH 2115/2505] Don't print out handlers without route with --help --- src/bin/lwan/main.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/bin/lwan/main.c b/src/bin/lwan/main.c index 635b9dc9f..c4956a514 100644 --- a/src/bin/lwan/main.c +++ b/src/bin/lwan/main.c @@ -47,10 +47,18 @@ static void print_handler_info(void) { const struct lwan_handler_info *handler; + int count = 0; printf("Built-in handlers:"); LWAN_SECTION_FOREACH(lwan_handler, handler) { + if (!handler->route) + continue; + printf(" %s", handler->name); + count++; + } + if (!count) { + printf(" (none)"); } printf(".\n"); } From 6513d1215def7ac0d054774a65e9a08ad61b97fc Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 16 Sep 2023 10:43:44 -0700 Subject: [PATCH 2116/2505] Print TLS flags in --help summary --- src/bin/lwan/main.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/bin/lwan/main.c b/src/bin/lwan/main.c index c4956a514..1c0a5f63a 100644 --- a/src/bin/lwan/main.c +++ b/src/bin/lwan/main.c @@ -114,7 +114,11 @@ print_help(const char *argv0, const struct lwan_config *config) const char *config_file = lwan_get_config_path(path_buf, sizeof(path_buf)); printf("Usage: %s [--root /path/to/root/dir] [--listen addr:port]\n", argv0); - printf(" [--config] [--user username] [--chroot]\n"); +#ifdef LWAN_HAVE_MBEDTLS + printf(" [--tls-listen addr:port] [--cert-path /cert/path] [--cert-key /key/path]\n"); +#endif + printf(" [--config /path/to/config/file] [--user username]\n"); + printf(" [--chroot /path/to/chroot/directory]\n"); printf("\n"); #ifdef LWAN_HAVE_MBEDTLS printf("Serve files through HTTP or HTTPS.\n\n"); From 101431ff7d3d9a7c6ac75babbd801a0ec92067ed Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 20 Sep 2023 19:59:37 -0700 Subject: [PATCH 2117/2505] Tweak help output --- src/bin/lwan/main.c | 61 ++++++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/src/bin/lwan/main.c b/src/bin/lwan/main.c index 1c0a5f63a..e5592aedb 100644 --- a/src/bin/lwan/main.c +++ b/src/bin/lwan/main.c @@ -35,12 +35,14 @@ enum args { static void print_module_info(void) { const struct lwan_module_info *module; + int count = 0; printf("Built-in modules:"); LWAN_SECTION_FOREACH(lwan_module, module) { printf(" %s", module->name); + count++; } - printf(".\n"); + printf(count ? ".\n" : " (none)\n"); } static void @@ -57,52 +59,55 @@ print_handler_info(void) printf(" %s", handler->name); count++; } - if (!count) { - printf(" (none)"); - } - printf(".\n"); + printf(count ? ".\n" : " (none)\n"); } static void print_build_time_configuration(void) { printf("Build-time configuration:"); -#ifdef LWAN_HAVE_LUA + +#if defined(LWAN_HAVE_LUA_JIT) + printf(" LuaJIT"); +#elif defined(LWAN_HAVE_LUA) printf(" Lua"); #endif -#ifdef LWAN_HAVE_BROTLI + +#if defined(LWAN_HAVE_BROTLI) printf(" Brotli"); #endif -#ifdef LWAN_HAVE_ZSTD +#if defined(LWAN_HAVE_ZSTD) printf(" zstd"); #endif -#ifdef LWAN_HAVE_MBEDTLS + +#if defined(LWAN_HAVE_MBEDTLS) printf(" mbedTLS"); #endif -#ifdef LWAN_HAVE_LIBUCONTEXT + +#if defined(LWAN_HAVE_LIBUCONTEXT) printf(" libucontext"); #endif -#ifdef LWAN_HAVE_EPOLL + +#if defined(LWAN_HAVE_EPOLL) printf(" epoll"); -#endif -#ifdef LWAN_HAVE_KQUEUE +#elif defined(LWAN_HAVE_KQUEUE) printf(" kqueue"); #endif -#ifdef LWAN_HAVE_SO_ATTACH_REUSEPORT_CBPF + +#if defined(LWAN_HAVE_SO_ATTACH_REUSEPORT_CBPF) printf(" sockopt-reuseport-CBPF"); -#endif -#ifdef LWAN_HAVE_SO_INCOMING_CPU +#elif defined(LWAN_HAVE_SO_INCOMING_CPU) printf(" sockopt-reuseport-incoming-cpu"); #endif -#ifdef LWAN_HAVE_VALGRIND + +#if defined(LWAN_HAVE_VALGRIND) printf(" valgrind"); #endif -#ifdef LWAN_HAVE_SYSLOG + +#if defined(LWAN_HAVE_SYSLOG) printf(" syslog"); #endif -#ifdef HAVE_PYTHON - printf(" python"); -#endif + printf(".\n"); } @@ -114,13 +119,13 @@ print_help(const char *argv0, const struct lwan_config *config) const char *config_file = lwan_get_config_path(path_buf, sizeof(path_buf)); printf("Usage: %s [--root /path/to/root/dir] [--listen addr:port]\n", argv0); -#ifdef LWAN_HAVE_MBEDTLS +#if defined(LWAN_HAVE_MBEDTLS) printf(" [--tls-listen addr:port] [--cert-path /cert/path] [--cert-key /key/path]\n"); #endif printf(" [--config /path/to/config/file] [--user username]\n"); printf(" [--chroot /path/to/chroot/directory]\n"); printf("\n"); -#ifdef LWAN_HAVE_MBEDTLS +#if defined(LWAN_HAVE_MBEDTLS) printf("Serve files through HTTP or HTTPS.\n\n"); #else printf("Serve files through HTTP.\n\n"); @@ -129,7 +134,7 @@ print_help(const char *argv0, const struct lwan_config *config) printf(" -r, --root Path to serve files from (default: ./wwwroot).\n"); printf("\n"); printf(" -l, --listen Listener (default: %s).\n", config->listener); -#ifdef LWAN_HAVE_MBEDTLS +#if defined(LWAN_HAVE_MBEDTLS) printf(" -L, --tls-listen TLS Listener (default: %s).\n", config->tls_listener ? config->tls_listener : "not listening"); @@ -138,7 +143,7 @@ print_help(const char *argv0, const struct lwan_config *config) printf(" -c, --config Path to config file path.\n"); printf(" -u, --user Username to drop privileges to (root required).\n"); printf(" -C, --chroot Chroot to path passed to --root (root required).\n"); -#ifdef LWAN_HAVE_MBEDTLS +#if defined(LWAN_HAVE_MBEDTLS) printf("\n"); printf(" -P, --cert-path Path to TLS certificate.\n"); printf(" -K, --cert-key Path to TLS key.\n"); @@ -157,7 +162,7 @@ print_help(const char *argv0, const struct lwan_config *config) printf(" %s\n", argv0); printf(" Use /etc/%s:\n", config_file); printf(" %s -c /etc/%s\n", argv0, config_file); -#ifdef LWAN_HAVE_MBEDTLS +#if defined(LWAN_HAVE_MBEDTLS) printf(" Serve system docs with HTTP and HTTPS:\n"); printf(" %s -P /path/to/cert.pem -K /path/to/cert.key \\\n" " -l '*:8080' -L '*:8081' -r /usr/share/doc\n", argv0); @@ -184,7 +189,7 @@ parse_args(int argc, char *argv[], struct lwan_config *config, char *root, { .name = "config", .has_arg = 1, .val = 'c' }, { .name = "chroot", .val = 'C' }, { .name = "user", .val = 'u', .has_arg = 1 }, -#ifdef LWAN_HAVE_MBEDTLS +#if defined(LWAN_HAVE_MBEDTLS) { .name = "tls-listen", .val = 'L', .has_arg = 1 }, { .name = "cert-path", .val = 'P', .has_arg = 1 }, { .name = "cert-key", .val = 'K', .has_arg = 1 }, @@ -196,7 +201,7 @@ parse_args(int argc, char *argv[], struct lwan_config *config, char *root, while ((c = getopt_long(argc, argv, "L:P:K:hr:l:c:u:C", opts, &optidx)) != -1) { switch (c) { -#ifdef LWAN_HAVE_MBEDTLS +#if defined(LWAN_HAVE_MBEDTLS) case 'L': free(config->tls_listener); config->tls_listener = strdup(optarg); From cd0afdd6b737e6172f55bd1a50dfcefbb6941aa6 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 20 Sep 2023 20:00:45 -0700 Subject: [PATCH 2118/2505] Implement isalpha() and isalnum() in lwan-tables --- src/lib/lwan-private.h | 2 ++ src/lib/lwan-request.c | 4 +-- src/lib/lwan-tables.c | 81 ++++++++++++++++++++++++++++++++++-------- 3 files changed, 70 insertions(+), 17 deletions(-) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index c91e15f8d..c4d7705b5 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -130,6 +130,8 @@ const char *lwan_get_config_path(char *path_buf, size_t path_buf_len); uint8_t lwan_char_isspace(char ch) __attribute__((pure)); uint8_t lwan_char_isxdigit(char ch) __attribute__((pure)); uint8_t lwan_char_isdigit(char ch) __attribute__((pure)); +uint8_t lwan_char_isalpha(char ch) __attribute__((pure)); +uint8_t lwan_char_isalnum(char ch) __attribute__((pure)); static ALWAYS_INLINE __attribute__((pure)) size_t lwan_nextpow2(size_t number) { diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 2e6364dd3..96dfce7e3 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -2162,9 +2162,9 @@ void lwan_request_foreach_header_for_cgi(struct lwan_request *request, /* FIXME: RFC7230/RFC3875 compliance */ for (char *p = header_name; *p; p++) { - if (isalpha(*p)) + if (lwan_char_isalpha(*p)) *p &= ~0x20; - else if (!isdigit(*p)) + else if (!lwan_char_isdigit(*p)) *p = '_'; } diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index 3710ee890..6e6a83e6c 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -202,9 +202,10 @@ lwan_http_status_as_descriptive_string(enum lwan_http_status status) } enum { - CHAR_PROP_SPACE = 1<<0, - CHAR_PROP_HEX = 1<<1, - CHAR_PROP_DIG = 1<<2, + CHAR_PROP_SPACE = 1 << 0, + CHAR_PROP_HEX = 1 << 1, + CHAR_PROP_DIG = 1 << 2, + CHAR_PROP_ALPHA = 1 << 3, }; static const uint8_t char_prop_tbl[256] = { @@ -222,18 +223,58 @@ static const uint8_t char_prop_tbl[256] = { ['7'] = CHAR_PROP_HEX | CHAR_PROP_DIG, ['8'] = CHAR_PROP_HEX | CHAR_PROP_DIG, ['9'] = CHAR_PROP_HEX | CHAR_PROP_DIG, - ['a'] = CHAR_PROP_HEX, - ['b'] = CHAR_PROP_HEX, - ['c'] = CHAR_PROP_HEX, - ['d'] = CHAR_PROP_HEX, - ['e'] = CHAR_PROP_HEX, - ['f'] = CHAR_PROP_HEX, - ['A'] = CHAR_PROP_HEX, - ['B'] = CHAR_PROP_HEX, - ['C'] = CHAR_PROP_HEX, - ['D'] = CHAR_PROP_HEX, - ['E'] = CHAR_PROP_HEX, - ['F'] = CHAR_PROP_HEX, + ['a'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA, + ['b'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA, + ['c'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA, + ['d'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA, + ['e'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA, + ['f'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA, + ['g'] = CHAR_PROP_ALPHA, + ['h'] = CHAR_PROP_ALPHA, + ['i'] = CHAR_PROP_ALPHA, + ['j'] = CHAR_PROP_ALPHA, + ['k'] = CHAR_PROP_ALPHA, + ['l'] = CHAR_PROP_ALPHA, + ['m'] = CHAR_PROP_ALPHA, + ['n'] = CHAR_PROP_ALPHA, + ['o'] = CHAR_PROP_ALPHA, + ['p'] = CHAR_PROP_ALPHA, + ['q'] = CHAR_PROP_ALPHA, + ['r'] = CHAR_PROP_ALPHA, + ['s'] = CHAR_PROP_ALPHA, + ['t'] = CHAR_PROP_ALPHA, + ['u'] = CHAR_PROP_ALPHA, + ['v'] = CHAR_PROP_ALPHA, + ['w'] = CHAR_PROP_ALPHA, + ['x'] = CHAR_PROP_ALPHA, + ['y'] = CHAR_PROP_ALPHA, + ['z'] = CHAR_PROP_ALPHA, + ['A'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA, + ['B'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA, + ['C'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA, + ['D'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA, + ['E'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA, + ['F'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA, + ['G'] = CHAR_PROP_ALPHA, + ['H'] = CHAR_PROP_ALPHA, + ['I'] = CHAR_PROP_ALPHA, + ['J'] = CHAR_PROP_ALPHA, + ['K'] = CHAR_PROP_ALPHA, + ['L'] = CHAR_PROP_ALPHA, + ['M'] = CHAR_PROP_ALPHA, + ['N'] = CHAR_PROP_ALPHA, + ['O'] = CHAR_PROP_ALPHA, + ['P'] = CHAR_PROP_ALPHA, + ['Q'] = CHAR_PROP_ALPHA, + ['R'] = CHAR_PROP_ALPHA, + ['S'] = CHAR_PROP_ALPHA, + ['T'] = CHAR_PROP_ALPHA, + ['U'] = CHAR_PROP_ALPHA, + ['V'] = CHAR_PROP_ALPHA, + ['W'] = CHAR_PROP_ALPHA, + ['X'] = CHAR_PROP_ALPHA, + ['Y'] = CHAR_PROP_ALPHA, + ['Z'] = CHAR_PROP_ALPHA, }; ALWAYS_INLINE uint8_t lwan_char_isspace(char ch) @@ -250,3 +291,13 @@ ALWAYS_INLINE uint8_t lwan_char_isdigit(char ch) { return char_prop_tbl[(unsigned char)ch] & CHAR_PROP_DIG; } + +ALWAYS_INLINE uint8_t lwan_char_isalpha(char ch) +{ + return char_prop_tbl[(unsigned char)ch] & CHAR_PROP_ALPHA; +} + +ALWAYS_INLINE uint8_t lwan_char_isalnum(char ch) +{ + return char_prop_tbl[(unsigned char)ch] & (CHAR_PROP_ALPHA | CHAR_PROP_DIG); +} From 762f21261b1e14a4be6b4afb5bbb2c05ca3a0cc8 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 20 Sep 2023 20:02:10 -0700 Subject: [PATCH 2119/2505] Add more tests for lwan_http_status_*() functions --- src/lib/lwan-tables.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index 6e6a83e6c..e2d39762d 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -95,17 +95,25 @@ void lwan_tables_init(void) "application/octet-stream")); assert(streq(lwan_determine_mime_type_for_file_name(""), "application/octet-stream")); - assert(streq(lwan_determine_mime_type_for_file_name(".gif"), - "image/gif")); + assert(streq(lwan_determine_mime_type_for_file_name(".gif"), "image/gif")); assert(streq(lwan_determine_mime_type_for_file_name(".JS"), "text/javascript")); assert(streq(lwan_determine_mime_type_for_file_name(".BZ2"), "application/x-bzip2")); -} -void -lwan_tables_shutdown(void) -{ +#ifndef NDEBUG +#define ASSERT_STATUS(id, code, short, long) \ + do { \ + const char *status = lwan_http_status_as_string_with_code(HTTP_##id); \ + assert(!strncmp(status, #code, 3)); \ + const char *status_as_str = lwan_http_status_as_string(HTTP_##id); \ + assert(!strcmp(status_as_str, short)); \ + const char *descr = lwan_http_status_as_descriptive_string(HTTP_##id); \ + assert(!strcmp(descr, long)); \ + } while (0); +FOR_EACH_HTTP_STATUS(ASSERT_STATUS) +#undef ASSERT_STATUS +#endif } static int From 6ef6b56f3521a1e419050bbd91cb9aa8d464f534 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 20 Sep 2023 20:03:17 -0700 Subject: [PATCH 2120/2505] Reduce size of lwan_http_status tables by half --- src/lib/lwan-tables.c | 44 +++++++++++++++---------------------------- 1 file changed, 15 insertions(+), 29 deletions(-) diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index e2d39762d..a1af6b226 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -167,46 +167,32 @@ lwan_determine_mime_type_for_file_name(const char *file_name) return "application/octet-stream"; } -#define GENERATE_ENTRY(id, code, short, long) \ - [HTTP_ ## id] = {.status = #code " " short, .description = long}, -static const struct { - const char *status; - const char *description; -} status_table[] = { - FOR_EACH_HTTP_STATUS(GENERATE_ENTRY) -}; -#undef GENERATE_ENTRY - -const char * -lwan_http_status_as_string_with_code(enum lwan_http_status status) +ALWAYS_INLINE const char * +lwan_http_status_as_string_with_code(const enum lwan_http_status status) { - if (LIKELY(status < N_ELEMENTS(status_table))) { - const char *ret = status_table[status].status; +#define ENTRY(id, code, short, long) [HTTP_##id] = #code " " short "\0" long, + static const char *table[] = {FOR_EACH_HTTP_STATUS(ENTRY)}; +#undef GENERATE_ENTRY - if (LIKELY(ret)) - return ret; + if (LIKELY((size_t)status < N_ELEMENTS(table))) { + const char *entry = table[status]; + if (LIKELY(entry)) + return entry; } - return "999 Invalid"; + return "999 Invalid\0Unknown status code"; } -ALWAYS_INLINE const char * -lwan_http_status_as_string(enum lwan_http_status status) +const char * +lwan_http_status_as_string(const enum lwan_http_status status) { return lwan_http_status_as_string_with_code(status) + 4; } -const char * -lwan_http_status_as_descriptive_string(enum lwan_http_status status) +const char *lwan_http_status_as_descriptive_string(const enum lwan_http_status status) { - if (LIKELY(status < N_ELEMENTS(status_table))) { - const char *ret = status_table[status].description; - - if (LIKELY(ret)) - return ret; - } - - return "Invalid"; + const char *str = lwan_http_status_as_string_with_code(status); + return str + strlen(str) + 1; } enum { From 6509d2d8558b4b5047201cb16694f20d909f0509 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 20 Sep 2023 20:04:30 -0700 Subject: [PATCH 2121/2505] dispatch_table can be NULL when disposing of template This can happen if template was never rendered but has been destroyed. --- src/lib/lwan-template.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index 796ebd139..d39d503e3 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -1017,7 +1017,8 @@ unbake_direct_addresses(struct lwan_tpl *tpl) { struct chunk *iter; - assert(tpl->dispatch_table); + if (!tpl->dispatch_table) + return; LWAN_ARRAY_FOREACH (&tpl->chunks, iter) { for (enum action action = ACTION_APPEND; action <= ACTION_LAST; action++) { From 0b2ba39ade1d3687ae6b39b3e84420a05c7dcef8 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 20 Sep 2023 20:05:18 -0700 Subject: [PATCH 2122/2505] Assert conn_flags enum has enough space for epoll event flags --- src/lib/lwan.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index e03416842..1ed172e8b 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -318,8 +318,13 @@ enum lwan_connection_flags { CONN_LISTENER = 1 << 11, CONN_USE_DYNAMIC_BUFFER = 1 << 12, + + CONN_FLAG_LAST = CONN_USE_DYNAMIC_BUFFER, }; +static_assert(CONN_FLAG_LAST < ((1 << 15) - 1), + "Enough space for epoll events in conn flags"); + enum lwan_connection_coro_yield { /* Returns to the event loop and terminates the coroutine, freeing * all resources associated with it, including calling deferred From 0ddc7dbb18b599a204f97f2fac85f9a7c832128a Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 20 Sep 2023 20:06:24 -0700 Subject: [PATCH 2123/2505] Generate QR code for smolsites --- src/samples/smolsite/CMakeLists.txt | 72 +- src/samples/smolsite/main.c | 145 ++-- src/samples/smolsite/qrcodegen.c | 1028 +++++++++++++++++++++++++++ src/samples/smolsite/qrcodegen.h | 385 ++++++++++ src/samples/smolsite/smolsite.html | 47 +- 5 files changed, 1598 insertions(+), 79 deletions(-) create mode 100644 src/samples/smolsite/qrcodegen.c create mode 100644 src/samples/smolsite/qrcodegen.h diff --git a/src/samples/smolsite/CMakeLists.txt b/src/samples/smolsite/CMakeLists.txt index 900a12bf7..aef6689fb 100644 --- a/src/samples/smolsite/CMakeLists.txt +++ b/src/samples/smolsite/CMakeLists.txt @@ -1,39 +1,43 @@ +check_function_exists(fmemopen HAVE_FMEMOPEN) +if (HAVE_FMEMOPEN) + find_program(ZIP NAMES zip) + if (ZIP) + add_executable(smolsite + main.c + junzip.c + qrcodegen.c + ../clock/gifenc.c + ) -find_program(ZIP NAMES zip) -if (ZIP) - add_executable(smolsite - main.c - junzip.c - ) + target_link_libraries(smolsite + ${LWAN_COMMON_LIBS} + ${ADDITIONAL_LIBRARIES} + ) - target_link_libraries(smolsite - ${LWAN_COMMON_LIBS} - ${ADDITIONAL_LIBRARIES} - ) + add_custom_command( + OUTPUT ${CMAKE_BINARY_DIR}/smolsite.zip + COMMAND ${ZIP} -DXjq9 ${CMAKE_BINARY_DIR}/smolsite.zip ${CMAKE_SOURCE_DIR}/src/samples/smolsite/index.html + DEPENDS ${CMAKE_SOURCE_DIR}/src/samples/smolsite/index.html + COMMENT "Zipping smolsite ZIP" + ) + add_custom_target(generate_smolsite_zip + DEPENDS ${CMAKE_BINARY_DIR}/smolsite.zip + ) - add_custom_command( - OUTPUT ${CMAKE_BINARY_DIR}/smolsite.zip - COMMAND ${ZIP} -DXjq9 ${CMAKE_BINARY_DIR}/smolsite.zip ${CMAKE_SOURCE_DIR}/src/samples/smolsite/index.html - DEPENDS ${CMAKE_SOURCE_DIR}/src/samples/smolsite/index.html - COMMENT "Zipping smolsite ZIP" - ) - add_custom_target(generate_smolsite_zip - DEPENDS ${CMAKE_BINARY_DIR}/smolsite.zip - ) + add_custom_command( + OUTPUT ${CMAKE_BINARY_DIR}/smolsite.h + COMMAND bin2hex + ${CMAKE_SOURCE_DIR}/src/samples/smolsite/smolsite.html smolsite_html + ${CMAKE_BINARY_DIR}/smolsite.zip smolsite_zip > ${CMAKE_BINARY_DIR}/smolsite.h + DEPENDS ${CMAKE_SOURCE_DIR}/src/samples/smolsite/smolsite.html + ${CMAKE_BINARY_DIR}/smolsite.zip + bin2hex + COMMENT "Bundling smolsite template" + ) + add_custom_target(generate_smolsite + DEPENDS ${CMAKE_BINARY_DIR}/smolsite.h + ) - add_custom_command( - OUTPUT ${CMAKE_BINARY_DIR}/smolsite.h - COMMAND bin2hex - ${CMAKE_SOURCE_DIR}/src/samples/smolsite/smolsite.html smolsite_html - ${CMAKE_BINARY_DIR}/smolsite.zip smolsite_zip > ${CMAKE_BINARY_DIR}/smolsite.h - DEPENDS ${CMAKE_SOURCE_DIR}/src/samples/smolsite/smolsite.html - ${CMAKE_BINARY_DIR}/smolsite.zip - bin2hex - COMMENT "Bundling smolsite template" - ) - add_custom_target(generate_smolsite - DEPENDS ${CMAKE_BINARY_DIR}/smolsite.h - ) - - add_dependencies(smolsite generate_smolsite generate_smolsite_zip) + add_dependencies(smolsite generate_smolsite generate_smolsite_zip) + endif () endif () diff --git a/src/samples/smolsite/main.c b/src/samples/smolsite/main.c index 9e9a38504..601f95688 100644 --- a/src/samples/smolsite/main.c +++ b/src/samples/smolsite/main.c @@ -35,6 +35,8 @@ #include "lwan-template.h" #include "junzip.h" +#include "qrcodegen.h" +#include "../clock/gifenc.h" #include "smolsite.h" @@ -53,16 +55,20 @@ struct site { struct cache_entry entry; struct lwan_value zipped; struct hash *files; + struct lwan_strbuf qr_code; + int has_qr_code; }; struct iframe_tpl_vars { const char *digest; + int has_qr_code; }; #undef TPL_STRUCT #define TPL_STRUCT struct iframe_tpl_vars static const struct lwan_var_descriptor iframe_tpl_desc[] = { TPL_VAR_STR(digest), + TPL_VAR_INT(has_qr_code), TPL_VAR_SENTINEL, }; @@ -100,7 +106,7 @@ static struct hash *pending_sites(void) static __thread struct hash *pending_sites; if (!pending_sites) { - pending_sites = hash_str_new(free, free); + pending_sites = hash_str_new(free, NULL); if (!pending_sites) { lwan_status_critical("Could not allocate pending sites hash table"); } @@ -158,21 +164,80 @@ static int file_cb( return 0; } +static bool generate_qr_code_gif(const char *b64, struct lwan_strbuf *output) +{ + uint8_t qrcode[qrcodegen_BUFFER_LEN_MAX]; + uint8_t tempBuffer[qrcodegen_BUFFER_LEN_MAX]; + char *url; + bool ok; + + if (!lwan_strbuf_init(output)) + return false; + + if (asprintf(&url, "https://smolsite.zip/%s", b64) < 0) + return false; + + ok = qrcodegen_encodeText(url, tempBuffer, qrcode, qrcodegen_Ecc_LOW, + qrcodegen_VERSION_MIN, qrcodegen_VERSION_MAX, + qrcodegen_Mask_AUTO, true); + free(url); + if (!ok) + return false; + + int size = qrcodegen_getSize(qrcode); + if ((int)(uint16_t)size != size) + return false; + + ge_GIF *gif = + ge_new_gif(output, (uint16_t)size, (uint16_t)size, NULL, 4, -1); + if (!gif) { + lwan_strbuf_free(output); + return false; + } + + for (int y = 0; y < size; y++) { + for (int x = 0; x < size; x++) { + gif->frame[y * size + x] = qrcodegen_getModule(qrcode, x, y) ? 0 : 15; + } + } + ge_add_frame(gif, 0); + ge_close_gif(gif); + + return true; +} + static struct cache_entry *create_site(const void *key, void *context) { - const struct lwan_value *zipped = + struct lwan_strbuf qr_code = LWAN_STRBUF_STATIC_INIT; + const struct lwan_value *base64_encoded = hash_find(pending_sites(), (const void *)key); + unsigned char *decoded = NULL; + size_t decoded_len; + + if (!base64_encoded) + return NULL; - if (!zipped) + if (UNLIKELY(!base64_validate((unsigned char *)base64_encoded->value, + base64_encoded->len))) { + return NULL; + } + + decoded = base64_decode((unsigned char *)base64_encoded->value, + base64_encoded->len, &decoded_len); + if (UNLIKELY(!decoded)) return NULL; struct site *site = malloc(sizeof(*site)); if (!site) goto no_site; - site->zipped = *zipped; + site->has_qr_code = + generate_qr_code_gif((const char *)base64_encoded->value, &qr_code); + + site->qr_code = qr_code; + site->zipped = (struct lwan_value) {.value = (char *)decoded, .len = decoded_len}; - FILE *zip_mem = fmemopen(zipped->value, zipped->len, "rb"); + FILE *zip_mem = fmemopen(decoded, decoded_len, "rb"); if (!zip_mem) goto no_file; @@ -194,6 +259,7 @@ static struct cache_entry *create_site(const void *key, void *context) goto no_central_dir; jzfile_free(zip); + return (struct cache_entry *)site; no_central_dir: @@ -204,13 +270,15 @@ static struct cache_entry *create_site(const void *key, void *context) no_file: free(site); no_site: - free(zipped->value); + free(decoded); + lwan_strbuf_free(&qr_code); return NULL; } static void destroy_site(struct cache_entry *entry, void *context) { struct site *site = (struct site *)entry; + lwan_strbuf_free(&site->qr_code); hash_free(site->files); free(site->zipped.value); free(site); @@ -222,7 +290,6 @@ static void remove_from_pending_defer(void *data) hash_del(pending_sites(), key); } - LWAN_HANDLER_ROUTE(view_root, "/") { char digest_str[41]; @@ -250,57 +317,55 @@ LWAN_HANDLER_ROUTE(view_root, "/") site = (struct site *)cache_coro_get_and_ref_entry( sites, request->conn->coro, digest_str); if (!site) { - struct lwan_value *zip = malloc(sizeof(*zip)); - if (UNLIKELY(!zip)) - return HTTP_INTERNAL_ERROR; - char *key = strdup(digest_str); - if (UNLIKELY(!key)) + if (!key) { + lwan_status_debug("a"); return HTTP_INTERNAL_ERROR; - if (UNLIKELY(hash_add_unique(pending_sites(), key, zip))) { - free(key); - free(zip); + } + + if (UNLIKELY(hash_add_unique(pending_sites(), key, &request->url))) { + lwan_status_debug("b"); return HTTP_INTERNAL_ERROR; - } + } coro_defer(request->conn->coro, remove_from_pending_defer, key); - /* This base64 decoding stuff could go to create_site(), but - * then we'd need to allocate a new buffer, copy the encoded - * ZIP into that buffer, and free it inside create_site(). It's - * just more work than just decoding it. - */ - unsigned char *decoded; - size_t decoded_len; - - if (UNLIKELY(!base64_validate((unsigned char *)request->url.value, - request->url.len))) { - return HTTP_BAD_REQUEST; - } - - decoded = base64_decode((unsigned char *)request->url.value, - request->url.len, &decoded_len); - if (UNLIKELY(!decoded)) - return HTTP_BAD_REQUEST; - - zip->value = (char *)decoded; - zip->len = decoded_len; - site = (struct site *)cache_coro_get_and_ref_entry( - sites, request->conn->coro, digest_str); - if (UNLIKELY(!site)) + sites, request->conn->coro, key); + if (UNLIKELY(!site)) { + lwan_status_debug("c"); return HTTP_INTERNAL_ERROR; +} } response->mime_type = "text/html; charset=utf-8"; - struct iframe_tpl_vars vars = {.digest = digest_str}; + struct iframe_tpl_vars vars = { + .digest = digest_str, + .has_qr_code = site->has_qr_code, + }; if (!lwan_tpl_apply_with_buffer(iframe_tpl, response->buffer, &vars)) return HTTP_INTERNAL_ERROR; return HTTP_OK; } +LWAN_HANDLER_ROUTE(qr_code, "/q/") +{ + struct site *site = (struct site *)cache_coro_get_and_ref_entry( + sites, request->conn->coro, request->url.value); + if (!site) + return HTTP_NOT_FOUND; + if (!site->has_qr_code) + return HTTP_NOT_FOUND; + + lwan_strbuf_set_static(response->buffer, + lwan_strbuf_get_buffer(&site->qr_code), + lwan_strbuf_get_length(&site->qr_code)); + response->mime_type = "image/gif"; + return HTTP_OK; +} + LWAN_HANDLER_ROUTE(view_site, "/s/") { if (request->url.len < 40) diff --git a/src/samples/smolsite/qrcodegen.c b/src/samples/smolsite/qrcodegen.c new file mode 100644 index 000000000..871bc9243 --- /dev/null +++ b/src/samples/smolsite/qrcodegen.c @@ -0,0 +1,1028 @@ +/* + * QR Code generator library (C) + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/page/qr-code-generator-library + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +#include +#include +#include +#include +#include "qrcodegen.h" + +#ifndef QRCODEGEN_TEST + #define testable static // Keep functions private +#else + #define testable // Expose private functions +#endif + + +/*---- Forward declarations for private functions ----*/ + +// Regarding all public and private functions defined in this source file: +// - They require all pointer/array arguments to be not null unless the array length is zero. +// - They only read input scalar/array arguments, write to output pointer/array +// arguments, and return scalar values; they are "pure" functions. +// - They don't read mutable global variables or write to any global variables. +// - They don't perform I/O, read the clock, print to console, etc. +// - They allocate a small and constant amount of stack memory. +// - They don't allocate or free any memory on the heap. +// - They don't recurse or mutually recurse. All the code +// could be inlined into the top-level public functions. +// - They run in at most quadratic time with respect to input arguments. +// Most functions run in linear time, and some in constant time. +// There are no unbounded loops or non-obvious termination conditions. +// - They are completely thread-safe if the caller does not give the +// same writable buffer to concurrent calls to these functions. + +testable void appendBitsToBuffer(unsigned int val, int numBits, uint8_t buffer[], int *bitLen); + +testable void addEccAndInterleave(uint8_t data[], int version, enum qrcodegen_Ecc ecl, uint8_t result[]); +testable int getNumDataCodewords(int version, enum qrcodegen_Ecc ecl); +testable int getNumRawDataModules(int ver); + +testable void reedSolomonComputeDivisor(int degree, uint8_t result[]); +testable void reedSolomonComputeRemainder(const uint8_t data[], int dataLen, + const uint8_t generator[], int degree, uint8_t result[]); +testable uint8_t reedSolomonMultiply(uint8_t x, uint8_t y); + +testable void initializeFunctionModules(int version, uint8_t qrcode[]); +static void drawLightFunctionModules(uint8_t qrcode[], int version); +static void drawFormatBits(enum qrcodegen_Ecc ecl, enum qrcodegen_Mask mask, uint8_t qrcode[]); +testable int getAlignmentPatternPositions(int version, uint8_t result[7]); +static void fillRectangle(int left, int top, int width, int height, uint8_t qrcode[]); + +static void drawCodewords(const uint8_t data[], int dataLen, uint8_t qrcode[]); +static void applyMask(const uint8_t functionModules[], uint8_t qrcode[], enum qrcodegen_Mask mask); +static long getPenaltyScore(const uint8_t qrcode[]); +static int finderPenaltyCountPatterns(const int runHistory[7], int qrsize); +static int finderPenaltyTerminateAndCount(bool currentRunColor, int currentRunLength, int runHistory[7], int qrsize); +static void finderPenaltyAddHistory(int currentRunLength, int runHistory[7], int qrsize); + +testable bool getModuleBounded(const uint8_t qrcode[], int x, int y); +testable void setModuleBounded(uint8_t qrcode[], int x, int y, bool isDark); +testable void setModuleUnbounded(uint8_t qrcode[], int x, int y, bool isDark); +static bool getBit(int x, int i); + +testable int calcSegmentBitLength(enum qrcodegen_Mode mode, size_t numChars); +testable int getTotalBits(const struct qrcodegen_Segment segs[], size_t len, int version); +static int numCharCountBits(enum qrcodegen_Mode mode, int version); + + + +/*---- Private tables of constants ----*/ + +// The set of all legal characters in alphanumeric mode, where each character +// value maps to the index in the string. For checking text and encoding segments. +static const char *ALPHANUMERIC_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"; + +// Sentinel value for use in only some functions. +#define LENGTH_OVERFLOW -1 + +// For generating error correction codes. +testable const int8_t ECC_CODEWORDS_PER_BLOCK[4][41] = { + // Version: (note that index 0 is for padding, and is set to an illegal value) + //0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level + {-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Low + {-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28}, // Medium + {-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Quartile + {-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // High +}; + +#define qrcodegen_REED_SOLOMON_DEGREE_MAX 30 // Based on the table above + +// For generating error correction codes. +testable const int8_t NUM_ERROR_CORRECTION_BLOCKS[4][41] = { + // Version: (note that index 0 is for padding, and is set to an illegal value) + //0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level + {-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, // Low + {-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, // Medium + {-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, // Quartile + {-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, // High +}; + +// For automatic mask pattern selection. +static const int PENALTY_N1 = 3; +static const int PENALTY_N2 = 3; +static const int PENALTY_N3 = 40; +static const int PENALTY_N4 = 10; + + + +/*---- High-level QR Code encoding functions ----*/ + +// Public function - see documentation comment in header file. +bool qrcodegen_encodeText(const char *text, uint8_t tempBuffer[], uint8_t qrcode[], + enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl) { + + size_t textLen = strlen(text); + if (textLen == 0) + return qrcodegen_encodeSegmentsAdvanced(NULL, 0, ecl, minVersion, maxVersion, mask, boostEcl, tempBuffer, qrcode); + size_t bufLen = (size_t)qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion); + + struct qrcodegen_Segment seg; + if (qrcodegen_isNumeric(text)) { + if (qrcodegen_calcSegmentBufferSize(qrcodegen_Mode_NUMERIC, textLen) > bufLen) + goto fail; + seg = qrcodegen_makeNumeric(text, tempBuffer); + } else if (qrcodegen_isAlphanumeric(text)) { + if (qrcodegen_calcSegmentBufferSize(qrcodegen_Mode_ALPHANUMERIC, textLen) > bufLen) + goto fail; + seg = qrcodegen_makeAlphanumeric(text, tempBuffer); + } else { + if (textLen > bufLen) + goto fail; + for (size_t i = 0; i < textLen; i++) + tempBuffer[i] = (uint8_t)text[i]; + seg.mode = qrcodegen_Mode_BYTE; + seg.bitLength = calcSegmentBitLength(seg.mode, textLen); + if (seg.bitLength == LENGTH_OVERFLOW) + goto fail; + seg.numChars = (int)textLen; + seg.data = tempBuffer; + } + return qrcodegen_encodeSegmentsAdvanced(&seg, 1, ecl, minVersion, maxVersion, mask, boostEcl, tempBuffer, qrcode); + +fail: + qrcode[0] = 0; // Set size to invalid value for safety + return false; +} + + +// Public function - see documentation comment in header file. +bool qrcodegen_encodeBinary(uint8_t dataAndTemp[], size_t dataLen, uint8_t qrcode[], + enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl) { + + struct qrcodegen_Segment seg; + seg.mode = qrcodegen_Mode_BYTE; + seg.bitLength = calcSegmentBitLength(seg.mode, dataLen); + if (seg.bitLength == LENGTH_OVERFLOW) { + qrcode[0] = 0; // Set size to invalid value for safety + return false; + } + seg.numChars = (int)dataLen; + seg.data = dataAndTemp; + return qrcodegen_encodeSegmentsAdvanced(&seg, 1, ecl, minVersion, maxVersion, mask, boostEcl, dataAndTemp, qrcode); +} + + +// Appends the given number of low-order bits of the given value to the given byte-based +// bit buffer, increasing the bit length. Requires 0 <= numBits <= 16 and val < 2^numBits. +testable void appendBitsToBuffer(unsigned int val, int numBits, uint8_t buffer[], int *bitLen) { + assert(0 <= numBits && numBits <= 16 && (unsigned long)val >> numBits == 0); + for (int i = numBits - 1; i >= 0; i--, (*bitLen)++) + buffer[*bitLen >> 3] |= ((val >> i) & 1) << (7 - (*bitLen & 7)); +} + + + +/*---- Low-level QR Code encoding functions ----*/ + +// Public function - see documentation comment in header file. +bool qrcodegen_encodeSegments(const struct qrcodegen_Segment segs[], size_t len, + enum qrcodegen_Ecc ecl, uint8_t tempBuffer[], uint8_t qrcode[]) { + return qrcodegen_encodeSegmentsAdvanced(segs, len, ecl, + qrcodegen_VERSION_MIN, qrcodegen_VERSION_MAX, qrcodegen_Mask_AUTO, true, tempBuffer, qrcode); +} + + +// Public function - see documentation comment in header file. +bool qrcodegen_encodeSegmentsAdvanced(const struct qrcodegen_Segment segs[], size_t len, enum qrcodegen_Ecc ecl, + int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl, uint8_t tempBuffer[], uint8_t qrcode[]) { + assert(segs != NULL || len == 0); + assert(qrcodegen_VERSION_MIN <= minVersion && minVersion <= maxVersion && maxVersion <= qrcodegen_VERSION_MAX); + assert(0 <= (int)ecl && (int)ecl <= 3 && -1 <= (int)mask && (int)mask <= 7); + + // Find the minimal version number to use + int version, dataUsedBits; + for (version = minVersion; ; version++) { + int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; // Number of data bits available + dataUsedBits = getTotalBits(segs, len, version); + if (dataUsedBits != LENGTH_OVERFLOW && dataUsedBits <= dataCapacityBits) + break; // This version number is found to be suitable + if (version >= maxVersion) { // All versions in the range could not fit the given data + qrcode[0] = 0; // Set size to invalid value for safety + return false; + } + } + assert(dataUsedBits != LENGTH_OVERFLOW); + + // Increase the error correction level while the data still fits in the current version number + for (int i = (int)qrcodegen_Ecc_MEDIUM; i <= (int)qrcodegen_Ecc_HIGH; i++) { // From low to high + if (boostEcl && dataUsedBits <= getNumDataCodewords(version, (enum qrcodegen_Ecc)i) * 8) + ecl = (enum qrcodegen_Ecc)i; + } + + // Concatenate all segments to create the data bit string + memset(qrcode, 0, (size_t)qrcodegen_BUFFER_LEN_FOR_VERSION(version) * sizeof(qrcode[0])); + int bitLen = 0; + for (size_t i = 0; i < len; i++) { + const struct qrcodegen_Segment *seg = &segs[i]; + appendBitsToBuffer((unsigned int)seg->mode, 4, qrcode, &bitLen); + appendBitsToBuffer((unsigned int)seg->numChars, numCharCountBits(seg->mode, version), qrcode, &bitLen); + for (int j = 0; j < seg->bitLength; j++) { + int bit = (seg->data[j >> 3] >> (7 - (j & 7))) & 1; + appendBitsToBuffer((unsigned int)bit, 1, qrcode, &bitLen); + } + } + assert(bitLen == dataUsedBits); + + // Add terminator and pad up to a byte if applicable + int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; + assert(bitLen <= dataCapacityBits); + int terminatorBits = dataCapacityBits - bitLen; + if (terminatorBits > 4) + terminatorBits = 4; + appendBitsToBuffer(0, terminatorBits, qrcode, &bitLen); + appendBitsToBuffer(0, (8 - bitLen % 8) % 8, qrcode, &bitLen); + assert(bitLen % 8 == 0); + + // Pad with alternating bytes until data capacity is reached + for (uint8_t padByte = 0xEC; bitLen < dataCapacityBits; padByte ^= 0xEC ^ 0x11) + appendBitsToBuffer(padByte, 8, qrcode, &bitLen); + + // Compute ECC, draw modules + addEccAndInterleave(qrcode, version, ecl, tempBuffer); + initializeFunctionModules(version, qrcode); + drawCodewords(tempBuffer, getNumRawDataModules(version) / 8, qrcode); + drawLightFunctionModules(qrcode, version); + initializeFunctionModules(version, tempBuffer); + + // Do masking + if (mask == qrcodegen_Mask_AUTO) { // Automatically choose best mask + long minPenalty = LONG_MAX; + for (int i = 0; i < 8; i++) { + enum qrcodegen_Mask msk = (enum qrcodegen_Mask)i; + applyMask(tempBuffer, qrcode, msk); + drawFormatBits(ecl, msk, qrcode); + long penalty = getPenaltyScore(qrcode); + if (penalty < minPenalty) { + mask = msk; + minPenalty = penalty; + } + applyMask(tempBuffer, qrcode, msk); // Undoes the mask due to XOR + } + } + assert(0 <= (int)mask && (int)mask <= 7); + applyMask(tempBuffer, qrcode, mask); // Apply the final choice of mask + drawFormatBits(ecl, mask, qrcode); // Overwrite old format bits + return true; +} + + + +/*---- Error correction code generation functions ----*/ + +// Appends error correction bytes to each block of the given data array, then interleaves +// bytes from the blocks and stores them in the result array. data[0 : dataLen] contains +// the input data. data[dataLen : rawCodewords] is used as a temporary work area and will +// be clobbered by this function. The final answer is stored in result[0 : rawCodewords]. +testable void addEccAndInterleave(uint8_t data[], int version, enum qrcodegen_Ecc ecl, uint8_t result[]) { + // Calculate parameter numbers + assert(0 <= (int)ecl && (int)ecl < 4 && qrcodegen_VERSION_MIN <= version && version <= qrcodegen_VERSION_MAX); + int numBlocks = NUM_ERROR_CORRECTION_BLOCKS[(int)ecl][version]; + int blockEccLen = ECC_CODEWORDS_PER_BLOCK [(int)ecl][version]; + int rawCodewords = getNumRawDataModules(version) / 8; + int dataLen = getNumDataCodewords(version, ecl); + int numShortBlocks = numBlocks - rawCodewords % numBlocks; + int shortBlockDataLen = rawCodewords / numBlocks - blockEccLen; + + // Split data into blocks, calculate ECC, and interleave + // (not concatenate) the bytes into a single sequence + uint8_t rsdiv[qrcodegen_REED_SOLOMON_DEGREE_MAX]; + reedSolomonComputeDivisor(blockEccLen, rsdiv); + const uint8_t *dat = data; + for (int i = 0; i < numBlocks; i++) { + int datLen = shortBlockDataLen + (i < numShortBlocks ? 0 : 1); + uint8_t *ecc = &data[dataLen]; // Temporary storage + reedSolomonComputeRemainder(dat, datLen, rsdiv, blockEccLen, ecc); + for (int j = 0, k = i; j < datLen; j++, k += numBlocks) { // Copy data + if (j == shortBlockDataLen) + k -= numShortBlocks; + result[k] = dat[j]; + } + for (int j = 0, k = dataLen + i; j < blockEccLen; j++, k += numBlocks) // Copy ECC + result[k] = ecc[j]; + dat += datLen; + } +} + + +// Returns the number of 8-bit codewords that can be used for storing data (not ECC), +// for the given version number and error correction level. The result is in the range [9, 2956]. +testable int getNumDataCodewords(int version, enum qrcodegen_Ecc ecl) { + int v = version, e = (int)ecl; + assert(0 <= e && e < 4); + return getNumRawDataModules(v) / 8 + - ECC_CODEWORDS_PER_BLOCK [e][v] + * NUM_ERROR_CORRECTION_BLOCKS[e][v]; +} + + +// Returns the number of data bits that can be stored in a QR Code of the given version number, after +// all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8. +// The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table. +testable int getNumRawDataModules(int ver) { + assert(qrcodegen_VERSION_MIN <= ver && ver <= qrcodegen_VERSION_MAX); + int result = (16 * ver + 128) * ver + 64; + if (ver >= 2) { + int numAlign = ver / 7 + 2; + result -= (25 * numAlign - 10) * numAlign - 55; + if (ver >= 7) + result -= 36; + } + assert(208 <= result && result <= 29648); + return result; +} + + + +/*---- Reed-Solomon ECC generator functions ----*/ + +// Computes a Reed-Solomon ECC generator polynomial for the given degree, storing in result[0 : degree]. +// This could be implemented as a lookup table over all possible parameter values, instead of as an algorithm. +testable void reedSolomonComputeDivisor(int degree, uint8_t result[]) { + assert(1 <= degree && degree <= qrcodegen_REED_SOLOMON_DEGREE_MAX); + // Polynomial coefficients are stored from highest to lowest power, excluding the leading term which is always 1. + // For example the polynomial x^3 + 255x^2 + 8x + 93 is stored as the uint8 array {255, 8, 93}. + memset(result, 0, (size_t)degree * sizeof(result[0])); + result[degree - 1] = 1; // Start off with the monomial x^0 + + // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), + // drop the highest monomial term which is always 1x^degree. + // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). + uint8_t root = 1; + for (int i = 0; i < degree; i++) { + // Multiply the current product by (x - r^i) + for (int j = 0; j < degree; j++) { + result[j] = reedSolomonMultiply(result[j], root); + if (j + 1 < degree) + result[j] ^= result[j + 1]; + } + root = reedSolomonMultiply(root, 0x02); + } +} + + +// Computes the Reed-Solomon error correction codeword for the given data and divisor polynomials. +// The remainder when data[0 : dataLen] is divided by divisor[0 : degree] is stored in result[0 : degree]. +// All polynomials are in big endian, and the generator has an implicit leading 1 term. +testable void reedSolomonComputeRemainder(const uint8_t data[], int dataLen, + const uint8_t generator[], int degree, uint8_t result[]) { + assert(1 <= degree && degree <= qrcodegen_REED_SOLOMON_DEGREE_MAX); + memset(result, 0, (size_t)degree * sizeof(result[0])); + for (int i = 0; i < dataLen; i++) { // Polynomial division + uint8_t factor = data[i] ^ result[0]; + memmove(&result[0], &result[1], (size_t)(degree - 1) * sizeof(result[0])); + result[degree - 1] = 0; + for (int j = 0; j < degree; j++) + result[j] ^= reedSolomonMultiply(generator[j], factor); + } +} + +#undef qrcodegen_REED_SOLOMON_DEGREE_MAX + + +// Returns the product of the two given field elements modulo GF(2^8/0x11D). +// All inputs are valid. This could be implemented as a 256*256 lookup table. +testable uint8_t reedSolomonMultiply(uint8_t x, uint8_t y) { + // Russian peasant multiplication + uint8_t z = 0; + for (int i = 7; i >= 0; i--) { + z = (uint8_t)((z << 1) ^ ((z >> 7) * 0x11D)); + z ^= ((y >> i) & 1) * x; + } + return z; +} + + + +/*---- Drawing function modules ----*/ + +// Clears the given QR Code grid with light modules for the given +// version's size, then marks every function module as dark. +testable void initializeFunctionModules(int version, uint8_t qrcode[]) { + // Initialize QR Code + int qrsize = version * 4 + 17; + memset(qrcode, 0, (size_t)((qrsize * qrsize + 7) / 8 + 1) * sizeof(qrcode[0])); + qrcode[0] = (uint8_t)qrsize; + + // Fill horizontal and vertical timing patterns + fillRectangle(6, 0, 1, qrsize, qrcode); + fillRectangle(0, 6, qrsize, 1, qrcode); + + // Fill 3 finder patterns (all corners except bottom right) and format bits + fillRectangle(0, 0, 9, 9, qrcode); + fillRectangle(qrsize - 8, 0, 8, 9, qrcode); + fillRectangle(0, qrsize - 8, 9, 8, qrcode); + + // Fill numerous alignment patterns + uint8_t alignPatPos[7]; + int numAlign = getAlignmentPatternPositions(version, alignPatPos); + for (int i = 0; i < numAlign; i++) { + for (int j = 0; j < numAlign; j++) { + // Don't draw on the three finder corners + if (!((i == 0 && j == 0) || (i == 0 && j == numAlign - 1) || (i == numAlign - 1 && j == 0))) + fillRectangle(alignPatPos[i] - 2, alignPatPos[j] - 2, 5, 5, qrcode); + } + } + + // Fill version blocks + if (version >= 7) { + fillRectangle(qrsize - 11, 0, 3, 6, qrcode); + fillRectangle(0, qrsize - 11, 6, 3, qrcode); + } +} + + +// Draws light function modules and possibly some dark modules onto the given QR Code, without changing +// non-function modules. This does not draw the format bits. This requires all function modules to be previously +// marked dark (namely by initializeFunctionModules()), because this may skip redrawing dark function modules. +static void drawLightFunctionModules(uint8_t qrcode[], int version) { + // Draw horizontal and vertical timing patterns + int qrsize = qrcodegen_getSize(qrcode); + for (int i = 7; i < qrsize - 7; i += 2) { + setModuleBounded(qrcode, 6, i, false); + setModuleBounded(qrcode, i, 6, false); + } + + // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules) + for (int dy = -4; dy <= 4; dy++) { + for (int dx = -4; dx <= 4; dx++) { + int dist = abs(dx); + if (abs(dy) > dist) + dist = abs(dy); + if (dist == 2 || dist == 4) { + setModuleUnbounded(qrcode, 3 + dx, 3 + dy, false); + setModuleUnbounded(qrcode, qrsize - 4 + dx, 3 + dy, false); + setModuleUnbounded(qrcode, 3 + dx, qrsize - 4 + dy, false); + } + } + } + + // Draw numerous alignment patterns + uint8_t alignPatPos[7]; + int numAlign = getAlignmentPatternPositions(version, alignPatPos); + for (int i = 0; i < numAlign; i++) { + for (int j = 0; j < numAlign; j++) { + if ((i == 0 && j == 0) || (i == 0 && j == numAlign - 1) || (i == numAlign - 1 && j == 0)) + continue; // Don't draw on the three finder corners + for (int dy = -1; dy <= 1; dy++) { + for (int dx = -1; dx <= 1; dx++) + setModuleBounded(qrcode, alignPatPos[i] + dx, alignPatPos[j] + dy, dx == 0 && dy == 0); + } + } + } + + // Draw version blocks + if (version >= 7) { + // Calculate error correction code and pack bits + int rem = version; // version is uint6, in the range [7, 40] + for (int i = 0; i < 12; i++) + rem = (rem << 1) ^ ((rem >> 11) * 0x1F25); + long bits = (long)version << 12 | rem; // uint18 + assert(bits >> 18 == 0); + + // Draw two copies + for (int i = 0; i < 6; i++) { + for (int j = 0; j < 3; j++) { + int k = qrsize - 11 + j; + setModuleBounded(qrcode, k, i, (bits & 1) != 0); + setModuleBounded(qrcode, i, k, (bits & 1) != 0); + bits >>= 1; + } + } + } +} + + +// Draws two copies of the format bits (with its own error correction code) based +// on the given mask and error correction level. This always draws all modules of +// the format bits, unlike drawLightFunctionModules() which might skip dark modules. +static void drawFormatBits(enum qrcodegen_Ecc ecl, enum qrcodegen_Mask mask, uint8_t qrcode[]) { + // Calculate error correction code and pack bits + assert(0 <= (int)mask && (int)mask <= 7); + static const int table[] = {1, 0, 3, 2}; + int data = table[(int)ecl] << 3 | (int)mask; // errCorrLvl is uint2, mask is uint3 + int rem = data; + for (int i = 0; i < 10; i++) + rem = (rem << 1) ^ ((rem >> 9) * 0x537); + int bits = (data << 10 | rem) ^ 0x5412; // uint15 + assert(bits >> 15 == 0); + + // Draw first copy + for (int i = 0; i <= 5; i++) + setModuleBounded(qrcode, 8, i, getBit(bits, i)); + setModuleBounded(qrcode, 8, 7, getBit(bits, 6)); + setModuleBounded(qrcode, 8, 8, getBit(bits, 7)); + setModuleBounded(qrcode, 7, 8, getBit(bits, 8)); + for (int i = 9; i < 15; i++) + setModuleBounded(qrcode, 14 - i, 8, getBit(bits, i)); + + // Draw second copy + int qrsize = qrcodegen_getSize(qrcode); + for (int i = 0; i < 8; i++) + setModuleBounded(qrcode, qrsize - 1 - i, 8, getBit(bits, i)); + for (int i = 8; i < 15; i++) + setModuleBounded(qrcode, 8, qrsize - 15 + i, getBit(bits, i)); + setModuleBounded(qrcode, 8, qrsize - 8, true); // Always dark +} + + +// Calculates and stores an ascending list of positions of alignment patterns +// for this version number, returning the length of the list (in the range [0,7]). +// Each position is in the range [0,177), and are used on both the x and y axes. +// This could be implemented as lookup table of 40 variable-length lists of unsigned bytes. +testable int getAlignmentPatternPositions(int version, uint8_t result[7]) { + if (version == 1) + return 0; + int numAlign = version / 7 + 2; + int step = (version == 32) ? 26 : + (version * 4 + numAlign * 2 + 1) / (numAlign * 2 - 2) * 2; + for (int i = numAlign - 1, pos = version * 4 + 10; i >= 1; i--, pos -= step) + result[i] = (uint8_t)pos; + result[0] = 6; + return numAlign; +} + + +// Sets every module in the range [left : left + width] * [top : top + height] to dark. +static void fillRectangle(int left, int top, int width, int height, uint8_t qrcode[]) { + for (int dy = 0; dy < height; dy++) { + for (int dx = 0; dx < width; dx++) + setModuleBounded(qrcode, left + dx, top + dy, true); + } +} + + + +/*---- Drawing data modules and masking ----*/ + +// Draws the raw codewords (including data and ECC) onto the given QR Code. This requires the initial state of +// the QR Code to be dark at function modules and light at codeword modules (including unused remainder bits). +static void drawCodewords(const uint8_t data[], int dataLen, uint8_t qrcode[]) { + int qrsize = qrcodegen_getSize(qrcode); + int i = 0; // Bit index into the data + // Do the funny zigzag scan + for (int right = qrsize - 1; right >= 1; right -= 2) { // Index of right column in each column pair + if (right == 6) + right = 5; + for (int vert = 0; vert < qrsize; vert++) { // Vertical counter + for (int j = 0; j < 2; j++) { + int x = right - j; // Actual x coordinate + bool upward = ((right + 1) & 2) == 0; + int y = upward ? qrsize - 1 - vert : vert; // Actual y coordinate + if (!getModuleBounded(qrcode, x, y) && i < dataLen * 8) { + bool dark = getBit(data[i >> 3], 7 - (i & 7)); + setModuleBounded(qrcode, x, y, dark); + i++; + } + // If this QR Code has any remainder bits (0 to 7), they were assigned as + // 0/false/light by the constructor and are left unchanged by this method + } + } + } + assert(i == dataLen * 8); +} + + +// XORs the codeword modules in this QR Code with the given mask pattern +// and given pattern of function modules. The codeword bits must be drawn +// before masking. Due to the arithmetic of XOR, calling applyMask() with +// the same mask value a second time will undo the mask. A final well-formed +// QR Code needs exactly one (not zero, two, etc.) mask applied. +static void applyMask(const uint8_t functionModules[], uint8_t qrcode[], enum qrcodegen_Mask mask) { + assert(0 <= (int)mask && (int)mask <= 7); // Disallows qrcodegen_Mask_AUTO + int qrsize = qrcodegen_getSize(qrcode); + for (int y = 0; y < qrsize; y++) { + for (int x = 0; x < qrsize; x++) { + if (getModuleBounded(functionModules, x, y)) + continue; + bool invert; + switch ((int)mask) { + case 0: invert = (x + y) % 2 == 0; break; + case 1: invert = y % 2 == 0; break; + case 2: invert = x % 3 == 0; break; + case 3: invert = (x + y) % 3 == 0; break; + case 4: invert = (x / 3 + y / 2) % 2 == 0; break; + case 5: invert = x * y % 2 + x * y % 3 == 0; break; + case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break; + case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break; + default: assert(false); return; + } + bool val = getModuleBounded(qrcode, x, y); + setModuleBounded(qrcode, x, y, val ^ invert); + } + } +} + + +// Calculates and returns the penalty score based on state of the given QR Code's current modules. +// This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. +static long getPenaltyScore(const uint8_t qrcode[]) { + int qrsize = qrcodegen_getSize(qrcode); + long result = 0; + + // Adjacent modules in row having same color, and finder-like patterns + for (int y = 0; y < qrsize; y++) { + bool runColor = false; + int runX = 0; + int runHistory[7] = {0}; + for (int x = 0; x < qrsize; x++) { + if (getModuleBounded(qrcode, x, y) == runColor) { + runX++; + if (runX == 5) + result += PENALTY_N1; + else if (runX > 5) + result++; + } else { + finderPenaltyAddHistory(runX, runHistory, qrsize); + if (!runColor) + result += finderPenaltyCountPatterns(runHistory, qrsize) * PENALTY_N3; + runColor = getModuleBounded(qrcode, x, y); + runX = 1; + } + } + result += finderPenaltyTerminateAndCount(runColor, runX, runHistory, qrsize) * PENALTY_N3; + } + // Adjacent modules in column having same color, and finder-like patterns + for (int x = 0; x < qrsize; x++) { + bool runColor = false; + int runY = 0; + int runHistory[7] = {0}; + for (int y = 0; y < qrsize; y++) { + if (getModuleBounded(qrcode, x, y) == runColor) { + runY++; + if (runY == 5) + result += PENALTY_N1; + else if (runY > 5) + result++; + } else { + finderPenaltyAddHistory(runY, runHistory, qrsize); + if (!runColor) + result += finderPenaltyCountPatterns(runHistory, qrsize) * PENALTY_N3; + runColor = getModuleBounded(qrcode, x, y); + runY = 1; + } + } + result += finderPenaltyTerminateAndCount(runColor, runY, runHistory, qrsize) * PENALTY_N3; + } + + // 2*2 blocks of modules having same color + for (int y = 0; y < qrsize - 1; y++) { + for (int x = 0; x < qrsize - 1; x++) { + bool color = getModuleBounded(qrcode, x, y); + if ( color == getModuleBounded(qrcode, x + 1, y) && + color == getModuleBounded(qrcode, x, y + 1) && + color == getModuleBounded(qrcode, x + 1, y + 1)) + result += PENALTY_N2; + } + } + + // Balance of dark and light modules + int dark = 0; + for (int y = 0; y < qrsize; y++) { + for (int x = 0; x < qrsize; x++) { + if (getModuleBounded(qrcode, x, y)) + dark++; + } + } + int total = qrsize * qrsize; // Note that size is odd, so dark/total != 1/2 + // Compute the smallest integer k >= 0 such that (45-5k)% <= dark/total <= (55+5k)% + int k = (int)((labs(dark * 20L - total * 10L) + total - 1) / total) - 1; + assert(0 <= k && k <= 9); + result += k * PENALTY_N4; + assert(0 <= result && result <= 2568888L); // Non-tight upper bound based on default values of PENALTY_N1, ..., N4 + return result; +} + + +// Can only be called immediately after a light run is added, and +// returns either 0, 1, or 2. A helper function for getPenaltyScore(). +static int finderPenaltyCountPatterns(const int runHistory[7], int qrsize) { + int n = runHistory[1]; + assert(n <= qrsize * 3); (void)qrsize; + bool core = n > 0 && runHistory[2] == n && runHistory[3] == n * 3 && runHistory[4] == n && runHistory[5] == n; + // The maximum QR Code size is 177, hence the dark run length n <= 177. + // Arithmetic is promoted to int, so n*4 will not overflow. + return (core && runHistory[0] >= n * 4 && runHistory[6] >= n ? 1 : 0) + + (core && runHistory[6] >= n * 4 && runHistory[0] >= n ? 1 : 0); +} + + +// Must be called at the end of a line (row or column) of modules. A helper function for getPenaltyScore(). +static int finderPenaltyTerminateAndCount(bool currentRunColor, int currentRunLength, int runHistory[7], int qrsize) { + if (currentRunColor) { // Terminate dark run + finderPenaltyAddHistory(currentRunLength, runHistory, qrsize); + currentRunLength = 0; + } + currentRunLength += qrsize; // Add light border to final run + finderPenaltyAddHistory(currentRunLength, runHistory, qrsize); + return finderPenaltyCountPatterns(runHistory, qrsize); +} + + +// Pushes the given value to the front and drops the last value. A helper function for getPenaltyScore(). +static void finderPenaltyAddHistory(int currentRunLength, int runHistory[7], int qrsize) { + if (runHistory[0] == 0) + currentRunLength += qrsize; // Add light border to initial run + memmove(&runHistory[1], &runHistory[0], 6 * sizeof(runHistory[0])); + runHistory[0] = currentRunLength; +} + + + +/*---- Basic QR Code information ----*/ + +// Public function - see documentation comment in header file. +int qrcodegen_getSize(const uint8_t qrcode[]) { + assert(qrcode != NULL); + int result = qrcode[0]; + assert((qrcodegen_VERSION_MIN * 4 + 17) <= result + && result <= (qrcodegen_VERSION_MAX * 4 + 17)); + return result; +} + + +// Public function - see documentation comment in header file. +bool qrcodegen_getModule(const uint8_t qrcode[], int x, int y) { + assert(qrcode != NULL); + int qrsize = qrcode[0]; + return (0 <= x && x < qrsize && 0 <= y && y < qrsize) && getModuleBounded(qrcode, x, y); +} + + +// Returns the color of the module at the given coordinates, which must be in bounds. +testable bool getModuleBounded(const uint8_t qrcode[], int x, int y) { + int qrsize = qrcode[0]; + assert(21 <= qrsize && qrsize <= 177 && 0 <= x && x < qrsize && 0 <= y && y < qrsize); + int index = y * qrsize + x; + return getBit(qrcode[(index >> 3) + 1], index & 7); +} + + +// Sets the color of the module at the given coordinates, which must be in bounds. +testable void setModuleBounded(uint8_t qrcode[], int x, int y, bool isDark) { + int qrsize = qrcode[0]; + assert(21 <= qrsize && qrsize <= 177 && 0 <= x && x < qrsize && 0 <= y && y < qrsize); + int index = y * qrsize + x; + int bitIndex = index & 7; + int byteIndex = (index >> 3) + 1; + if (isDark) + qrcode[byteIndex] |= 1 << bitIndex; + else + qrcode[byteIndex] &= (1 << bitIndex) ^ 0xFF; +} + + +// Sets the color of the module at the given coordinates, doing nothing if out of bounds. +testable void setModuleUnbounded(uint8_t qrcode[], int x, int y, bool isDark) { + int qrsize = qrcode[0]; + if (0 <= x && x < qrsize && 0 <= y && y < qrsize) + setModuleBounded(qrcode, x, y, isDark); +} + + +// Returns true iff the i'th bit of x is set to 1. Requires x >= 0 and 0 <= i <= 14. +static bool getBit(int x, int i) { + return ((x >> i) & 1) != 0; +} + + + +/*---- Segment handling ----*/ + +// Public function - see documentation comment in header file. +bool qrcodegen_isNumeric(const char *text) { + assert(text != NULL); + for (; *text != '\0'; text++) { + if (*text < '0' || *text > '9') + return false; + } + return true; +} + + +// Public function - see documentation comment in header file. +bool qrcodegen_isAlphanumeric(const char *text) { + assert(text != NULL); + for (; *text != '\0'; text++) { + if (strchr(ALPHANUMERIC_CHARSET, *text) == NULL) + return false; + } + return true; +} + + +// Public function - see documentation comment in header file. +size_t qrcodegen_calcSegmentBufferSize(enum qrcodegen_Mode mode, size_t numChars) { + int temp = calcSegmentBitLength(mode, numChars); + if (temp == LENGTH_OVERFLOW) + return SIZE_MAX; + assert(0 <= temp && temp <= INT16_MAX); + return ((size_t)temp + 7) / 8; +} + + +// Returns the number of data bits needed to represent a segment +// containing the given number of characters using the given mode. Notes: +// - Returns LENGTH_OVERFLOW on failure, i.e. numChars > INT16_MAX +// or the number of needed bits exceeds INT16_MAX (i.e. 32767). +// - Otherwise, all valid results are in the range [0, INT16_MAX]. +// - For byte mode, numChars measures the number of bytes, not Unicode code points. +// - For ECI mode, numChars must be 0, and the worst-case number of bits is returned. +// An actual ECI segment can have shorter data. For non-ECI modes, the result is exact. +testable int calcSegmentBitLength(enum qrcodegen_Mode mode, size_t numChars) { + // All calculations are designed to avoid overflow on all platforms + if (numChars > (unsigned int)INT16_MAX) + return LENGTH_OVERFLOW; + long result = (long)numChars; + if (mode == qrcodegen_Mode_NUMERIC) + result = (result * 10 + 2) / 3; // ceil(10/3 * n) + else if (mode == qrcodegen_Mode_ALPHANUMERIC) + result = (result * 11 + 1) / 2; // ceil(11/2 * n) + else if (mode == qrcodegen_Mode_BYTE) + result *= 8; + else if (mode == qrcodegen_Mode_KANJI) + result *= 13; + else if (mode == qrcodegen_Mode_ECI && numChars == 0) + result = 3 * 8; + else { // Invalid argument + assert(false); + return LENGTH_OVERFLOW; + } + assert(result >= 0); + if (result > INT16_MAX) + return LENGTH_OVERFLOW; + return (int)result; +} + + +// Public function - see documentation comment in header file. +struct qrcodegen_Segment qrcodegen_makeBytes(const uint8_t data[], size_t len, uint8_t buf[]) { + assert(data != NULL || len == 0); + struct qrcodegen_Segment result; + result.mode = qrcodegen_Mode_BYTE; + result.bitLength = calcSegmentBitLength(result.mode, len); + assert(result.bitLength != LENGTH_OVERFLOW); + result.numChars = (int)len; + if (len > 0) + memcpy(buf, data, len * sizeof(buf[0])); + result.data = buf; + return result; +} + + +// Public function - see documentation comment in header file. +struct qrcodegen_Segment qrcodegen_makeNumeric(const char *digits, uint8_t buf[]) { + assert(digits != NULL); + struct qrcodegen_Segment result; + size_t len = strlen(digits); + result.mode = qrcodegen_Mode_NUMERIC; + int bitLen = calcSegmentBitLength(result.mode, len); + assert(bitLen != LENGTH_OVERFLOW); + result.numChars = (int)len; + if (bitLen > 0) + memset(buf, 0, ((size_t)bitLen + 7) / 8 * sizeof(buf[0])); + result.bitLength = 0; + + unsigned int accumData = 0; + int accumCount = 0; + for (; *digits != '\0'; digits++) { + char c = *digits; + assert('0' <= c && c <= '9'); + accumData = accumData * 10 + (unsigned int)(c - '0'); + accumCount++; + if (accumCount == 3) { + appendBitsToBuffer(accumData, 10, buf, &result.bitLength); + accumData = 0; + accumCount = 0; + } + } + if (accumCount > 0) // 1 or 2 digits remaining + appendBitsToBuffer(accumData, accumCount * 3 + 1, buf, &result.bitLength); + assert(result.bitLength == bitLen); + result.data = buf; + return result; +} + + +// Public function - see documentation comment in header file. +struct qrcodegen_Segment qrcodegen_makeAlphanumeric(const char *text, uint8_t buf[]) { + assert(text != NULL); + struct qrcodegen_Segment result; + size_t len = strlen(text); + result.mode = qrcodegen_Mode_ALPHANUMERIC; + int bitLen = calcSegmentBitLength(result.mode, len); + assert(bitLen != LENGTH_OVERFLOW); + result.numChars = (int)len; + if (bitLen > 0) + memset(buf, 0, ((size_t)bitLen + 7) / 8 * sizeof(buf[0])); + result.bitLength = 0; + + unsigned int accumData = 0; + int accumCount = 0; + for (; *text != '\0'; text++) { + const char *temp = strchr(ALPHANUMERIC_CHARSET, *text); + assert(temp != NULL); + accumData = accumData * 45 + (unsigned int)(temp - ALPHANUMERIC_CHARSET); + accumCount++; + if (accumCount == 2) { + appendBitsToBuffer(accumData, 11, buf, &result.bitLength); + accumData = 0; + accumCount = 0; + } + } + if (accumCount > 0) // 1 character remaining + appendBitsToBuffer(accumData, 6, buf, &result.bitLength); + assert(result.bitLength == bitLen); + result.data = buf; + return result; +} + + +// Public function - see documentation comment in header file. +struct qrcodegen_Segment qrcodegen_makeEci(long assignVal, uint8_t buf[]) { + struct qrcodegen_Segment result; + result.mode = qrcodegen_Mode_ECI; + result.numChars = 0; + result.bitLength = 0; + if (assignVal < 0) + assert(false); + else if (assignVal < (1 << 7)) { + memset(buf, 0, 1 * sizeof(buf[0])); + appendBitsToBuffer((unsigned int)assignVal, 8, buf, &result.bitLength); + } else if (assignVal < (1 << 14)) { + memset(buf, 0, 2 * sizeof(buf[0])); + appendBitsToBuffer(2, 2, buf, &result.bitLength); + appendBitsToBuffer((unsigned int)assignVal, 14, buf, &result.bitLength); + } else if (assignVal < 1000000L) { + memset(buf, 0, 3 * sizeof(buf[0])); + appendBitsToBuffer(6, 3, buf, &result.bitLength); + appendBitsToBuffer((unsigned int)(assignVal >> 10), 11, buf, &result.bitLength); + appendBitsToBuffer((unsigned int)(assignVal & 0x3FF), 10, buf, &result.bitLength); + } else + assert(false); + result.data = buf; + return result; +} + + +// Calculates the number of bits needed to encode the given segments at the given version. +// Returns a non-negative number if successful. Otherwise returns LENGTH_OVERFLOW if a segment +// has too many characters to fit its length field, or the total bits exceeds INT16_MAX. +testable int getTotalBits(const struct qrcodegen_Segment segs[], size_t len, int version) { + assert(segs != NULL || len == 0); + long result = 0; + for (size_t i = 0; i < len; i++) { + int numChars = segs[i].numChars; + int bitLength = segs[i].bitLength; + assert(0 <= numChars && numChars <= INT16_MAX); + assert(0 <= bitLength && bitLength <= INT16_MAX); + int ccbits = numCharCountBits(segs[i].mode, version); + assert(0 <= ccbits && ccbits <= 16); + if (numChars >= (1L << ccbits)) + return LENGTH_OVERFLOW; // The segment's length doesn't fit the field's bit width + result += 4L + ccbits + bitLength; + if (result > INT16_MAX) + return LENGTH_OVERFLOW; // The sum might overflow an int type + } + assert(0 <= result && result <= INT16_MAX); + return (int)result; +} + + +// Returns the bit width of the character count field for a segment in the given mode +// in a QR Code at the given version number. The result is in the range [0, 16]. +static int numCharCountBits(enum qrcodegen_Mode mode, int version) { + assert(qrcodegen_VERSION_MIN <= version && version <= qrcodegen_VERSION_MAX); + int i = (version + 7) / 17; + switch (mode) { + case qrcodegen_Mode_NUMERIC : { static const int temp[] = {10, 12, 14}; return temp[i]; } + case qrcodegen_Mode_ALPHANUMERIC: { static const int temp[] = { 9, 11, 13}; return temp[i]; } + case qrcodegen_Mode_BYTE : { static const int temp[] = { 8, 16, 16}; return temp[i]; } + case qrcodegen_Mode_KANJI : { static const int temp[] = { 8, 10, 12}; return temp[i]; } + case qrcodegen_Mode_ECI : return 0; + default: assert(false); return -1; // Dummy value + } +} + + +#undef LENGTH_OVERFLOW diff --git a/src/samples/smolsite/qrcodegen.h b/src/samples/smolsite/qrcodegen.h new file mode 100644 index 000000000..6bbc15762 --- /dev/null +++ b/src/samples/smolsite/qrcodegen.h @@ -0,0 +1,385 @@ +/* + * QR Code generator library (C) + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/page/qr-code-generator-library + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +#pragma once + +#include +#include +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* + * This library creates QR Code symbols, which is a type of two-dimension barcode. + * Invented by Denso Wave and described in the ISO/IEC 18004 standard. + * A QR Code structure is an immutable square grid of dark and light cells. + * The library provides functions to create a QR Code from text or binary data. + * The library covers the QR Code Model 2 specification, supporting all versions (sizes) + * from 1 to 40, all 4 error correction levels, and 4 character encoding modes. + * + * Ways to create a QR Code object: + * - High level: Take the payload data and call qrcodegen_encodeText() or qrcodegen_encodeBinary(). + * - Low level: Custom-make the list of segments and call + * qrcodegen_encodeSegments() or qrcodegen_encodeSegmentsAdvanced(). + * (Note that all ways require supplying the desired error correction level and various byte buffers.) + */ + + +/*---- Enum and struct types----*/ + +/* + * The error correction level in a QR Code symbol. + */ +enum qrcodegen_Ecc { + // Must be declared in ascending order of error protection + // so that an internal qrcodegen function works properly + qrcodegen_Ecc_LOW = 0 , // The QR Code can tolerate about 7% erroneous codewords + qrcodegen_Ecc_MEDIUM , // The QR Code can tolerate about 15% erroneous codewords + qrcodegen_Ecc_QUARTILE, // The QR Code can tolerate about 25% erroneous codewords + qrcodegen_Ecc_HIGH , // The QR Code can tolerate about 30% erroneous codewords +}; + + +/* + * The mask pattern used in a QR Code symbol. + */ +enum qrcodegen_Mask { + // A special value to tell the QR Code encoder to + // automatically select an appropriate mask pattern + qrcodegen_Mask_AUTO = -1, + // The eight actual mask patterns + qrcodegen_Mask_0 = 0, + qrcodegen_Mask_1, + qrcodegen_Mask_2, + qrcodegen_Mask_3, + qrcodegen_Mask_4, + qrcodegen_Mask_5, + qrcodegen_Mask_6, + qrcodegen_Mask_7, +}; + + +/* + * Describes how a segment's data bits are interpreted. + */ +enum qrcodegen_Mode { + qrcodegen_Mode_NUMERIC = 0x1, + qrcodegen_Mode_ALPHANUMERIC = 0x2, + qrcodegen_Mode_BYTE = 0x4, + qrcodegen_Mode_KANJI = 0x8, + qrcodegen_Mode_ECI = 0x7, +}; + + +/* + * A segment of character/binary/control data in a QR Code symbol. + * The mid-level way to create a segment is to take the payload data + * and call a factory function such as qrcodegen_makeNumeric(). + * The low-level way to create a segment is to custom-make the bit buffer + * and initialize a qrcodegen_Segment struct with appropriate values. + * Even in the most favorable conditions, a QR Code can only hold 7089 characters of data. + * Any segment longer than this is meaningless for the purpose of generating QR Codes. + * Moreover, the maximum allowed bit length is 32767 because + * the largest QR Code (version 40) has 31329 modules. + */ +struct qrcodegen_Segment { + // The mode indicator of this segment. + enum qrcodegen_Mode mode; + + // The length of this segment's unencoded data. Measured in characters for + // numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode. + // Always zero or positive. Not the same as the data's bit length. + int numChars; + + // The data bits of this segment, packed in bitwise big endian. + // Can be null if the bit length is zero. + uint8_t *data; + + // The number of valid data bits used in the buffer. Requires + // 0 <= bitLength <= 32767, and bitLength <= (capacity of data array) * 8. + // The character count (numChars) must agree with the mode and the bit buffer length. + int bitLength; +}; + + + +/*---- Macro constants and functions ----*/ + +#define qrcodegen_VERSION_MIN 1 // The minimum version number supported in the QR Code Model 2 standard +#define qrcodegen_VERSION_MAX 40 // The maximum version number supported in the QR Code Model 2 standard + +// Calculates the number of bytes needed to store any QR Code up to and including the given version number, +// as a compile-time constant. For example, 'uint8_t buffer[qrcodegen_BUFFER_LEN_FOR_VERSION(25)];' +// can store any single QR Code from version 1 to 25 (inclusive). The result fits in an int (or int16). +// Requires qrcodegen_VERSION_MIN <= n <= qrcodegen_VERSION_MAX. +#define qrcodegen_BUFFER_LEN_FOR_VERSION(n) ((((n) * 4 + 17) * ((n) * 4 + 17) + 7) / 8 + 1) + +// The worst-case number of bytes needed to store one QR Code, up to and including +// version 40. This value equals 3918, which is just under 4 kilobytes. +// Use this more convenient value to avoid calculating tighter memory bounds for buffers. +#define qrcodegen_BUFFER_LEN_MAX qrcodegen_BUFFER_LEN_FOR_VERSION(qrcodegen_VERSION_MAX) + + + +/*---- Functions (high level) to generate QR Codes ----*/ + +/* + * Encodes the given text string to a QR Code, returning true if successful. + * If the data is too long to fit in any version in the given range + * at the given ECC level, then false is returned. + * + * The input text must be encoded in UTF-8 and contain no NULs. + * Requires 1 <= minVersion <= maxVersion <= 40. + * + * The smallest possible QR Code version within the given range is automatically + * chosen for the output. Iff boostEcl is true, then the ECC level of the result + * may be higher than the ecl argument if it can be done without increasing the + * version. The mask is either between qrcodegen_Mask_0 to 7 to force that mask, or + * qrcodegen_Mask_AUTO to automatically choose an appropriate mask (which may be slow). + * + * About the arrays, letting len = qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion): + * - Before calling the function: + * - The array ranges tempBuffer[0 : len] and qrcode[0 : len] must allow + * reading and writing; hence each array must have a length of at least len. + * - The two ranges must not overlap (aliasing). + * - The initial state of both ranges can be uninitialized + * because the function always writes before reading. + * - After the function returns: + * - Both ranges have no guarantee on which elements are initialized and what values are stored. + * - tempBuffer contains no useful data and should be treated as entirely uninitialized. + * - If successful, qrcode can be passed into qrcodegen_getSize() and qrcodegen_getModule(). + * + * If successful, the resulting QR Code may use numeric, + * alphanumeric, or byte mode to encode the text. + * + * In the most optimistic case, a QR Code at version 40 with low ECC + * can hold any UTF-8 string up to 2953 bytes, or any alphanumeric string + * up to 4296 characters, or any digit string up to 7089 characters. + * These numbers represent the hard upper limit of the QR Code standard. + * + * Please consult the QR Code specification for information on + * data capacities per version, ECC level, and text encoding mode. + */ +bool qrcodegen_encodeText(const char *text, uint8_t tempBuffer[], uint8_t qrcode[], + enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl); + + +/* + * Encodes the given binary data to a QR Code, returning true if successful. + * If the data is too long to fit in any version in the given range + * at the given ECC level, then false is returned. + * + * Requires 1 <= minVersion <= maxVersion <= 40. + * + * The smallest possible QR Code version within the given range is automatically + * chosen for the output. Iff boostEcl is true, then the ECC level of the result + * may be higher than the ecl argument if it can be done without increasing the + * version. The mask is either between qrcodegen_Mask_0 to 7 to force that mask, or + * qrcodegen_Mask_AUTO to automatically choose an appropriate mask (which may be slow). + * + * About the arrays, letting len = qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion): + * - Before calling the function: + * - The array ranges dataAndTemp[0 : len] and qrcode[0 : len] must allow + * reading and writing; hence each array must have a length of at least len. + * - The two ranges must not overlap (aliasing). + * - The input array range dataAndTemp[0 : dataLen] should normally be + * valid UTF-8 text, but is not required by the QR Code standard. + * - The initial state of dataAndTemp[dataLen : len] and qrcode[0 : len] + * can be uninitialized because the function always writes before reading. + * - After the function returns: + * - Both ranges have no guarantee on which elements are initialized and what values are stored. + * - dataAndTemp contains no useful data and should be treated as entirely uninitialized. + * - If successful, qrcode can be passed into qrcodegen_getSize() and qrcodegen_getModule(). + * + * If successful, the resulting QR Code will use byte mode to encode the data. + * + * In the most optimistic case, a QR Code at version 40 with low ECC can hold any byte + * sequence up to length 2953. This is the hard upper limit of the QR Code standard. + * + * Please consult the QR Code specification for information on + * data capacities per version, ECC level, and text encoding mode. + */ +bool qrcodegen_encodeBinary(uint8_t dataAndTemp[], size_t dataLen, uint8_t qrcode[], + enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl); + + +/*---- Functions (low level) to generate QR Codes ----*/ + +/* + * Encodes the given segments to a QR Code, returning true if successful. + * If the data is too long to fit in any version at the given ECC level, + * then false is returned. + * + * The smallest possible QR Code version is automatically chosen for + * the output. The ECC level of the result may be higher than the + * ecl argument if it can be done without increasing the version. + * + * About the byte arrays, letting len = qrcodegen_BUFFER_LEN_FOR_VERSION(qrcodegen_VERSION_MAX): + * - Before calling the function: + * - The array ranges tempBuffer[0 : len] and qrcode[0 : len] must allow + * reading and writing; hence each array must have a length of at least len. + * - The two ranges must not overlap (aliasing). + * - The initial state of both ranges can be uninitialized + * because the function always writes before reading. + * - The input array segs can contain segments whose data buffers overlap with tempBuffer. + * - After the function returns: + * - Both ranges have no guarantee on which elements are initialized and what values are stored. + * - tempBuffer contains no useful data and should be treated as entirely uninitialized. + * - Any segment whose data buffer overlaps with tempBuffer[0 : len] + * must be treated as having invalid values in that array. + * - If successful, qrcode can be passed into qrcodegen_getSize() and qrcodegen_getModule(). + * + * Please consult the QR Code specification for information on + * data capacities per version, ECC level, and text encoding mode. + * + * This function allows the user to create a custom sequence of segments that switches + * between modes (such as alphanumeric and byte) to encode text in less space. + * This is a low-level API; the high-level API is qrcodegen_encodeText() and qrcodegen_encodeBinary(). + */ +bool qrcodegen_encodeSegments(const struct qrcodegen_Segment segs[], size_t len, + enum qrcodegen_Ecc ecl, uint8_t tempBuffer[], uint8_t qrcode[]); + + +/* + * Encodes the given segments to a QR Code, returning true if successful. + * If the data is too long to fit in any version in the given range + * at the given ECC level, then false is returned. + * + * Requires 1 <= minVersion <= maxVersion <= 40. + * + * The smallest possible QR Code version within the given range is automatically + * chosen for the output. Iff boostEcl is true, then the ECC level of the result + * may be higher than the ecl argument if it can be done without increasing the + * version. The mask is either between qrcodegen_Mask_0 to 7 to force that mask, or + * qrcodegen_Mask_AUTO to automatically choose an appropriate mask (which may be slow). + * + * About the byte arrays, letting len = qrcodegen_BUFFER_LEN_FOR_VERSION(qrcodegen_VERSION_MAX): + * - Before calling the function: + * - The array ranges tempBuffer[0 : len] and qrcode[0 : len] must allow + * reading and writing; hence each array must have a length of at least len. + * - The two ranges must not overlap (aliasing). + * - The initial state of both ranges can be uninitialized + * because the function always writes before reading. + * - The input array segs can contain segments whose data buffers overlap with tempBuffer. + * - After the function returns: + * - Both ranges have no guarantee on which elements are initialized and what values are stored. + * - tempBuffer contains no useful data and should be treated as entirely uninitialized. + * - Any segment whose data buffer overlaps with tempBuffer[0 : len] + * must be treated as having invalid values in that array. + * - If successful, qrcode can be passed into qrcodegen_getSize() and qrcodegen_getModule(). + * + * Please consult the QR Code specification for information on + * data capacities per version, ECC level, and text encoding mode. + * + * This function allows the user to create a custom sequence of segments that switches + * between modes (such as alphanumeric and byte) to encode text in less space. + * This is a low-level API; the high-level API is qrcodegen_encodeText() and qrcodegen_encodeBinary(). + */ +bool qrcodegen_encodeSegmentsAdvanced(const struct qrcodegen_Segment segs[], size_t len, enum qrcodegen_Ecc ecl, + int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl, uint8_t tempBuffer[], uint8_t qrcode[]); + + +/* + * Tests whether the given string can be encoded as a segment in numeric mode. + * A string is encodable iff each character is in the range 0 to 9. + */ +bool qrcodegen_isNumeric(const char *text); + + +/* + * Tests whether the given string can be encoded as a segment in alphanumeric mode. + * A string is encodable iff each character is in the following set: 0 to 9, A to Z + * (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon. + */ +bool qrcodegen_isAlphanumeric(const char *text); + + +/* + * Returns the number of bytes (uint8_t) needed for the data buffer of a segment + * containing the given number of characters using the given mode. Notes: + * - Returns SIZE_MAX on failure, i.e. numChars > INT16_MAX or the internal + * calculation of the number of needed bits exceeds INT16_MAX (i.e. 32767). + * - Otherwise, all valid results are in the range [0, ceil(INT16_MAX / 8)], i.e. at most 4096. + * - It is okay for the user to allocate more bytes for the buffer than needed. + * - For byte mode, numChars measures the number of bytes, not Unicode code points. + * - For ECI mode, numChars must be 0, and the worst-case number of bytes is returned. + * An actual ECI segment can have shorter data. For non-ECI modes, the result is exact. + */ +size_t qrcodegen_calcSegmentBufferSize(enum qrcodegen_Mode mode, size_t numChars); + + +/* + * Returns a segment representing the given binary data encoded in + * byte mode. All input byte arrays are acceptable. Any text string + * can be converted to UTF-8 bytes and encoded as a byte mode segment. + */ +struct qrcodegen_Segment qrcodegen_makeBytes(const uint8_t data[], size_t len, uint8_t buf[]); + + +/* + * Returns a segment representing the given string of decimal digits encoded in numeric mode. + */ +struct qrcodegen_Segment qrcodegen_makeNumeric(const char *digits, uint8_t buf[]); + + +/* + * Returns a segment representing the given text string encoded in alphanumeric mode. + * The characters allowed are: 0 to 9, A to Z (uppercase only), space, + * dollar, percent, asterisk, plus, hyphen, period, slash, colon. + */ +struct qrcodegen_Segment qrcodegen_makeAlphanumeric(const char *text, uint8_t buf[]); + + +/* + * Returns a segment representing an Extended Channel Interpretation + * (ECI) designator with the given assignment value. + */ +struct qrcodegen_Segment qrcodegen_makeEci(long assignVal, uint8_t buf[]); + + +/*---- Functions to extract raw data from QR Codes ----*/ + +/* + * Returns the side length of the given QR Code, assuming that encoding succeeded. + * The result is in the range [21, 177]. Note that the length of the array buffer + * is related to the side length - every 'uint8_t qrcode[]' must have length at least + * qrcodegen_BUFFER_LEN_FOR_VERSION(version), which equals ceil(size^2 / 8 + 1). + */ +int qrcodegen_getSize(const uint8_t qrcode[]); + + +/* + * Returns the color of the module (pixel) at the given coordinates, which is false + * for light or true for dark. The top left corner has the coordinates (x=0, y=0). + * If the given coordinates are out of bounds, then false (light) is returned. + */ +bool qrcodegen_getModule(const uint8_t qrcode[], int x, int y); + + +#ifdef __cplusplus +} +#endif diff --git a/src/samples/smolsite/smolsite.html b/src/samples/smolsite/smolsite.html index a32e823e0..285376a5d 100644 --- a/src/samples/smolsite/smolsite.html +++ b/src/samples/smolsite/smolsite.html @@ -25,15 +25,46 @@ text-align: right; font-size: 16px; text-shadow: 0 0 16px black; + float: right; + } + .darkened { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-color: rgba(255, 255, 255, .8); + backdrop-filter: blur(2px); + z-index: 998; + } + .big { + position: absolute; + width: auto; + height: calc(100% - 32px); + left: 16px; + top: 16px; + image-rendering: pixelated; + image-rendering: -moz-crisp-edges; + image-rendering: crisp-edges; + z-index: 999; } - -

Hosted in the URL by 🗜️ - smolsite.zip. Powered by the - Lwan web server. -

+ + +{{has_qr_code?}} +
+ QR Code +{{/has_qr_code?}} + +

The address of this site contains the whole website! Learn how it works on 🗜️smolsite.zip.

\ No newline at end of file From fbf5b5a6d59c83eb611db567f591faa066347bd1 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 20 Sep 2023 20:06:49 -0700 Subject: [PATCH 2124/2505] Add FAQ to smolsite sample --- src/samples/smolsite/index.html | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/samples/smolsite/index.html b/src/samples/smolsite/index.html index 2c7cc0a0c..5e4f35ab0 100644 --- a/src/samples/smolsite/index.html +++ b/src/samples/smolsite/index.html @@ -25,8 +25,36 @@

It's a smolsite!

 $ zip -DXjq9 somesite.zip index.html mylogo.png
-$ echo "https://smolsite.zip/%60cat%20somesite.zip%20|%20base64%20--wrap%200%60"
+$ echo "https://smolsite.zip/%60base64%20--wrap%200%20%3C%20somesite.zip%60"
 
+ +

FAQ

+

I don't have a command line, can I still share a smolsite?

+

Sure, drag the ZIP file to the bottom purple bar.

+ +

Can I host a website on a piece of paper?

+

Yes! Click on the QR code below to see a printable/scannable version +of this website.

+ +

Is this open source?

+

Yes, it is open source.

+ +

Is the ZIP file unpacked in the server?

The contents are +processed in the server and kept in RAM for 15 minutes. The content is gone +after that, unless someone opens the same link again.

+ +

Is this protected against ZIP bombs?

Deflated data is sent back +unmodified to browsers, and no recursive unzipping happens. So, yes, it's +somewhat protected against ZIP bombs.

+ +

Can content be taken down?

We'll do our best to ensure exact URLs +are blocked from being shared by smolsite. (We can't fully take the content +down, as it is in the URL and that's something we do not control: if someone +has a link to improper content, they can extract it without the need of +smolsite.) +Send the full link to abuse.smolsite@tia.mat.br +with the reason the content should be taken down and we'll take it from there.

+ From 1d87bffa1aca480b3da526e737c7dcaa7aa40fc2 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 21 Sep 2023 21:21:12 -0700 Subject: [PATCH 2125/2505] Add endpoint to benchmark against openresty [1] mentions that "OpenResty is the fastest web engine out there" while also mentioning Lwan, so I decided to add the same endpoint used in the blog post to benchmark both on my machine. Lwan did ~340krps and OpenResty did ~190krps. Not bad. :) [1] https://berwyn.hashnode.dev/openresty-vs-lua-54-a-benchmark --- src/bin/testrunner/testrunner.conf | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/bin/testrunner/testrunner.conf b/src/bin/testrunner/testrunner.conf index e1e27dc53..4a43fbac5 100644 --- a/src/bin/testrunner/testrunner.conf +++ b/src/bin/testrunner/testrunner.conf @@ -97,6 +97,20 @@ site { req:say('Hello') end''' } + lua /multiply { + # Implementation of https://github.com/berwynhoyt/nginx-lua-benchmark + + default type = text/html + + cache period = 30s + + script = '''function handle_get_root(req) + local param_a = req:query_param[[a]] + local param_b = req:query_param[[b]] + req:set_response("

RESULT: " .. param_a .. "*" .. param_b .. + "=" .. param_a * param_b .. "

") + end''' + } lua /lua { default type = text/html script file = test.lua From a4b5fc0fed4dc005861048753fb79a230e1fcd16 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 23 Sep 2023 15:01:43 -0700 Subject: [PATCH 2126/2505] Use a perfect hash table to lookup HTTP status codes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use a naïve, brute-force approach to generate the table. This reduces the table even further (data section has reduced by over 3800 bytes!), while removing two branches per lookup (at the expense of a little bit more arithmethic). I haven't benchmarked both solutions. --- src/bin/tools/CMakeLists.txt | 4 +- src/bin/tools/statuslookupgen.c | 103 ++++++++++++++++++++++++++++++++ src/lib/CMakeLists.txt | 13 ++++ src/lib/lwan-http-status.h | 24 ++++++++ src/lib/lwan-tables.c | 16 ++--- src/lib/lwan.h | 23 +------ 6 files changed, 148 insertions(+), 35 deletions(-) create mode 100644 src/bin/tools/statuslookupgen.c create mode 100644 src/lib/lwan-http-status.h diff --git a/src/bin/tools/CMakeLists.txt b/src/bin/tools/CMakeLists.txt index c045780e7..061d25e1f 100644 --- a/src/bin/tools/CMakeLists.txt +++ b/src/bin/tools/CMakeLists.txt @@ -40,5 +40,7 @@ else () add_executable(weighttp weighttp.c) target_link_libraries(weighttp ${CMAKE_THREAD_LIBS_INIT}) - export(TARGETS weighttp configdump mimegen bin2hex FILE ${CMAKE_BINARY_DIR}/ImportExecutables.cmake) + add_executable(statuslookupgen statuslookupgen.c) + + export(TARGETS statuslookupgen weighttp configdump mimegen bin2hex FILE ${CMAKE_BINARY_DIR}/ImportExecutables.cmake) endif () diff --git a/src/bin/tools/statuslookupgen.c b/src/bin/tools/statuslookupgen.c new file mode 100644 index 000000000..d851cf590 --- /dev/null +++ b/src/bin/tools/statuslookupgen.c @@ -0,0 +1,103 @@ +/* Naïve brute-force perfect hash table generator for HTTP status lookup */ + +#include +#include +#include +#include +#include + +#include "../../lib/lwan-http-status.h" + +static inline uint32_t rotate(int v, int n) +{ + uint32_t vv = (uint32_t)v; + return vv << (32 - n) | vv >> n; +} + +int main(void) +{ + uint32_t max_key = 0; + int min_key = INT_MAX; +#define COMPARE_MAX(ignore1, key, ignore2, ignore3) \ + do { \ + if (key > max_key) \ + max_key = key; \ + if (key < min_key) \ + min_key = key; \ + } while (0); + FOR_EACH_HTTP_STATUS(COMPARE_MAX) +#undef COMPARE_MAX + +#define SELECT_KEY(ignore1, key, ignore2, ignore3) key, + const int keys[] = {FOR_EACH_HTTP_STATUS(SELECT_KEY)}; +#undef SELECT_KEY + +#define N_KEYS ((int)(sizeof(keys) / sizeof(keys[0]))) + + int best_rot = INT_MAX; + uint32_t best_mod = 64; + int best_subtract = INT_MAX; + + if (N_KEYS >= best_mod) { + fprintf(stderr, "table too large!\n"); + return 1; + } + + for (int subtract = 0; subtract < min_key; subtract++) { + for (int rot = 0; rot < 32; rot++) { + for (uint32_t mod = 1; mod < best_mod; mod++) { + uint64_t set = 0; + int set_bits = 0; + + for (int key = 0; key < N_KEYS; key++) { + uint32_t k = rotate(keys[key] - subtract, rot) % mod; + + if (!(set & 1ull<= 64) { + fprintf(stderr, "table would be larger than 64 items!\n"); + return 1; + } + + uint64_t set_values = 0xffffffffffffffffull; + printf("static ALWAYS_INLINE const char *lwan_lookup_http_status_impl(enum lwan_http_status status) {\n"); + printf(" static const char *table[] = {\n"); +#define PRINT_V(ignored1, key, short_desc, long_desc) do { \ + uint32_t k = rotate(key - best_subtract, best_rot) % best_mod; \ + set_values &= ~(1ull<> %d)) %% %d];\n", 32 - best_rot, best_rot, best_mod); + + printf("}\n"); + +} diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index b023977ca..a353a78ad 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -88,6 +88,19 @@ add_custom_target(generate_mime_types_table ) add_dependencies(lwan-static generate_mime_types_table) + +add_custom_command( + OUTPUT ${CMAKE_BINARY_DIR}/lookup-http-status.h + COMMAND statuslookupgen > ${CMAKE_BINARY_DIR}/lookup-http-status.h + DEPENDS statuslookupgen + COMMENT "Building HTTP status lookup perfect hash table" +) +add_custom_target(generate_lookup_http_status + DEPENDS ${CMAKE_BINARY_DIR}/lookup-http-status.h +) +add_dependencies(lwan-static generate_lookup_http_status) + + add_custom_command( OUTPUT ${CMAKE_BINARY_DIR}/servefile-data.h COMMAND bin2hex diff --git a/src/lib/lwan-http-status.h b/src/lib/lwan-http-status.h new file mode 100644 index 000000000..1bf66a208 --- /dev/null +++ b/src/lib/lwan-http-status.h @@ -0,0 +1,24 @@ +#pragma once + +#define FOR_EACH_HTTP_STATUS(X) \ + X(SWITCHING_PROTOCOLS, 101, "Switching protocols", "Protocol is switching over from HTTP") \ + X(OK, 200, "OK", "Success") \ + X(PARTIAL_CONTENT, 206, "Partial content", "Delivering part of requested resource") \ + X(MOVED_PERMANENTLY, 301, "Moved permanently", "This content has moved to another place") \ + X(NOT_MODIFIED, 304, "Not modified", "The content has not changed since previous request") \ + X(TEMPORARY_REDIRECT, 307, "Temporary Redirect", "This content can be temporarily found at a different location") \ + X(BAD_REQUEST, 400, "Bad request", "The client has issued a bad request") \ + X(NOT_AUTHORIZED, 401, "Not authorized", "Client has no authorization to access this resource") \ + X(FORBIDDEN, 403, "Forbidden", "Access to this resource has been denied") \ + X(NOT_FOUND, 404, "Not found", "The requested resource could not be found on this server") \ + X(NOT_ALLOWED, 405, "Not allowed", "The requested method is not allowed by this server") \ + X(NOT_ACCEPTABLE, 406, "Not acceptable", "No suitable accepted-encoding header provided") \ + X(TIMEOUT, 408, "Request timeout", "Client did not produce a request within expected timeframe") \ + X(TOO_LARGE, 413, "Request too large", "The request entity is too large") \ + X(RANGE_UNSATISFIABLE, 416, "Requested range unsatisfiable", "The server can't supply the requested portion of the requested resource") \ + X(I_AM_A_TEAPOT, 418, "I'm a teapot", "Client requested to brew coffee but device is a teapot") \ + X(CLIENT_TOO_HIGH, 420, "Client too high", "Client is too high to make a request") \ + X(INTERNAL_ERROR, 500, "Internal server error", "The server encountered an internal error that couldn't be recovered from") \ + X(NOT_IMPLEMENTED, 501, "Not implemented", "Server lacks the ability to fulfil the request") \ + X(UNAVAILABLE, 503, "Service unavailable", "The server is either overloaded or down for maintenance") \ + X(SERVER_TOO_HIGH, 520, "Server too high", "The server is too high to answer the request") diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index a1af6b226..f11cc5a1a 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -167,20 +167,12 @@ lwan_determine_mime_type_for_file_name(const char *file_name) return "application/octet-stream"; } +#include "lookup-http-status.h" /* genrated by statuslookupgen */ + ALWAYS_INLINE const char * lwan_http_status_as_string_with_code(const enum lwan_http_status status) { -#define ENTRY(id, code, short, long) [HTTP_##id] = #code " " short "\0" long, - static const char *table[] = {FOR_EACH_HTTP_STATUS(ENTRY)}; -#undef GENERATE_ENTRY - - if (LIKELY((size_t)status < N_ELEMENTS(table))) { - const char *entry = table[status]; - if (LIKELY(entry)) - return entry; - } - - return "999 Invalid\0Unknown status code"; + return lwan_lookup_http_status_impl(status); } const char * @@ -191,7 +183,7 @@ lwan_http_status_as_string(const enum lwan_http_status status) const char *lwan_http_status_as_descriptive_string(const enum lwan_http_status status) { - const char *str = lwan_http_status_as_string_with_code(status); + const char *str = lwan_lookup_http_status_impl(status); return str + strlen(str) + 1; } diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 1ed172e8b..1720195b7 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -163,28 +163,7 @@ static ALWAYS_INLINE uint64_t string_as_uint64(const char *s) #define LWAN_ARRAY_PARAM(length) [static length] #endif -#define FOR_EACH_HTTP_STATUS(X) \ - X(SWITCHING_PROTOCOLS, 101, "Switching protocols", "Protocol is switching over from HTTP") \ - X(OK, 200, "OK", "Success") \ - X(PARTIAL_CONTENT, 206, "Partial content", "Delivering part of requested resource") \ - X(MOVED_PERMANENTLY, 301, "Moved permanently", "This content has moved to another place") \ - X(NOT_MODIFIED, 304, "Not modified", "The content has not changed since previous request") \ - X(TEMPORARY_REDIRECT, 307, "Temporary Redirect", "This content can be temporarily found at a different location") \ - X(BAD_REQUEST, 400, "Bad request", "The client has issued a bad request") \ - X(NOT_AUTHORIZED, 401, "Not authorized", "Client has no authorization to access this resource") \ - X(FORBIDDEN, 403, "Forbidden", "Access to this resource has been denied") \ - X(NOT_FOUND, 404, "Not found", "The requested resource could not be found on this server") \ - X(NOT_ALLOWED, 405, "Not allowed", "The requested method is not allowed by this server") \ - X(NOT_ACCEPTABLE, 406, "Not acceptable", "No suitable accepted-encoding header provided") \ - X(TIMEOUT, 408, "Request timeout", "Client did not produce a request within expected timeframe") \ - X(TOO_LARGE, 413, "Request too large", "The request entity is too large") \ - X(RANGE_UNSATISFIABLE, 416, "Requested range unsatisfiable", "The server can't supply the requested portion of the requested resource") \ - X(I_AM_A_TEAPOT, 418, "I'm a teapot", "Client requested to brew coffee but device is a teapot") \ - X(CLIENT_TOO_HIGH, 420, "Client too high", "Client is too high to make a request") \ - X(INTERNAL_ERROR, 500, "Internal server error", "The server encountered an internal error that couldn't be recovered from") \ - X(NOT_IMPLEMENTED, 501, "Not implemented", "Server lacks the ability to fulfil the request") \ - X(UNAVAILABLE, 503, "Service unavailable", "The server is either overloaded or down for maintenance") \ - X(SERVER_TOO_HIGH, 520, "Server too high", "The server is too high to answer the request") +#include "lwan-http-status.h" #define GENERATE_ENUM_ITEM(id, code, short, long) HTTP_ ## id = code, enum lwan_http_status { From 633eec979bddf523673a71b7a5ce45ae8c9397fe Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 24 Sep 2023 16:20:24 -0700 Subject: [PATCH 2127/2505] lwan_tables_shutdown() got deleted somehow, re-add it --- src/lib/lwan-tables.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index f11cc5a1a..cbeca3f6a 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -38,6 +38,10 @@ static unsigned char uncompressed_mime_entries[MIME_UNCOMPRESSED_LEN]; static char *mime_types[MIME_ENTRIES]; static bool mime_entries_initialized = false; +void lwan_tables_shutdown(void) +{ +} + void lwan_tables_init(void) { if (mime_entries_initialized) From 6fea9adc42a4f4d7cb9977087963410c91fb4624 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 27 Sep 2023 20:16:48 -0700 Subject: [PATCH 2128/2505] Open github link in new window Closes #360 --- src/samples/smolsite/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/samples/smolsite/index.html b/src/samples/smolsite/index.html index 5e4f35ab0..fa90b791f 100644 --- a/src/samples/smolsite/index.html +++ b/src/samples/smolsite/index.html @@ -37,7 +37,7 @@

Can I host a website on a piece of paper?

of this website.

Is this open source?

-

Yes, it is open source.

+

Yes, it is open source.

Is the ZIP file unpacked in the server?

The contents are processed in the server and kept in RAM for 15 minutes. The content is gone From b181b6675be967b76c27bc40948f523ff1349bd3 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 27 Sep 2023 20:17:26 -0700 Subject: [PATCH 2129/2505] Don't send referer header when requesting QR code Let's save a bit of bandwidth, shall we? --- src/samples/smolsite/smolsite.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/samples/smolsite/smolsite.html b/src/samples/smolsite/smolsite.html index 285376a5d..011131f84 100644 --- a/src/samples/smolsite/smolsite.html +++ b/src/samples/smolsite/smolsite.html @@ -61,7 +61,7 @@ {{has_qr_code?}}

- QR Code + QR Code {{/has_qr_code?}}

The address of this site contains the whole website! Learn how it works on 🗜️smolsite.zip.

From 0bfcf6f880a1f768b676a97d903f4f7bc6612927 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 30 Sep 2023 09:07:43 -0700 Subject: [PATCH 2130/2505] Allow caches to be set to read-only This avoids locking, atomics, and entry lifetime management if those aren't really needed in some applications. --- src/lib/lwan-cache.c | 32 ++++++++++++++++- src/lib/lwan-cache.h | 2 ++ src/samples/techempower/techempower.c | 51 +++++++-------------------- 3 files changed, 46 insertions(+), 39 deletions(-) diff --git a/src/lib/lwan-cache.c b/src/lib/lwan-cache.c index 36bd90e6b..01d1bdb25 100644 --- a/src/lib/lwan-cache.c +++ b/src/lib/lwan-cache.c @@ -39,7 +39,8 @@ enum { FREE_KEY_ON_DESTROY = 1 << 2, /* Cache flags */ - SHUTTING_DOWN = 1 << 0 + SHUTTING_DOWN = 1 << 0, + READ_ONLY = 1 << 1, }; struct cache { @@ -201,6 +202,17 @@ struct cache_entry *cache_get_and_ref_entry(struct cache *cache, *error = 0; + if (cache->flags & READ_ONLY) { + entry = hash_find(cache->hash.table, key); +#ifndef NDEBUG + if (LIKELY(entry)) + ATOMIC_INC(cache->stats.hits); + else + ATOMIC_INC(cache->stats.misses); +#endif + return entry; + } + /* If the lock can't be obtained, return an error to allow, for instance, * yielding from the coroutine and trying to obtain the lock at a later * time. */ @@ -291,6 +303,9 @@ void cache_entry_unref(struct cache *cache, struct cache_entry *entry) { assert(entry); + if (cache->flags & READ_ONLY) + return; + /* FIXME: There's a race condition in this function: if the cache is * destroyed while there are either temporary or floating entries, * calling the destroy_entry callback function will dereference @@ -325,6 +340,11 @@ static bool cache_pruner_job(void *data) struct list_head queue; unsigned int evicted = 0; + /* This job might start execution as we mark ourselves as read-only, + * and before this job is removed from the job thread. */ + if (cache->flags & READ_ONLY) + return true; + if (UNLIKELY(pthread_rwlock_trywrlock(&cache->queue.lock) == EBUSY)) return false; @@ -418,6 +438,10 @@ struct cache_entry *cache_coro_get_and_ref_entry(struct cache *cache, struct coro *coro, const void *key) { + /* If a cache is read-only, cache_get_and_ref_entry() should be + * used directly. */ + assert(!(cache->flags & READ_ONLY)); + for (int tries = GET_AND_REF_TRIES; tries; tries--) { int error; struct cache_entry *ce = cache_get_and_ref_entry(cache, key, &error); @@ -444,3 +468,9 @@ struct cache_entry *cache_coro_get_and_ref_entry(struct cache *cache, return NULL; } + +void cache_make_read_only(struct cache *cache) +{ + cache->flags |= READ_ONLY; + lwan_job_del(cache_pruner_job, cache); +} diff --git a/src/lib/lwan-cache.h b/src/lib/lwan-cache.h index c146fd4f5..066bdfc1a 100644 --- a/src/lib/lwan-cache.h +++ b/src/lib/lwan-cache.h @@ -56,3 +56,5 @@ struct cache_entry *cache_get_and_ref_entry(struct cache *cache, void cache_entry_unref(struct cache *cache, struct cache_entry *entry); struct cache_entry *cache_coro_get_and_ref_entry(struct cache *cache, struct coro *coro, const void *key); + +void cache_make_read_only(struct cache *cache); diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index d907525e3..efba1933a 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -314,39 +314,6 @@ static void cached_queries_free(struct cache_entry *entry, void *context) free(entry); } -static struct cache_entry *my_cache_coro_get_and_ref_entry( - struct cache *cache, struct lwan_request *request, int key) -{ - /* Using this function instead of cache_coro_get_and_ref_entry() will avoid - * calling coro_defer(), which, in cases where the number of cached queries - * is too high, will trigger reallocations of the coro_defer array (and the - * "demotion" from the storage inlined in the coro struct to somewhere in - * the heap). - * - * For large number of cached elements, too, this will reduce the number of - * indirect calls that are performed every time a request is serviced. - */ - - for (int tries = 64; tries; tries--) { - int error; - struct cache_entry *ce = - cache_get_and_ref_entry(cache, (void *)(uintptr_t)key, &error); - - if (LIKELY(ce)) - return ce; - - if (error != EWOULDBLOCK) - break; - - coro_yield(request->conn->coro, CONN_CORO_WANT_WRITE); - - if (tries > 16) - lwan_request_sleep(request, (unsigned int)(tries / 8)); - } - - return NULL; -} - LWAN_HANDLER(cached_queries) { const char *queries_str = lwan_request_get_query_param(request, "count"); @@ -360,15 +327,15 @@ LWAN_HANDLER(cached_queries) for (long i = 0; i < queries; i++) { struct db_json_cached *jc; int key = (int)lwan_random_uint64() % 10000; + int error; - jc = (struct db_json_cached *)my_cache_coro_get_and_ref_entry( - cached_queries_cache, request, key); - if (UNLIKELY(!jc)) - return HTTP_INTERNAL_ERROR; + jc = (struct db_json_cached *)cache_get_and_ref_entry( + cached_queries_cache, (void *)(intptr_t)key, &error); qj.queries[i] = jc->db_json; - cache_entry_unref(cached_queries_cache, (struct cache_entry *)jc); + /* No need to unref the cache entry here: cache is marked as read-only, + * so it would be a no-op. */ } /* Avoid reallocations/copies while building response. Each response @@ -526,6 +493,14 @@ int main(void) 3600 /* 1 hour */); if (!cached_queries_cache) lwan_status_critical("Could not create cached queries cache"); + /* Pre-populate the cache and make it read-only to avoid locking in the fast + * path. */ + for (int i = 0; i < 10000; i++) { + int error; + (void)cache_get_and_ref_entry(cached_queries_cache, (void *)(intptr_t)i, + &error); + } + cache_make_read_only(cached_queries_cache); lwan_main_loop(&l); From e600ef434e1b22c370f17a5a9f5506c2c5411b43 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 30 Sep 2023 09:10:37 -0700 Subject: [PATCH 2131/2505] Ensure statuslookupgen.c uses uint32_t values --- src/bin/tools/statuslookupgen.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/bin/tools/statuslookupgen.c b/src/bin/tools/statuslookupgen.c index d851cf590..782ae090b 100644 --- a/src/bin/tools/statuslookupgen.c +++ b/src/bin/tools/statuslookupgen.c @@ -8,10 +8,9 @@ #include "../../lib/lwan-http-status.h" -static inline uint32_t rotate(int v, int n) +static inline uint32_t rotate(uint32_t v, int n) { - uint32_t vv = (uint32_t)v; - return vv << (32 - n) | vv >> n; + return v << (32 - n) | v >> n; } int main(void) @@ -50,7 +49,7 @@ int main(void) int set_bits = 0; for (int key = 0; key < N_KEYS; key++) { - uint32_t k = rotate(keys[key] - subtract, rot) % mod; + uint32_t k = rotate((uint32_t)keys[key] - (uint32_t)subtract, rot) % mod; if (!(set & 1ull< Date: Sat, 30 Sep 2023 09:11:37 -0700 Subject: [PATCH 2132/2505] Factor out code to obtain number of queries --- src/samples/techempower/techempower.c | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index efba1933a..0d365ad3c 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -241,15 +241,18 @@ LWAN_HANDLER(db) &db_json); } -LWAN_HANDLER(queries) +static long get_number_of_queries(struct lwan_request *request) { - enum lwan_http_status ret = HTTP_INTERNAL_ERROR; const char *queries_str = lwan_request_get_query_param(request, "queries"); - long queries; + return LIKELY(queries_str) + ? LWAN_MIN(500, LWAN_MAX(1, parse_long(queries_str, -1))) + : 1; +} - queries = LIKELY(queries_str) - ? LWAN_MIN(500, LWAN_MAX(1, parse_long(queries_str, -1))) - : 1; +LWAN_HANDLER(queries) +{ + enum lwan_http_status ret = HTTP_INTERNAL_ERROR; + long queries = get_number_of_queries(request); struct db_stmt *stmt = db_prepare_stmt(get_db(), random_number_query, sizeof(random_number_query) - 1); @@ -316,12 +319,7 @@ static void cached_queries_free(struct cache_entry *entry, void *context) LWAN_HANDLER(cached_queries) { - const char *queries_str = lwan_request_get_query_param(request, "count"); - long queries; - - queries = LIKELY(queries_str) - ? LWAN_MIN(500, LWAN_MAX(1, parse_long(queries_str, -1))) - : 1; + long queries = get_number_of_queries(request); struct queries_json qj = {.queries_len = (size_t)queries}; for (long i = 0; i < queries; i++) { From 17cafa2234567af58e71bed2cf9ae0cef9963372 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 7 Oct 2023 09:34:11 -0700 Subject: [PATCH 2133/2505] Reduce search space for mod operation in statuslookupgen The mod will never be smaller than the number of keys, so let's start with that rather than 1. --- src/bin/tools/statuslookupgen.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/tools/statuslookupgen.c b/src/bin/tools/statuslookupgen.c index 782ae090b..14babcafc 100644 --- a/src/bin/tools/statuslookupgen.c +++ b/src/bin/tools/statuslookupgen.c @@ -44,7 +44,7 @@ int main(void) for (int subtract = 0; subtract < min_key; subtract++) { for (int rot = 0; rot < 32; rot++) { - for (uint32_t mod = 1; mod < best_mod; mod++) { + for (uint32_t mod = N_KEYS; mod < best_mod; mod++) { uint64_t set = 0; int set_bits = 0; From 021ee7dedf39eec06cc74cb027cfff360515181b Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 7 Oct 2023 09:36:08 -0700 Subject: [PATCH 2134/2505] Add another Lwan quote --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 0dd8e5201..cb7b2d1ad 100644 --- a/README.md +++ b/README.md @@ -1154,6 +1154,8 @@ in no particular order. Contributions are appreciated: > source code for pure entertainment, it's so good. *high five*" -- > [@kwilczynski](https://twitter.com/kwilczynski/status/692881117003644929) +> "mad science" -- [jwz](https://jwz.org/b/yjFZ) + > "Nice work with Lwan! I haven't looked _that_ carefully yet but so far I > like what I saw. You definitely have the right ideas." -- > [@thinkingfish](https://twitter.com/thinkingfish/status/521574267612196864) From 8236b72d94ad29229ad6bb7bdf425a1a05df2d1d Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 7 Oct 2023 09:38:15 -0700 Subject: [PATCH 2135/2505] Add test for lwan_char_*() functions --- src/lib/lwan-tables.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index cbeca3f6a..a265967d1 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -291,3 +291,19 @@ ALWAYS_INLINE uint8_t lwan_char_isalnum(char ch) { return char_prop_tbl[(unsigned char)ch] & (CHAR_PROP_ALPHA | CHAR_PROP_DIG); } + +#ifndef NDEBUG +#include +__attribute__((constructor)) +static void test_lwan_char_tables(void) +{ + for (int i = 0; i < 256; i++) { + assert(!!isxdigit((char)i) == !!lwan_char_isxdigit((char)i)); + assert(!!isdigit((char)i) == !!lwan_char_isdigit((char)i)); + assert(!!isalpha((char)i) == !!lwan_char_isalpha((char)i)); + assert(!!isalnum((char)i) == !!lwan_char_isalnum((char)i)); + + /* isspace() and lwan_char_isspace() differs on purpose */ + } +} +#endif \ No newline at end of file From 3a8f9e7b3a452047d3c2165dbed73c3c305774a8 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 7 Oct 2023 09:39:26 -0700 Subject: [PATCH 2136/2505] Tweak smolsite index a little bit --- src/samples/smolsite/index.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/samples/smolsite/index.html b/src/samples/smolsite/index.html index fa90b791f..b55978405 100644 --- a/src/samples/smolsite/index.html +++ b/src/samples/smolsite/index.html @@ -21,11 +21,11 @@

It's a smolsite!

This whole site fits inside the URL!

-

To host yours: use the command-line: put everything in a ZIP file, Base 64 encode it, and tack it after "https://smolsite.zip/":

+

To host yours: use the command-line: put everything in a ZIP file, Base 64 encode it, and tack it after "https://smolsite.zip/". For instance, if your +smol site is comprised of two files, index.html and mylogo.png, you can generate a URL like so:

-$ zip -DXjq9 somesite.zip index.html mylogo.png
-$ echo "https://smolsite.zip/%60base64%20--wrap%200%20%3C%20somesite.zip%60"
+$ echo "https://smolsite.zip/%60zip%20-DXjq9%20-%20index.html%20mylogo.png%20|%20base64%20--wrap%200%60"
 

FAQ

@@ -37,7 +37,7 @@

Can I host a website on a piece of paper?

of this website.

Is this open source?

-

Yes, it is open source.

+

Yes, it's open source, part of the Lwan web server project.

Is the ZIP file unpacked in the server?

The contents are processed in the server and kept in RAM for 15 minutes. The content is gone From dc0c313d560e78685538214745aa49580a993b63 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 12 Oct 2023 21:08:08 -0700 Subject: [PATCH 2137/2505] Reduce status lookup table by ~9 entries Try a bit harder when finding a value to subtract from the key before rotating. Going all the way up to max_key instead of min_key lets us have a 35-item array instead of a 44-item array, saving a bit over a cache line. --- src/bin/tools/statuslookupgen.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bin/tools/statuslookupgen.c b/src/bin/tools/statuslookupgen.c index 14babcafc..423e8b097 100644 --- a/src/bin/tools/statuslookupgen.c +++ b/src/bin/tools/statuslookupgen.c @@ -35,21 +35,21 @@ int main(void) int best_rot = INT_MAX; uint32_t best_mod = 64; - int best_subtract = INT_MAX; + uint32_t best_subtract = UINT_MAX; if (N_KEYS >= best_mod) { fprintf(stderr, "table too large!\n"); return 1; } - for (int subtract = 0; subtract < min_key; subtract++) { + for (uint32_t subtract = 0; subtract < max_key; subtract++) { for (int rot = 0; rot < 32; rot++) { for (uint32_t mod = N_KEYS; mod < best_mod; mod++) { uint64_t set = 0; int set_bits = 0; for (int key = 0; key < N_KEYS; key++) { - uint32_t k = rotate((uint32_t)keys[key] - (uint32_t)subtract, rot) % mod; + uint32_t k = rotate((uint32_t)keys[key] - subtract, rot) % mod; if (!(set & 1ull< Date: Thu, 12 Oct 2023 21:09:53 -0700 Subject: [PATCH 2138/2505] Use .incbin assembly directive to include bin2hex files --- src/bin/tools/bin2hex.c | 47 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/src/bin/tools/bin2hex.c b/src/bin/tools/bin2hex.c index 034e3936d..60cf6b9bf 100644 --- a/src/bin/tools/bin2hex.c +++ b/src/bin/tools/bin2hex.c @@ -28,7 +28,7 @@ #include "lwan.h" -static int bin2hex(const char *path, const char *identifier) +static int bin2hex_mmap(const char *path, const char *identifier) { int fd = open(path, O_RDONLY | O_CLOEXEC); struct stat st; @@ -77,12 +77,41 @@ static int bin2hex(const char *path, const char *identifier) return 0; } +static int bin2hex_incbin(const char *path, const char *identifier) +{ + printf("__asm__(\".section \\\".rodata\\\"\\n\"\n"); + printf(" \"%s_start:\\n\"\n", identifier); + printf(" \".incbin \\\"%s\\\"\\n\"\n", path); + printf(" \"%s_end:\\n\"\n", identifier); + printf(" \".previous\\n\");\n"); + printf("static struct lwan_value %s_value;\n", identifier); + printf("__attribute__((visibility(\"internal\"))) extern char %s_start[], %s_end[];\n", identifier, identifier); + + return 0; +} + +static int bin2hex(const char *path, const char *identifier) +{ + int r = 0; + + printf("\n/* Contents of %s available through %s_value */\n", path, identifier); + + printf("#if defined(__GNUC__) || defined(__clang__)\n"); + r |= bin2hex_incbin(path, identifier); + printf("#else\n"); + r |= bin2hex_mmap(path, identifier); + printf("#endif\n\n"); + + return r; +} + int main(int argc, char *argv[]) { int arg; if (argc < 2) { - fprintf(stderr, "Usage: %s /path/to/file file_identifier [...]\n", argv[0]); + fprintf(stderr, "Usage: %s /path/to/file file_identifier [...]\n", + argv[0]); return 1; } @@ -106,5 +135,19 @@ int main(int argc, char *argv[]) } } + printf("#if defined(__GNUC__) || defined(__clang__)\n"); + printf("__attribute__((constructor (101))) static void\n"); + printf("initialize_bin2hex_%016lx(void)\n", (uintptr_t)argv); + printf("{\n"); + for (arg = 1; arg < argc; arg += 2) { + const char *identifier = argv[arg + 1]; + + printf(" %s_value = (struct lwan_value) {.value = (char *)%s_start, " + ".len = (size_t)(%s_end - %s_start)};\n", + identifier, identifier, identifier, identifier); + } + printf("}\n"); + printf("#endif\n"); + return 0; } From bd218b7c9d9efac28ed15d302f8b0bb91a81d1f8 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 21 Oct 2023 16:04:03 -0700 Subject: [PATCH 2139/2505] Support kqueue1() OpenBSD 7.4 added kqueue1(2), allowing each kqueue to be created with the CLOEXEC flag. Use that if available, and use fcntl(2) to set the flag when it isn't. Untested. --- CMakeLists.txt | 1 + src/cmake/lwan-build-config.h.cmake | 1 + src/lib/missing.c | 22 +++++++++++++++++++++- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 837eb9892..d5d85e10d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -160,6 +160,7 @@ if (LWAN_HAVE_SYS_EVENT) sys/event.h sys/types.h sys/time.h ) check_function_exists(kqueue LWAN_HAVE_KQUEUE) + check_function_exists(kqueue1 LWAN_HAVE_KQUEUE1) endif () check_include_file(alloca.h LWAN_HAVE_ALLOCA_H) if (LWAN_HAVE_SYS_AUXV) diff --git a/src/cmake/lwan-build-config.h.cmake b/src/cmake/lwan-build-config.h.cmake index eec754eec..e70b3e139 100644 --- a/src/cmake/lwan-build-config.h.cmake +++ b/src/cmake/lwan-build-config.h.cmake @@ -34,6 +34,7 @@ #cmakedefine LWAN_HAVE_REALLOCARRAY #cmakedefine LWAN_HAVE_EPOLL #cmakedefine LWAN_HAVE_KQUEUE +#cmakedefine LWAN_HAVE_KQUEUE1 #cmakedefine LWAN_HAVE_DLADDR #cmakedefine LWAN_HAVE_POSIX_FADVISE #cmakedefine LWAN_HAVE_LINUX_CAPABILITY diff --git a/src/lib/missing.c b/src/lib/missing.c index ee649ac12..e8df77109 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -142,7 +142,27 @@ int clock_gettime(clockid_t clk_id, struct timespec *ts) #include "hash.h" -int epoll_create1(int flags __attribute__((unused))) { return kqueue(); } +int epoll_create1(int flags) +{ +#if defined(LWAN_HAVE_KQUEUE1) + return kqueue1(flags & EPOLL_CLOEXEC ? O_CLOEXEC : 0); +#else + int fd = kqueue(); + + if (flags & EPOLL_CLOEXEC) { + int flags; + + flags = fcntl(fd, F_GETFD); + if (flags < 0) + return -1; + + if (fcntl(fd, F_SETFD, flags | O_CLOEXEC) < 0) + return -1; + } + + return fd; +#endif +} static int epoll_no_event_marker; From b90fcdbd8ad2d493c494cc3e8bef5ac9f3db22f2 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 4 Nov 2023 19:00:21 -0700 Subject: [PATCH 2140/2505] Bail quickly on collision while building perfect hash table --- src/bin/tools/statuslookupgen.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/bin/tools/statuslookupgen.c b/src/bin/tools/statuslookupgen.c index 423e8b097..7c7913a86 100644 --- a/src/bin/tools/statuslookupgen.c +++ b/src/bin/tools/statuslookupgen.c @@ -50,11 +50,12 @@ int main(void) for (int key = 0; key < N_KEYS; key++) { uint32_t k = rotate((uint32_t)keys[key] - subtract, rot) % mod; + + if (set & 1ull< Date: Sat, 4 Nov 2023 19:01:38 -0700 Subject: [PATCH 2141/2505] Actually ignore spaces in parse_time_period() I forgot to write code to advance the pointer! --- src/lib/lwan-config.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 35224ccbc..cd63471e9 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -125,6 +125,7 @@ unsigned int parse_time_period(const char *str, unsigned int default_value) * https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=44910 */ if (isspace(*str)) { ignored_spaces++; + str++; if (ignored_spaces > 1024) break; From 9dd7692b4c2616ee9c5ddf475c56112268fe3575 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 4 Nov 2023 19:02:29 -0700 Subject: [PATCH 2142/2505] Remove last remnants of calls to strncasecmp() --- src/lib/lwan-request.c | 3 +-- src/lib/lwan.c | 2 +- src/lib/missing.c | 11 +++++++++-- src/lib/missing/string.h | 1 + 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 96dfce7e3..508511750 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1669,10 +1669,9 @@ lwan_request_get_header_from_helper(struct lwan_request_parser_helper *helper, if (UNLIKELY((size_t)(end - start) < header_len_with_separator)) continue; - STRING_SWITCH_SMALL (start + header_len) { case STR2_INT(':', ' '): - if (!strncasecmp(start, header, header_len)) { + if (strcaseequal_neutral_len(start, header, header_len)) { *end = '\0'; return start + header_len_with_separator; } diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 8840f8317..56a9ce523 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -157,7 +157,7 @@ static bool can_override_header(const char *name) return false; if (strcaseequal_neutral(name, "Transfer-Encoding")) return false; - if (!strncasecmp(name, "Access-Control-Allow-", + if (strcaseequal_neutral_len(name, "Access-Control-Allow-", sizeof("Access-Control-Allow-") - 1)) return false; diff --git a/src/lib/missing.c b/src/lib/missing.c index e8df77109..f2fa1fc33 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -690,9 +690,9 @@ static inline int isalpha_neutral(char c) return table[uc >> 3] & 1 << (uc & 7); } -bool strcaseequal_neutral(const char *a, const char *b) +bool strcaseequal_neutral_len(const char *a, const char *b, size_t len) { - for (;;) { + while (len--) { char ca = *a++; char cb = *b++; @@ -715,6 +715,13 @@ bool strcaseequal_neutral(const char *a, const char *b) return false; } } + + return false; +} + +ALWAYS_INLINE bool strcaseequal_neutral(const char *a, const char *b) +{ + return strcaseequal_neutral_len(a, b, SIZE_MAX); } #ifndef NDEBUG diff --git a/src/lib/missing/string.h b/src/lib/missing/string.h index b33ed6d88..be42e986d 100644 --- a/src/lib/missing/string.h +++ b/src/lib/missing/string.h @@ -77,5 +77,6 @@ static inline void *mempmove(void *dest, const void *src, size_t len) } bool strcaseequal_neutral(const char *a, const char *b); +bool strcaseequal_neutral_len(const char *a, const char *b, size_t len); #endif /* MISSING_STRING_H */ From 8cf968988aaed4124ca95b2e3e6e9f92af6140ef Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 10 Nov 2023 20:10:58 -0800 Subject: [PATCH 2143/2505] Install lwan-http-status.h Closes #361 --- src/lib/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index a353a78ad..6c507d255 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -180,6 +180,7 @@ install(FILES lwan-config.h lwan-coro.h lwan.h + lwan-http-status.h lwan-mod-serve-files.h lwan-mod-rewrite.h lwan-mod-response.h From 89259ef315333efee556ccbbac724d26805e31e0 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 10 Nov 2023 20:12:27 -0800 Subject: [PATCH 2144/2505] parse_time_period() should return default_value on too many spaces --- src/lib/lwan-config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index cd63471e9..46ed4c3a2 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -128,7 +128,7 @@ unsigned int parse_time_period(const char *str, unsigned int default_value) str++; if (ignored_spaces > 1024) - break; + return default_value; continue; } From a799db03e073e2c38e63fa86c9f3d8963bf93f70 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 10 Nov 2023 20:18:41 -0800 Subject: [PATCH 2145/2505] Tweak SO_ATTACH_REUSEPORT_CBPF usage I've been playing with this for months and never found the reason (more context in the comments I'm adding here) why this doesn't really work the way it's intended to. Instead of throwing away this patch, let's just commit it and make this something for future me to continue investigating when I'm in a better headspace. --- src/lib/lwan-thread.c | 79 +++++++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 29 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index a2e584988..1acbb1b77 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -866,6 +866,11 @@ static int create_listen_socket(struct lwan_thread *t, /* Ignore errors here, as this is just a hint */ #if defined(LWAN_HAVE_SO_ATTACH_REUSEPORT_CBPF) + /* FIXME: this doesn't seem to work as expected. if the program returns + * a fixed number, sockets are always accepted by a thread pinned to + * that particular CPU; if SKF_AD_CPU is used, sockets are accepted by + * random threads as if this BPF script weren't installed at all. */ + /* From socket(7): "These options may be set repeatedly at any time on * any socket in the group to replace the current BPF program used by * all sockets in the group." */ @@ -910,8 +915,15 @@ static void *thread_io_loop(void *data) struct coro_switcher switcher; struct timeout_queue tq; - lwan_status_debug("Worker thread #%zd starting", - t - t->lwan->thread.threads + 1); + if (t->cpu == UINT_MAX) { + lwan_status_info("Worker thread #%zd starting", + t - t->lwan->thread.threads + 1); + } else { + lwan_status_info("Worker thread #%zd starting on CPU %d", + t - t->lwan->thread.threads + 1, + t->cpu); + } + lwan_set_thread_name("worker"); events = calloc((size_t)max_events, sizeof(*events)); @@ -1290,8 +1302,10 @@ void lwan_thread_init(struct lwan *l) if (!l->thread.threads) lwan_status_critical("Could not allocate memory for threads"); + for (unsigned int i = 0; i < l->thread.count; i++) + l->thread.threads[i].cpu = UINT_MAX; + uint32_t *schedtbl; - bool adj_affinity; #if defined(__x86_64__) && defined(__linux__) if (l->online_cpus > 1) { @@ -1313,10 +1327,17 @@ void lwan_thread_init(struct lwan *l) * a way that false sharing is avoided. */ schedtbl = calloc(l->thread.count, sizeof(uint32_t)); - adj_affinity = topology_to_schedtbl(l, schedtbl, l->thread.count); + bool adjust_affinity = topology_to_schedtbl(l, schedtbl, l->thread.count); - for (unsigned int i = 0; i < total_conns; i++) - l->conns[i].thread = &l->thread.threads[schedtbl[i % l->thread.count]]; + for (unsigned int i = 0; i < total_conns; i++) { + unsigned int thread_id = schedtbl[i % l->thread.count]; + l->conns[i].thread = &l->thread.threads[thread_id]; + } + + if (!adjust_affinity) { + free(schedtbl); + schedtbl = NULL; + } } else #endif /* __x86_64__ && __linux__ */ { @@ -1328,7 +1349,6 @@ void lwan_thread_init(struct lwan *l) l->conns[i].thread = &l->thread.threads[i % l->thread.count]; schedtbl = NULL; - adj_affinity = false; } if (l->config.use_dynamic_buffer) { @@ -1338,27 +1358,30 @@ void lwan_thread_init(struct lwan *l) } for (unsigned int i = 0; i < l->thread.count; i++) { - struct lwan_thread *thread = NULL; - + struct lwan_thread *thread; + if (schedtbl) { - /* This is not the most elegant thing, but this assures that the - * listening sockets are added to the SO_REUSEPORT group in a - * specific order, because that's what the CBPF program to direct - * the incoming connection to the right CPU will use. */ - for (uint32_t thread_id = 0; thread_id < l->thread.count; - thread_id++) { - if (schedtbl[thread_id % l->thread.count] == i) { - thread = &l->thread.threads[thread_id]; - break; - } - } - if (!thread) { - /* FIXME: can this happen when we have a offline CPU? */ - lwan_status_critical( - "Could not figure out which CPU thread %d should go to", i); - } + /* For SO_ATTACH_REUSEPORT_CBPF to work with the program + * we provide the kernel, sockets have to be added to the + * reuseport group in an order consistent with the + * CPU ID (SKF_AD_CPU field): so group the threads + * according to the CPU topology to avoid false sharing + * the connections array, and pin the N-th thread to the + * N-th CPU. */ + + /* FIXME: I don't know why this isn't working as I intended: + * clients are still accepted by a thread that's not the + * worker thread that's supposed to handle that particular + * file descriptor. According to socket(7), the plain + * SO_REUSEPORT mechanism might be used if the returned + * index is wrong, so maybe that's what's happening? I don't + * know, gotta debug the kernel to figure this out. */ + thread = &l->thread.threads[schedtbl[i]]; + + /* FIXME: figure out which CPUs are actually online */ + thread->cpu = i; } else { - thread = &l->thread.threads[i % l->thread.count]; + thread = &l->thread.threads[i]; } if (pthread_barrier_init(&l->thread.barrier, NULL, 2)) @@ -1378,10 +1401,8 @@ void lwan_thread_init(struct lwan *l) thread->tls_listen_fd = -1; } - if (adj_affinity) { - l->thread.threads[i].cpu = schedtbl[i % l->thread.count]; + if (schedtbl) adjust_thread_affinity(thread); - } pthread_barrier_wait(&l->thread.barrier); } From 74456161f923b9e4c0d49c535b7a2c1b84d87022 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 16 Nov 2023 20:19:36 -0800 Subject: [PATCH 2146/2505] Add mention of Lwan in ISCC --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index cb7b2d1ad..5ec0e75fa 100644 --- a/README.md +++ b/README.md @@ -1007,6 +1007,7 @@ Mentions in academic journals: * [A dynamic predictive race detector for C/C++ programs (in English, published 2017)](https://link.springer.com/article/10.1007/s11227-017-1996-8) uses Lwan as a "real world example". * [High-precision Data Race Detection Method for Large Scale Programs (in Chinese, published 2021)](http://www.jos.org.cn/jos/article/abstract/6260) also uses Lwan as one of the case studies. +* [AGE: Automatic Performance Evaluation of API Gateways (in English, published 2023)](https://www.computer.org/csdl/proceedings-article/iscc/2023/10218286/1PYLvz6ihBm) mentions Lwan as part of its usage in the KrakenD benchmarks. Mentions in magazines: From 329fe4d81d6c45c64396a8247700cb994cf75355 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 18 Nov 2023 12:14:51 -0800 Subject: [PATCH 2147/2505] Clean up statuslookupgen a bit more --- src/bin/tools/statuslookupgen.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/bin/tools/statuslookupgen.c b/src/bin/tools/statuslookupgen.c index 7c7913a86..b13363420 100644 --- a/src/bin/tools/statuslookupgen.c +++ b/src/bin/tools/statuslookupgen.c @@ -71,12 +71,8 @@ int main(void) fprintf(stderr, "could not figure out the hash table parameters!\n"); return 1; } - if (best_mod >= 64) { - fprintf(stderr, "table would be larger than 64 items!\n"); - return 1; - } - uint64_t set_values = 0xffffffffffffffffull; + uint64_t set_values = ~0ull; printf("static ALWAYS_INLINE const char *lwan_lookup_http_status_impl(enum lwan_http_status status) {\n"); printf(" static const char *table[] = {\n"); #define PRINT_V(ignored1, key, short_desc, long_desc) do { \ From 47fe6cf7e9e951f767d4d1e0f402f833508db4d9 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 18 Nov 2023 12:15:38 -0800 Subject: [PATCH 2148/2505] Consider number of sockets when delivering sockets with CBPF --- src/lib/lwan-thread.c | 46 +++++++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 1acbb1b77..53a36fd7c 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -864,7 +864,7 @@ static int create_listen_socket(struct lwan_thread *t, if (listen_fd < 0) lwan_status_critical("Could not create listen_fd"); - /* Ignore errors here, as this is just a hint */ + /* Ignore errors here, as this is just a hint */ #if defined(LWAN_HAVE_SO_ATTACH_REUSEPORT_CBPF) /* FIXME: this doesn't seem to work as expected. if the program returns * a fixed number, sockets are always accepted by a thread pinned to @@ -875,20 +875,46 @@ static int create_listen_socket(struct lwan_thread *t, * any socket in the group to replace the current BPF program used by * all sockets in the group." */ if (num == 0) { - /* From socket(7): "The BPF program must return an index between 0 and - * N-1 representing the socket which should receive the packet (where N - * is the number of sockets in the group)." */ - const uint32_t cpu_ad_off = (uint32_t)SKF_AD_OFF + SKF_AD_CPU; + /* From socket(7): "The BPF program must return an index between 0 + * and N-1 representing the socket which should receive the packet + * (where N is the number of sockets in the group)." + * + * This should work because sockets are created in the same + * reuseport group, in the same order as the logical CPU#, and the + * worker threads for these sockets are pinned to the same CPU#. The + * MOD operation is there for cases where we have more CPUs than + * threads (e.g. by setting the "threads" setting in the configuration + * file); this isn't strictly necessary as any invalid value returned + * by this program will direct the connection to a random socket in + * the group. + * + * Unfortunately, this program doesn't work that way. Sockets seem + * to be delivered to a different thread every time. Maybe if we + * change this to eBPF, we'll be able to fetch the file descriptor + * and feed that into our scheduling table. */ + const uint32_t cpu_ad_cpu = (uint32_t)SKF_AD_OFF + SKF_AD_CPU; + const uint32_t n_sockets = lwan->thread.count; struct sock_filter filter[] = { - {BPF_LD | BPF_W | BPF_ABS, 0, 0, cpu_ad_off}, /* A = curr_cpu_index */ - {BPF_RET | BPF_A, 0, 0, 0}, /* return A */ + {BPF_LD | BPF_W | BPF_ABS, 0, 0, cpu_ad_cpu}, /* A = current_cpu_idx */ + {BPF_ALU | BPF_MOD, 0, 0, n_sockets}, /* A %= socket_count */ + {BPF_RET | BPF_A, 0, 0, 0}, /* return A */ }; struct sock_fprog fprog = {.filter = filter, .len = N_ELEMENTS(filter)}; + if (n_sockets > 1 && (n_sockets & (n_sockets - 1)) == 0) { + /* Not sure if the kernel will perform strength reduction + * on CBPF, so do it here. This is a common case. */ + assert(filter[1].code == (BPF_ALU | BPF_MOD)); + assert(filter[1].k == n_sockets); + + filter[1].code = BPF_ALU | BPF_MOD; + filter[1].k = n_sockets - 1; + } + (void)setsockopt(listen_fd, SOL_SOCKET, SO_ATTACH_REUSEPORT_CBPF, &fprog, sizeof(fprog)); - (void)setsockopt(listen_fd, SOL_SOCKET, SO_LOCK_FILTER, - (int[]){1}, sizeof(int)); + (void)setsockopt(listen_fd, SOL_SOCKET, SO_LOCK_FILTER, (int[]){1}, + sizeof(int)); } #elif defined(LWAN_HAVE_SO_INCOMING_CPU) && defined(__x86_64__) (void)setsockopt(listen_fd, SOL_SOCKET, SO_INCOMING_CPU, &t->cpu, @@ -897,7 +923,7 @@ static int create_listen_socket(struct lwan_thread *t, struct epoll_event event = { .events = EPOLLIN | EPOLLET | EPOLLERR, - .data.ptr = &t->lwan->conns[listen_fd], + .data.ptr = &lwan->conns[listen_fd], }; if (epoll_ctl(t->epoll_fd, EPOLL_CTL_ADD, listen_fd, &event) < 0) lwan_status_critical_perror("Could not add socket to epoll"); From 4f58b3c75e3fbb93051476846f82a0ce1085e81e Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 18 Nov 2023 16:50:17 -0800 Subject: [PATCH 2149/2505] Fix strength reduction in reuseport CBPF code --- src/lib/lwan-thread.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 53a36fd7c..6ba65281d 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -907,7 +907,7 @@ static int create_listen_socket(struct lwan_thread *t, assert(filter[1].code == (BPF_ALU | BPF_MOD)); assert(filter[1].k == n_sockets); - filter[1].code = BPF_ALU | BPF_MOD; + filter[1].code = BPF_ALU | BPF_AND; filter[1].k = n_sockets - 1; } From a3659162f26eeed3c9d321a1dd3569d1a3c0166b Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 28 Nov 2023 22:18:24 -0800 Subject: [PATCH 2150/2505] strcaseequal_neutral_len() was not returning true when it should've Add a few tests on every debug startup too --- src/lib/missing.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/lib/missing.c b/src/lib/missing.c index f2fa1fc33..b552cfef3 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -716,7 +716,7 @@ bool strcaseequal_neutral_len(const char *a, const char *b, size_t len) } } - return false; + return (ssize_t)len < 0; } ALWAYS_INLINE bool strcaseequal_neutral(const char *a, const char *b) @@ -735,6 +735,11 @@ __attribute__((constructor)) static void test_strcaseequal_neutral(void) assert(strcaseequal_neutral("LwAN", "lwam") == false); assert(strcaseequal_neutral("LwAn", "lWaM") == false); + assert(strcaseequal_neutral_len("Host: localhost:8080", "Host", 4) == true); + assert(strcaseequal_neutral_len("Host", "Host: localhost:8080", 4) == true); + assert(strcaseequal_neutral_len("Host", "Hosh: not-localhost:1234", 4) == false); + assert(strcaseequal_neutral_len("Host: something-else:443", "Host: localhost:8080", 4) == true); + static_assert(CHAR_BIT == 8, "sane CHAR_BIT value"); static_assert('*' == 42, "ASCII character set"); static_assert('0' == 48, "ASCII character set"); From 34b1abe71cf4b828f088278cb7108f66f74ef81d Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 28 Nov 2023 22:19:56 -0800 Subject: [PATCH 2151/2505] Use Host header in pastebin sample rather than hardcoded constants (This allows one to insert any string through the Host header in the response, as there's no validation other than "there's no newline", which is part of the HTTP parsing code.) --- src/samples/pastebin/main.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/samples/pastebin/main.c b/src/samples/pastebin/main.c index 74fbb6671..7ea736b80 100644 --- a/src/samples/pastebin/main.c +++ b/src/samples/pastebin/main.c @@ -29,8 +29,6 @@ #include "lwan-cache.h" #include "lwan-private.h" -#define SERVER_NAME "paste.lwan.ws" -#define SERVER_PORT 443 #define CACHE_FOR_HOURS 2 static struct cache *pastes; @@ -123,9 +121,15 @@ static enum lwan_http_status post_paste(struct lwan_request *request, cache_coro_get_and_ref_entry(pastes, request->conn->coro, key); if (paste) { + const char *host_hdr = lwan_request_get_header(request, "Host"); + + if (!host_hdr) + return HTTP_BAD_REQUEST; + response->mime_type = "text/plain"; lwan_strbuf_printf(response->buffer, "https://%s/p/%zu\n\n", - SERVER_NAME, (uint64_t)(uintptr_t)key); + host_hdr, (uint64_t)(uintptr_t)key); + return HTTP_OK; } } @@ -136,6 +140,11 @@ static enum lwan_http_status post_paste(struct lwan_request *request, static enum lwan_http_status doc(struct lwan_request *request, struct lwan_response *response) { + const char *host_hdr = lwan_request_get_header(request, "Host"); + + if (!host_hdr) + return HTTP_BAD_REQUEST; + response->mime_type = "text/plain"; lwan_strbuf_printf( @@ -152,7 +161,7 @@ static enum lwan_http_status doc(struct lwan_request *request, "response with different MIME-type.\n" "\n" "Items are cached for %d hours and are not stored on disk", - SERVER_NAME, SERVER_NAME, CACHE_FOR_HOURS); + host_hdr, host_hdr, CACHE_FOR_HOURS); return HTTP_OK; } From 3b2f3de86a01ab4ab20a4c2c5b2f9e3515e6eb7b Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 2 Dec 2023 10:10:22 -0800 Subject: [PATCH 2152/2505] Tweaks to README.md --- README.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 5ec0e75fa..c27f600f6 100644 --- a/README.md +++ b/README.md @@ -117,14 +117,15 @@ To disable optimizations and build a more debugging-friendly version: This will generate a few binaries: - `src/bin/lwan/lwan`: The main Lwan executable. May be executed with `--help` for guidance. - - `src/bin/testrunner/testrunner`: Contains code to execute the test suite. + - `src/bin/testrunner/testrunner`: Contains code to execute the test suite (`src/scripts/testsuite.py`). - `src/samples/freegeoip/freegeoip`: [FreeGeoIP sample implementation](https://freegeoip.lwan.ws). Requires SQLite. - `src/samples/techempower/techempower`: Code for the TechEmpower Web Framework benchmark. Requires SQLite and MySQL libraries. - `src/samples/clock/clock`: [Clock sample](https://time.lwan.ws). Generates a GIF file that always shows the local time. - - `src/bin/tools/mimegen`: Builds the extension-MIME type table. Used during build process. - - `src/bin/tools/bin2hex`: Generates a C file from a binary file, suitable for use with #include. - - `src/bin/tools/configdump`: Dumps a configuration file using the configuration reader API. + - `src/bin/tools/mimegen`: Builds the extension-MIME type table. Used during the build process. + - `src/bin/tools/bin2hex`: Generates a C file from a binary file, suitable for use with #include. Used during the build process. + - `src/bin/tools/configdump`: Dumps a configuration file using the configuration reader API. Used for testing. - `src/bin/tools/weighttp`: Rewrite of the `weighttp` HTTP benchmarking tool. + - `src/bin/tools/statuslookupgen`: Generates a perfect hash table for HTTP status codes and their descriptions. Used during the build process. #### Remarks @@ -132,8 +133,8 @@ Passing `-DCMAKE_BUILD_TYPE=Release` will enable some compiler optimizations (such as [LTO](http://gcc.gnu.org/wiki/LinkTimeOptimization)) and tune the code for current architecture. -> :exclamation: **Important:** *Please use the release build when benchmarking*, as -> the default is the Debug build, which not only logs all requests to the +> :exclamation: **Important:** *Please use the release build when benchmarking*. +> The default is the Debug build, which not only logs all requests to the > standard output, but does so while holding a lock, severely holding down > the server. @@ -143,7 +144,7 @@ Valgrind *(if its headers are present)* and includes debugging messages that are stripped in the release version. Debugging messages are printed for each and every request. -On debug builds, sanitizers can be enabled. To select which one to build Lwan +On these builds, sanitizers can be enabled. To select which one to build Lwan with, specify one of the following options to the CMake invocation line: - `-DSANITIZER=ubsan` selects the Undefined Behavior Sanitizer. @@ -164,6 +165,10 @@ If you're building Lwan for a distribution, it might be wise to use the `-DMTUNE_NATIVE=OFF` option, otherwise the generated binary may fail to run on some computers. +TLS support is enabled automatically in the presence of a suitable mbedTLS +installation on Linux systems with headers new enough to support kTLS, but +can be disabled by passing `-DENABLE_TLS=NO` to CMake. + ### Tests ~/lwan/build$ make testsuite @@ -213,7 +218,7 @@ mess with them. > for help on that. Configuration File ----------------- +------------------ ### Format From 83bb0ff6d69b03cc7d7833a175c53e03ab9a58a8 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 2 Dec 2023 10:10:38 -0800 Subject: [PATCH 2153/2505] Use strcaseequal_neutral() in parse_bool() --- src/bin/tools/CMakeLists.txt | 1 + src/lib/lwan-config.c | 26 ++++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/bin/tools/CMakeLists.txt b/src/bin/tools/CMakeLists.txt index 061d25e1f..032381d7e 100644 --- a/src/bin/tools/CMakeLists.txt +++ b/src/bin/tools/CMakeLists.txt @@ -35,6 +35,7 @@ else () ${CMAKE_SOURCE_DIR}/src/lib/lwan-config.c ${CMAKE_SOURCE_DIR}/src/lib/lwan-status.c ${CMAKE_SOURCE_DIR}/src/lib/lwan-strbuf.c + ${CMAKE_SOURCE_DIR}/src/lib/missing.c ) add_executable(weighttp weighttp.c) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 46ed4c3a2..128f2c9b5 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -200,15 +200,37 @@ bool parse_bool(const char *value, bool default_value) if (!value) return default_value; - if (streq(value, "true") || streq(value, "on") || streq(value, "yes")) + if (strcaseequal_neutral(value, "true") || + strcaseequal_neutral(value, "on") || strcaseequal_neutral(value, "yes")) return true; - if (streq(value, "false") || streq(value, "off") || streq(value, "no")) + if (strcaseequal_neutral(value, "false") || + strcaseequal_neutral(value, "off") || strcaseequal_neutral(value, "no")) return false; return parse_int(value, default_value); } +#ifndef NDEBUG +__attribute__((constructor)) +static void test_parse_bool(void) +{ + assert(parse_bool("true", false) == true); + assert(parse_bool("on", false) == true); + assert(parse_bool("yes", false) == true); + + assert(parse_bool("false", true) == false); + assert(parse_bool("off", true) == false); + assert(parse_bool("no", true) == false); + + assert(parse_bool("0", 1) == false); + assert(parse_bool("1", 0) == true); + + assert(parse_bool("abacate", true) == true); + assert(parse_bool("abacate", false) == false); +} +#endif + bool config_error(struct config *conf, const char *fmt, ...) { va_list values; From f366ef9408f90044e1571f3df60846df7c6938d0 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 2 Dec 2023 10:11:26 -0800 Subject: [PATCH 2154/2505] Simplify string-end case of strcaseequal_neutral_len() --- src/lib/missing.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/missing.c b/src/lib/missing.c index b552cfef3..cf85f190c 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -716,7 +716,8 @@ bool strcaseequal_neutral_len(const char *a, const char *b, size_t len) } } - return (ssize_t)len < 0; + assert((ssize_t)len < 0); + return true; } ALWAYS_INLINE bool strcaseequal_neutral(const char *a, const char *b) From 505a408b1efbba0c36645dfe94c75e752e8cc93d Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 2 Dec 2023 13:03:59 -0800 Subject: [PATCH 2155/2505] Ensure the right bit is checked in request_has_body() --- src/lib/lwan-request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 508511750..eab1c730f 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1428,7 +1428,7 @@ static inline bool request_has_body(const struct lwan_request *request) { /* 3rd bit set in method: request method has body. See lwan.h, * definition of FOR_EACH_REQUEST_METHOD() for more info. */ - return lwan_request_get_method(request) & 1 << 3; + return lwan_request_get_method(request) & (1 << 3); } static enum lwan_http_status From 9e26564a3b4254af38356b70309c6e73f6482580 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 2 Dec 2023 13:08:03 -0800 Subject: [PATCH 2156/2505] Show emojis instead of thread IDs Idea by @theacodes. --- src/lib/lwan-status.c | 46 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-status.c b/src/lib/lwan-status.c index 68d0537e7..b3ea89e67 100644 --- a/src/lib/lwan-status.c +++ b/src/lib/lwan-status.c @@ -43,9 +43,11 @@ enum lwan_status_type { }; static bool can_use_colors(void); +static bool can_use_emojis(void); static volatile bool quiet = false; static bool use_colors; +static bool use_emojis; void lwan_status_init(struct lwan *l) { @@ -56,6 +58,7 @@ void lwan_status_init(struct lwan *l) (void)l; #endif use_colors = can_use_colors(); + use_emojis = can_use_emojis(); } void lwan_status_shutdown(struct lwan *l __attribute__((unused))) {} @@ -83,6 +86,21 @@ static bool can_use_colors(void) return true; } +static bool can_use_emojis(void) +{ + if (!can_use_colors()) + return false; + + const char *lang = secure_getenv("LANG"); + if (!lang) + return false; + + if (!strstr(lang, ".UTF-8")) + return false; + + return true; +} + static int status_index(enum lwan_status_type type) { return use_colors ? (int)type : STATUS_NONE; @@ -141,6 +159,29 @@ static long gettid_cached(void) } #endif +static const char *get_thread_emoji(void) +{ + static const char *emojis[] = { + "🐶", "🐱", "🐭", "🐹", "🐰", "🦊", "🐻", "🐼", "🐨", "🐯", "🦁", "🐮", + "🐷", "🐽", "🐸", "🐵", "🐔", "🐧", "🐦", "🐤", "🦆", "🦉", "🦇", "🐺", + "🐗", "🐴", "🦄", "🐝", "🪱", "🐛", "🦋", "🐌", "🐞", "🐜", "🪰", "🪲", + "🪳", "🦟", "🦗", "🦂", "🐢", "🐍", "🦎", "🦖", "🦕", "🐙", "🦑", "🦐", + "🦞", "🦀", "🐡", "🐠", "🐟", "🐬", "🐳", "🐋", "🦈", "🦭", "🐊", "🐅", + "🐆", "🦓", "🦍", "🦧", "🦣", "🐘", "🦛", "🦏", "🐪", "🐫", "🦒", "🦘", + "🦬", "🐃", "🐂", "🐄", "🐎", "🐖", "🐏", "🐑", "🦙", "🐐", "🦌", "🐕", + "🐩", "🐈", "🐓", "🦃", "🦤", "🦚", "🦜", "🦢", "🦩", "🕊", "🐇", "🦝", + "🦨", "🦡", "🦫", "🦦", "🦥", "🐁", "🐀", "🐿", "🦔", "🐉", "🐲", + }; + static __thread const char *emoji; + static unsigned int last_emoji_id; + + if (!emoji) { + emoji = emojis[ATOMIC_INC(last_emoji_id) % N_ELEMENTS(emojis)]; + } + + return emoji; +} + #define FORMAT_WITH_COLOR(fmt, color) "\033[" color "m" fmt "\033[0m" #ifdef LWAN_HAVE_SYSLOG @@ -236,7 +277,10 @@ static void status_out( #ifndef NDEBUG char *base_name = basename(strdupa(file)); if (LIKELY(use_colors)) { - printf(FORMAT_WITH_COLOR("%ld ", "32;1"), gettid_cached()); + if (LIKELY(use_emojis)) + printf("%s ", get_thread_emoji()); + else + printf(FORMAT_WITH_COLOR("%ld ", "32;1"), gettid_cached()); printf(FORMAT_WITH_COLOR("%s:%d ", "3"), base_name, line); printf(FORMAT_WITH_COLOR("%s() ", "33"), func); } else { From d453ab1d50aeb274fbdd2788f1128d53351b4cda Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 9 Dec 2023 11:18:01 -0800 Subject: [PATCH 2157/2505] Fix some warnings in get_thread_emoji() in release mode --- src/lib/lwan-status.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-status.c b/src/lib/lwan-status.c index b3ea89e67..b62aadf5d 100644 --- a/src/lib/lwan-status.c +++ b/src/lib/lwan-status.c @@ -157,7 +157,6 @@ static long gettid_cached(void) return tid; #endif } -#endif static const char *get_thread_emoji(void) { @@ -176,11 +175,12 @@ static const char *get_thread_emoji(void) static unsigned int last_emoji_id; if (!emoji) { - emoji = emojis[ATOMIC_INC(last_emoji_id) % N_ELEMENTS(emojis)]; + emoji = emojis[ATOMIC_INC(last_emoji_id) % (int)N_ELEMENTS(emojis)]; } return emoji; } +#endif #define FORMAT_WITH_COLOR(fmt, color) "\033[" color "m" fmt "\033[0m" From 88ade5f03d1442f097aff047925dca58402dd2d4 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 6 Jan 2024 16:35:35 -0800 Subject: [PATCH 2158/2505] Add macro to define self-tests in debug builds --- src/lib/lwan-config.c | 5 +---- src/lib/lwan-coro.c | 4 +--- src/lib/lwan-tables.c | 12 +++++------- src/lib/missing.c | 4 +--- src/lib/missing/assert.h | 10 ++++++++++ 5 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 128f2c9b5..7f086d8e3 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -211,9 +211,7 @@ bool parse_bool(const char *value, bool default_value) return parse_int(value, default_value); } -#ifndef NDEBUG -__attribute__((constructor)) -static void test_parse_bool(void) +LWAN_SELF_TEST(parse_bool) { assert(parse_bool("true", false) == true); assert(parse_bool("on", false) == true); @@ -229,7 +227,6 @@ static void test_parse_bool(void) assert(parse_bool("abacate", true) == true); assert(parse_bool("abacate", false) == false); } -#endif bool config_error(struct config *conf, const char *fmt, ...) { diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index a9b685989..45774c613 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -71,8 +71,7 @@ void __asan_unpoison_memory_region(void const volatile *addr, size_t size); #define ALLOCATE_STACK_WITH_MMAP #endif -#ifndef NDEBUG -__attribute__((constructor)) static void assert_sizes_are_sane(void) +LWAN_SELF_TEST(sizes_are_same) { /* This is done in runtime rather than during compilation time because * in Glibc >= 2.34, SIGSTKSZ is defined as sysconf(_SC_MINSIGSTKSZ). */ @@ -86,7 +85,6 @@ __attribute__((constructor)) static void assert_sizes_are_sane(void) assert((CORO_STACK_SIZE >= PAGE_SIZE)); #endif } -#endif typedef void (*defer1_func)(void *data); typedef void (*defer2_func)(void *data1, void *data2); diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index a265967d1..bdf7b02f6 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -104,8 +104,10 @@ void lwan_tables_init(void) "text/javascript")); assert(streq(lwan_determine_mime_type_for_file_name(".BZ2"), "application/x-bzip2")); +} -#ifndef NDEBUG +LWAN_SELF_TEST(status_codes) +{ #define ASSERT_STATUS(id, code, short, long) \ do { \ const char *status = lwan_http_status_as_string_with_code(HTTP_##id); \ @@ -115,9 +117,8 @@ void lwan_tables_init(void) const char *descr = lwan_http_status_as_descriptive_string(HTTP_##id); \ assert(!strcmp(descr, long)); \ } while (0); -FOR_EACH_HTTP_STATUS(ASSERT_STATUS) + FOR_EACH_HTTP_STATUS(ASSERT_STATUS) #undef ASSERT_STATUS -#endif } static int @@ -292,10 +293,8 @@ ALWAYS_INLINE uint8_t lwan_char_isalnum(char ch) return char_prop_tbl[(unsigned char)ch] & (CHAR_PROP_ALPHA | CHAR_PROP_DIG); } -#ifndef NDEBUG #include -__attribute__((constructor)) -static void test_lwan_char_tables(void) +LWAN_SELF_TEST(compare_with_ctype) { for (int i = 0; i < 256; i++) { assert(!!isxdigit((char)i) == !!lwan_char_isxdigit((char)i)); @@ -306,4 +305,3 @@ static void test_lwan_char_tables(void) /* isspace() and lwan_char_isspace() differs on purpose */ } } -#endif \ No newline at end of file diff --git a/src/lib/missing.c b/src/lib/missing.c index cf85f190c..02d50651d 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -725,8 +725,7 @@ ALWAYS_INLINE bool strcaseequal_neutral(const char *a, const char *b) return strcaseequal_neutral_len(a, b, SIZE_MAX); } -#ifndef NDEBUG -__attribute__((constructor)) static void test_strcaseequal_neutral(void) +LWAN_SELF_TEST(strcaseequal_neutral) { assert(strcaseequal_neutral("LWAN", "lwan") == true); assert(strcaseequal_neutral("LwAn", "lWaN") == true); @@ -746,7 +745,6 @@ __attribute__((constructor)) static void test_strcaseequal_neutral(void) static_assert('0' == 48, "ASCII character set"); static_assert('a' == 97, "ASCII character set"); } -#endif #ifndef LWAN_HAVE_STPCPY char *stpncpy(char *restrict dst, const char *restrict src, size_t sz) diff --git a/src/lib/missing/assert.h b/src/lib/missing/assert.h index ab8415549..9d9f145c6 100644 --- a/src/lib/missing/assert.h +++ b/src/lib/missing/assert.h @@ -43,4 +43,14 @@ #endif #endif +/* Macro to enable self-test on startup in debug builds. + * Details: https://tia.mat.br/posts/2023/12/11/self-test.html */ +#if defined(NDEBUG) +#define LWAN_SELF_TEST(name) \ + __attribute__((unused)) static void self_test_##name(void) +#else +#define LWAN_SELF_TEST(name) \ + __attribute__((constructor)) static void self_test_##name(void) +#endif + #endif /* MISSING_ASSERT_H */ From 1ccbed74233b467688a083010a216880f003a37a Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 30 Jan 2024 11:57:50 -0800 Subject: [PATCH 2159/2505] Move epoll implementation to its own file This should help with #363. --- src/lib/CMakeLists.txt | 1 + src/lib/missing-epoll.c | 177 ++++++++++++++++++++++++++++++++++++++++ src/lib/missing.c | 147 --------------------------------- 3 files changed, 178 insertions(+), 147 deletions(-) create mode 100644 src/lib/missing-epoll.c diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index 6c507d255..62d992c08 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -38,6 +38,7 @@ set(SOURCES lwan-pubsub.c missing.c missing-pthread.c + missing-epoll.c murmur3.c patterns.c realpathat.c diff --git a/src/lib/missing-epoll.c b/src/lib/missing-epoll.c new file mode 100644 index 000000000..198fa1bca --- /dev/null +++ b/src/lib/missing-epoll.c @@ -0,0 +1,177 @@ +/* + * lwan - web server + * Copyright (c) 2012 L. A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lwan.h" + +#if !defined(LWAN_HAVE_EPOLL) && defined(LWAN_HAVE_KQUEUE) +#include +#include +#include + +#include "hash.h" + +int epoll_create1(int flags) +{ +#if defined(LWAN_HAVE_KQUEUE1) + return kqueue1(flags & EPOLL_CLOEXEC ? O_CLOEXEC : 0); +#else + int fd = kqueue(); + + if (flags & EPOLL_CLOEXEC) { + int flags; + + flags = fcntl(fd, F_GETFD); + if (flags < 0) + return -1; + + if (fcntl(fd, F_SETFD, flags | O_CLOEXEC) < 0) + return -1; + } + + return fd; +#endif +} + +static int epoll_no_event_marker; + +int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) +{ + struct kevent ev; + + switch (op) { + case EPOLL_CTL_ADD: + case EPOLL_CTL_MOD: { + int events = 0; + void *udata = event->data.ptr; + int flags = EV_ADD; + + if (event->events & EPOLLIN) { + events = EVFILT_READ; + } else if (event->events & EPOLLOUT) { + events = EVFILT_WRITE; + } else { + events = EVFILT_WRITE; + udata = &epoll_no_event_marker; + } + + if (event->events & EPOLLONESHOT) + flags |= EV_ONESHOT; + if (event->events & EPOLLET) + flags |= EV_CLEAR; + + flags |= EV_ERROR; /* EPOLLERR is always set. */ + flags |= EV_EOF; /* EPOLLHUP is always set. */ + + EV_SET(&ev, fd, events, flags, 0, 0, udata); + break; + } + + case EPOLL_CTL_DEL: + EV_SET(&ev, fd, 0, EV_DELETE, 0, 0, 0); + break; + + default: + errno = EINVAL; + return -1; + } + + return kevent(epfd, &ev, 1, NULL, 0, NULL); +} + +static struct timespec *to_timespec(struct timespec *t, int ms) +{ + if (ms < 0) + return NULL; + + t->tv_sec = ms / 1000; + t->tv_nsec = (ms % 1000) * 1000000; + + return t; +} + +int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) +{ + struct epoll_event *ev = events; + struct kevent evs[maxevents]; + struct timespec tmspec; + struct hash *coalesce; + int i, r; + + coalesce = hash_int_new(NULL, NULL); + if (UNLIKELY(!coalesce)) + return -1; + + r = kevent(epfd, NULL, 0, evs, maxevents, to_timespec(&tmspec, timeout)); + if (UNLIKELY(r < 0)) { + hash_free(coalesce); + return -1; + } + + for (i = 0; i < r; i++) { + struct kevent *kev = &evs[i]; + uint32_t mask = (uint32_t)(uintptr_t)hash_find( + coalesce, (void *)(intptr_t)evs[i].ident); + + if (kev->flags & EV_ERROR) + mask |= EPOLLERR; + if (kev->flags & EV_EOF) + mask |= EPOLLRDHUP; + + if (kev->filter == EVFILT_READ) + mask |= EPOLLIN; + else if (kev->filter == EVFILT_WRITE && evs[i].udata != &epoll_no_event_marker) + mask |= EPOLLOUT; + + hash_add(coalesce, (void *)(intptr_t)evs[i].ident, + (void *)(uintptr_t)mask); + } + + for (i = 0; i < r; i++) { + void *maskptr; + + maskptr = hash_find(coalesce, (void *)(intptr_t)evs[i].ident); + if (maskptr) { + struct kevent *kev = &evs[i]; + + if (kev->udata == &epoll_no_event_marker) + continue; + + ev->data.ptr = kev->udata; + ev->events = (uint32_t)(uintptr_t)maskptr; + ev++; + } + } + + hash_free(coalesce); + return (int)(intptr_t)(ev - events); +} +#elif !defined(LWAN_HAVE_EPOLL) +#error epoll() not implemented for this platform +#endif + diff --git a/src/lib/missing.c b/src/lib/missing.c index 02d50651d..ed0f56d88 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -27,7 +27,6 @@ #include #include #include -#include #include #include #include @@ -135,152 +134,6 @@ int clock_gettime(clockid_t clk_id, struct timespec *ts) } #endif -#if !defined(LWAN_HAVE_EPOLL) && defined(LWAN_HAVE_KQUEUE) -#include -#include -#include - -#include "hash.h" - -int epoll_create1(int flags) -{ -#if defined(LWAN_HAVE_KQUEUE1) - return kqueue1(flags & EPOLL_CLOEXEC ? O_CLOEXEC : 0); -#else - int fd = kqueue(); - - if (flags & EPOLL_CLOEXEC) { - int flags; - - flags = fcntl(fd, F_GETFD); - if (flags < 0) - return -1; - - if (fcntl(fd, F_SETFD, flags | O_CLOEXEC) < 0) - return -1; - } - - return fd; -#endif -} - -static int epoll_no_event_marker; - -int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) -{ - struct kevent ev; - - switch (op) { - case EPOLL_CTL_ADD: - case EPOLL_CTL_MOD: { - int events = 0; - void *udata = event->data.ptr; - int flags = EV_ADD; - - if (event->events & EPOLLIN) { - events = EVFILT_READ; - } else if (event->events & EPOLLOUT) { - events = EVFILT_WRITE; - } else { - events = EVFILT_WRITE; - udata = &epoll_no_event_marker; - } - - if (event->events & EPOLLONESHOT) - flags |= EV_ONESHOT; - if (event->events & EPOLLET) - flags |= EV_CLEAR; - - flags |= EV_ERROR; /* EPOLLERR is always set. */ - flags |= EV_EOF; /* EPOLLHUP is always set. */ - - EV_SET(&ev, fd, events, flags, 0, 0, udata); - break; - } - - case EPOLL_CTL_DEL: - EV_SET(&ev, fd, 0, EV_DELETE, 0, 0, 0); - break; - - default: - errno = EINVAL; - return -1; - } - - return kevent(epfd, &ev, 1, NULL, 0, NULL); -} - -static struct timespec *to_timespec(struct timespec *t, int ms) -{ - if (ms < 0) - return NULL; - - t->tv_sec = ms / 1000; - t->tv_nsec = (ms % 1000) * 1000000; - - return t; -} - -int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) -{ - struct epoll_event *ev = events; - struct kevent evs[maxevents]; - struct timespec tmspec; - struct hash *coalesce; - int i, r; - - coalesce = hash_int_new(NULL, NULL); - if (UNLIKELY(!coalesce)) - return -1; - - r = kevent(epfd, NULL, 0, evs, maxevents, to_timespec(&tmspec, timeout)); - if (UNLIKELY(r < 0)) { - hash_free(coalesce); - return -1; - } - - for (i = 0; i < r; i++) { - struct kevent *kev = &evs[i]; - uint32_t mask = (uint32_t)(uintptr_t)hash_find( - coalesce, (void *)(intptr_t)evs[i].ident); - - if (kev->flags & EV_ERROR) - mask |= EPOLLERR; - if (kev->flags & EV_EOF) - mask |= EPOLLRDHUP; - - if (kev->filter == EVFILT_READ) - mask |= EPOLLIN; - else if (kev->filter == EVFILT_WRITE && evs[i].udata != &epoll_no_event_marker) - mask |= EPOLLOUT; - - hash_add(coalesce, (void *)(intptr_t)evs[i].ident, - (void *)(uintptr_t)mask); - } - - for (i = 0; i < r; i++) { - void *maskptr; - - maskptr = hash_find(coalesce, (void *)(intptr_t)evs[i].ident); - if (maskptr) { - struct kevent *kev = &evs[i]; - - if (kev->udata == &epoll_no_event_marker) - continue; - - ev->data.ptr = kev->udata; - ev->events = (uint32_t)(uintptr_t)maskptr; - ev++; - } - } - - hash_free(coalesce); - return (int)(intptr_t)(ev - events); -} -#elif !defined(LWAN_HAVE_EPOLL) -#error epoll() not implemented for this platform -#endif - #if defined(__linux__) || defined(__CYGWIN__) #if defined(LWAN_HAVE_GETAUXVAL) #include From 44675ea7c1e148e923ebc82c14524e3d5077dd13 Mon Sep 17 00:00:00 2001 From: Yonas Yanfa Date: Wed, 31 Jan 2024 17:04:41 -0500 Subject: [PATCH 2160/2505] Define MSG_FASTOPEN For some reason, this gets picked up on FreeBSD and not Linux. --- src/bin/tools/weighttp.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/bin/tools/weighttp.c b/src/bin/tools/weighttp.c index 66516fb86..4febdfc98 100644 --- a/src/bin/tools/weighttp.c +++ b/src/bin/tools/weighttp.c @@ -46,6 +46,9 @@ #include #include +#ifndef MSG_FASTOPEN +#define MSG_FASTOPEN 0 +#endif #ifndef MSG_DONTWAIT #define MSG_DONTWAIT 0 #endif From fb7749de5575f11d794c1854b8d0db8f0c6b9474 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 24 Feb 2024 09:22:50 -0800 Subject: [PATCH 2161/2505] Ignore presence of Upgrade header on Safari browsers First user-agent specific workaround in Lwan. Ugh. This will ignore the absence of the Upgrade header when establishing websocket connections if we detect that the user agent is Safari. Hopefully newer versions of Safari have fixed this, but like many workarounds, this one is probably forever. Fixes #345. --- src/lib/lwan-request.c | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index eab1c730f..93073de84 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1371,8 +1371,36 @@ prepare_websocket_handshake(struct lwan_request *request, char **encoded) return HTTP_BAD_REQUEST; const char *upgrade = lwan_request_get_header(request, "Upgrade"); - if (UNLIKELY(!upgrade || !streq(upgrade, "websocket"))) + if (UNLIKELY(!upgrade)) { + /* Ugh. Some older versions of Safari do not send an Upgrade header + * when establishing websocket connections. I don't want to ignore + * this header in the name of being as strict as possible, but if + * the handshake doesn't work in those browsers, this whole thing is + * useless. Sniffing the user agent here for compatibility reasons + * makes me want to puke in my mouth a little bit, but seems to be + * the best way to workaround this, while being more strict for those + * user agents that are actually compliant with the RFC. */ + const char *user_agent = lwan_request_get_header(request, "User-Agent"); + if (!user_agent) + return HTTP_BAD_REQUEST; + + /* A bunch of browsers include the substring "Safari" in their user + * agent, so look for them first. If we find "Chrome" or "Android" + * then it's not Safari. Hopefully this catches other Chrome-based + * browsers too, where the handshake is correctly performed with the + * Upgrade header. */ + if (strstr(user_agent, "Chrome") || strstr(user_agent, "Android")) + return HTTP_BAD_REQUEST; + + /* If we find a "Safari" string in the user agent at this point, + * then we're almost certain it's a Safari browser, and we can + * ignore the absence of an Upgrade header. If it's not there, then + * they should've sent the Upgrade header. Blergh. */ + if (!strstr(user_agent, "Safari")) + return HTTP_BAD_REQUEST; + } else if (UNLIKELY(!streq(upgrade, "websocket"))) { return HTTP_BAD_REQUEST; + } const char *sec_websocket_key = lwan_request_get_header(request, "Sec-WebSocket-Key"); From ff581cddd4b90fa54ec87cd535885b5185e203dd Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 25 Feb 2024 10:31:19 -0800 Subject: [PATCH 2162/2505] Use standard GitHub admonition syntax rather than bespoke one --- README.md | 88 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 66 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index c27f600f6..ed1b950a6 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,9 @@ The build system will look for these libraries and enable/link if available. - Client libraries for either [MySQL](https://dev.mysql.com) or [MariaDB](https://mariadb.org) - [SQLite 3](http://sqlite.org) -> :bulb: **Note:** On some systems, +> [!NOTE] +> +> On some systems, > [libucontext](https://github.com/kaniini/libucontext) will be downloaded > and built alongside Lwan. This will require a network connection, so keep > this in mind when packaging Lwan for non-x86_64 or non-aarch64 @@ -133,7 +135,9 @@ Passing `-DCMAKE_BUILD_TYPE=Release` will enable some compiler optimizations (such as [LTO](http://gcc.gnu.org/wiki/LinkTimeOptimization)) and tune the code for current architecture. -> :exclamation: **Important:** *Please use the release build when benchmarking*. +> [!IMPORTANT] +> +> *Please use the release build when benchmarking*. > The default is the Debug build, which not only logs all requests to the > standard output, but does so while holding a lock, severely holding down > the server. @@ -199,7 +203,9 @@ Running Set up the server by editing the provided `lwan.conf`; the format is explained in details below. -> :bulb: **Note:** Lwan will try to find a configuration file based in the +> [!NOTE] +> +> Lwan will try to find a configuration file based in the > executable name in the current directory; `testrunner.conf` will be used > for the `testrunner` binary, `lwan.conf` for the `lwan` binary, and so on. @@ -213,7 +219,9 @@ settings for the environment it's running on. Many of these settings can be tweaked in the configuration file, but it's usually a good idea to not mess with them. -> :magic_wand: **Tip:** Optionally, the `lwan` binary can be used for one-shot +> [!TIP] +> +> Optionally, the `lwan` binary can be used for one-shot > static file serving without any configuration file. Run it with `--help` > for help on that. @@ -231,7 +239,9 @@ can be empty; in this case, curly brackets are optional. an implementation detail, code reading configuration options will only be given the version with underscores). -> :magic_wand: **Tip:** Values can contain environment variables. Use the +> [!TIP] +> +> Values can contain environment variables. Use the > syntax `${VARIABLE_NAME}`. Default values can be specified with a colon > (e.g. `${VARIABLE_NAME:foo}`, which evaluates to `${VARIABLE_NAME}` if > it's set, or `foo` otherwise). @@ -281,7 +291,9 @@ just added together; for instance, "1M 1w" specifies "1 month and 1 week" | `M` | 30-day Months | | `y` | 365-day Years | -> :bulb: **Note:** A number with a multiplier not in this table is ignored; a +> [!NOTE] +> +> A number with a multiplier not in this table is ignored; a > warning is issued while reading the configuration file. No spaces must > exist between the number and its multiplier. @@ -335,7 +347,9 @@ e.g. instantiating the `serve_files` module, Lwan will refuse to start. (This check is only performed on Linux as a safeguard for malconfiguration.) -> :magic_wand: **Tip:** Declare a Straitjacket right before a `site` section +> [!TIP] +> +> Declare a Straitjacket right before a `site` section > in such a way that configuration files and private data (e.g. TLS keys) > are out of reach of the server after initialization has taken place. @@ -375,7 +389,9 @@ actual values while servicing requests. These include but is not limited to: - `Transfer-Encoding` - All `Access-Control-Allow-` headers -> :bulb: **Note:** Header names are also case-insensitive (and case-preserving). Overriding +> [!NOTE] +> +> Header names are also case-insensitive (and case-preserving). Overriding > `SeRVeR` will override the `Server` header, but send it the way it was > written in the configuration file. @@ -385,11 +401,15 @@ Only two listeners are supported per Lwan process: the HTTP listener (`listener` section), and the HTTPS listener (`tls_listener` section). Only one listener of each type is allowed. -> :warning: **Warning:** TLS support is experimental. Although it is stable +> [!WARNING] +> +> TLS support is experimental. Although it is stable > during initial testing, your mileage may vary. Only TLSv1.2 is supported > at this point, but TLSv1.3 is planned. -> :bulb: **Note:** TLS support requires :penguin: Linux with the `tls.ko` +> [!NOTE] +> +> TLS support requires :penguin: Linux with the `tls.ko` > module built-in or loaded. Support for other operating systems may be > added in the future. FreeBSD seems possible, other operating systems > do not seem to offer similar feature. For unsupported operating systems, @@ -409,11 +429,15 @@ where the TLS certificate and private key files are located) and takes an optional boolean `hsts` key, which controls if `Strict-Transport-Security` headers will be sent on HTTPS responses. -> :magic_wand: **Tip:** To generate these keys for testing purposes, the +> [!TIP] +> +> To generate these keys for testing purposes, the > OpenSSL command-line tool can be used like the following: > `openssl req -nodes -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 7` -> :bulb: **Note:** It's recommended that a [Straitjacket](#Straitjacket) with a `chroot` option is declared +> [!NOTE] +> +> It's recommended that a [Straitjacket](#Straitjacket) with a `chroot` option is declared > right after a `tls_listener` section, in such a way that the paths to the > certificate and key are out of reach from that point on. @@ -467,7 +491,9 @@ section can be present in the declaration of a module instance. Handlers do not take any configuration options, but may include the `authorization` section. -> :magic_wand: **Tip:** Executing Lwan with the `--help` command-line +> [!TIP] +> +> Executing Lwan with the `--help` command-line > argument will show a list of built-in modules and handlers. The following is some basic documentation for the modules shipped with Lwan. @@ -490,7 +516,9 @@ best to serve files in the fastest way possible according to some heuristics. | `read_ahead` | `int` | `131702` | Maximum amount of bytes to read ahead when caching open files. A value of `0` disables readahead. Readahead is performed by a low priority thread to not block the I/O threads while file extents are being read from the filesystem. | | `cache_for` | `time` | `5s` | Time to keep file metadata (size, compressed contents, open file descriptor, etc.) in cache | -> :bulb: **Note:** Files smaller than 16KiB will be compressed in RAM for +> [!NOTE] +> +> Files smaller than 16KiB will be compressed in RAM for > the duration specified in the `cache_for` setting. Lwan will always try > to compress with deflate, and will optionally compress with Brotli and > zstd (if Lwan has been built with proper support). @@ -534,7 +562,9 @@ Scripts can be served from files or embedded in the configuration file, and the results of loading them, the standard Lua modules, and (optionally, if using LuaJIT) optimizing the code will be cached for a while. -> :bulb: **Note:** Lua scripts can't use global variables, as they may be not +> [!NOTE] +> +> Lua scripts can't use global variables, as they may be not > only serviced by different threads, but the state will be available only > for the amount of time specified in the `cache_period` configuration > option. This is because each I/O thread in Lwan will create an instance @@ -549,7 +579,9 @@ the following signature: `handle_${METHOD}_${ENDPOINT}(req)`, where `${METHOD}` can be a HTTP method (i.e. `get`, `post`, `head`, etc.), and `${ENDPOINT}` is the desired endpoint to be handled by that function. -> :magic_wand: **Tip:** Use the `root` endpoint for a catchall. For example, +> [!TIP] +> +> Use the `root` endpoint for a catchall. For example, > the handler function `handle_get_root()` will be called if no other handler > could be found for that request. If no catchall is specified, the server > will return a `404 Not Found` error. @@ -599,7 +631,9 @@ The `rewrite` module will match to either redirect to another URL, or rewrite the request in a way that Lwan will handle the request as if it were made in that way originally. -> :information_source: **Info:** Forked from Lua 5.3.1, the regular expresion +> [!INFORMATION] +> +> Forked from Lua 5.3.1, the regular expresion > engine may not be as feature-packed as most general-purpose engines, but > has been chosen specifically because it is a [deterministic finite > automaton](https://en.wikipedia.org/wiki/Deterministic_finite_automaton) @@ -609,7 +643,9 @@ will handle the request as if it were made in that way originally. The new URL can be specified using a simple text substitution syntax, or use Lua scripts. -> :magic_wand: **Tip:** Lua scripts will contain the same metamethods +> [!TIP] +> +> Lua scripts will contain the same metamethods > available in the `req` metatable provided by the Lua module, so it can be > quite powerful. @@ -686,7 +722,9 @@ pattern using the same substitution syntax used for the `rewrite as` or foo-%1-bar }` will substitute `%1` with the first match from the pattern this condition is related to. -> :bulb: **Note:** Conditions that do not require a section have to be written +> [!NOTE] +> +> Conditions that do not require a section have to be written > as a key; for instance, `condition has_query_string = yes`. For example, if one wants to send `site-dark-mode.css` if there is a @@ -729,7 +767,9 @@ pattern (%g+) { } ``` -> :bulb: **Note:** In general, this is not necessary, as the file serving +> [!NOTE] +> +> In general, this is not necessary, as the file serving > module will do this automatically and pick the smallest file available for > the requested encoding, but this shows it's possible to have a similar > feature by configuration alone. @@ -775,7 +815,9 @@ Lwan and a [FastCGI](https://en.wikipedia.org/wiki/FastCGI) server accessible by Lwan. This is useful, for instance, to serve pages from a scripting language such as PHP. -> :bulb: **Note:** This is a preliminary version of this module, and +> [!NOTE] +> +> This is a preliminary version of this module, and > as such, it's not well optimized, some features are missing, and > some values provided to the environment are hardcoded. @@ -798,7 +840,9 @@ section with a `basic` parameter, and set one of its options. | `realm` | `str` | `Lwan` | Realm for authorization. This is usually shown in the user/password UI in browsers | | `password_file` | `str` | `NULL` | Path for a file containing username and passwords (in clear text). The file format is the same as the configuration file format used by Lwan | -> :warning: **Warning:** Not only passwords are stored in clear text in a file +> [!WARNING] +> +> Not only passwords are stored in clear text in a file > that should be accessible by the server, they'll be kept in memory for a few > seconds. Avoid using this feature if possible. From c685abcb5c8b4b3e608c1080a1cca0ee34cde376 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 3 Mar 2024 10:55:58 -0800 Subject: [PATCH 2163/2505] There's no [!INFORMATION] admonition in GitHub markup Use [!NOTE] instead --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ed1b950a6..19a3ca42d 100644 --- a/README.md +++ b/README.md @@ -631,7 +631,7 @@ The `rewrite` module will match to either redirect to another URL, or rewrite the request in a way that Lwan will handle the request as if it were made in that way originally. -> [!INFORMATION] +> [!NOTE] > > Forked from Lua 5.3.1, the regular expresion > engine may not be as feature-packed as most general-purpose engines, but From c451ec8d7487b923438e2bd23984d50f2b852251 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 3 Mar 2024 10:56:43 -0800 Subject: [PATCH 2164/2505] Reduce memory allocations in MySQL DAL --- src/samples/techempower/database.c | 119 ++++++++++++++------------ src/samples/techempower/database.h | 22 ++--- src/samples/techempower/techempower.c | 19 ++-- 3 files changed, 83 insertions(+), 77 deletions(-) diff --git a/src/samples/techempower/database.c b/src/samples/techempower/database.c index a6473f78c..c48b0a895 100644 --- a/src/samples/techempower/database.c +++ b/src/samples/techempower/database.c @@ -18,6 +18,7 @@ * USA. */ +#include #include #include #include @@ -30,17 +31,19 @@ struct db_stmt { bool (*bind)(const struct db_stmt *stmt, - struct db_row *rows, - size_t n_rows); - bool (*step)(const struct db_stmt *stmt, const char *signature, va_list ap); + struct db_row *rows); + bool (*step)(const struct db_stmt *stmt, va_list ap); void (*finalize)(struct db_stmt *stmt); + const char *param_signature; + const char *result_signature; }; struct db { void (*disconnect)(struct db *db); struct db_stmt *(*prepare)(const struct db *db, const char *sql, - const size_t sql_len); + const char *param_signature, + const char *result_signature); }; /* MySQL */ @@ -56,28 +59,23 @@ struct db_stmt_mysql { MYSQL_BIND *param_bind; MYSQL_BIND *result_bind; bool must_execute_again; + bool results_are_bound; + MYSQL_BIND param_result_bind[]; }; static bool db_stmt_bind_mysql(const struct db_stmt *stmt, - struct db_row *rows, - size_t n_rows) + struct db_row *rows) { struct db_stmt_mysql *stmt_mysql = (struct db_stmt_mysql *)stmt; + const char *signature = stmt->param_signature; stmt_mysql->must_execute_again = true; + mysql_stmt_reset(stmt_mysql->stmt); - if (!stmt_mysql->param_bind) { - stmt_mysql->param_bind = calloc(n_rows, sizeof(MYSQL_BIND)); - if (!stmt_mysql->param_bind) - return false; - } else { - mysql_stmt_reset(stmt_mysql->stmt); - } - - for (size_t row = 0; row < n_rows && rows[row].kind; row++) { + for (size_t row = 0; signature[row]; row++) { MYSQL_BIND *param = &stmt_mysql->param_bind[row]; - switch (rows[row].kind) { + switch (signature[row]) { case 's': param->buffer_type = MYSQL_TYPE_STRING; param->buffer = rows[row].u.s; @@ -98,28 +96,24 @@ static bool db_stmt_bind_mysql(const struct db_stmt *stmt, } static bool db_stmt_step_mysql(const struct db_stmt *stmt, - const char *signature, va_list ap) { struct db_stmt_mysql *stmt_mysql = (struct db_stmt_mysql *)stmt; if (stmt_mysql->must_execute_again) { stmt_mysql->must_execute_again = false; + stmt_mysql->results_are_bound = false; if (mysql_stmt_execute(stmt_mysql->stmt)) return false; } - if (!stmt_mysql->result_bind) { - if (*signature == '\0') - return false; + if (!stmt_mysql->results_are_bound) { + const char *signature = stmt->result_signature; - stmt_mysql->result_bind = - calloc(strlen(signature), sizeof(*stmt_mysql->result_bind)); - if (!stmt_mysql->result_bind) + if (*signature == '\0') return false; - free(stmt_mysql->param_bind); - stmt_mysql->param_bind = NULL; + stmt_mysql->results_are_bound = true; MYSQL_BIND *result = stmt_mysql->result_bind; for (size_t r = 0; signature[r]; r++) { @@ -148,9 +142,7 @@ static bool db_stmt_step_mysql(const struct db_stmt *stmt, return mysql_stmt_fetch(stmt_mysql->stmt) == 0; out: - free(stmt_mysql->result_bind); - stmt_mysql->result_bind = NULL; - + stmt_mysql->results_are_bound = false; return false; } @@ -159,16 +151,18 @@ static void db_stmt_finalize_mysql(struct db_stmt *stmt) struct db_stmt_mysql *stmt_mysql = (struct db_stmt_mysql *)stmt; mysql_stmt_close(stmt_mysql->stmt); - free(stmt_mysql->result_bind); - free(stmt_mysql->param_bind); free(stmt_mysql); } static struct db_stmt * -db_prepare_mysql(const struct db *db, const char *sql, const size_t sql_len) +db_prepare_mysql(const struct db *db, + const char *sql, + const char *param_signature, + const char *result_signature) { const struct db_mysql *db_mysql = (const struct db_mysql *)db; - struct db_stmt_mysql *stmt_mysql = malloc(sizeof(*stmt_mysql)); + const size_t n_bounds = strlen(param_signature) + strlen(result_signature); + struct db_stmt_mysql *stmt_mysql = malloc(sizeof(*stmt_mysql) + n_bounds * sizeof(MYSQL_BIND)); if (!stmt_mysql) return NULL; @@ -177,15 +171,24 @@ db_prepare_mysql(const struct db *db, const char *sql, const size_t sql_len) if (!stmt_mysql->stmt) goto out_free_stmt; - if (mysql_stmt_prepare(stmt_mysql->stmt, sql, sql_len)) + if (mysql_stmt_prepare(stmt_mysql->stmt, sql, strlen(sql))) goto out_close_stmt; + assert(strlen(param_signature) == mysql_stmt_param_count(stmt_mysql->stmt)); + assert(strlen(result_signature) == mysql_stmt_field_count(stmt_mysql->stmt)); + stmt_mysql->base.bind = db_stmt_bind_mysql; stmt_mysql->base.step = db_stmt_step_mysql; stmt_mysql->base.finalize = db_stmt_finalize_mysql; - stmt_mysql->result_bind = NULL; - stmt_mysql->param_bind = NULL; + stmt_mysql->param_bind = &stmt_mysql->param_result_bind[0]; + stmt_mysql->result_bind = &stmt_mysql->param_result_bind[strlen(param_signature)]; stmt_mysql->must_execute_again = true; + stmt_mysql->results_are_bound = false; + + stmt_mysql->base.param_signature = param_signature; + stmt_mysql->base.result_signature = result_signature; + + memset(stmt_mysql->param_result_bind, 0, n_bounds * sizeof(MYSQL_BIND)); return (struct db_stmt *)stmt_mysql; @@ -252,26 +255,26 @@ struct db_stmt_sqlite { }; static bool db_stmt_bind_sqlite(const struct db_stmt *stmt, - struct db_row *rows, - size_t n_rows) + struct db_row *rows) { const struct db_stmt_sqlite *stmt_sqlite = (const struct db_stmt_sqlite *)stmt; + const char *signature = stmt->param_signature; sqlite3_reset(stmt_sqlite->sqlite); sqlite3_clear_bindings(stmt_sqlite->sqlite); - for (size_t row = 1; row <= n_rows; row++) { - const struct db_row *r = &rows[row - 1]; + for (size_t row = 0; signature[row]; row++) { + const struct db_row *r = &rows[row]; int ret; - switch (r->kind) { + switch (signature[row]) { case 's': - ret = sqlite3_bind_text(stmt_sqlite->sqlite, (int)row, r->u.s, -1, + ret = sqlite3_bind_text(stmt_sqlite->sqlite, (int)row + 1, r->u.s, -1, NULL); break; case 'i': - ret = sqlite3_bind_int(stmt_sqlite->sqlite, (int)row, r->u.i); + ret = sqlite3_bind_int(stmt_sqlite->sqlite, (int)row + 1, r->u.i); break; default: return false; @@ -285,11 +288,11 @@ static bool db_stmt_bind_sqlite(const struct db_stmt *stmt, } static bool db_stmt_step_sqlite(const struct db_stmt *stmt, - const char *signature, va_list ap) { const struct db_stmt_sqlite *stmt_sqlite = (const struct db_stmt_sqlite *)stmt; + const char *signature = stmt->result_signature; if (sqlite3_step(stmt_sqlite->sqlite) != SQLITE_ROW) return false; @@ -326,7 +329,10 @@ static void db_stmt_finalize_sqlite(struct db_stmt *stmt) } static struct db_stmt * -db_prepare_sqlite(const struct db *db, const char *sql, const size_t sql_len) +db_prepare_sqlite(const struct db *db, + const char *sql, + const char *param_signature, + const char *result_signature) { const struct db_sqlite *db_sqlite = (const struct db_sqlite *)db; struct db_stmt_sqlite *stmt_sqlite = malloc(sizeof(*stmt_sqlite)); @@ -334,7 +340,7 @@ db_prepare_sqlite(const struct db *db, const char *sql, const size_t sql_len) if (!stmt_sqlite) return NULL; - int ret = sqlite3_prepare_v2(db_sqlite->sqlite, sql, (int)sql_len, + int ret = sqlite3_prepare_v2(db_sqlite->sqlite, sql, (int)strlen(sql), &stmt_sqlite->sqlite, NULL); if (ret != SQLITE_OK) { free(stmt_sqlite); @@ -345,6 +351,9 @@ db_prepare_sqlite(const struct db *db, const char *sql, const size_t sql_len) stmt_sqlite->base.step = db_stmt_step_sqlite; stmt_sqlite->base.finalize = db_stmt_finalize_sqlite; + stmt_sqlite->base.param_signature = param_signature; + stmt_sqlite->base.result_signature = result_signature; + return (struct db_stmt *)stmt_sqlite; } @@ -384,20 +393,18 @@ db_connect_sqlite(const char *path, bool read_only, const char *pragmas[]) /* Generic */ -inline bool -db_stmt_bind(const struct db_stmt *stmt, struct db_row *rows, size_t n_rows) +inline bool db_stmt_bind(const struct db_stmt *stmt, struct db_row *rows) { - return stmt->bind(stmt, rows, n_rows); + return stmt->bind(stmt, rows); } -inline bool -db_stmt_step(const struct db_stmt *stmt, const char *signature, ...) +inline bool db_stmt_step(const struct db_stmt *stmt, ...) { va_list ap; bool ret; - va_start(ap, signature); - ret = stmt->step(stmt, signature, ap); + va_start(ap, stmt); + ret = stmt->step(stmt, ap); va_end(ap); return ret; @@ -407,8 +414,10 @@ inline void db_stmt_finalize(struct db_stmt *stmt) { stmt->finalize(stmt); } inline void db_disconnect(struct db *db) { db->disconnect(db); } -inline struct db_stmt * -db_prepare_stmt(const struct db *db, const char *sql, const size_t sql_len) +inline struct db_stmt *db_prepare_stmt(const struct db *db, + const char *sql, + const char *param_signature, + const char *result_signature) { - return db->prepare(db, sql, sql_len); + return db->prepare(db, sql, param_signature, result_signature); } diff --git a/src/samples/techempower/database.h b/src/samples/techempower/database.h index e9aadfe2a..323cdcbf4 100644 --- a/src/samples/techempower/database.h +++ b/src/samples/techempower/database.h @@ -30,24 +30,24 @@ struct db_row { char *s; int i; } u; - char kind; /* 's' = string, 'i' = 'int', '\0' = last */ size_t buffer_length; }; -bool db_stmt_bind(const struct db_stmt *stmt, - struct db_row *rows, - size_t n_rows); - -bool db_stmt_step(const struct db_stmt *stmt, const char *signature, ...); +struct db_stmt *db_prepare_stmt(const struct db *db, + const char *sql, + const char *param_signature, + const char *result_signature); void db_stmt_finalize(struct db_stmt *stmt); -void db_disconnect(struct db *db); -struct db_stmt * -db_prepare_stmt(const struct db *db, const char *sql, const size_t sql_len); -struct db * -db_connect_sqlite(const char *path, bool read_only, const char *pragmas[]); +bool db_stmt_bind(const struct db_stmt *stmt, struct db_row *rows); +bool db_stmt_step(const struct db_stmt *stmt, ...); + +struct db *db_connect_sqlite(const char *path, + bool read_only, + const char *pragmas[]); struct db *db_connect_mysql(const char *host, const char *user, const char *pass, const char *database); +void db_disconnect(struct db *db); diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index 0d365ad3c..4782874ed 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -196,13 +196,13 @@ LWAN_HANDLER(json) static bool db_query_key(struct db_stmt *stmt, struct db_json *out, int key) { - struct db_row row = {.kind = 'i', .u.i = key + 1}; - if (UNLIKELY(!db_stmt_bind(stmt, &row, 1))) + struct db_row row = {.u.i = key + 1}; + if (UNLIKELY(!db_stmt_bind(stmt, &row))) return false; long random_number; long id; - if (UNLIKELY(!db_stmt_step(stmt, "ii", &random_number, &id))) + if (UNLIKELY(!db_stmt_step(stmt, &random_number, &id))) return false; out->id = (int)id; @@ -219,8 +219,7 @@ static inline bool db_query(struct db_stmt *stmt, struct db_json *out) LWAN_HANDLER(db) { - struct db_stmt *stmt = db_prepare_stmt(get_db(), random_number_query, - sizeof(random_number_query) - 1); + struct db_stmt *stmt = db_prepare_stmt(get_db(), random_number_query, "i", "ii"); struct db_json db_json; if (UNLIKELY(!stmt)) { @@ -254,8 +253,7 @@ LWAN_HANDLER(queries) enum lwan_http_status ret = HTTP_INTERNAL_ERROR; long queries = get_number_of_queries(request); - struct db_stmt *stmt = db_prepare_stmt(get_db(), random_number_query, - sizeof(random_number_query) - 1); + struct db_stmt *stmt = db_prepare_stmt(get_db(), random_number_query, "i", "ii"); if (UNLIKELY(!stmt)) return HTTP_INTERNAL_ERROR; @@ -295,8 +293,7 @@ static struct cache_entry *cached_queries_new(const void *keyptr, void *context) if (UNLIKELY(!entry)) return NULL; - stmt = db_prepare_stmt(get_db(), cached_random_number_query, - sizeof(cached_random_number_query) - 1); + stmt = db_prepare_stmt(get_db(), cached_random_number_query, "i", "ii"); if (UNLIKELY(!stmt)) { free(entry); return NULL; @@ -392,7 +389,7 @@ static int fortune_list_generator(struct coro *coro, void *data) struct fortune_array fortunes; struct db_stmt *stmt; - stmt = db_prepare_stmt(get_db(), fortune_query, sizeof(fortune_query) - 1); + stmt = db_prepare_stmt(get_db(), fortune_query, "", "is"); if (UNLIKELY(!stmt)) return 0; @@ -400,7 +397,7 @@ static int fortune_list_generator(struct coro *coro, void *data) long id; char fortune_buffer[256]; - while (db_stmt_step(stmt, "is", &id, &fortune_buffer, sizeof(fortune_buffer))) { + while (db_stmt_step(stmt, &id, &fortune_buffer, sizeof(fortune_buffer))) { if (!append_fortune(coro, &fortunes, (int)id, fortune_buffer)) goto out; } From c82374bb68734824194c99befb44dea44d5bd569 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 3 Mar 2024 15:15:38 -0800 Subject: [PATCH 2165/2505] Update CodeCoverage.cmake --- src/cmake/CodeCoverage.cmake | 757 +++++++++++++++++++++++++++++++---- 1 file changed, 671 insertions(+), 86 deletions(-) diff --git a/src/cmake/CodeCoverage.cmake b/src/cmake/CodeCoverage.cmake index 30d0147bd..dd2b81702 100644 --- a/src/cmake/CodeCoverage.cmake +++ b/src/cmake/CodeCoverage.cmake @@ -1,4 +1,4 @@ -# Copyright (c) 2012 - 2015, Lars Bilke +# Copyright (c) 2012 - 2017, Lars Bilke # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -26,7 +26,7 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # -# +# CHANGES: # # 2012-01-31, Lars Bilke # - Enable Code Coverage @@ -35,132 +35,717 @@ # - Added support for Clang. # - Some additional usage instructions. # +# 2016-02-03, Lars Bilke +# - Refactored functions to use named parameters +# +# 2017-06-02, Lars Bilke +# - Merged with modified version from github.com/ufz/ogs +# +# 2019-05-06, Anatolii Kurotych +# - Remove unnecessary --coverage flag +# +# 2019-12-13, FeRD (Frank Dana) +# - Deprecate COVERAGE_LCOVR_EXCLUDES and COVERAGE_GCOVR_EXCLUDES lists in favor +# of tool-agnostic COVERAGE_EXCLUDES variable, or EXCLUDE setup arguments. +# - CMake 3.4+: All excludes can be specified relative to BASE_DIRECTORY +# - All setup functions: accept BASE_DIRECTORY, EXCLUDE list +# - Set lcov basedir with -b argument +# - Add automatic --demangle-cpp in lcovr, if 'c++filt' is available (can be +# overridden with NO_DEMANGLE option in setup_target_for_coverage_lcovr().) +# - Delete output dir, .info file on 'make clean' +# - Remove Python detection, since version mismatches will break gcovr +# - Minor cleanup (lowercase function names, update examples...) +# +# 2019-12-19, FeRD (Frank Dana) +# - Rename Lcov outputs, make filtered file canonical, fix cleanup for targets +# +# 2020-01-19, Bob Apthorpe +# - Added gfortran support +# +# 2020-02-17, FeRD (Frank Dana) +# - Make all add_custom_target()s VERBATIM to auto-escape wildcard characters +# in EXCLUDEs, and remove manual escaping from gcovr targets +# +# 2021-01-19, Robin Mueller +# - Add CODE_COVERAGE_VERBOSE option which will allow to print out commands which are run +# - Added the option for users to set the GCOVR_ADDITIONAL_ARGS variable to supply additional +# flags to the gcovr command +# +# 2020-05-04, Mihchael Davis +# - Add -fprofile-abs-path to make gcno files contain absolute paths +# - Fix BASE_DIRECTORY not working when defined +# - Change BYPRODUCT from folder to index.html to stop ninja from complaining about double defines +# +# 2021-05-10, Martin Stump +# - Check if the generator is multi-config before warning about non-Debug builds +# +# 2022-02-22, Marko Wehle +# - Change gcovr output from -o for --xml and --html output respectively. +# This will allow for Multiple Output Formats at the same time by making use of GCOVR_ADDITIONAL_ARGS, e.g. GCOVR_ADDITIONAL_ARGS "--txt". +# +# 2022-09-28, Sebastian Mueller +# - fix append_coverage_compiler_flags_to_target to correctly add flags +# - replace "-fprofile-arcs -ftest-coverage" with "--coverage" (equivalent) +# # USAGE: - -# 0. (Mac only) If you use Xcode 5.1 make sure to patch geninfo as described here: -# http://stackoverflow.com/a/22404544/80480 # # 1. Copy this file into your cmake modules path. # -# 2. Add the following line to your CMakeLists.txt: -# INCLUDE(CodeCoverage) +# 2. Add the following line to your CMakeLists.txt (best inside an if-condition +# using a CMake option() to enable it just optionally): +# include(CodeCoverage) +# +# 3. Append necessary compiler flags for all supported source files: +# append_coverage_compiler_flags() +# Or for specific target: +# append_coverage_compiler_flags_to_target(YOUR_TARGET_NAME) # -# 3. Set compiler flags to turn off optimization and enable coverage: -# SET(CMAKE_CXX_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage") -# SET(CMAKE_C_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage") +# 3.a (OPTIONAL) Set appropriate optimization flags, e.g. -O0, -O1 or -Og # -# 3. Use the function SETUP_TARGET_FOR_COVERAGE to create a custom make target -# which runs your test executable and produces a lcov code coverage report: +# 4. If you need to exclude additional directories from the report, specify them +# using full paths in the COVERAGE_EXCLUDES variable before calling +# setup_target_for_coverage_*(). # Example: -# SETUP_TARGET_FOR_COVERAGE( -# my_coverage_target # Name for custom target. -# test_driver # Name of the test driver executable that runs the tests. -# # NOTE! This should always have a ZERO as exit code -# # otherwise the coverage generation will not complete. -# coverage # Name of output directory. -# ) +# set(COVERAGE_EXCLUDES +# '${PROJECT_SOURCE_DIR}/src/dir1/*' +# '/path/to/my/src/dir2/*') +# Or, use the EXCLUDE argument to setup_target_for_coverage_*(). +# Example: +# setup_target_for_coverage_lcov( +# NAME coverage +# EXECUTABLE testrunner +# EXCLUDE "${PROJECT_SOURCE_DIR}/src/dir1/*" "/path/to/my/src/dir2/*") +# +# 4.a NOTE: With CMake 3.4+, COVERAGE_EXCLUDES or EXCLUDE can also be set +# relative to the BASE_DIRECTORY (default: PROJECT_SOURCE_DIR) +# Example: +# set(COVERAGE_EXCLUDES "dir1/*") +# setup_target_for_coverage_gcovr_html( +# NAME coverage +# EXECUTABLE testrunner +# BASE_DIRECTORY "${PROJECT_SOURCE_DIR}/src" +# EXCLUDE "dir2/*") # -# 4. Build a Debug build: -# cmake -DCMAKE_BUILD_TYPE=Debug .. -# make -# make my_coverage_target +# 5. Use the functions described below to create a custom make target which +# runs your test executable and produces a code coverage report. # +# 6. Build a Debug build: +# cmake -DCMAKE_BUILD_TYPE=Debug .. +# make +# make my_coverage_target # +include(CMakeParseArguments) + +option(CODE_COVERAGE_VERBOSE "Verbose information" FALSE) + +get_property(GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG) + # Check prereqs -FIND_PROGRAM( GCOV_PATH gcov ) -FIND_PROGRAM( LCOV_PATH lcov ) -FIND_PROGRAM( GENHTML_PATH genhtml ) -FIND_PROGRAM( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/tests) +find_program( GCOV_PATH gcov ) +find_program( LCOV_PATH NAMES lcov lcov.bat lcov.exe lcov.perl) +find_program( FASTCOV_PATH NAMES fastcov fastcov.py ) +find_program( GENHTML_PATH NAMES genhtml genhtml.perl genhtml.bat ) +find_program( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/scripts/test) +find_program( CPPFILT_PATH NAMES c++filt ) + +if(NOT GCOV_PATH) + message(FATAL_ERROR "gcov not found! Aborting...") +endif() # NOT GCOV_PATH -IF(GCOV_PATH) +# Check supported compiler (Clang, GNU and Flang) +get_property(LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) +foreach(LANG ${LANGUAGES}) + if("${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") + if("${CMAKE_${LANG}_COMPILER_VERSION}" VERSION_LESS 3) + message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...") + endif() + elseif(NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES "GNU" + AND NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(LLVM)?[Ff]lang") + message(FATAL_ERROR "Compiler is not GNU or Flang! Aborting...") + endif() +endforeach() -IF(NOT CMAKE_COMPILER_IS_GNUCC) - # Clang version 3.0.0 and greater now supports gcov as well. - MESSAGE(WARNING "Compiler is not GNU gcc! Clang Version 3.0.0 and greater supports gcov as well, but older versions don't.") +set(COVERAGE_COMPILER_FLAGS "-g --coverage" + CACHE INTERNAL "") - IF(NOT "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") - MESSAGE(FATAL_ERROR "Compiler is not GNU gcc! Aborting...") - ENDIF() -ENDIF() # NOT CMAKE_COMPILER_IS_GNUCC +if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)") + include(CheckCXXCompilerFlag) + check_cxx_compiler_flag(-fprofile-abs-path HAVE_cxx_fprofile_abs_path) + if(HAVE_cxx_fprofile_abs_path) + set(COVERAGE_CXX_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path") + endif() +endif() +if(CMAKE_C_COMPILER_ID MATCHES "(GNU|Clang)") + include(CheckCCompilerFlag) + check_c_compiler_flag(-fprofile-abs-path HAVE_c_fprofile_abs_path) + if(HAVE_c_fprofile_abs_path) + set(COVERAGE_C_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path") + endif() +endif() -SET(CMAKE_CXX_FLAGS_COVERAGE - "-g -O0 --coverage -fprofile-arcs -ftest-coverage" +set(CMAKE_Fortran_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} + CACHE STRING "Flags used by the Fortran compiler during coverage builds." + FORCE ) +set(CMAKE_CXX_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} CACHE STRING "Flags used by the C++ compiler during coverage builds." FORCE ) -SET(CMAKE_C_FLAGS_COVERAGE - "-g -O0 --coverage -fprofile-arcs -ftest-coverage" +set(CMAKE_C_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} CACHE STRING "Flags used by the C compiler during coverage builds." FORCE ) -SET(CMAKE_EXE_LINKER_FLAGS_COVERAGE +set(CMAKE_EXE_LINKER_FLAGS_COVERAGE "" CACHE STRING "Flags used for linking binaries during coverage builds." FORCE ) -SET(CMAKE_SHARED_LINKER_FLAGS_COVERAGE +set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE "" CACHE STRING "Flags used by the shared libraries linker during coverage builds." FORCE ) -MARK_AS_ADVANCED( +mark_as_advanced( + CMAKE_Fortran_FLAGS_COVERAGE CMAKE_CXX_FLAGS_COVERAGE CMAKE_C_FLAGS_COVERAGE CMAKE_EXE_LINKER_FLAGS_COVERAGE CMAKE_SHARED_LINKER_FLAGS_COVERAGE ) -# Param _targetname The name of new the custom make target -# Param _testrunner The name of the target which runs the tests. -# MUST return ZERO always, even on errors. -# If not, no coverage report will be created! -# Param _outputname lcov output is generated as _outputname.info -# HTML report is generated in _outputname/index.html -# Optional fourth parameter is passed as arguments to _testrunner -# Pass them in list form, e.g.: "-j;2" for -j 2 -FUNCTION(SETUP_TARGET_FOR_COVERAGE _targetname _testrunner _outputname) - IF(NOT LCOV_PATH) - MESSAGE(FATAL_ERROR "lcov not found! Aborting...") - ENDIF() # NOT LCOV_PATH +if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") + link_libraries(gcov) +endif() + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_lcov( +# NAME testrunner_coverage # New target name +# EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES testrunner # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative +# # to BASE_DIRECTORY, with CMake 3.4+) +# NO_DEMANGLE # Don't demangle C++ symbols +# # even if c++filt is found +# ) +function(setup_target_for_coverage_lcov) + + set(options NO_DEMANGLE SONARQUBE) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES LCOV_ARGS GENHTML_ARGS) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT LCOV_PATH) + message(FATAL_ERROR "lcov not found! Aborting...") + endif() # NOT LCOV_PATH + + if(NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() # NOT GENHTML_PATH + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(DEFINED Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (CMake 3.4+: Also compute absolute paths) + set(LCOV_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_LCOV_EXCLUDES}) + if(CMAKE_VERSION VERSION_GREATER 3.4) + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) + endif() + list(APPEND LCOV_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES LCOV_EXCLUDES) + + # Conditional arguments + if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE}) + set(GENHTML_EXTRA_ARGS "--demangle-cpp") + endif() + + # Setting up commands which will be run to generate coverage data. + # Cleanup lcov + set(LCOV_CLEAN_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -directory . + -b ${BASEDIR} --zerocounters + ) + # Create baseline to make sure untouched files show up in the report + set(LCOV_BASELINE_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -c -i -d . -b + ${BASEDIR} -o ${Coverage_NAME}.base + ) + # Run tests + set(LCOV_EXEC_TESTS_CMD + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + ) + # Capturing lcov counters and generating report + set(LCOV_CAPTURE_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --directory . -b + ${BASEDIR} --capture --output-file ${Coverage_NAME}.capture + ) + # add baseline counters + set(LCOV_BASELINE_COUNT_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -a ${Coverage_NAME}.base + -a ${Coverage_NAME}.capture --output-file ${Coverage_NAME}.total + ) + # filter collected data to final coverage report + set(LCOV_FILTER_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --remove + ${Coverage_NAME}.total ${LCOV_EXCLUDES} --output-file ${Coverage_NAME}.info + ) + # Generate HTML output + set(LCOV_GEN_HTML_CMD + ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} -o + ${Coverage_NAME} ${Coverage_NAME}.info + ) + if(${Coverage_SONARQUBE}) + # Generate SonarQube output + set(GCOVR_XML_CMD + ${GCOVR_PATH} --sonarqube ${Coverage_NAME}_sonarqube.xml -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} + ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} + ) + set(GCOVR_XML_CMD_COMMAND + COMMAND ${GCOVR_XML_CMD} + ) + set(GCOVR_XML_CMD_BYPRODUCTS ${Coverage_NAME}_sonarqube.xml) + set(GCOVR_XML_CMD_COMMENT COMMENT "SonarQube code coverage info report saved in ${Coverage_NAME}_sonarqube.xml.") + endif() + + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Executed command report") + message(STATUS "Command to clean up lcov: ") + string(REPLACE ";" " " LCOV_CLEAN_CMD_SPACED "${LCOV_CLEAN_CMD}") + message(STATUS "${LCOV_CLEAN_CMD_SPACED}") + + message(STATUS "Command to create baseline: ") + string(REPLACE ";" " " LCOV_BASELINE_CMD_SPACED "${LCOV_BASELINE_CMD}") + message(STATUS "${LCOV_BASELINE_CMD_SPACED}") + + message(STATUS "Command to run the tests: ") + string(REPLACE ";" " " LCOV_EXEC_TESTS_CMD_SPACED "${LCOV_EXEC_TESTS_CMD}") + message(STATUS "${LCOV_EXEC_TESTS_CMD_SPACED}") + + message(STATUS "Command to capture counters and generate report: ") + string(REPLACE ";" " " LCOV_CAPTURE_CMD_SPACED "${LCOV_CAPTURE_CMD}") + message(STATUS "${LCOV_CAPTURE_CMD_SPACED}") + + message(STATUS "Command to add baseline counters: ") + string(REPLACE ";" " " LCOV_BASELINE_COUNT_CMD_SPACED "${LCOV_BASELINE_COUNT_CMD}") + message(STATUS "${LCOV_BASELINE_COUNT_CMD_SPACED}") + + message(STATUS "Command to filter collected data: ") + string(REPLACE ";" " " LCOV_FILTER_CMD_SPACED "${LCOV_FILTER_CMD}") + message(STATUS "${LCOV_FILTER_CMD_SPACED}") + + message(STATUS "Command to generate lcov HTML output: ") + string(REPLACE ";" " " LCOV_GEN_HTML_CMD_SPACED "${LCOV_GEN_HTML_CMD}") + message(STATUS "${LCOV_GEN_HTML_CMD_SPACED}") + + if(${Coverage_SONARQUBE}) + message(STATUS "Command to generate SonarQube XML output: ") + string(REPLACE ";" " " GCOVR_XML_CMD_SPACED "${GCOVR_XML_CMD}") + message(STATUS "${GCOVR_XML_CMD_SPACED}") + endif() + endif() + + # Setup target + add_custom_target(${Coverage_NAME} + COMMAND ${LCOV_CLEAN_CMD} + COMMAND ${LCOV_BASELINE_CMD} + COMMAND ${LCOV_EXEC_TESTS_CMD} + COMMAND ${LCOV_CAPTURE_CMD} + COMMAND ${LCOV_BASELINE_COUNT_CMD} + COMMAND ${LCOV_FILTER_CMD} + COMMAND ${LCOV_GEN_HTML_CMD} + ${GCOVR_XML_CMD_COMMAND} + + # Set output files as GENERATED (will be removed on 'make clean') + BYPRODUCTS + ${Coverage_NAME}.base + ${Coverage_NAME}.capture + ${Coverage_NAME}.total + ${Coverage_NAME}.info + ${GCOVR_XML_CMD_BYPRODUCTS} + ${Coverage_NAME}/index.html + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report." + ) + + # Show where to find the lcov info report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Lcov code coverage info report saved in ${Coverage_NAME}.info." + ${GCOVR_XML_CMD_COMMENT} + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." + ) + +endfunction() # setup_target_for_coverage_lcov + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_gcovr_xml( +# NAME ctest_coverage # New target name +# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES executable_target # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative +# # to BASE_DIRECTORY, with CMake 3.4+) +# ) +# The user can set the variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the +# GCVOR command. +function(setup_target_for_coverage_gcovr_xml) + + set(options NONE) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT GCOVR_PATH) + message(FATAL_ERROR "gcovr not found! Aborting...") + endif() # NOT GCOVR_PATH + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(DEFINED Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (CMake 3.4+: Also compute absolute paths) + set(GCOVR_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES}) + if(CMAKE_VERSION VERSION_GREATER 3.4) + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) + endif() + list(APPEND GCOVR_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES GCOVR_EXCLUDES) + + # Combine excludes to several -e arguments + set(GCOVR_EXCLUDE_ARGS "") + foreach(EXCLUDE ${GCOVR_EXCLUDES}) + list(APPEND GCOVR_EXCLUDE_ARGS "-e") + list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}") + endforeach() + + # Set up commands which will be run to generate coverage data + # Run tests + set(GCOVR_XML_EXEC_TESTS_CMD + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + ) + # Running gcovr + set(GCOVR_XML_CMD + ${GCOVR_PATH} --xml ${Coverage_NAME}.xml -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} + ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} + ) + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Executed command report") + + message(STATUS "Command to run tests: ") + string(REPLACE ";" " " GCOVR_XML_EXEC_TESTS_CMD_SPACED "${GCOVR_XML_EXEC_TESTS_CMD}") + message(STATUS "${GCOVR_XML_EXEC_TESTS_CMD_SPACED}") + + message(STATUS "Command to generate gcovr XML coverage data: ") + string(REPLACE ";" " " GCOVR_XML_CMD_SPACED "${GCOVR_XML_CMD}") + message(STATUS "${GCOVR_XML_CMD_SPACED}") + endif() + + add_custom_target(${Coverage_NAME} + COMMAND ${GCOVR_XML_EXEC_TESTS_CMD} + COMMAND ${GCOVR_XML_CMD} + + BYPRODUCTS ${Coverage_NAME}.xml + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Running gcovr to produce Cobertura code coverage report." + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Cobertura code coverage report saved in ${Coverage_NAME}.xml." + ) +endfunction() # setup_target_for_coverage_gcovr_xml + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_gcovr_html( +# NAME ctest_coverage # New target name +# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES executable_target # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative +# # to BASE_DIRECTORY, with CMake 3.4+) +# ) +# The user can set the variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the +# GCVOR command. +function(setup_target_for_coverage_gcovr_html) + + set(options NONE) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT GCOVR_PATH) + message(FATAL_ERROR "gcovr not found! Aborting...") + endif() # NOT GCOVR_PATH + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(DEFINED Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (CMake 3.4+: Also compute absolute paths) + set(GCOVR_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES}) + if(CMAKE_VERSION VERSION_GREATER 3.4) + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) + endif() + list(APPEND GCOVR_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES GCOVR_EXCLUDES) + + # Combine excludes to several -e arguments + set(GCOVR_EXCLUDE_ARGS "") + foreach(EXCLUDE ${GCOVR_EXCLUDES}) + list(APPEND GCOVR_EXCLUDE_ARGS "-e") + list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}") + endforeach() + + # Set up commands which will be run to generate coverage data + # Run tests + set(GCOVR_HTML_EXEC_TESTS_CMD + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + ) + # Create folder + set(GCOVR_HTML_FOLDER_CMD + ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/${Coverage_NAME} + ) + # Running gcovr + set(GCOVR_HTML_CMD + ${GCOVR_PATH} --html ${Coverage_NAME}/index.html --html-details -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} + ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} + ) + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Executed command report") + + message(STATUS "Command to run tests: ") + string(REPLACE ";" " " GCOVR_HTML_EXEC_TESTS_CMD_SPACED "${GCOVR_HTML_EXEC_TESTS_CMD}") + message(STATUS "${GCOVR_HTML_EXEC_TESTS_CMD_SPACED}") + + message(STATUS "Command to create a folder: ") + string(REPLACE ";" " " GCOVR_HTML_FOLDER_CMD_SPACED "${GCOVR_HTML_FOLDER_CMD}") + message(STATUS "${GCOVR_HTML_FOLDER_CMD_SPACED}") + + message(STATUS "Command to generate gcovr HTML coverage data: ") + string(REPLACE ";" " " GCOVR_HTML_CMD_SPACED "${GCOVR_HTML_CMD}") + message(STATUS "${GCOVR_HTML_CMD_SPACED}") + endif() + + add_custom_target(${Coverage_NAME} + COMMAND ${GCOVR_HTML_EXEC_TESTS_CMD} + COMMAND ${GCOVR_HTML_FOLDER_CMD} + COMMAND ${GCOVR_HTML_CMD} + + BYPRODUCTS ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html # report directory + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Running gcovr to produce HTML code coverage report." + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." + ) + +endfunction() # setup_target_for_coverage_gcovr_html + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_fastcov( +# NAME testrunner_coverage # New target name +# EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES testrunner # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/" "src/dir2/" # Patterns to exclude. +# NO_DEMANGLE # Don't demangle C++ symbols +# # even if c++filt is found +# SKIP_HTML # Don't create html report +# POST_CMD perl -i -pe s!${PROJECT_SOURCE_DIR}/!!g ctest_coverage.json # E.g. for stripping source dir from file paths +# ) +function(setup_target_for_coverage_fastcov) + + set(options NO_DEMANGLE SKIP_HTML) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES FASTCOV_ARGS GENHTML_ARGS POST_CMD) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT FASTCOV_PATH) + message(FATAL_ERROR "fastcov not found! Aborting...") + endif() + + if(NOT Coverage_SKIP_HTML AND NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (Patterns, not paths, for fastcov) + set(FASTCOV_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_FASTCOV_EXCLUDES}) + list(APPEND FASTCOV_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES FASTCOV_EXCLUDES) + + # Conditional arguments + if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE}) + set(GENHTML_EXTRA_ARGS "--demangle-cpp") + endif() + + # Set up commands which will be run to generate coverage data + set(FASTCOV_EXEC_TESTS_CMD ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS}) + + set(FASTCOV_CAPTURE_CMD ${FASTCOV_PATH} ${Coverage_FASTCOV_ARGS} --gcov ${GCOV_PATH} + --search-directory ${BASEDIR} + --process-gcno + --output ${Coverage_NAME}.json + --exclude ${FASTCOV_EXCLUDES} + ) + + set(FASTCOV_CONVERT_CMD ${FASTCOV_PATH} + -C ${Coverage_NAME}.json --lcov --output ${Coverage_NAME}.info + ) + + if(Coverage_SKIP_HTML) + set(FASTCOV_HTML_CMD ";") + else() + set(FASTCOV_HTML_CMD ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} + -o ${Coverage_NAME} ${Coverage_NAME}.info + ) + endif() + + set(FASTCOV_POST_CMD ";") + if(Coverage_POST_CMD) + set(FASTCOV_POST_CMD ${Coverage_POST_CMD}) + endif() + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Code coverage commands for target ${Coverage_NAME} (fastcov):") + + message(" Running tests:") + string(REPLACE ";" " " FASTCOV_EXEC_TESTS_CMD_SPACED "${FASTCOV_EXEC_TESTS_CMD}") + message(" ${FASTCOV_EXEC_TESTS_CMD_SPACED}") - IF(NOT GENHTML_PATH) - MESSAGE(FATAL_ERROR "genhtml not found! Aborting...") - ENDIF() # NOT GENHTML_PATH + message(" Capturing fastcov counters and generating report:") + string(REPLACE ";" " " FASTCOV_CAPTURE_CMD_SPACED "${FASTCOV_CAPTURE_CMD}") + message(" ${FASTCOV_CAPTURE_CMD_SPACED}") - SET(coverage_info "${CMAKE_BINARY_DIR}/${_outputname}.info") - SET(coverage_cleaned "${coverage_info}.cleaned") - - SEPARATE_ARGUMENTS(test_command UNIX_COMMAND "${_testrunner}") + message(" Converting fastcov .json to lcov .info:") + string(REPLACE ";" " " FASTCOV_CONVERT_CMD_SPACED "${FASTCOV_CONVERT_CMD}") + message(" ${FASTCOV_CONVERT_CMD_SPACED}") - # Setup target - ADD_CUSTOM_TARGET(${_targetname} + if(NOT Coverage_SKIP_HTML) + message(" Generating HTML report: ") + string(REPLACE ";" " " FASTCOV_HTML_CMD_SPACED "${FASTCOV_HTML_CMD}") + message(" ${FASTCOV_HTML_CMD_SPACED}") + endif() + if(Coverage_POST_CMD) + message(" Running post command: ") + string(REPLACE ";" " " FASTCOV_POST_CMD_SPACED "${FASTCOV_POST_CMD}") + message(" ${FASTCOV_POST_CMD_SPACED}") + endif() + endif() - # Cleanup lcov - ${LCOV_PATH} --initial --directory . --capture --output-file ${coverage_info} - COMMAND ${LCOV_PATH} --directory . --zerocounters + # Setup target + add_custom_target(${Coverage_NAME} - # Run tests - COMMAND ${test_command} ${ARGV3} || exit 0 + # Cleanup fastcov + COMMAND ${FASTCOV_PATH} ${Coverage_FASTCOV_ARGS} --gcov ${GCOV_PATH} + --search-directory ${BASEDIR} + --zerocounters - # Capturing lcov counters and generating report - COMMAND ${LCOV_PATH} --directory . --capture --output-file ${coverage_info} - COMMAND ${LCOV_PATH} --remove ${coverage_info} '/usr/*' --output-file ${coverage_cleaned} - COMMAND ${GENHTML_PATH} -o ${_outputname} ${coverage_cleaned} - COMMAND ${CMAKE_COMMAND} -E remove ${coverage_cleaned} ${coverage} + COMMAND ${FASTCOV_EXEC_TESTS_CMD} + COMMAND ${FASTCOV_CAPTURE_CMD} + COMMAND ${FASTCOV_CONVERT_CMD} + COMMAND ${FASTCOV_HTML_CMD} + COMMAND ${FASTCOV_POST_CMD} - WORKING_DIRECTORY ${CMAKE_HOME_DIRECTORY} - COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report." - ) + # Set output files as GENERATED (will be removed on 'make clean') + BYPRODUCTS + ${Coverage_NAME}.info + ${Coverage_NAME}.json + ${Coverage_NAME}/index.html # report directory - # Show info where to find the report - ADD_CUSTOM_COMMAND(TARGET ${_targetname} POST_BUILD - COMMAND ; - COMMENT "Open ./${_outputname}/index.html in your browser to view the coverage report." - ) + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Resetting code coverage counters to zero. Processing code coverage counters and generating report." + ) -ENDFUNCTION() # SETUP_TARGET_FOR_COVERAGE + set(INFO_MSG "fastcov code coverage info report saved in ${Coverage_NAME}.info and ${Coverage_NAME}.json.") + if(NOT Coverage_SKIP_HTML) + string(APPEND INFO_MSG " Open ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html in your browser to view the coverage report.") + endif() + # Show where to find the fastcov info report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E echo ${INFO_MSG} + ) -ELSE () # GCOV_PATH +endfunction() # setup_target_for_coverage_fastcov -FUNCTION(SETUP_TARGET_FOR_COVERAGE) +function(append_coverage_compiler_flags) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + message(STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}") +endfunction() # append_coverage_compiler_flags -ENDFUNCTION() +# Setup coverage for specific library +function(append_coverage_compiler_flags_to_target name) + separate_arguments(_flag_list NATIVE_COMMAND "${COVERAGE_COMPILER_FLAGS}") + target_compile_options(${name} PRIVATE ${_flag_list}) + if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") + target_link_libraries(${name} PRIVATE gcov) + endif() +endfunction() -ENDIF() # GCOV_PATH +endif() # CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG From a5715676ece556333f1aa6eb862a27e126ba2091 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 19 Mar 2024 20:07:38 -0700 Subject: [PATCH 2166/2505] Foundational work to allow async/await on multiple file descriptors A slightly different version of this patch has been sitting on a local branch for almost two years, so I decided to take a look this weekend and ended up rewriting it this weekend so it's compatible with the current version of Lwan's event loop. While this won't allow a request handler to await on multiple file descriptors just yet, it lays down the foundations to do so by making all the necessary plumbing changes to allow a coro_yield() from a request handler to receive a pointer to the struct lwan_connection that caused the request handler to be awoken. The actual changes are minimal (with little to no overhead), but some comments had to be added throghout to explain certain things as the ownership of certain values isn't clear from the struct definition alone anymore. No APIs have been implemented to allow waiting for multiple file descriptors at once yet, but they will probably follow in the next few weeks. I got to make sure it also works with the HTTP connection coroutine as well as any other file descriptor a handler might decide to use (e.g. a pubsub subscription); this would allow, for instance, a chat application to wait on both the WebSocket connection and the pubsub subscription and react as fast as it's able to. Let's see what I end up coming up with. Tested to compile only; have not tested if this feature works yet, but normal requests seems to work fine. --- src/lib/lwan-request.c | 15 ++++--- src/lib/lwan-thread.c | 99 +++++++++++++++++++++++++++++++++--------- src/lib/lwan-tq.c | 4 ++ src/lib/lwan.h | 43 +++++++++++++++--- 4 files changed, 128 insertions(+), 33 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 93073de84..f9c6040ca 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -2023,27 +2023,28 @@ make_async_yield_value(int fd, enum lwan_connection_coro_yield event) return (int64_t)(((uint64_t)fd << 32 | event)); } -static inline void async_await_fd(struct coro *coro, - int fd, - enum lwan_connection_coro_yield events) +static inline struct lwan_connection *async_await_fd( + struct coro *coro, int fd, enum lwan_connection_coro_yield events) { assert(events >= CONN_CORO_ASYNC_AWAIT_READ && events <= CONN_CORO_ASYNC_AWAIT_READ_WRITE); - return (void)coro_yield(coro, make_async_yield_value(fd, events)); + int64_t from_coro = coro_yield(coro, make_async_yield_value(fd, events)); + return (struct lwan_connection *)(intptr_t)from_coro; } -void lwan_request_await_read(struct lwan_request *r, int fd) +struct lwan_connection *lwan_request_await_read(struct lwan_request *r, int fd) { return async_await_fd(r->conn->coro, fd, CONN_CORO_ASYNC_AWAIT_READ); } -void lwan_request_await_write(struct lwan_request *r, int fd) +struct lwan_connection *lwan_request_await_write(struct lwan_request *r, int fd) { return async_await_fd(r->conn->coro, fd, CONN_CORO_ASYNC_AWAIT_WRITE); } -void lwan_request_await_read_write(struct lwan_request *r, int fd) +struct lwan_connection *lwan_request_await_read_write(struct lwan_request *r, + int fd) { return async_await_fd(r->conn->coro, fd, CONN_CORO_ASYNC_AWAIT_READ_WRITE); } diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 6ba65281d..d3db08556 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -522,11 +522,23 @@ static void update_epoll_flags(const struct timeout_queue *tq, lwan_status_perror("epoll_ctl"); } -static void clear_async_await_flag(void *data) +static void unasync_await_conn(void *data1, void *data2) { - struct lwan_connection *async_fd_conn = data; + struct lwan_connection *async_fd_conn = data1; - async_fd_conn->flags &= ~CONN_ASYNC_AWAIT; + async_fd_conn->flags &= ~(CONN_ASYNC_AWAIT | CONN_HUNG_UP); + async_fd_conn->thread = data2; + + /* If this file descriptor number is used again in the future as an HTTP + * connection, we need the coro pointer to be NULL so a new coroutine is + * created! */ + async_fd_conn->coro = NULL; + + /* While not strictly necessary, make sure that prev/next point to + * something valid rather than whatever junk was left from when their + * storage was used for the parent pointer. */ + async_fd_conn->prev = -1; + async_fd_conn->next = -1; } static enum lwan_connection_coro_yield @@ -558,13 +570,31 @@ resume_async(const struct timeout_queue *tq, op = EPOLL_CTL_MOD; } else { + await_fd_conn->parent = conn; + + /* We assert() in the timeout queue that we're not freeing a + * coroutine when CONN_ASYNC_AWAIT is set in the connection, and are + * careful to not ever do that. This makes us get away with struct + * coro not being refcounted, even though this kinda feels like + * running with scissors. */ + assert(!await_fd_conn->coro); + await_fd_conn->coro = conn->coro; + + /* Since scheduling is performed during startup, we gotta take note + * of which thread was originally supposed to handle this particular + * file descriptor once we're done borrowing this lwan_connection + * for the awaited file descriptor. */ + struct lwan_thread *old_thread = await_fd_conn->thread; + await_fd_conn->thread = conn->thread; + op = EPOLL_CTL_ADD; flags |= CONN_ASYNC_AWAIT; - coro_defer(conn->coro, clear_async_await_flag, await_fd_conn); + + coro_defer2(conn->coro, unasync_await_conn, await_fd_conn, old_thread); } struct epoll_event event = {.events = conn_flags_to_epoll_events(flags), - .data.ptr = conn}; + .data.ptr = await_fd_conn}; if (LIKELY(!epoll_ctl(epoll_fd, op, await_fd, &event))) { await_fd_conn->flags &= ~CONN_EVENTS_MASK; await_fd_conn->flags |= flags; @@ -575,24 +605,27 @@ resume_async(const struct timeout_queue *tq, } static ALWAYS_INLINE void resume_coro(struct timeout_queue *tq, - struct lwan_connection *conn, + struct lwan_connection *conn_to_resume, + struct lwan_connection *conn_to_yield, int epoll_fd) { - assert(conn->coro); + assert(conn_to_resume->coro); + assert(conn_to_yield->coro); - int64_t from_coro = coro_resume(conn->coro); + int64_t from_coro = coro_resume_value(conn_to_resume->coro, + (int64_t)(intptr_t)conn_to_yield); enum lwan_connection_coro_yield yield_result = from_coro & 0xffffffff; if (UNLIKELY(yield_result >= CONN_CORO_ASYNC)) { yield_result = - resume_async(tq, yield_result, from_coro, conn, epoll_fd); + resume_async(tq, yield_result, from_coro, conn_to_resume, epoll_fd); } if (UNLIKELY(yield_result == CONN_CORO_ABORT)) { - timeout_queue_expire(tq, conn); + timeout_queue_expire(tq, conn_to_resume); } else { - update_epoll_flags(tq, conn, epoll_fd, yield_result); - timeout_queue_move_to_last(tq, conn); + update_epoll_flags(tq, conn_to_resume, epoll_fd, yield_result); + timeout_queue_move_to_last(tq, conn_to_resume); } } @@ -696,8 +729,7 @@ static ALWAYS_INLINE bool spawn_coro(struct lwan_connection *conn, #endif assert(!conn->coro); - assert(!(conn->flags & CONN_ASYNC_AWAIT)); - assert(!(conn->flags & CONN_AWAITED_FD)); + assert(!(conn->flags & (CONN_ASYNC_AWAIT | CONN_HUNG_UP))); assert(!(conn->flags & CONN_LISTENER)); assert(t); assert((uintptr_t)t >= (uintptr_t)tq->lwan->thread.threads); @@ -978,7 +1010,36 @@ static void *thread_io_loop(void *data) for (struct epoll_event *event = events; n_fds--; event++) { struct lwan_connection *conn = event->data.ptr; - assert(!(conn->flags & CONN_ASYNC_AWAIT)); + if (conn->flags & CONN_ASYNC_AWAIT) { + /* Assert that the connection is part of the conns array, + * since the storage for conn->parent is shared with + * prev/next. */ + assert(conn->parent >= lwan->conns); + assert(conn->parent <= &lwan->conns[lwan->thread.max_fd]); + + /* Also validate that conn->parent is in fact a HTTP client + * connection and not an awaited fd! */ + assert(!(conn->parent->flags & CONN_ASYNC_AWAIT)); + + /* CONN_ASYNC_AWAIT conns *must* have a coro and thread as + * it's the same as the HTTP client coro for API + * consistency, as struct lwan_connection isn't opaque. (If + * it were opaque, or at least a private API, though, we + * might be able to get away with reusing the space for + * these two pointers for something else in some cases. + * This has not been necessary yet, but might become useful + * in the future.) */ + assert(conn->coro); + assert(conn->coro == conn->parent->coro); + assert(conn->thread == conn->parent->thread); + + if (UNLIKELY(events->events & (EPOLLRDHUP | EPOLLHUP))) + conn->flags |= CONN_HUNG_UP; + + resume_coro(&tq, conn->parent, conn, epoll_fd); + + continue; + } if (conn->flags & CONN_LISTENER) { if (LIKELY(accept_waiting_clients(t, conn))) @@ -989,10 +1050,8 @@ static void *thread_io_loop(void *data) } if (UNLIKELY(event->events & (EPOLLRDHUP | EPOLLHUP))) { - if ((conn->flags & CONN_AWAITED_FD) != CONN_SUSPENDED_MASK) { - timeout_queue_expire(&tq, conn); - continue; - } + timeout_queue_expire(&tq, conn); + continue; } if (!conn->coro) { @@ -1004,7 +1063,7 @@ static void *thread_io_loop(void *data) created_coros = true; } - resume_coro(&tq, conn, epoll_fd); + resume_coro(&tq, conn, conn, epoll_fd); } if (created_coros) diff --git a/src/lib/lwan-tq.c b/src/lib/lwan-tq.c index a00a4a5e8..4f085049b 100644 --- a/src/lib/lwan-tq.c +++ b/src/lib/lwan-tq.c @@ -38,6 +38,8 @@ timeout_queue_idx_to_node(struct timeout_queue *tq, int idx) inline void timeout_queue_insert(struct timeout_queue *tq, struct lwan_connection *new_node) { + assert(!(new_node->flags & (CONN_HUNG_UP | CONN_ASYNC_AWAIT))); + new_node->next = -1; new_node->prev = tq->head.prev; struct lwan_connection *prev = timeout_queue_idx_to_node(tq, tq->head.prev); @@ -87,6 +89,8 @@ void timeout_queue_init(struct timeout_queue *tq, const struct lwan *lwan) void timeout_queue_expire(struct timeout_queue *tq, struct lwan_connection *conn) { + assert(!(conn->flags & (CONN_HUNG_UP | CONN_ASYNC_AWAIT))); + timeout_queue_remove(tq, conn); if (LIKELY(conn->coro)) { diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 1720195b7..342cc9ad5 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -275,7 +275,6 @@ enum lwan_connection_flags { CONN_SUSPENDED_MASK = 1 << 5, CONN_SUSPENDED = (EPOLLRDHUP << CONN_EPOLL_EVENT_SHIFT) | CONN_SUSPENDED_MASK, CONN_HAS_REMOVE_SLEEP_DEFER = 1 << 6, - CONN_AWAITED_FD = CONN_SUSPENDED_MASK | CONN_HAS_REMOVE_SLEEP_DEFER, /* Used when HTTP pipelining has been detected. This enables usage of the * MSG_MORE flags when sending responses to batch as many short responses @@ -298,7 +297,12 @@ enum lwan_connection_flags { CONN_USE_DYNAMIC_BUFFER = 1 << 12, - CONN_FLAG_LAST = CONN_USE_DYNAMIC_BUFFER, + /* Only valid when CONN_ASYNC_AWAIT is set. Set on file descriptors that + * got (EPOLLHUP|EPOLLRDHUP) events from epoll so that request handlers + * can deal with this fact. */ + CONN_HUNG_UP = 1 << 13, + + CONN_FLAG_LAST = CONN_HUNG_UP, }; static_assert(CONN_FLAG_LAST < ((1 << 15) - 1), @@ -373,10 +377,35 @@ struct lwan_connection { /* This structure is exactly 32-bytes on x86-64. If it is changed, * make sure the scheduler (lwan-thread.c) is updated as well. */ enum lwan_connection_flags flags; + unsigned int time_to_expire; + struct coro *coro; struct lwan_thread *thread; - int prev, next; /* for timeout queue */ + + /* This union is here to support async/await when a handler is waiting + * on multiple file descriptors. By storing a pointer to the parent + * connection here, we're able to register the awaited file descriptor + * in epoll using a pointer to the awaited file descriptor struct, + * allowing us to yield to the handler this information and signal which + * file descriptor caused the handler to be awoken. (We can yield just + * the file descriptor plus another integer with values to signal things + * like timeouts and whatnot. Future problems!) + * + * Also, when CONN_ASYNC_AWAIT is set, `coro` points to parent->coro, + * so that conn->coro is consistently usable. Gotta be careful though, + * because struct coros are not refcounted and this could explode with + * a double free. */ + union { + /* For HTTP client connections handling inside the timeout queue */ + struct { + int prev; + int next; + }; + + /* For awaited file descriptor, only valid if flags&CONN_ASYNC_AWAIT */ + struct lwan_connection *parent; + }; }; struct lwan_proxy { @@ -639,9 +668,11 @@ void lwan_response_websocket_write_binary(struct lwan_request *request); int lwan_response_websocket_read(struct lwan_request *request); int lwan_response_websocket_read_hint(struct lwan_request *request, size_t size_hint); -void lwan_request_await_read(struct lwan_request *r, int fd); -void lwan_request_await_write(struct lwan_request *r, int fd); -void lwan_request_await_read_write(struct lwan_request *r, int fd); +struct lwan_connection *lwan_request_await_read(struct lwan_request *r, int fd); +struct lwan_connection *lwan_request_await_write(struct lwan_request *r, + int fd); +struct lwan_connection *lwan_request_await_read_write(struct lwan_request *r, + int fd); ssize_t lwan_request_async_read(struct lwan_request *r, int fd, void *buf, size_t len); ssize_t lwan_request_async_read_flags(struct lwan_request *request, int fd, void *buf, size_t len, int flags); ssize_t lwan_request_async_write(struct lwan_request *r, int fd, const void *buf, size_t len); From 27ac61e7111d9a6b19858d5a65389a777669f25c Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 20 Mar 2024 21:14:05 -0700 Subject: [PATCH 2167/2505] Add more mentions of Lwan in academic journals and magazines --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 19a3ca42d..1b7d08e2b 100644 --- a/README.md +++ b/README.md @@ -1020,7 +1020,9 @@ Lwan in the wild ---------------- Here's a non-definitive list of third-party stuff that uses Lwan and have -been seen in the wild. *Help build this list!* +been seen in the wild. *If you see mentions of Lwan in the media or +academia, however small it might be, please contact the author! It'll make +her day!* * [This project uses Cython and Lwan](https://www.erp5.com/NXD-Blog.Multicore.Python.HTTP.Server) to make it possible to write handlers in Python. * [An experimental version of Node.js using Lwan](https://github.com/raadad/node-lwan) as its HTTP server is maintained by [@raadad](https://github.com/raadad). @@ -1057,10 +1059,14 @@ Mentions in academic journals: * [A dynamic predictive race detector for C/C++ programs (in English, published 2017)](https://link.springer.com/article/10.1007/s11227-017-1996-8) uses Lwan as a "real world example". * [High-precision Data Race Detection Method for Large Scale Programs (in Chinese, published 2021)](http://www.jos.org.cn/jos/article/abstract/6260) also uses Lwan as one of the case studies. * [AGE: Automatic Performance Evaluation of API Gateways (in English, published 2023)](https://www.computer.org/csdl/proceedings-article/iscc/2023/10218286/1PYLvz6ihBm) mentions Lwan as part of its usage in the KrakenD benchmarks. +* [Canary: Practical Static Detection of Inter-thread Value-Flow Bugs](https://rainoftime.github.io/files/PLDI21Canary.pdf) used Lwan as one of the pieces of software that have been analyzed. +* [Uma análise comparativa de ferramentas de análise estática para deteção de erros de memória](https://arxiv.org/pdf/1807.08015.pdf) used Lwan as one of the evaluation softwares. Mentions in magazines: * [Linux-Magazin (Germany) mentions Lwan in their December/2021 issue](https://www.linux-magazin.de/ausgaben/2021/12/tooltipps/) +* [Raspberry Pi Geek (Germany) mentions Lwan in their October/November 2022 issue)(https://www.discountmags.com/magazine/raspberry-pi-geek-october-6-2022-digital/in-this-issue/17) +* [LinuxUser (Germany) mentions Lwan in their October 2022 issue)(https://www.linux-community.de/ausgaben/linuxuser/2022/10/aktuelle-software-im-kurztest-30/) Some talks mentioning Lwan: From b14b915c8c54fd7504291904b6df2f99ff64b9d1 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 20 Mar 2024 21:15:06 -0700 Subject: [PATCH 2168/2505] Fix links in README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1b7d08e2b..21beb6052 100644 --- a/README.md +++ b/README.md @@ -1065,8 +1065,8 @@ Mentions in academic journals: Mentions in magazines: * [Linux-Magazin (Germany) mentions Lwan in their December/2021 issue](https://www.linux-magazin.de/ausgaben/2021/12/tooltipps/) -* [Raspberry Pi Geek (Germany) mentions Lwan in their October/November 2022 issue)(https://www.discountmags.com/magazine/raspberry-pi-geek-october-6-2022-digital/in-this-issue/17) -* [LinuxUser (Germany) mentions Lwan in their October 2022 issue)(https://www.linux-community.de/ausgaben/linuxuser/2022/10/aktuelle-software-im-kurztest-30/) +* [Raspberry Pi Geek (Germany) mentions Lwan in their October/November 2022 issue](https://www.discountmags.com/magazine/raspberry-pi-geek-october-6-2022-digital/in-this-issue/17) +* [LinuxUser (Germany) mentions Lwan in their October 2022 issue](https://www.linux-community.de/ausgaben/linuxuser/2022/10/aktuelle-software-im-kurztest-30/) Some talks mentioning Lwan: From 6ab4034786f7e6b99556d255483a9da2266e2582 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 22 Mar 2024 21:44:01 -0700 Subject: [PATCH 2169/2505] Add another Lwan quote from InfoQ --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 21beb6052..f4d0ffa56 100644 --- a/README.md +++ b/README.md @@ -1202,6 +1202,8 @@ in no particular order. Contributions are appreciated: > "Insane C thing" -- [Michael > Sproul](https://michaelsproul.github.io/iron-talk/) +> "The best performer is LWAN, a newcomer" -- [InfoQ](https://www.infoq.com/news/2015/04/web-frameworks-benchmark-2015/) + > "I've never had a chance to thank you for Lwan. It inspired me a lot to > develop [Zewo](https://github.com/Zewo/Zero)" -- > [@paulofariarl](https://twitter.com/paulofariarl/status/707926806373003265) From 1a6ae628a4f5749ee5201475d0e36c9edcb711e0 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 22 Mar 2024 21:44:18 -0700 Subject: [PATCH 2170/2505] Enable more compilation warnings on supported compilers --- CMakeLists.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d5d85e10d..f4054c1eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -306,11 +306,16 @@ endif () # # These warnings are only supported by GCC, and some only in newer versions. # +enable_warning_if_supported(-Waggressive-loop-optimizations) +enable_warning_if_supported(-Warith-conversion) enable_warning_if_supported(-Warray-bounds) enable_warning_if_supported(-Wdouble-promotion) enable_warning_if_supported(-Wduplicated-branches) enable_warning_if_supported(-Wduplicated-cond) +enable_warning_if_supported(-Wformat-overflow) +enable_warning_if_supported(-Wlogical-not-parentheses) enable_warning_if_supported(-Wlogical-op) +enable_warning_if_supported(-Wno-override-init) enable_warning_if_supported(-Wno-unused-parameter) enable_warning_if_supported(-Wrestrict) enable_warning_if_supported(-Wstringop-overflow) @@ -318,7 +323,6 @@ enable_warning_if_supported(-Wstringop-overread) enable_warning_if_supported(-Wstringop-truncation) enable_warning_if_supported(-Wunsequenced) enable_warning_if_supported(-Wvla) -enable_warning_if_supported(-Wno-override-init) # While a useful warning, this is giving false positives. enable_warning_if_supported(-Wno-free-nonheap-object) From 1cb2507b91485e9611fc4193030dd9c2e01bdb58 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 22 Mar 2024 21:44:34 -0700 Subject: [PATCH 2171/2505] Add 422 HTTP response code --- src/lib/lwan-http-status.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/lwan-http-status.h b/src/lib/lwan-http-status.h index 1bf66a208..10fbda39f 100644 --- a/src/lib/lwan-http-status.h +++ b/src/lib/lwan-http-status.h @@ -18,6 +18,7 @@ X(RANGE_UNSATISFIABLE, 416, "Requested range unsatisfiable", "The server can't supply the requested portion of the requested resource") \ X(I_AM_A_TEAPOT, 418, "I'm a teapot", "Client requested to brew coffee but device is a teapot") \ X(CLIENT_TOO_HIGH, 420, "Client too high", "Client is too high to make a request") \ + X(UNPROCESSABLE_CONTENT, 422, "Unprocessable content", "Request was understood by the server but it can't process the instructions") \ X(INTERNAL_ERROR, 500, "Internal server error", "The server encountered an internal error that couldn't be recovered from") \ X(NOT_IMPLEMENTED, 501, "Not implemented", "Server lacks the ability to fulfil the request") \ X(UNAVAILABLE, 503, "Service unavailable", "The server is either overloaded or down for maintenance") \ From 34879015e6269498ca56ee5a369352c106fc87a6 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 22 Mar 2024 21:45:09 -0700 Subject: [PATCH 2172/2505] Add private strbuf API to know if a buffer growing operation failed This is useful for instance, if we want to know if a strbuf with a fixed buffer of size 0 was written to or not -- for instance, if we want to know if some callback function that might write something to a string buffer actually didn't write anything. --- src/lib/lwan-private.h | 1 + src/lib/lwan-strbuf.c | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index c4d7705b5..e73b23792 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -112,6 +112,7 @@ void lwan_readahead_queue(int fd, off_t off, size_t size); void lwan_madvise_queue(void *addr, size_t size); char *lwan_strbuf_extend_unsafe(struct lwan_strbuf *s, size_t by); +bool lwan_strbuf_has_grow_buffer_failed_flag(const struct lwan_strbuf *s); void lwan_process_request(struct lwan *l, struct lwan_request *request); size_t lwan_prepare_response_header_full(struct lwan_request *request, diff --git a/src/lib/lwan-strbuf.c b/src/lib/lwan-strbuf.c index b945a8494..9c1114c4f 100644 --- a/src/lib/lwan-strbuf.c +++ b/src/lib/lwan-strbuf.c @@ -33,6 +33,12 @@ static const unsigned int BUFFER_MALLOCD = 1 << 0; static const unsigned int STRBUF_MALLOCD = 1 << 1; static const unsigned int BUFFER_FIXED = 1 << 2; +static const unsigned int GROW_BUFFER_FAILED = 1 << 3; + +bool lwan_strbuf_has_grow_buffer_failed_flag(const struct lwan_strbuf *s) +{ + return s->flags & GROW_BUFFER_FAILED; +} static inline size_t align_size(size_t unaligned_size) { @@ -44,7 +50,8 @@ static inline size_t align_size(size_t unaligned_size) return aligned_size; } -static bool grow_buffer_if_needed(struct lwan_strbuf *s, size_t size) +static ALWAYS_INLINE +bool grow_buffer_if_needed_internal(struct lwan_strbuf *s, size_t size) { if (s->flags & BUFFER_FIXED) return size < s->capacity; @@ -99,6 +106,16 @@ static bool grow_buffer_if_needed(struct lwan_strbuf *s, size_t size) return true; } +static bool grow_buffer_if_needed(struct lwan_strbuf *s, size_t size) +{ + if (UNLIKELY(!grow_buffer_if_needed_internal(s, size))) { + s->flags |= GROW_BUFFER_FAILED; + return false; + } + + return true; +} + bool lwan_strbuf_init_with_size(struct lwan_strbuf *s, size_t size) { if (UNLIKELY(!s)) From 242b82f5a49f584be93215256dd9a6f3928c3d6a Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 22 Mar 2024 21:49:23 -0700 Subject: [PATCH 2173/2505] Implement lambdas in templates --- src/lib/lwan-template.c | 61 ++++++++++++++++++++++++++++++++++++++--- src/lib/lwan-template.h | 26 ++++++++++++++---- 2 files changed, 77 insertions(+), 10 deletions(-) diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index d39d503e3..8a52e7c89 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -66,6 +66,7 @@ enum action { ACTION_IF_VARIABLE_NOT_EMPTY, ACTION_END_IF_VARIABLE_NOT_EMPTY, ACTION_APPLY_TPL, + ACTION_LAMBDA, ACTION_LAST }; @@ -74,6 +75,7 @@ enum flags { FLAGS_NEGATE = 1 << 0, FLAGS_QUOTE = 1 << 1, FLAGS_NO_FREE = 1 << 2, + FLAGS_LAMBDA = 1 << 3, }; #define FOR_EACH_LEXEME(X) \ @@ -765,7 +767,13 @@ static void *parser_identifier(struct parser *parser, struct lexeme *lexeme) (int)lexeme->value.len, lexeme->value.value); } - emit_chunk(parser, ACTION_VARIABLE, parser->flags, symbol); + if (symbol->offset == 0x1aabdacb) { + emit_chunk(parser, ACTION_LAMBDA, parser->flags, symbol); + assert(!symbol->get_is_empty); + assert(!symbol->list_desc); + } else { + emit_chunk(parser, ACTION_VARIABLE, parser->flags, symbol); + } parser->flags &= ~FLAGS_QUOTE; parser->tpl->minimum_size += lexeme->value.len + 1; @@ -782,6 +790,10 @@ static void *parser_identifier(struct parser *parser, struct lexeme *lexeme) } enum flags flags = FLAGS_NO_FREE | (parser->flags & FLAGS_NEGATE); + + if (symbol->offset == 0x1aabdacb) + flags |= FLAGS_LAMBDA; + emit_chunk(parser, ACTION_IF_VARIABLE_NOT_EMPTY, flags, symbol); parser_push_lexeme(parser, lexeme); @@ -1042,6 +1054,7 @@ static void free_chunk(struct chunk *chunk) return; switch (chunk->action) { + case ACTION_LAMBDA: case ACTION_LAST: case ACTION_APPEND_SMALL: case ACTION_VARIABLE: @@ -1335,6 +1348,12 @@ static void dump_program(const struct lwan_tpl *tpl) printf("%s (%zu) [%.*s]", instr("APPEND_SMALL", instr_buf), len, (int)len, (char *)&val); break; } + case ACTION_LAMBDA: { + struct lwan_var_descriptor *descriptor = iter->data; + + printf("%s %s()", instr("APPEND_LAMBDA", instr_buf), descriptor->name); + break; + } case ACTION_VARIABLE: { struct lwan_var_descriptor *descriptor = iter->data; @@ -1363,8 +1382,12 @@ static void dump_program(const struct lwan_tpl *tpl) case ACTION_IF_VARIABLE_NOT_EMPTY: { struct chunk_descriptor *cd = iter->data; - printf("%s [%s]", instr("IF_VAR_NOT_EMPTY", instr_buf), + printf("%s [%s]", + instr((iter->flags & FLAGS_LAMBDA) ? "IF_LAMBDA_NOT_EMPTY" + : "IF_VAR_NOT_EMPTY", + instr_buf), cd->descriptor->name); + indent++; break; } @@ -1494,6 +1517,7 @@ static const struct chunk *apply(struct lwan_tpl *tpl, [ACTION_APPLY_TPL] = &&action_apply_tpl, [ACTION_START_ITER] = &&action_start_iter, [ACTION_END_ITER] = &&action_end_iter, + [ACTION_LAMBDA] = &&action_lambda, [ACTION_LAST] = &&finalize, }; @@ -1549,6 +1573,12 @@ action_variable: { DISPATCH_NEXT_ACTION_FAST(); } +action_lambda: { + struct lwan_var_descriptor *descriptor = chunk->data; + descriptor->lambda(buf, variables); + DISPATCH_NEXT_ACTION_FAST(); + } + action_variable_str: lwan_append_str_to_strbuf(buf, (char *)variables + (uintptr_t)chunk->data); DISPATCH_NEXT_ACTION_FAST(); @@ -1560,8 +1590,23 @@ action_variable: { action_if_variable_not_empty: { struct chunk_descriptor *cd = chunk->data; - bool empty = cd->descriptor->get_is_empty((char *)variables + - cd->descriptor->offset); + bool empty; + + if (chunk->flags & FLAGS_LAMBDA) { + struct lwan_strbuf temp_buf; + char trash[1]; + + lwan_strbuf_init_with_fixed_buffer(&temp_buf, trash, 0); + + cd->descriptor->lambda(&temp_buf, variables); + empty = lwan_strbuf_has_grow_buffer_failed_flag(&temp_buf); + + lwan_strbuf_free(&temp_buf); + } else { + empty = cd->descriptor->get_is_empty((char *)variables + + cd->descriptor->offset); + } + if (chunk->flags & FLAGS_NEGATE) empty = !empty; if (empty) { @@ -1696,6 +1741,13 @@ struct test_struct { char *a_string; }; +static void hello_lambda(struct lwan_strbuf *b, void *v) +{ + struct test_struct *s = v; + + lwan_strbuf_append_printf("hello from lambda func! appending <%d> from test_struct", s->some_int); +} + int main(int argc, char *argv[]) { if (argc < 2) { @@ -1709,6 +1761,7 @@ int main(int argc, char *argv[]) struct lwan_var_descriptor desc[] = { TPL_VAR_INT(some_int), TPL_VAR_STR(a_string), + TPL_LAMBDA(hello, hello_lambda), TPL_VAR_SENTINEL }; struct lwan_tpl *tpl = lwan_tpl_compile_file(argv[1], desc); diff --git a/src/lib/lwan-template.h b/src/lib/lwan-template.h index d0d08db3f..22664872e 100644 --- a/src/lib/lwan-template.h +++ b/src/lib/lwan-template.h @@ -27,15 +27,30 @@ enum lwan_tpl_flag { LWAN_TPL_FLAG_CONST_TEMPLATE = 1 << 0 }; struct lwan_var_descriptor { const char *name; - const off_t offset; - void (*append_to_strbuf)(struct lwan_strbuf *buf, void *ptr); - bool (*get_is_empty)(void *ptr); + const off_t offset; - coro_function_t generator; - const struct lwan_var_descriptor *list_desc; + union { + struct { + void (*append_to_strbuf)(struct lwan_strbuf *buf, void *ptr); + bool (*get_is_empty)(void *ptr); + }; + struct { + coro_function_t generator; + const struct lwan_var_descriptor *list_desc; + }; + struct { + void (*lambda)(struct lwan_strbuf *buf, void *ptr); + }; + }; }; +#define TPL_LAMBDA(var_, lambda_) \ + { \ + .name = #var_, .offset = 0x1aabdacb, .lambda = lambda_, \ + /* 0x1aabdacb = lambda call back */ \ + } + #define TPL_VAR_SIMPLE(var_, append_to_lwan_strbuf_, get_is_empty_) \ { \ .name = #var_, .offset = offsetof(TPL_STRUCT, var_), \ @@ -64,7 +79,6 @@ struct lwan_var_descriptor { #define TPL_VAR_SENTINEL \ { \ - NULL, 0, NULL, NULL, NULL, NULL \ } /* From 3b2f1b8487b99c8df7e46e936198532a855be7fb Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 23 Mar 2024 08:00:15 -0700 Subject: [PATCH 2174/2505] Remove spurious \\n from servefile template Forgot to remove these when converting this template from a C array embedded in the code to a file. --- src/lib/servefile-template.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/servefile-template.html b/src/lib/servefile-template.html index 0d8427f4f..043173adc 100644 --- a/src/lib/servefile-template.html +++ b/src/lib/servefile-template.html @@ -9,9 +9,9 @@ -{{rel_path?}}

Index of {{rel_path}}

\n{{/rel_path?}} -{{^rel_path?}}

Index of /

\n{{/rel_path?}} -{{readme?}}
{{readme}}
\n{{/readme?}} +{{rel_path?}}

Index of {{rel_path}}

{{/rel_path?}} +{{^rel_path?}}

Index of /

{{/rel_path?}} +{{readme?}}
{{readme}}
{{/readme?}} From d8920a1bbc5bac6e44905bd90ba1888ed4daf594 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 24 Mar 2024 09:55:10 -0700 Subject: [PATCH 2175/2505] Maybe fix crash in template compiler? oss-fuzz issue: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=67610 --- ...usterfuzz-testcase-minimized-template_fuzzer-6208074244227072 | 1 + src/lib/lwan-template.h | 1 + 2 files changed, 2 insertions(+) create mode 100644 fuzz/regression/clusterfuzz-testcase-minimized-template_fuzzer-6208074244227072 diff --git a/fuzz/regression/clusterfuzz-testcase-minimized-template_fuzzer-6208074244227072 b/fuzz/regression/clusterfuzz-testcase-minimized-template_fuzzer-6208074244227072 new file mode 100644 index 000000000..1a8fa700a --- /dev/null +++ b/fuzz/regression/clusterfuzz-testcase-minimized-template_fuzzer-6208074244227072 @@ -0,0 +1 @@ +{{#readme \ No newline at end of file diff --git a/src/lib/lwan-template.h b/src/lib/lwan-template.h index 22664872e..21d0ab69a 100644 --- a/src/lib/lwan-template.h +++ b/src/lib/lwan-template.h @@ -79,6 +79,7 @@ struct lwan_var_descriptor { #define TPL_VAR_SENTINEL \ { \ + .name = NULL, .offset = 0, \ } /* From aa242b3b76629f9256cfd5d8f9d2445e56c69cf0 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 24 Mar 2024 14:05:40 -0700 Subject: [PATCH 2176/2505] Fix crash in oss-fuzz? oss-fuzz issue: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=67610 --- src/lib/lwan-template.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/lwan-template.h b/src/lib/lwan-template.h index 21d0ab69a..59fd96e99 100644 --- a/src/lib/lwan-template.h +++ b/src/lib/lwan-template.h @@ -27,8 +27,8 @@ enum lwan_tpl_flag { LWAN_TPL_FLAG_CONST_TEMPLATE = 1 << 0 }; struct lwan_var_descriptor { const char *name; - const off_t offset; + const struct lwan_var_descriptor *list_desc; union { struct { @@ -37,7 +37,6 @@ struct lwan_var_descriptor { }; struct { coro_function_t generator; - const struct lwan_var_descriptor *list_desc; }; struct { void (*lambda)(struct lwan_strbuf *buf, void *ptr); From 03b769f31a019e52dbcb72bddecd4e6bbb9c0abf Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 26 Mar 2024 20:35:53 -0700 Subject: [PATCH 2177/2505] Misc updates to README.md - Document Lua logging functions - Clarify that rewrite patterns can have multiple conditions as long as they're of different types - Add quotes --- README.md | 42 +++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f4d0ffa56..1d66e69b5 100644 --- a/README.md +++ b/README.md @@ -562,6 +562,15 @@ Scripts can be served from files or embedded in the configuration file, and the results of loading them, the standard Lua modules, and (optionally, if using LuaJIT) optimizing the code will be cached for a while. +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `default_type` | `str` | `text/plain` | Default MIME-Type for responses | +| `script_file` | `str` | `NULL` | Path to Lua script| +| `cache_period` | `time` | `15s` | Time to keep Lua state loaded in memory | +| `script` | `str` | `NULL` | Inline lua script | + +##### Writing request handlers + > [!NOTE] > > Lua scripts can't use global variables, as they may be not @@ -617,12 +626,22 @@ is generated), or a number matching an HTTP status code. Attempting to return an invalid HTTP status code or anything other than a number or `nil` will result in a `500 Internal Server Error` response being thrown. -| Option | Type | Default | Description | -|--------|------|---------|-------------| -| `default_type` | `str` | `text/plain` | Default MIME-Type for responses | -| `script_file` | `str` | `NULL` | Path to Lua script| -| `cache_period` | `time` | `15s` | Time to keep Lua state loaded in memory | -| `script` | `str` | `NULL` | Inline lua script | +##### Logging + +In addition to the metamethods in the `req` parameter, one can also log messages +with different logging levels by calling methods from `Lwan.log`: + + - `Lwan.log:warning(str)` + - `Lwan.log:info(str)` + - `Lwan.log:error(str)` + - `Lwan.log:critical(str)` (Will also abort Lwan! Use with caution) + - `Lwan.log:debug(str)` (Only available in debug builds) + +> [!NOTE] +> +> If Lwan is built with syslog support, these messages will also be sent to the +> system log, otherwise they'll be printed to the standard error. + #### Rewrite @@ -697,7 +716,8 @@ them must be specified at least. It's also possible to specify conditions to trigger a rewrite. To specify one, open a `condition` block, specify the condition type, and then the parameters -for that condition to be evaluated: +for that condition to be evaluated. Multiple conditions can be set per rewrite +rule as long as there's one condition per type: |Condition |Can use subst. syntax|Section required|Parameters|Description| |-------------------|---------------------|----------------|----------|-----------| @@ -1068,6 +1088,10 @@ Mentions in magazines: * [Raspberry Pi Geek (Germany) mentions Lwan in their October/November 2022 issue](https://www.discountmags.com/magazine/raspberry-pi-geek-october-6-2022-digital/in-this-issue/17) * [LinuxUser (Germany) mentions Lwan in their October 2022 issue](https://www.linux-community.de/ausgaben/linuxuser/2022/10/aktuelle-software-im-kurztest-30/) +Mentions in books: + +* [The Ascetic Programmer](https://asceticprogrammer.info/book) has a paragraph about Lwan. + Some talks mentioning Lwan: * [Talk about Lwan](https://www.youtube.com/watch?v=cttY9FdCzUE) at Polyconf16, given by [@lpereira](https://github.com/lpereira). @@ -1190,6 +1214,10 @@ Lwan quotes These are some of the quotes found in the wild about Lwan. They're presented in no particular order. Contributions are appreciated: +> "Lwan is like a classic, according to the definition given by Italian +> -- writer Italo Calvino: you can read it again and again" [Antonio +> Piccolboni](https://asceticprogrammer.info/book) + > "I read lwan's source code. Especially, the part of using coroutine was > very impressive and it was more interesting than a good novel. Thank you > for that." -- From a332884259126308fb5b0ccc502ae20b61ea15c2 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 26 Mar 2024 20:37:15 -0700 Subject: [PATCH 2178/2505] Implement debug logging in Lua Also change it to use X macros internally --- src/lib/lwan-lua.c | 24 +++++++++--------------- src/lib/lwan-status.h | 2 +- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index f6a95e5ba..59283d2ef 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -346,7 +346,9 @@ LWAN_LUA_METHOD(request_date) return 1; } -#define IMPLEMENT_LOG_FUNCTION(name) \ +#define FOR_EACH_LOG_FUNCTION(X) X(info) X(warning) X(error) X(critical) X(debug) + +#define IMPLEMENT_FUNCTION(name) \ static int lwan_lua_log_##name(lua_State *L) \ { \ size_t log_str_len = 0; \ @@ -355,26 +357,18 @@ LWAN_LUA_METHOD(request_date) lwan_status_##name("%.*s", (int)log_str_len, log_str); \ return 0; \ } - -IMPLEMENT_LOG_FUNCTION(info) -IMPLEMENT_LOG_FUNCTION(warning) -IMPLEMENT_LOG_FUNCTION(error) -IMPLEMENT_LOG_FUNCTION(critical) - -#undef IMPLEMENT_LOG_FUNCTION +FOR_EACH_LOG_FUNCTION(IMPLEMENT_FUNCTION) +#undef IMPLEMENT_FUNCTION static int luaopen_log(lua_State *L) { static const char *metatable_name = "Lwan.log"; +#define LOG_FUNCTION(name) {#name, lwan_lua_log_##name}, static const struct luaL_Reg functions[] = { -#define LOG_FUNCTION(name) {#name, lwan_lua_log_##name} - LOG_FUNCTION(info), - LOG_FUNCTION(warning), - LOG_FUNCTION(error), - LOG_FUNCTION(critical), - {}, -#undef LOG_FUNCTION + FOR_EACH_LOG_FUNCTION(LOG_FUNCTION) + {} }; +#undef LOG_FUNCTION luaL_newmetatable(L, metatable_name); luaL_register(L, metatable_name, functions); diff --git a/src/lib/lwan-status.h b/src/lib/lwan-status.h index 51b6eaa16..a3ca800e5 100644 --- a/src/lib/lwan-status.h +++ b/src/lib/lwan-status.h @@ -26,7 +26,7 @@ __attribute__((noinline,cold)) \ __VA_ARGS__; -#define lwan_status_debug(fmt, ...) +#define lwan_status_debug(fmt, ...) do {} while(0) #else #define DECLARE_STATUS_PROTO(type_, ...) \ void lwan_status_##type_##_debug(const char *file, const int line, \ From 10c0f9abad0a939e65ac8e65f6eeb18a4cf31fab Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 27 Mar 2024 22:45:12 -0700 Subject: [PATCH 2179/2505] Ensure that precompressed file data is initialized on failure --- src/lib/lwan-mod-serve-files.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index e0b476d0b..5fc7b959b 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -581,6 +581,9 @@ static bool sendfile_init(struct file_cache_entry *ce, sd->compressed.fd = fd; sd->compressed.size = compressed_sz; + } else { + sd->compressed.fd = -ENOENT; + sd->compressed.size = 0; } sd->uncompressed.size = (size_t)st->st_size; From 8685940b98f2a4516768abe7bb9dd679d598580b Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 27 Mar 2024 22:46:31 -0700 Subject: [PATCH 2180/2505] Print handler name and route when detecting URL map --- src/lib/lwan.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 56a9ce523..241b5055b 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -458,6 +458,9 @@ void lwan_detect_url_map(struct lwan *l) if (!iter->route) continue; + lwan_status_debug("Using handler `%s' for route `%s'", + iter->name, iter->route); + const struct lwan_url_map map = {.prefix = iter->route, .handler = iter->handler}; register_url_map(l, &map); From 284b34fbf51b40de1bf73771504d3791526b4957 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 29 Mar 2024 01:06:11 -0700 Subject: [PATCH 2181/2505] Use a homegrown strpbrk() alternative in url_decode() I haven't fully tested and/or benchmarked this. I'm just committing this before I end up losing this code; hopefully the fuzzer will find something. I need to write a proper benchmark soon, though. Maybe even make an AVX2 version, too. --- src/lib/lwan-request.c | 74 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index f9c6040ca..4c6cf9b7b 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -266,6 +266,78 @@ static ALWAYS_INLINE char *identify_http_method(struct lwan_request *request, return NULL; } +/* has_zero() routines stolen from the Bit Twiddling Hacks page */ +static ALWAYS_INLINE uint64_t has_zero64(uint64_t v) +{ + return (v - 0x0101010101010101ull) & ~v & 0x8080808080808080ull; +} + +static ALWAYS_INLINE uint32_t has_zero32(uint64_t v) +{ + return (v - 0x01010101u) & ~v & 0x80808080u; +} + +static char *find_pct_or_plus(const char *input) +{ + const uint64_t mask_plus64 = '+' * 0x0101010101010101ull; + const uint64_t mask_pct64 = '%' * 0x0101010101010101ull; + const uint32_t mask_plus32 = '+' * 0x01010101u; + const uint32_t mask_pct32 = '%' * 0x01010101u; + char *str = (char *)input; + + while (true) { + uint64_t v = string_as_uint64(str); + uint64_t has_plus = has_zero64(v ^ mask_plus64); + uint64_t has_pct = has_zero64(v ^ mask_pct64); + uint64_t has_zero = has_zero64(v); + uint64_t m = LWAN_MAX(has_plus, has_pct); + + if (has_zero && LWAN_MAX(has_zero, m) == has_zero) { + switch (__builtin_ctzll(has_zero) / 8) { + case 1 ... 3: + goto check_small; + case 4 ... 7: + goto check_at_least_four; + default: + return NULL; + } + } + + if (m) { + return str + __builtin_ctzll(m) / 8; + } + + str += 8; + } + +check_at_least_four: { + uint32_t v = string_as_uint32(str); + uint32_t has_plus = has_zero32(v ^ mask_plus32); + uint32_t has_pct = has_zero32(v ^ mask_pct32); + uint32_t has_zero = has_zero32(v); + uint32_t m = LWAN_MAX(has_plus, has_pct); + + if (has_zero && LWAN_MAX(has_zero, m) == has_zero) { + return NULL; + } + + if (m) { + return str + __builtin_ctz(m) / 8; + } + + str += 4; +} + +check_small: + while (*str) { + if (*str == '%' || *str == '+') + return str; + str++; + } + + return NULL; +} + __attribute__((nonnull(1))) static ssize_t url_decode(char *str) { static const unsigned char tbl1[256] = { @@ -286,7 +358,7 @@ __attribute__((nonnull(1))) static ssize_t url_decode(char *str) const char *inptr = str; char *outptr = str; - for (char *p = strpbrk(inptr, "+%"); p; p = strpbrk(inptr, "+%")) { + for (char *p = find_pct_or_plus(inptr); p; p = find_pct_or_plus(inptr)) { const ptrdiff_t diff = p - inptr; if (diff) outptr = mempmove(outptr, inptr, (size_t)diff); From c69a632032afca3263a2b0301e38817d644023a4 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 9 Apr 2024 08:43:02 -0700 Subject: [PATCH 2182/2505] Fix presentation of Lua logging function documentation --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1d66e69b5..95bff7967 100644 --- a/README.md +++ b/README.md @@ -631,11 +631,11 @@ in a `500 Internal Server Error` response being thrown. In addition to the metamethods in the `req` parameter, one can also log messages with different logging levels by calling methods from `Lwan.log`: - - `Lwan.log:warning(str)` - - `Lwan.log:info(str)` - - `Lwan.log:error(str)` - - `Lwan.log:critical(str)` (Will also abort Lwan! Use with caution) - - `Lwan.log:debug(str)` (Only available in debug builds) + - `Lwan.log:warning(str)` + - `Lwan.log:info(str)` + - `Lwan.log:error(str)` + - `Lwan.log:critical(str)` (Will also abort Lwan! Use with caution) + - `Lwan.log:debug(str)` (Only available in debug builds; no-op otherwise) > [!NOTE] > From b7c550ece21e1146a52fd26422f72f8d3dd498b9 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 13 Apr 2024 20:11:59 -0700 Subject: [PATCH 2183/2505] Remove `use_dynamic_buffer` option in lieu of `request_buffer_size` This allows better fine-tuning on a per-application basis. --- README.md | 2 +- src/bin/testrunner/testrunner.conf | 2 ++ src/lib/lwan-thread.c | 22 ++++++++------------ src/lib/lwan.c | 32 ++++++++++++++++++++++++++---- src/lib/lwan.h | 5 ++--- 5 files changed, 41 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 95bff7967..8391df6d0 100644 --- a/README.md +++ b/README.md @@ -321,9 +321,9 @@ can be decided automatically, so some configuration options are provided. | `proxy_protocol` | `bool` | `false` | Enables the [PROXY protocol](https://www.haproxy.com/blog/haproxy/proxy-protocol/). Versions 1 and 2 are supported. Only enable this setting if using Lwan behind a proxy, and the proxy supports this protocol; otherwise, this allows anybody to spoof origin IP addresses | | `max_post_data_size` | `int` | `40960` | Sets the maximum number of data size for POST requests, in bytes | | `max_put_data_size` | `int` | `40960` | Sets the maximum number of data size for PUT requests, in bytes | +| `request_buffer_size` | `int` | `4096` | Request buffer size length. If larger than the default of `4096`, it'll be dynamically allocated. | | `allow_temp_files` | `str` | `""` | Use temporary files; set to `post` for POST requests, `put` for PUT requests, or `all` (equivalent to setting to `post put`) for both.| | `error_template` | `str` | Default error template | Template for error codes. See variables below. | -| `use_dynamic_buffer` | `bool` | `false` | **Experimental:** use a dynamically-allocated buffer for requests. | #### Variables for `error_template` diff --git a/src/bin/testrunner/testrunner.conf b/src/bin/testrunner/testrunner.conf index 4a43fbac5..67737bcd6 100644 --- a/src/bin/testrunner/testrunner.conf +++ b/src/bin/testrunner/testrunner.conf @@ -20,6 +20,8 @@ proxy_protocol = true # small for testing purposes. max_post_data_size = 1000000 +request_buffer_size = 32768 + # Enable straitjacket by default. The `drop_capabilities` option is `true` # by default. Other options may require more privileges. straitjacket diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index d3db08556..25802d1ca 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -370,6 +370,7 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, struct lwan *lwan = conn->thread->lwan; int fd = lwan_connection_get_fd(lwan, conn); enum lwan_request_flags flags = lwan->config.request_flags; + const size_t request_buffer_size = lwan->config.request_buffer_size; struct lwan_strbuf strbuf = LWAN_STRBUF_STATIC_INIT; struct lwan_value buffer; char *next_request = NULL; @@ -391,17 +392,16 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, assert(!(conn->flags & CONN_TLS)); #endif - if (conn->flags & CONN_USE_DYNAMIC_BUFFER) { - const size_t dynamic_buffer_len = DEFAULT_BUFFER_SIZE * 16; + if (request_buffer_size > DEFAULT_BUFFER_SIZE) { buffer = (struct lwan_value){ - .value = coro_malloc(conn->coro, dynamic_buffer_len), - .len = dynamic_buffer_len, + .value = coro_malloc(conn->coro, request_buffer_size), + .len = request_buffer_size, }; init_gen = 2; } else { buffer = (struct lwan_value){ - .value = alloca(DEFAULT_BUFFER_SIZE), - .len = DEFAULT_BUFFER_SIZE, + .value = alloca(request_buffer_size), + .len = request_buffer_size, }; init_gen = 1; } @@ -723,9 +723,9 @@ static ALWAYS_INLINE bool spawn_coro(struct lwan_connection *conn, { struct lwan_thread *t = conn->thread; #if defined(LWAN_HAVE_MBEDTLS) - const enum lwan_connection_flags flags_to_keep = conn->flags & (CONN_TLS | CONN_USE_DYNAMIC_BUFFER); + const enum lwan_connection_flags flags_to_keep = conn->flags & CONN_TLS; #else - const enum lwan_connection_flags flags_to_keep = CONN_USE_DYNAMIC_BUFFER; + const enum lwan_connection_flags flags_to_keep = 0; #endif assert(!conn->coro); @@ -1436,12 +1436,6 @@ void lwan_thread_init(struct lwan *l) schedtbl = NULL; } - if (l->config.use_dynamic_buffer) { - lwan_status_debug("Using dynamically-allocated buffers"); - for (unsigned int i = 0; i < total_conns; i++) - l->conns[i].flags = CONN_USE_DYNAMIC_BUFFER; - } - for (unsigned int i = 0; i < l->thread.count; i++) { struct lwan_thread *thread; diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 241b5055b..d3cd7e7a3 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -60,6 +60,7 @@ static const struct lwan_config default_config = { .allow_cors = false, .expires = 1 * ONE_WEEK, .n_threads = 0, + .request_buffer_size = DEFAULT_BUFFER_SIZE, .max_post_data_size = 10 * DEFAULT_BUFFER_SIZE, .allow_post_temp_file = false, .max_put_data_size = 10 * DEFAULT_BUFFER_SIZE, @@ -648,23 +649,46 @@ static bool setup_from_config(struct lwan *lwan, const char *path) config_error(conf, "Invalid number of threads: %ld", n_threads); lwan->config.n_threads = (unsigned int)n_threads; + } else if (streq(line->key, "request_buffer_size")) { + long request_buffer_size = parse_long( + line->value, (long)default_config.request_buffer_size); + + if (request_buffer_size < 0) { + config_error(conf, "Negative request buffer size requested"); + } else if (request_buffer_size > 16 * (1 << 20)) { + config_error(conf, + "Request buffer can't be over 16MiB"); + } else if (request_buffer_size < DEFAULT_BUFFER_SIZE) { + lwan_status_warning("Request buffer size of %ld is smaller than the " + "recommended minimum of %d bytes. This might not " + "be sufficient!", + request_buffer_size, + DEFAULT_BUFFER_SIZE); + } + + lwan->config.request_buffer_size = (size_t)request_buffer_size; } else if (streq(line->key, "max_post_data_size")) { long max_post_data_size = parse_long( line->value, (long)default_config.max_post_data_size); - if (max_post_data_size < 0) + + if (max_post_data_size < 0) { config_error(conf, "Negative maximum post data size"); - else if (max_post_data_size > 128 * (1 << 20)) + } else if (max_post_data_size > 128 * (1 << 20)) { config_error(conf, "Maximum post data can't be over 128MiB"); + } + lwan->config.max_post_data_size = (size_t)max_post_data_size; } else if (streq(line->key, "max_put_data_size")) { long max_put_data_size = parse_long( line->value, (long)default_config.max_put_data_size); - if (max_put_data_size < 0) + + if (max_put_data_size < 0) { config_error(conf, "Negative maximum put data size"); - else if (max_put_data_size > 128 * (1 << 20)) + } else if (max_put_data_size > 128 * (1 << 20)) { config_error(conf, "Maximum put data can't be over 128MiB"); + } lwan->config.max_put_data_size = (size_t)max_put_data_size; } else if (streq(line->key, "allow_temp_files")) { bool has_post, has_put; diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 342cc9ad5..084ee83ce 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -295,12 +295,10 @@ enum lwan_connection_flags { * than a client. */ CONN_LISTENER = 1 << 11, - CONN_USE_DYNAMIC_BUFFER = 1 << 12, - /* Only valid when CONN_ASYNC_AWAIT is set. Set on file descriptors that * got (EPOLLHUP|EPOLLRDHUP) events from epoll so that request handlers * can deal with this fact. */ - CONN_HUNG_UP = 1 << 13, + CONN_HUNG_UP = 1 << 12, CONN_FLAG_LAST = CONN_HUNG_UP, }; @@ -526,6 +524,7 @@ struct lwan_config { size_t max_post_data_size; size_t max_put_data_size; + size_t request_buffer_size; unsigned int keep_alive_timeout; unsigned int expires; From 581528162c14763c3e58aed4f3037dc66f3aaaae Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 13 Apr 2024 20:13:11 -0700 Subject: [PATCH 2184/2505] Better error recovery when initializing a strbuf from a file If the strbuf hasn't been initialized yet, calling lwan_strbuf_free() on it will lead to issues. --- src/lib/lwan-strbuf.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-strbuf.c b/src/lib/lwan-strbuf.c index 9c1114c4f..29772969a 100644 --- a/src/lib/lwan-strbuf.c +++ b/src/lib/lwan-strbuf.c @@ -377,13 +377,13 @@ bool lwan_strbuf_init_from_file(struct lwan_strbuf *s, const char *path) return false; if (UNLIKELY(fstat(fd, &st) < 0)) - goto error; + goto error_close; size_t min_buf_size; if (UNLIKELY(__builtin_add_overflow(st.st_size, 1, &min_buf_size))) - goto error; + goto error_close; if (UNLIKELY(!lwan_strbuf_init_with_size(s, min_buf_size))) - goto error; + goto error_close; s->used = (size_t)st.st_size; @@ -406,6 +406,7 @@ bool lwan_strbuf_init_from_file(struct lwan_strbuf *s, const char *path) error: lwan_strbuf_free(s); +error_close: close(fd); return false; } From 7985267be6a2f78482b65e573157afd29ca1c9a8 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 13 Apr 2024 20:13:59 -0700 Subject: [PATCH 2185/2505] Use lwan_request_get_host() instead of the generic get_header() The Host header is ready to be fetched from the request helper struct, so fetch it rather than looping through all the headers to find the one we're looking for! --- src/samples/pastebin/main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/samples/pastebin/main.c b/src/samples/pastebin/main.c index 7ea736b80..60911015f 100644 --- a/src/samples/pastebin/main.c +++ b/src/samples/pastebin/main.c @@ -121,7 +121,7 @@ static enum lwan_http_status post_paste(struct lwan_request *request, cache_coro_get_and_ref_entry(pastes, request->conn->coro, key); if (paste) { - const char *host_hdr = lwan_request_get_header(request, "Host"); + const char *host_hdr = lwan_request_get_host(request); if (!host_hdr) return HTTP_BAD_REQUEST; @@ -140,7 +140,7 @@ static enum lwan_http_status post_paste(struct lwan_request *request, static enum lwan_http_status doc(struct lwan_request *request, struct lwan_response *response) { - const char *host_hdr = lwan_request_get_header(request, "Host"); + const char *host_hdr = lwan_request_get_host(request); if (!host_hdr) return HTTP_BAD_REQUEST; From b9bd2b1eb2c2ef87314d0371d035f7947e6bf410 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 13 Apr 2024 20:32:44 -0700 Subject: [PATCH 2186/2505] Cleanup how listening sockets are created/passed down from systemd --- src/lib/lwan-socket.c | 88 +++++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 46 deletions(-) diff --git a/src/lib/lwan-socket.c b/src/lib/lwan-socket.c index 996ffd493..700254f5c 100644 --- a/src/lib/lwan-socket.c +++ b/src/lib/lwan-socket.c @@ -170,18 +170,6 @@ sa_family_t lwan_socket_parse_address(char *listener, char **node, char **port) return parse_listener_ipv4(listener, node, port); } -static sa_family_t parse_listener(char *listener, char **node, char **port) -{ - if (streq(listener, "systemd")) { - lwan_status_critical( - "Listener configured to use systemd socket activation, " - "but started outside systemd."); - return AF_MAX; - } - - return lwan_socket_parse_address(listener, node, port); -} - static int listen_addrinfo(int fd, const struct addrinfo *addr, bool print_listening_msg, @@ -232,9 +220,7 @@ static int bind_and_listen_addrinfos(const struct addrinfo *addrs, /* Try each address until we bind one successfully. */ for (addr = addrs; addr; addr = addr->ai_next) { - int fd = socket(addr->ai_family, - addr->ai_socktype, - addr->ai_protocol); + int fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); if (fd < 0) continue; @@ -254,6 +240,30 @@ static int bind_and_listen_addrinfos(const struct addrinfo *addrs, lwan_status_critical("Could not bind socket"); } +static int set_socket_options(const struct lwan *l, int fd) +{ + SET_SOCKET_OPTION(SOL_SOCKET, SO_LINGER, + (&(struct linger){.l_onoff = 1, .l_linger = 1})); + +#ifdef __linux__ + +#ifndef TCP_FASTOPEN +#define TCP_FASTOPEN 23 +#endif + + SET_SOCKET_OPTION_MAY_FAIL(SOL_SOCKET, SO_REUSEADDR, (int[]){1}); + SET_SOCKET_OPTION_MAY_FAIL(SOL_TCP, TCP_FASTOPEN, (int[]){5}); + SET_SOCKET_OPTION_MAY_FAIL(SOL_TCP, TCP_QUICKACK, (int[]){0}); + SET_SOCKET_OPTION_MAY_FAIL(SOL_TCP, TCP_DEFER_ACCEPT, + (int[]){(int)l->config.keep_alive_timeout}); + + if (is_reno_supported()) + setsockopt(fd, IPPROTO_TCP, TCP_CONGESTION, "reno", 4); +#endif + + return fd; +} + static int setup_socket_normally(const struct lwan *l, bool print_listening_msg, bool is_https, @@ -261,7 +271,7 @@ static int setup_socket_normally(const struct lwan *l, { char *node, *port; char *listener = strdupa(listener_from_config); - sa_family_t family = parse_listener(listener, &node, &port); + sa_family_t family = lwan_socket_parse_address(listener, &node, &port); if (family == AF_MAX) { lwan_status_critical("Could not parse listener: %s", @@ -279,31 +289,7 @@ static int setup_socket_normally(const struct lwan *l, int fd = bind_and_listen_addrinfos(addrs, print_listening_msg, is_https); freeaddrinfo(addrs); - return fd; -} - -static int set_socket_options(const struct lwan *l, int fd) -{ - SET_SOCKET_OPTION(SOL_SOCKET, SO_LINGER, - (&(struct linger){.l_onoff = 1, .l_linger = 1})); - -#ifdef __linux__ - -#ifndef TCP_FASTOPEN -#define TCP_FASTOPEN 23 -#endif - - SET_SOCKET_OPTION_MAY_FAIL(SOL_SOCKET, SO_REUSEADDR, (int[]){1}); - SET_SOCKET_OPTION_MAY_FAIL(SOL_TCP, TCP_FASTOPEN, (int[]){5}); - SET_SOCKET_OPTION_MAY_FAIL(SOL_TCP, TCP_QUICKACK, (int[]){0}); - SET_SOCKET_OPTION_MAY_FAIL(SOL_TCP, TCP_DEFER_ACCEPT, - (int[]){(int)l->config.keep_alive_timeout}); - - if (is_reno_supported()) - setsockopt(fd, IPPROTO_TCP, TCP_CONGESTION, "reno", 4); -#endif - - return fd; + return set_socket_options(l, fd); } static int from_systemd_socket(const struct lwan *l, int fd) @@ -320,14 +306,16 @@ int lwan_create_listen_socket(const struct lwan *l, bool print_listening_msg, bool is_https) { - const char *listener = is_https ? l->config.tls_listener - : l->config.listener; + const char *listener = + is_https ? l->config.tls_listener : l->config.listener; if (!strncmp(listener, "systemd:", sizeof("systemd:") - 1)) { char **names = NULL; int n = sd_listen_fds_with_names(false, &names); int fd = -1; + listener += sizeof("systemd:") - 1; + if (n < 0) { errno = -n; lwan_status_perror( @@ -335,7 +323,11 @@ int lwan_create_listen_socket(const struct lwan *l, return n; } - listener += sizeof("systemd:") - 1; + if (n == 0) { + lwan_status_critical("Not invoked from systemd " + "(expecting socket name %s)", + listener); + } for (int i = 0; i < n; i++) { if (!strcmp(names[i], listener)) { @@ -363,6 +355,11 @@ int lwan_create_listen_socket(const struct lwan *l, return n; } + if (n == 0) { + lwan_status_critical( + "Not invoked from systemd (expecting 1 socket)"); + } + if (n != 1) { lwan_status_critical( "%d listeners passed from systemd. Must specify listeners with " @@ -373,8 +370,7 @@ int lwan_create_listen_socket(const struct lwan *l, return from_systemd_socket(l, SD_LISTEN_FDS_START); } - int fd = setup_socket_normally(l, print_listening_msg, is_https, listener); - return set_socket_options(l, fd); + return setup_socket_normally(l, print_listening_msg, is_https, listener); } #undef SET_SOCKET_OPTION From 3ec5f7e3ee6f7d559da7d888d0023e095bf5ef16 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 13 Apr 2024 21:22:16 -0700 Subject: [PATCH 2187/2505] Generate assertions in statuslookupgen to validate input Although inputs to this function have to come from an enum that is generated by the same X-macro that is used to build the perfect hash table, anything could theoretically be passed to it if casted from an integer. So assert that the returned string actually matches the requested HTTP status code. (The lookup function wouldn't crash otherwise, but could return either a 999 Invalid code, or some code that is valid but has nothing to do with the requested code. Since this is just an assertion, this is mostly here to ensure that issues found during debugging are actually caught.) --- src/bin/tools/statuslookupgen.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/bin/tools/statuslookupgen.c b/src/bin/tools/statuslookupgen.c index b13363420..5a73892a0 100644 --- a/src/bin/tools/statuslookupgen.c +++ b/src/bin/tools/statuslookupgen.c @@ -92,7 +92,11 @@ int main(void) printf("\n"); printf(" const uint32_t k = (uint32_t)status - %d;\n", best_subtract); - printf(" return table[((k << %d) | (k >> %d)) %% %d];\n", 32 - best_rot, best_rot, best_mod); + printf(" const char *ret = table[((k << %d) | (k >> %d)) %% %d];\n", 32 - best_rot, best_rot, best_mod); + printf(" assert((uint32_t)(ret[2] - '0') == ((uint32_t)status %% 10));\n"); + printf(" assert((uint32_t)(ret[1] - '0') == ((uint32_t)(status / 10) %% 10));\n"); + printf(" assert((uint32_t)(ret[0] - '0') == ((uint32_t)(status / 100) %% 10));\n"); + printf(" return ret;\n"); printf("}\n"); From b40d91aa0fcc0770dae40cd39c3ffdd8cf7fba3c Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 14 Apr 2024 00:29:50 -0700 Subject: [PATCH 2188/2505] lwan_request_get_header_from_helper() doesn't need to exist anymore This was probably a remnant of some refactoring job. --- src/lib/lwan-private.h | 2 -- src/lib/lwan-request.c | 10 ++-------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index e73b23792..cb9401ecc 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -272,8 +272,6 @@ uint64_t lwan_request_get_id(struct lwan_request *request); ssize_t lwan_find_headers(char **header_start, struct lwan_value *buffer, char **next_request); -const char *lwan_request_get_header_from_helper(struct lwan_request_parser_helper *helper, - const char *header); sa_family_t lwan_socket_parse_address(char *listener, char **node, char **port); diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 4c6cf9b7b..482fd9f56 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1753,10 +1753,10 @@ const char *lwan_request_get_cookie(struct lwan_request *request, return value_lookup(lwan_request_get_cookies(request), key); } -const char * -lwan_request_get_header_from_helper(struct lwan_request_parser_helper *helper, +const char *lwan_request_get_header(struct lwan_request *request, const char *header) { + const struct lwan_request_parser_helper *helper = request->helper; const size_t header_len = strlen(header); const size_t header_len_with_separator = header_len + HEADER_VALUE_SEPARATOR_LEN; @@ -1781,12 +1781,6 @@ lwan_request_get_header_from_helper(struct lwan_request_parser_helper *helper, return NULL; } -inline const char *lwan_request_get_header(struct lwan_request *request, - const char *header) -{ - return lwan_request_get_header_from_helper(request->helper, header); -} - const char *lwan_request_get_host(struct lwan_request *request) { const struct lwan_request_parser_helper *helper = request->helper; From 78f36b3f07d220fe08e898d9a56dfbb9007158f6 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 14 Apr 2024 07:59:57 -0700 Subject: [PATCH 2189/2505] Fix compilation warning in FastCGI module --- src/lib/lwan-mod-fastcgi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index 0d9a4418d..13339f284 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -385,7 +385,7 @@ static bool discard_unknown_record(struct lwan_request *request, int fd) { char buffer[256]; - size_t to_read = record->len_content + record->len_padding; + size_t to_read = (size_t)record->len_content + (size_t)record->len_padding; if (record->type > 11) { /* Per the spec, 11 is the maximum (unknown type), so anything From 67f5b0c8dc4cb75eec9561337b37f902666c8815 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 14 Apr 2024 08:03:23 -0700 Subject: [PATCH 2190/2505] Fix warnings in weighttp --- src/bin/tools/weighttp.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/bin/tools/weighttp.c b/src/bin/tools/weighttp.c index 4febdfc98..374171566 100644 --- a/src/bin/tools/weighttp.c +++ b/src/bin/tools/weighttp.c @@ -1373,10 +1373,14 @@ config_base64_encode_pad (char * const restrict dst, const size_t dstsz, return -1; int s = 0, d = 0; - unsigned int v; const unsigned char * const src = (const unsigned char *)ssrc; for (; s < tuplen; s += 3, d += 4) { - v = (src[s+0] << 16) | (src[s+1] << 8) | src[s+2]; + unsigned int v; + + v = (unsigned int)(src[s+0] << 16); + v |= (unsigned int)(src[s+1] << 8); + v |= (unsigned int)src[s+2]; + dst[d+0] = base64_table[(v >> 18) & 0x3f]; dst[d+1] = base64_table[(v >> 12) & 0x3f]; dst[d+2] = base64_table[(v >> 6) & 0x3f]; @@ -1384,12 +1388,15 @@ config_base64_encode_pad (char * const restrict dst, const size_t dstsz, } if (rem) { + unsigned int v; + if (1 == rem) { - v = (src[s+0] << 4); + v = (unsigned int)(src[s+0] << 4); dst[d+2] = base64_table[64]; /* pad */ } else { /*(2 == rem)*/ - v = (src[s+0] << 10) | (src[s+1] << 2); + v = (unsigned int)(src[s+0] << 10); + v |= (unsigned int)(src[s+1] << 2); dst[d+2] = base64_table[v & 0x3f]; v >>= 6; } dst[d+0] = base64_table[(v >> 6) & 0x3f]; From 237ba58ea6ac3fee0619bf5431176d2d1e286de5 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 14 Apr 2024 08:04:09 -0700 Subject: [PATCH 2191/2505] Fix warnings in gifenc --- src/samples/clock/gifenc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/samples/clock/gifenc.c b/src/samples/clock/gifenc.c index cca1b5efc..efd2d9c03 100644 --- a/src/samples/clock/gifenc.c +++ b/src/samples/clock/gifenc.c @@ -93,9 +93,9 @@ ge_GIF *ge_new_gif(struct lwan_strbuf *buf, 3); if (palette) { - lwan_strbuf_append_str(buf, palette, 3 << depth); + lwan_strbuf_append_str(buf, palette, 3ull << depth); } else if (depth <= 4) { - lwan_strbuf_append_str(buf, vga, 3 << depth); + lwan_strbuf_append_str(buf, vga, 3ull << depth); } else { lwan_strbuf_append_str(buf, vga, sizeof(vga)); From 7ce06aa4f1438ba7ac9dfd0f63310528c8223530 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 14 Apr 2024 08:07:02 -0700 Subject: [PATCH 2192/2505] Fix warnings in qrcodegen --- src/samples/smolsite/qrcodegen.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/samples/smolsite/qrcodegen.c b/src/samples/smolsite/qrcodegen.c index 871bc9243..e28d6b06a 100644 --- a/src/samples/smolsite/qrcodegen.c +++ b/src/samples/smolsite/qrcodegen.c @@ -188,7 +188,7 @@ bool qrcodegen_encodeBinary(uint8_t dataAndTemp[], size_t dataLen, uint8_t qrcod testable void appendBitsToBuffer(unsigned int val, int numBits, uint8_t buffer[], int *bitLen) { assert(0 <= numBits && numBits <= 16 && (unsigned long)val >> numBits == 0); for (int i = numBits - 1; i >= 0; i--, (*bitLen)++) - buffer[*bitLen >> 3] |= ((val >> i) & 1) << (7 - (*bitLen & 7)); + buffer[*bitLen >> 3] |= (uint8_t)(((val >> i) & 1) << (7 - (*bitLen & 7))); } @@ -407,7 +407,7 @@ testable uint8_t reedSolomonMultiply(uint8_t x, uint8_t y) { uint8_t z = 0; for (int i = 7; i >= 0; i--) { z = (uint8_t)((z << 1) ^ ((z >> 7) * 0x11D)); - z ^= ((y >> i) & 1) * x; + z ^= (uint8_t)(((y >> i) & 1) * x); } return z; } @@ -786,9 +786,9 @@ testable void setModuleBounded(uint8_t qrcode[], int x, int y, bool isDark) { int bitIndex = index & 7; int byteIndex = (index >> 3) + 1; if (isDark) - qrcode[byteIndex] |= 1 << bitIndex; + qrcode[byteIndex] |= (uint8_t)(1 << bitIndex); else - qrcode[byteIndex] &= (1 << bitIndex) ^ 0xFF; + qrcode[byteIndex] &= (uint8_t)((1 << bitIndex) ^ 0xFF); } From e68709fbed8a179b4c7221319b55ea86bc36de31 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 14 Apr 2024 08:08:27 -0700 Subject: [PATCH 2193/2505] Fix warnings in lwan-tables when building in release mode --- src/lib/lwan-tables.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index bdf7b02f6..354dda431 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -110,12 +110,11 @@ LWAN_SELF_TEST(status_codes) { #define ASSERT_STATUS(id, code, short, long) \ do { \ - const char *status = lwan_http_status_as_string_with_code(HTTP_##id); \ - assert(!strncmp(status, #code, 3)); \ - const char *status_as_str = lwan_http_status_as_string(HTTP_##id); \ - assert(!strcmp(status_as_str, short)); \ - const char *descr = lwan_http_status_as_descriptive_string(HTTP_##id); \ - assert(!strcmp(descr, long)); \ + assert(!strncmp(lwan_http_status_as_string_with_code(HTTP_##id), \ + #code, 3)); \ + assert(!strcmp(lwan_http_status_as_string(HTTP_##id), short)); \ + assert( \ + !strcmp(lwan_http_status_as_descriptive_string(HTTP_##id), long)); \ } while (0); FOR_EACH_HTTP_STATUS(ASSERT_STATUS) #undef ASSERT_STATUS From a842cb2275c3b51c06a683d304f890890077402a Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 14 Apr 2024 09:33:40 -0700 Subject: [PATCH 2194/2505] Use AVX2 when unmasking websockets frames This already used SSE2, but let's use a wider vector if available to get this over more quickly. --- src/lib/lwan-websocket.c | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index b9ca7bc4c..ab4f4253c 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -26,7 +26,7 @@ #include #if defined(__x86_64__) -#include +#include #endif #include "lwan-io-wrappers.h" @@ -156,19 +156,39 @@ static void unmask(char *msg, size_t msg_len, char mask[static 4]) if (sizeof(void *) == 8) { const uint64_t mask64 = (uint64_t)mask32 << 32 | mask32; -#if defined(__x86_64__) +#if defined(__AVX2__) + const size_t len256 = msg_len / 32; + if (len256) { + const __m256i mask256 = + _mm256_setr_epi64x((int64_t)mask64, (int64_t)mask64, + (int64_t)mask64, (int64_t)mask64); + for (size_t i = 0; i < len256; i++) { + __m256i v = _mm256_loadu_si256((__m256i *)msg); + _mm256_storeu_si256((__m256i *)msg, + _mm256_xor_si256(v, mask256)); + msg += 32; + } + + msg_len = (size_t)(msg_end - msg); + } +#endif + +#if defined(__SSE2__) const size_t len128 = msg_len / 16; if (len128) { - const __m128i mask128 = _mm_setr_epi64((__m64)mask64, (__m64)mask64); + const __m128i mask128 = + _mm_setr_epi64((__m64)mask64, (__m64)mask64); for (size_t i = 0; i < len128; i++) { __m128i v = _mm_loadu_si128((__m128i *)msg); _mm_storeu_si128((__m128i *)msg, _mm_xor_si128(v, mask128)); msg += 16; } + + msg_len = (size_t)(msg_end - msg); } #endif - const size_t len64 = (size_t)((msg_end - msg) / 8); + const size_t len64 = msg_len / 8; for (size_t i = 0; i < len64; i++) { uint64_t v = string_as_uint64(msg); v ^= mask64; From 0533382633a9e818f2dd3d7540040eefc86019dc Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 14 Apr 2024 14:26:04 -0700 Subject: [PATCH 2195/2505] Respond with HTTP_UNAVAILABLE if coro_malloc() fails --- src/lib/lwan-thread.c | 174 +++++++++++++++++++++++------------------- 1 file changed, 95 insertions(+), 79 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 25802d1ca..bf76346a8 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -360,6 +360,85 @@ static bool lwan_setup_tls(const struct lwan *l, struct lwan_connection *conn) } #endif +__attribute__((cold)) +static bool send_buffer_without_coro(int fd, const char *buf, size_t buf_len, int flags) +{ + size_t total_sent = 0; + + for (int try = 0; try < 10; try++) { + size_t to_send = buf_len - total_sent; + if (!to_send) + return true; + + ssize_t sent = send(fd, buf + total_sent, to_send, flags); + if (sent <= 0) { + if (errno == EINTR) + continue; + if (errno == EAGAIN) + continue; + break; + } + + total_sent += (size_t)sent; + } + + return false; +} + +__attribute__((cold)) +static bool send_string_without_coro(int fd, const char *str, int flags) +{ + return send_buffer_without_coro(fd, str, strlen(str), flags); +} + +__attribute__((cold)) static void +send_last_response_without_coro(const struct lwan *l, + const struct lwan_connection *conn, + enum lwan_http_status status) +{ + int fd = lwan_connection_get_fd(l, conn); + + if (conn->flags & CONN_TLS) { + /* There's nothing that can be done here if a client is expecting a + * TLS connection: the TLS handshake requires a coroutine as it + * might yield. (In addition, the TLS handshake might allocate + * memory, and if you couldn't create a coroutine at this point, + * it's unlikely you'd be able to allocate memory for the TLS + * context anyway.) */ + goto shutdown_and_close; + } + + if (!send_string_without_coro(fd, "HTTP/1.0 ", MSG_MORE)) + goto shutdown_and_close; + + if (!send_string_without_coro( + fd, lwan_http_status_as_string_with_code(status), MSG_MORE)) + goto shutdown_and_close; + + if (!send_string_without_coro(fd, "\r\nConnection: close", MSG_MORE)) + goto shutdown_and_close; + + if (!send_string_without_coro(fd, "\r\nContent-Type: text/html", MSG_MORE)) + goto shutdown_and_close; + + if (send_buffer_without_coro(fd, l->headers.value, l->headers.len, + MSG_MORE)) { + struct lwan_strbuf buffer; + + lwan_strbuf_init(&buffer); + lwan_fill_default_response(&buffer, status); + + send_buffer_without_coro(fd, lwan_strbuf_get_buffer(&buffer), + lwan_strbuf_get_length(&buffer), 0); + + lwan_strbuf_free(&buffer); + } + +shutdown_and_close: + shutdown(fd, SHUT_RDWR); + close(fd); +} + __attribute__((noreturn)) static int process_request_coro(struct coro *coro, void *data) { @@ -397,12 +476,28 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, .value = coro_malloc(conn->coro, request_buffer_size), .len = request_buffer_size, }; + + if (UNLIKELY(!buffer.value)) { + /* If CONN_TLS is set at this point, we can send responses just + * fine and they'll be encrypted by the kernel. However, + * send_last_response_without_coro() can't send the response if + * this bit is set as it has been designed to be used in cases + * where coroutines were not created yet. */ + conn->flags &= ~CONN_TLS; + + send_last_response_without_coro(lwan, conn, HTTP_UNAVAILABLE); + + coro_yield(conn->coro, CONN_CORO_ABORT); + __builtin_unreachable(); + } + init_gen = 2; } else { buffer = (struct lwan_value){ .value = alloca(request_buffer_size), .len = request_buffer_size, }; + init_gen = 1; } @@ -638,85 +733,6 @@ static void update_date_cache(struct lwan_thread *thread) thread->date.expires); } -__attribute__((cold)) -static bool send_buffer_without_coro(int fd, const char *buf, size_t buf_len, int flags) -{ - size_t total_sent = 0; - - for (int try = 0; try < 10; try++) { - size_t to_send = buf_len - total_sent; - if (!to_send) - return true; - - ssize_t sent = send(fd, buf + total_sent, to_send, flags); - if (sent <= 0) { - if (errno == EINTR) - continue; - if (errno == EAGAIN) - continue; - break; - } - - total_sent += (size_t)sent; - } - - return false; -} - -__attribute__((cold)) -static bool send_string_without_coro(int fd, const char *str, int flags) -{ - return send_buffer_without_coro(fd, str, strlen(str), flags); -} - -__attribute__((cold)) static void -send_last_response_without_coro(const struct lwan *l, - const struct lwan_connection *conn, - enum lwan_http_status status) -{ - int fd = lwan_connection_get_fd(l, conn); - - if (conn->flags & CONN_TLS) { - /* There's nothing that can be done here if a client is expecting a - * TLS connection: the TLS handshake requires a coroutine as it - * might yield. (In addition, the TLS handshake might allocate - * memory, and if you couldn't create a coroutine at this point, - * it's unlikely you'd be able to allocate memory for the TLS - * context anyway.) */ - goto shutdown_and_close; - } - - if (!send_string_without_coro(fd, "HTTP/1.0 ", MSG_MORE)) - goto shutdown_and_close; - - if (!send_string_without_coro( - fd, lwan_http_status_as_string_with_code(status), MSG_MORE)) - goto shutdown_and_close; - - if (!send_string_without_coro(fd, "\r\nConnection: close", MSG_MORE)) - goto shutdown_and_close; - - if (!send_string_without_coro(fd, "\r\nContent-Type: text/html", MSG_MORE)) - goto shutdown_and_close; - - if (send_buffer_without_coro(fd, l->headers.value, l->headers.len, - MSG_MORE)) { - struct lwan_strbuf buffer; - - lwan_strbuf_init(&buffer); - lwan_fill_default_response(&buffer, status); - - send_buffer_without_coro(fd, lwan_strbuf_get_buffer(&buffer), - lwan_strbuf_get_length(&buffer), 0); - - lwan_strbuf_free(&buffer); - } - -shutdown_and_close: - shutdown(fd, SHUT_RDWR); - close(fd); -} - static ALWAYS_INLINE bool spawn_coro(struct lwan_connection *conn, struct coro_switcher *switcher, struct timeout_queue *tq) From 8cd57c3b3c8ece2c0b889f18e20fa454da6d5777 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 14 Apr 2024 16:15:31 -0700 Subject: [PATCH 2196/2505] Properly calculate error_when_n_packets for any request buffer size --- src/lib/lwan-thread.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index bf76346a8..f48d813e1 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -450,12 +450,12 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, int fd = lwan_connection_get_fd(lwan, conn); enum lwan_request_flags flags = lwan->config.request_flags; const size_t request_buffer_size = lwan->config.request_buffer_size; + const int error_when_n_packets = lwan_calculate_n_packets(request_buffer_size); struct lwan_strbuf strbuf = LWAN_STRBUF_STATIC_INIT; struct lwan_value buffer; char *next_request = NULL; char *header_start[N_HEADER_START]; struct lwan_proxy proxy; - const int error_when_n_packets = lwan_calculate_n_packets(DEFAULT_BUFFER_SIZE); size_t init_gen; coro_defer(coro, lwan_strbuf_free_defer, &strbuf); From 0768148e25804f226910db7dfe45f4a5d109005e Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 14 Apr 2024 16:15:56 -0700 Subject: [PATCH 2197/2505] Inline header_start in request helper struct --- src/lib/lwan-private.h | 40 ++++++++++++++++++++-------------------- src/lib/lwan-thread.c | 2 -- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index cb9401ecc..c27cf3001 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -24,27 +24,31 @@ #include "lwan.h" -struct lwan_request_parser_helper { - struct lwan_value *buffer; /* The whole request buffer */ - char *next_request; /* For pipelined requests */ +#define N_HEADER_START 64 +#define DEFAULT_BUFFER_SIZE 4096 +#define DEFAULT_HEADERS_SIZE 2048 - char **header_start; /* Headers: n: start, n+1: end */ - size_t n_header_start; /* len(header_start) */ +struct lwan_request_parser_helper { + struct lwan_value *buffer; /* The whole request buffer */ + char *next_request; /* For pipelined requests */ - struct lwan_value accept_encoding; /* Accept-Encoding: */ + struct lwan_value accept_encoding; /* Accept-Encoding: */ - struct lwan_value query_string; /* Stuff after ? and before # */ + struct lwan_value query_string; /* Stuff after ? and before # */ - struct lwan_value body_data; /* Request body for POST and PUT */ - struct lwan_value content_type; /* Content-Type: for POST and PUT */ - struct lwan_value content_length; /* Content-Length: */ + struct lwan_value body_data; /* Request body for POST and PUT */ + struct lwan_value content_type; /* Content-Type: for POST and PUT */ + struct lwan_value content_length; /* Content-Length: */ - struct lwan_value connection; /* Connection: */ + struct lwan_value connection; /* Connection: */ - struct lwan_value host; /* Host: */ + struct lwan_value host; /* Host: */ struct lwan_key_value_array cookies, query_params, post_params; + char *header_start[N_HEADER_START]; /* Headers: n: start, n+1: end */ + size_t n_header_start; /* len(header_start) */ + struct { /* If-Modified-Since: */ struct lwan_value raw; time_t parsed; @@ -55,17 +59,13 @@ struct lwan_request_parser_helper { off_t from, to; } range; - uint64_t request_id; /* Request ID for debugging purposes */ + uint64_t request_id; /* Request ID for debugging purposes */ - time_t error_when_time; /* Time to abort request read */ - int error_when_n_packets; /* Max. number of packets */ - int urls_rewritten; /* Times URLs have been rewritten */ + time_t error_when_time; /* Time to abort request read */ + int error_when_n_packets; /* Max. number of packets */ + int urls_rewritten; /* Times URLs have been rewritten */ }; -#define DEFAULT_BUFFER_SIZE 4096 -#define DEFAULT_HEADERS_SIZE 2048 - -#define N_HEADER_START 64 #define LWAN_CONCAT(a_, b_) a_ ## b_ #define LWAN_TMP_ID_DETAIL(n_) LWAN_CONCAT(lwan_tmp_id, n_) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index f48d813e1..f231caf62 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -454,7 +454,6 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, struct lwan_strbuf strbuf = LWAN_STRBUF_STATIC_INIT; struct lwan_value buffer; char *next_request = NULL; - char *header_start[N_HEADER_START]; struct lwan_proxy proxy; size_t init_gen; @@ -506,7 +505,6 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, .buffer = &buffer, .next_request = next_request, .error_when_n_packets = error_when_n_packets, - .header_start = header_start, }; struct lwan_request request = {.conn = conn, .global_response_headers = &lwan->headers, From 313f2e5de01d3b2af5838842f44f2234f2180826 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 14 Apr 2024 16:59:17 -0700 Subject: [PATCH 2198/2505] Fix compile warnings in lwan-lua when building in release mode --- src/lib/lwan-lua.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index 59283d2ef..fe7bf787a 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -353,8 +353,11 @@ LWAN_LUA_METHOD(request_date) { \ size_t log_str_len = 0; \ const char *log_str = lua_tolstring(L, -1, &log_str_len); \ - if (log_str_len) \ + if (log_str_len) { \ lwan_status_##name("%.*s", (int)log_str_len, log_str); \ + (void)log_str_len; \ + (void)log_str; \ + } \ return 0; \ } FOR_EACH_LOG_FUNCTION(IMPLEMENT_FUNCTION) From 7ea760eb9b6a9b1d86d30cc3cd10c5ace8837a5a Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 14 Apr 2024 17:06:00 -0700 Subject: [PATCH 2199/2505] Make `request_buffer_size` be at least DEFAULT_BUFFER_SIZE --- src/lib/lwan.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index d3cd7e7a3..5e330e673 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -653,17 +653,16 @@ static bool setup_from_config(struct lwan *lwan, const char *path) long request_buffer_size = parse_long( line->value, (long)default_config.request_buffer_size); - if (request_buffer_size < 0) { - config_error(conf, "Negative request buffer size requested"); - } else if (request_buffer_size > 16 * (1 << 20)) { + if (request_buffer_size > 16 * (1 << 20)) { config_error(conf, "Request buffer can't be over 16MiB"); } else if (request_buffer_size < DEFAULT_BUFFER_SIZE) { - lwan_status_warning("Request buffer size of %ld is smaller than the " - "recommended minimum of %d bytes. This might not " - "be sufficient!", - request_buffer_size, - DEFAULT_BUFFER_SIZE); + lwan_status_warning("Using request buffer size of %d bytes instead of the " + "requested %ld bytes", + DEFAULT_BUFFER_SIZE, + request_buffer_size); + + request_buffer_size = DEFAULT_BUFFER_SIZE; } lwan->config.request_buffer_size = (size_t)request_buffer_size; From 054212ca6aa773b9a8495bdf22c88cb2cc0c37e8 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 16 Apr 2024 07:35:59 -0700 Subject: [PATCH 2200/2505] Remove `use_dynamic_buffer` bool from lwan_config struct No need for this to be there anymore. Also, change all the bools at the end to bitfields of width 1 in an unsigned int. --- src/lib/lwan.c | 4 ---- src/lib/lwan.h | 11 +++++------ 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 5e330e673..cf599ace3 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -65,7 +65,6 @@ static const struct lwan_config default_config = { .allow_post_temp_file = false, .max_put_data_size = 10 * DEFAULT_BUFFER_SIZE, .allow_put_temp_file = false, - .use_dynamic_buffer = false, }; LWAN_HANDLER_ROUTE(brew_coffee, NULL /* do not autodetect this route */) @@ -633,9 +632,6 @@ static bool setup_from_config(struct lwan *lwan, const char *path) } else if (streq(line->key, "allow_cors")) { lwan->config.allow_cors = parse_bool(line->value, default_config.allow_cors); - } else if (streq(line->key, "use_dynamic_buffer")) { - lwan->config.use_dynamic_buffer = - parse_bool(line->value, default_config.use_dynamic_buffer); } else if (streq(line->key, "expires")) { lwan->config.expires = parse_time_period(line->value, default_config.expires); diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 084ee83ce..fb5ccb0a5 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -530,12 +530,11 @@ struct lwan_config { unsigned int expires; unsigned int n_threads; - bool quiet; - bool proxy_protocol; - bool allow_cors; - bool allow_post_temp_file; - bool allow_put_temp_file; - bool use_dynamic_buffer; + unsigned int quiet : 1; + unsigned int proxy_protocol : 1; + unsigned int allow_cors : 1; + unsigned int allow_post_temp_file : 1; + unsigned int allow_put_temp_file : 1; }; struct lwan { From fd62765e948869e7f9196e2b3bf896a7b7e15825 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 17 Apr 2024 20:44:26 -0700 Subject: [PATCH 2201/2505] Make the hash table ref-counted --- src/bin/tools/mimegen.c | 2 +- src/lib/hash.c | 16 +++++++++++++++- src/lib/hash.h | 5 ++++- src/lib/lwan-cache.c | 4 ++-- src/lib/lwan-http-authorize.c | 4 ++-- src/lib/lwan-template.c | 4 ++-- src/lib/lwan.c | 4 ++-- src/lib/missing-epoll.c | 4 ++-- src/samples/smolsite/main.c | 4 ++-- 9 files changed, 32 insertions(+), 15 deletions(-) diff --git a/src/bin/tools/mimegen.c b/src/bin/tools/mimegen.c index f84c54ad3..96156b9aa 100644 --- a/src/bin/tools/mimegen.c +++ b/src/bin/tools/mimegen.c @@ -367,7 +367,7 @@ int main(int argc, char *argv[]) free(compressed); free(output.ptr); free(exts); - hash_free(ext_mime); + hash_unref(ext_mime); fclose(fp); return 0; diff --git a/src/lib/hash.c b/src/lib/hash.c index e3d2d64ec..46c474668 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -55,6 +55,8 @@ struct hash { void (*free_key)(void *value); struct hash_bucket *buckets; + + unsigned int refs; }; struct hash_entry { @@ -251,6 +253,8 @@ hash_internal_new(unsigned int (*hash_value)(const void *key), hash->n_buckets_mask = MIN_BUCKETS - 1; hash->count = 0; + hash->refs = 1; + return hash; } @@ -284,13 +288,23 @@ hash_n_buckets(const struct hash *hash) return hash->n_buckets_mask + 1; } -void hash_free(struct hash *hash) +struct hash *hash_ref(struct hash *hash) +{ + hash->refs++; + return hash; +} + +void hash_unref(struct hash *hash) { struct hash_bucket *bucket, *bucket_end; if (hash == NULL) return; + hash->refs--; + if (hash->refs) + return; + bucket = hash->buckets; bucket_end = hash->buckets + hash_n_buckets(hash); for (; bucket < bucket_end; bucket++) { diff --git a/src/lib/hash.h b/src/lib/hash.h index e035fcfa0..f68e8ef47 100644 --- a/src/lib/hash.h +++ b/src/lib/hash.h @@ -17,7 +17,10 @@ struct hash *hash_int64_new(void (*free_key)(void *value), void (*free_value)(void *value)); struct hash *hash_str_new(void (*free_key)(void *value), void (*free_value)(void *value)); -void hash_free(struct hash *hash); + +struct hash *hash_ref(struct hash *hash); +void hash_unref(struct hash *hash); + int hash_add(struct hash *hash, const void *key, const void *value); int hash_add_unique(struct hash *hash, const void *key, const void *value); int hash_del(struct hash *hash, const void *key); diff --git a/src/lib/lwan-cache.c b/src/lib/lwan-cache.c index 01d1bdb25..5bcd0e5d7 100644 --- a/src/lib/lwan-cache.c +++ b/src/lib/lwan-cache.c @@ -155,7 +155,7 @@ struct cache *cache_create_full(cache_create_entry_cb create_entry_cb, error_no_queue_lock: pthread_rwlock_destroy(&cache->hash.lock); error_no_hash_lock: - hash_free(cache->hash.table); + hash_unref(cache->hash.table); error_no_hash: free(cache); @@ -186,7 +186,7 @@ void cache_destroy(struct cache *cache) cache_pruner_job(cache); pthread_rwlock_destroy(&cache->hash.lock); pthread_rwlock_destroy(&cache->queue.lock); - hash_free(cache->hash.table); + hash_unref(cache->hash.table); free(cache); } diff --git a/src/lib/lwan-http-authorize.c b/src/lib/lwan-http-authorize.c index 007b73580..99e561b8f 100644 --- a/src/lib/lwan-http-authorize.c +++ b/src/lib/lwan-http-authorize.c @@ -116,7 +116,7 @@ create_realm_file(const void *key, void *context __attribute__((unused))) error: config_close(f); error_no_close: - hash_free(rpf->entries); + hash_unref(rpf->entries); free(rpf); return NULL; } @@ -125,7 +125,7 @@ static void destroy_realm_file(struct cache_entry *entry, void *context __attribute__((unused))) { struct realm_password_file_t *rpf = (struct realm_password_file_t *)entry; - hash_free(rpf->entries); + hash_unref(rpf->entries); free(rpf); } diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index 8a52e7c89..26ab8d1dd 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -245,7 +245,7 @@ static int symtab_push(struct parser *parser, return 0; hash_add_err: - hash_free(tab->hash); + hash_unref(tab->hash); hash_new_err: free(tab); @@ -258,7 +258,7 @@ static void symtab_pop(struct parser *parser) assert(tab); - hash_free(tab->hash); + hash_unref(tab->hash); parser->symtab = tab->next; free(tab); } diff --git a/src/lib/lwan.c b/src/lib/lwan.c index cf599ace3..ec6c0cf5f 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -111,7 +111,7 @@ static void destroy_urlmap(void *data) if (module->destroy) module->destroy(url_map->data); } else if (url_map->data && url_map->flags & HANDLER_DATA_IS_HASH_TABLE) { - hash_free(url_map->data); + hash_unref(url_map->data); } free(url_map->authorization.realm); @@ -410,7 +410,7 @@ static void parse_listener_prefix(struct config *c, add_url_map(&lwan->url_map_trie, prefix, &url_map); out: - hash_free(hash); + hash_unref(hash); config_close(isolated); } diff --git a/src/lib/missing-epoll.c b/src/lib/missing-epoll.c index 198fa1bca..c9e33a1c5 100644 --- a/src/lib/missing-epoll.c +++ b/src/lib/missing-epoll.c @@ -129,7 +129,7 @@ int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) r = kevent(epfd, NULL, 0, evs, maxevents, to_timespec(&tmspec, timeout)); if (UNLIKELY(r < 0)) { - hash_free(coalesce); + hash_unref(coalesce); return -1; } @@ -168,7 +168,7 @@ int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) } } - hash_free(coalesce); + hash_unref(coalesce); return (int)(intptr_t)(ev - events); } #elif !defined(LWAN_HAVE_EPOLL) diff --git a/src/samples/smolsite/main.c b/src/samples/smolsite/main.c index 601f95688..a608306a2 100644 --- a/src/samples/smolsite/main.c +++ b/src/samples/smolsite/main.c @@ -263,7 +263,7 @@ static struct cache_entry *create_site(const void *key, void *context) return (struct cache_entry *)site; no_central_dir: - hash_free(site->files); + hash_unref(site->files); no_hash: no_end_record: jzfile_free(zip); @@ -279,7 +279,7 @@ static void destroy_site(struct cache_entry *entry, void *context) { struct site *site = (struct site *)entry; lwan_strbuf_free(&site->qr_code); - hash_free(site->files); + hash_unref(site->files); free(site->zipped.value); free(site); } From 699425d8dd0547293eee7ac448a00bdc007aa1cb Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 17 Apr 2024 20:48:20 -0700 Subject: [PATCH 2202/2505] Allow constants to be specified in configuration files --- README.md | 23 ++++++++ src/bin/testrunner/testrunner.conf | 11 ++-- src/bin/tools/CMakeLists.txt | 2 + src/lib/lwan-config.c | 87 +++++++++++++++++++++++++----- 4 files changed, 108 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 8391df6d0..97cc485cf 100644 --- a/README.md +++ b/README.md @@ -266,6 +266,29 @@ playlist chiptune { Some examples can be found in `lwan.conf` and `techempower.conf`. +#### Constants + +Constants can be defined and reused throughout the configuration file by +specifying them in a `constants` section anywhere in the configuration +file. A constant will be available only after that section defines a +particular constant. Constants can be re-defined. If a constant isn't +defined, its value will be obtained from an environment variable. If +it's not defined in either one `constants` section, or in the environment, +Lwan will abort with an appropriate error message. + +``` +constants { + user_name = ${USER} + home_directory = ${HOME} + buffer_size = 1000000 +} +``` + +The same syntax for default values specified above is valid here (e.g. +specifying `user_name` to be `${USER:nobody}` will set `${user_name} to +`nobody` if `${USER}` isn't set in the environment variable or isn't +another constant.) + #### Value types | Type | Description | diff --git a/src/bin/testrunner/testrunner.conf b/src/bin/testrunner/testrunner.conf index 67737bcd6..5c9ff9673 100644 --- a/src/bin/testrunner/testrunner.conf +++ b/src/bin/testrunner/testrunner.conf @@ -1,3 +1,8 @@ +constants { + buffer_size = 1000000 + cache_for = ${CACHE_FOR:5} +} + # Timeout in seconds to keep a connection alive. keep_alive_timeout = ${KEEP_ALIVE_TIMEOUT:15} @@ -18,9 +23,9 @@ proxy_protocol = true # Maximum post data size of slightly less than 1MiB. The default is too # small for testing purposes. -max_post_data_size = 1000000 +max_post_data_size = ${buffer_size} -request_buffer_size = 32768 +request_buffer_size = ${buffer_size} # Enable straitjacket by default. The `drop_capabilities` option is `true` # by default. Other options may require more privileges. @@ -165,7 +170,7 @@ site { # request headers. serve precompressed files = true - cache for = ${CACHE_FOR:5} + cache for = ${cache_for} } } diff --git a/src/bin/tools/CMakeLists.txt b/src/bin/tools/CMakeLists.txt index 032381d7e..c5d19b73c 100644 --- a/src/bin/tools/CMakeLists.txt +++ b/src/bin/tools/CMakeLists.txt @@ -36,6 +36,8 @@ else () ${CMAKE_SOURCE_DIR}/src/lib/lwan-status.c ${CMAKE_SOURCE_DIR}/src/lib/lwan-strbuf.c ${CMAKE_SOURCE_DIR}/src/lib/missing.c + ${CMAKE_SOURCE_DIR}/src/lib/hash.c + ${CMAKE_SOURCE_DIR}/src/lib/murmur3.c ) add_executable(weighttp weighttp.c) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 7f086d8e3..674b8aef5 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -102,6 +102,7 @@ struct parser { struct config { struct parser parser; char *error_message; + struct hash *constants; struct { void *addr; size_t sz; @@ -544,14 +545,17 @@ static void *parse_section_end(struct parser *parser); #define ENV_VAR_NAME_LEN_MAX 64 static __attribute__((noinline)) const char * -secure_getenv_len(struct parser *parser, const char *key, size_t len) +get_constant(struct parser *parser, const char *key, size_t len) { if (UNLIKELY(len > ENV_VAR_NAME_LEN_MAX)) { return PARSER_ERROR(parser, "Variable name \"%.*s\" exceeds %d bytes", (int)len, key, ENV_VAR_NAME_LEN_MAX); } - return secure_getenv(strndupa(key, len)); + const char *key_copy = strndupa(key, len); + const char *value = + hash_find(config_from_parser(parser)->constants, key_copy); + return value ? value : secure_getenv(key_copy); } static void *parse_key_value(struct parser *parser) @@ -577,11 +581,13 @@ static void *parse_key_value(struct parser *parser) while ((lexeme = lex_next(&parser->lexer))) { switch (lexeme->type) { case LEXEME_VARIABLE: { - const char *value = secure_getenv_len(parser, lexeme->value.value, - lexeme->value.len); + const char *value = + get_constant(parser, lexeme->value.value, lexeme->value.len); if (!value) { return PARSER_ERROR( - parser, "Variable '$%.*s' not defined in environment", + parser, + "Variable '$%.*s' not defined in a constants section " + "or as an environment variable", (int)lexeme->value.len, lexeme->value.value); } @@ -591,13 +597,13 @@ static void *parse_key_value(struct parser *parser) } case LEXEME_VARIABLE_DEFAULT: { - const char *value = secure_getenv_len(parser, lexeme->value.value, - lexeme->value.len); + const char *value = + get_constant(parser, lexeme->value.value, lexeme->value.len); const struct lexeme *var_name = lexeme; if (!(lexeme = lex_next(&parser->lexer))) { return PARSER_ERROR( - parser, "Default value for variable '$%.*s' not given", + parser, "Default value for constant '$%.*s' not given", (int)var_name->value.len, var_name->value.value); } @@ -794,7 +800,7 @@ static void *parse_config(struct parser *parser) return NULL; } -static const struct config_line *parser_next(struct parser *parser) +static const struct config_line *parser_next_internal(struct parser *parser) { while (parser->state) { const struct config_line *line; @@ -810,6 +816,55 @@ static const struct config_line *parser_next(struct parser *parser) return config_ring_buffer_get_ptr_or_null(&parser->items); } +static bool parse_constants(struct config *config, const struct config_line *l) +{ + while ((l = config_read_line(config))) { + switch (l->type) { + case CONFIG_LINE_TYPE_LINE: { + char *k = strdup(l->key); + char *v = strdup(l->value); + + if (!k || !v) + lwan_status_critical("Can't allocate memory for constant"); + + hash_add(config->constants, k, v); + break; + } + + case CONFIG_LINE_TYPE_SECTION_END: + return true; + + case CONFIG_LINE_TYPE_SECTION: + config_error(config, "Constants section can't be nested"); + return false; + } + } + + return true; +} + +static const struct config_line *parser_next(struct parser *parser) +{ + while (true) { + const struct config_line *l = parser_next_internal(parser); + + if (!l) + return NULL; + + if (l->type == CONFIG_LINE_TYPE_SECTION && streq(l->key, "constants")) { + struct config *config = config_from_parser(parser); + + if (parse_constants(config, l)) + continue; + + return PARSER_ERROR(parser, "Could not parse constants section: %s", + config_last_error(config)); + } + + return l; + } +} + static struct config * config_open_path(const char *path, void **data, size_t *size) { @@ -864,6 +919,8 @@ config_init_data(struct config *config, const void *data, size_t len) config->error_message = NULL; config->opened_brackets = 0; + config->constants = hash_str_new(free, free); + lwan_strbuf_init(&config->parser.strbuf); config_ring_buffer_init(&config->parser.items); lexeme_ring_buffer_init(&config->parser.buffer); @@ -907,6 +964,8 @@ void config_close(struct config *config) munmap(config->mapped.addr, config->mapped.sz); #endif + hash_unref(config->constants); + free(config->error_message); lwan_strbuf_free(&config->parser.strbuf); free(config); @@ -956,6 +1015,8 @@ struct config *config_isolate_section(struct config *current_conf, memcpy(isolated, current_conf, sizeof(*isolated)); lwan_strbuf_init(&isolated->parser.strbuf); + isolated->constants = hash_ref(current_conf->constants); + isolated->mapped.addr = NULL; isolated->mapped.sz = 0; /* Keep opened_brackets from the original */ @@ -965,12 +1026,14 @@ struct config *config_isolate_section(struct config *current_conf, pos = isolated->parser.lexer.pos; if (!find_section_end(isolated)) { + config_error(current_conf, + "Could not find section end while trying to isolate: %s", + config_last_error(isolated)); + + hash_unref(isolated->constants); lwan_strbuf_free(&isolated->parser.strbuf); free(isolated); - config_error(current_conf, - "Could not find section end while trying to isolate"); - return NULL; } From 644857726c961b3ea8d0bc0a6b0cb3a102ec5d01 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 17 Apr 2024 20:50:41 -0700 Subject: [PATCH 2203/2505] Ensure alloca() is called with a constant in process_request_coro() --- src/lib/lwan-thread.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index f231caf62..42078c3aa 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -493,8 +493,8 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, init_gen = 2; } else { buffer = (struct lwan_value){ - .value = alloca(request_buffer_size), - .len = request_buffer_size, + .value = alloca(DEFAULT_BUFFER_SIZE), + .len = DEFAULT_BUFFER_SIZE, }; init_gen = 1; From 344c141872a4e63878e52eaa580880d01f28e465 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 17 Apr 2024 20:51:42 -0700 Subject: [PATCH 2204/2505] Stop using assertions as optimization hints Some code that shouldn't be generated in release builds was being generated. Maybe a different macro should be introduced instead. --- src/lib/missing/assert.h | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/lib/missing/assert.h b/src/lib/missing/assert.h index 9d9f145c6..be1916a62 100644 --- a/src/lib/missing/assert.h +++ b/src/lib/missing/assert.h @@ -29,20 +29,6 @@ # define static_assert(expr, msg) #endif -/* Use assertions as optimization hints */ -#ifndef NDEBUG -#undef assert -#ifdef __clang__ -#define assert(expr) __builtin_assume(expr) -#else -#define assert(expr) \ - do { \ - if (!(expr)) \ - __builtin_unreachable(); \ - } while (0) -#endif -#endif - /* Macro to enable self-test on startup in debug builds. * Details: https://tia.mat.br/posts/2023/12/11/self-test.html */ #if defined(NDEBUG) From f06c8d881a6bbee34ca6b29290775e5f5374ba4c Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 17 Apr 2024 20:53:22 -0700 Subject: [PATCH 2205/2505] Simplify websockets unmasking a bit more --- src/lib/lwan-websocket.c | 56 +++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index ab4f4253c..b83c6950f 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -150,44 +150,40 @@ static size_t get_frame_length(struct lwan_request *request, uint16_t header) static void unmask(char *msg, size_t msg_len, char mask[static 4]) { - const uint32_t mask32 = string_as_uint32(mask); - char *msg_end = msg + msg_len; - - if (sizeof(void *) == 8) { - const uint64_t mask64 = (uint64_t)mask32 << 32 | mask32; + const int32_t mask32 = (int32_t)string_as_uint32(mask); + const char *msg_end = msg + msg_len; #if defined(__AVX2__) - const size_t len256 = msg_len / 32; - if (len256) { - const __m256i mask256 = - _mm256_setr_epi64x((int64_t)mask64, (int64_t)mask64, - (int64_t)mask64, (int64_t)mask64); - for (size_t i = 0; i < len256; i++) { - __m256i v = _mm256_loadu_si256((__m256i *)msg); - _mm256_storeu_si256((__m256i *)msg, - _mm256_xor_si256(v, mask256)); - msg += 32; - } - - msg_len = (size_t)(msg_end - msg); + const size_t len256 = msg_len / 32; + if (len256) { + const __m256i mask256 = _mm256_setr_epi32( + mask32, mask32, mask32, mask32, mask32, mask32, mask32, mask32); + for (size_t i = 0; i < len256; i++) { + __m256i v = _mm256_loadu_si256((__m256i *)msg); + _mm256_storeu_si256((__m256i *)msg, _mm256_xor_si256(v, mask256)); + msg += 32; } + + msg_len = (size_t)(msg_end - msg); + } #endif #if defined(__SSE2__) - const size_t len128 = msg_len / 16; - if (len128) { - const __m128i mask128 = - _mm_setr_epi64((__m64)mask64, (__m64)mask64); - for (size_t i = 0; i < len128; i++) { - __m128i v = _mm_loadu_si128((__m128i *)msg); - _mm_storeu_si128((__m128i *)msg, _mm_xor_si128(v, mask128)); - msg += 16; - } - - msg_len = (size_t)(msg_end - msg); + const size_t len128 = msg_len / 16; + if (len128) { + const __m128i mask128 = _mm_setr_epi32(mask32, mask32, mask32, mask32); + for (size_t i = 0; i < len128; i++) { + __m128i v = _mm_loadu_si128((__m128i *)msg); + _mm_storeu_si128((__m128i *)msg, _mm_xor_si128(v, mask128)); + msg += 16; } + + msg_len = (size_t)(msg_end - msg); + } #endif + if (sizeof(void *) == 8) { + const uint64_t mask64 = (uint64_t)mask32 << 32 | (uint64_t)mask32; const size_t len64 = msg_len / 8; for (size_t i = 0; i < len64; i++) { uint64_t v = string_as_uint64(msg); @@ -199,7 +195,7 @@ static void unmask(char *msg, size_t msg_len, char mask[static 4]) const size_t len32 = (size_t)((msg_end - msg) / 4); for (size_t i = 0; i < len32; i++) { uint32_t v = string_as_uint32(msg); - v ^= mask32; + v ^= (uint32_t)mask32; msg = mempcpy(msg, &v, sizeof(v)); } From e9fb3bfb854db0b2ca67d089992450bdc06cec82 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 19 Apr 2024 07:27:45 -0700 Subject: [PATCH 2206/2505] Disallow constants section anywhere other than the main scope Fixes an OOM scenario (reproducer in fuzz/regresion/...). Thanks to oss-fuzz: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=68129 --- ...e-minimized-config_fuzzer-5462763322277888 | 233 ++++++++++++++++++ src/lib/lwan-config.c | 3 +- 2 files changed, 235 insertions(+), 1 deletion(-) create mode 100644 fuzz/regression/clusterfuzz-testcase-minimized-config_fuzzer-5462763322277888 diff --git a/fuzz/regression/clusterfuzz-testcase-minimized-config_fuzzer-5462763322277888 b/fuzz/regression/clusterfuzz-testcase-minimized-config_fuzzer-5462763322277888 new file mode 100644 index 000000000..304fbd5fc --- /dev/null +++ b/fuzz/regression/clusterfuzz-testcase-minimized-config_fuzzer-5462763322277888 @@ -0,0 +1,233 @@ +$=$�=$=$=$=$=$=$=$=$=$=$=$=$=$��=$=$=$$==$=$=$=$=$=$=$=$=$=$=$=$=$=$=$=$=$=$=$=$=$��=$=$=$=$=$=$=$=$=$=""$=$=$=$=$=$=$=$=$off +!=off +' ] +���" := +"��������������" := +"������=" +constants{M=Z=M""$"������������� := +"������=" +constants{M=]=${M:}${M:.}${M:}${I:}${M:}${N:}${M:}${M:}${M:}${M:}${M:}${M:M:}Z=M${M:}${I:}${M:}$$=$=$=$=$=$=$=""$"����������������ʘ�����������dd4" ���˜���$=$=$=$=$=$=$=$=$=$=$=$=$=$�������������� := +"������=" +constants{M=]=${M:}${M:.}${M:}${I:}${M:}${N:}${M:}${M:}${M:{{M:}${M:}${M:M:}Z=M${M:}${I:�dd4" ���˜���$=$=$=$=$T$=$=$=$=$=$=$=$=$��=$=$=" +" +" +" +" +" +" + +"�" +" +" +" +" +" +" +" +" +" +" +" +" +" + +" +" +" +" +" +" +" +" +" +" +" +" +" +" +" +" +" +" + +" +" +" +" +" +" +" +" +" +" +" +" +" +" +" +" +" +" + +"����������" := +"������=" +constants{M=Z=M""$"������������� := +"������=" +constants{M=]=${M:}${M:.}${M:}${I:}${M:}${N:}${M:}${M:}${M:}${M:}${M:}${M:M:}Z=M${M:}${I:}${M:}$$=$=$=$=$=$=$=""$"��=$=$=$=$=$=$=$=$=$=$=$=$=$�������������� := +"������=" +constants{M=]=${M:}${M:.}${M:}${I:}${M:}${N:}${M:}${M:}${M:{{M:}${M:}${M:M:}Z=M${M:}${I:�dd4" ���˜���$=$=$=$=$T$=$=$=$=$=$=$=$=$��=$=$=" +" +" +" +" + +" +" +" + +" +" +" +" +" +"$=$=$=$�������=$constan�s$=$=$=$=$=$=$��=$=$=$=$=$=$=$B$=$=""$=$=$=�=$=$" +" +" +" +" +" +" +" +" +" +" +" +" +" + +"����������" := +"������=" +constants{M=Z=M""$"������������� := +"������=" +constants{M=]=${M:}${M:.}${M:}${I:}${M:}${N:}${M:}${M:}${M:}${M:}${M:}${M:M:}Z=M${M:}${I:}${M:}$$=$=$=$=$=$=$=""$"��=$=$=$=$=$=$=$=$=$=$=$=$=$�������������� := +"������=" +constants{M=]=${M:}${M:.}${M:}${I:}${M:}${N:}${M:}${M:}${M:{{M:}${M:}${M:M:}Z=M${M:}${I:�dd4" ���˜���$=$=$=$=$T$=$=$=$=$=$=$=$=$��=$=$=" +" +" +" +" + +" +" +" + +" +" +" +" +" +"$=$=$=$�������=$constan:}${M:}${M:}${M:}${M:M:}Z=M${M:}${I:}${M:}$$=$=$=$=$=$=$=""$"����������������ʘ�����������dd4" ���˜���$=$=$=$=$=$=$=$=$=$=$=$=$=$�������������� := +"������=" +constants{M=]=${M:}${M:.}${M:}${I:}${M:}${N:}${M:}${M:}${M:{{M:}${M:}${M:M:}Z=M${M:}${I:�dd4" ���˜���$=$=$=$=$T$=$=$=$=$=$=$=$=$��=$=$=" +" +" +" +" + +" +" +" + +" +" +" +" +" +"$=$=$=$�������=$constan:}${M:}${M:}${M:}${M:M:}Z=M${M:}${I:}${M:}$$=$=$=$=$=$=$=""$"����������������ʘ�����������dd4" ���˜���$=$=$=$=$=$=$=$=$=$=$=$=$=$�������������� := +"������=" +constants{M=]=${M:}${M:.}${M:}${I:}${M:}${N:}${M:}${M:}${M:{{M:}${M:}${M:M:}Z=M${M:}${I:�dd4" ���˜���$=$=$=$=$T$=$=$=$=$=$=$=$=$��=$=$=" +" +" +" +" + +" +" +" + +" +" +" +" +" +"$=$=$=$�������=$constan:}${M:}${M:}${M:}${M:M:}Z=M${M:}${I:}${M:}$$=$=$=$=$=$=$=""$"����������������ʘ�����������dd4" ���˜���$=$=$=$=$=$=$=$=$=$=$=$=$=$�������������� := +"������=" +constants{M=]=${M:}${M:.}${M:}${I:}${M:}${N:}${M:}${M:}${M:{{M:}${M:}${M:M:}Z=M${M:}${I:�dd4" ���˜���$=$=$=$=$T$=$=$=$=$=$=$=$=$��=$=$=" +" +" +" +" + +" +" +" + +" +" +" +" +" +"$=$=$=$�������=$constan:}${M:}${M:}${M:}${M:M:}Z=M${M:}${I:}${M:}$$=$=$=$=$=$=$=""$"����������������ʘ�����������dd4" ���˜���$=$=$=$=$=$=$=$=$=$=$=$=$=$�������������� := +"������=" +constants{M=]=${M:}${M:.}${M:}${I:}${M:}${N:}${M:}${M:}${M:{{M:}${M:}${M:M:}Z=M${M:}${I:�dd4" ���˜���$=$=$=$=$T$=$=$=$=$=$=$=$=$��=$=$=" +" +" +" +" +" +" + +"�" +" +" +" +" +" +" +" +" +" +" +" +" +" + +" +" +" +" +" +" +" +" +" +" +" +�s$=$=$=$=$=$=$��=$=$=$=$=$=$=$B$=$=""$=$=$=�=$=$=$=$=$=$=$B$=$=""$=$=$=$=$=$�$=$=$=off +(=off +(=of{M:}$����+Z=M""$"�������������" : +" +" +" +" +" +" +" +" +" +" +" +"= +"������*=" + +co \ No newline at end of file diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 674b8aef5..865e95ef0 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -851,7 +851,8 @@ static const struct config_line *parser_next(struct parser *parser) if (!l) return NULL; - if (l->type == CONFIG_LINE_TYPE_SECTION && streq(l->key, "constants")) { + if (l->type == CONFIG_LINE_TYPE_SECTION && streq(l->key, "constants") && + config_from_parser(parser)->opened_brackets == 1) { struct config *config = config_from_parser(parser); if (parse_constants(config, l)) From 1d376b5d8715eb6c5fdf10b0aae06ee86269a076 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 23 Apr 2024 19:02:47 -0700 Subject: [PATCH 2207/2505] Limit size of values when expanding variables Fuzzers are really good at finding these limits! This one was found in oss-fuzz. Limit size of a value in a key/value line to 1MiB, which is more than sufficient for a configuration file. --- src/lib/lwan-config.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 865e95ef0..7f0648f52 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -579,6 +579,12 @@ static void *parse_key_value(struct parser *parser) lwan_strbuf_append_char(&parser->strbuf, '\0'); while ((lexeme = lex_next(&parser->lexer))) { + if (UNLIKELY(lwan_strbuf_get_length(&parser->strbuf) > 1ull<<20)) { + /* 1MiB is more than sufficient for a value. (Without this validation + * here, fuzzers often generates values that are gigabite sized.) */ + return PARSER_ERROR(parser, "Value too long"); + } + switch (lexeme->type) { case LEXEME_VARIABLE: { const char *value = From dc350c79847aab23ddbed9038b67c4eaa79e71d5 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 27 Apr 2024 09:05:08 -0700 Subject: [PATCH 2208/2505] Try to reuse mask vectors when unmasking websocket frames --- src/lib/lwan-websocket.c | 89 ++++++++++++++++++++++++++-------------- 1 file changed, 58 insertions(+), 31 deletions(-) diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index b83c6950f..603b44ebf 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -150,63 +150,90 @@ static size_t get_frame_length(struct lwan_request *request, uint16_t header) static void unmask(char *msg, size_t msg_len, char mask[static 4]) { - const int32_t mask32 = (int32_t)string_as_uint32(mask); - const char *msg_end = msg + msg_len; + /* TODO: handle alignment of `msg` to use (at least) NT loads + * as we're rewriting msg anyway. (NT writes aren't that + * useful as the unmasked value will be used right after.) */ #if defined(__AVX2__) - const size_t len256 = msg_len / 32; - if (len256) { - const __m256i mask256 = _mm256_setr_epi32( - mask32, mask32, mask32, mask32, mask32, mask32, mask32, mask32); - for (size_t i = 0; i < len256; i++) { - __m256i v = _mm256_loadu_si256((__m256i *)msg); + const __m256i mask256 = + _mm256_castps_si256(_mm256_broadcast_ss((const float *)mask)); + if (msg_len >= 32) { + do { + __m256i v = _mm256_lddqu_si256((const __m256i *)msg); _mm256_storeu_si256((__m256i *)msg, _mm256_xor_si256(v, mask256)); - msg += 32; - } - msg_len = (size_t)(msg_end - msg); + msg += 32; + msg_len -= 32; + } while (msg_len >= 32); } #endif #if defined(__SSE2__) - const size_t len128 = msg_len / 16; - if (len128) { - const __m128i mask128 = _mm_setr_epi32(mask32, mask32, mask32, mask32); - for (size_t i = 0; i < len128; i++) { - __m128i v = _mm_loadu_si128((__m128i *)msg); +#if defined(__AVX2__) + const __m128i mask128 = _mm256_extracti128_si256(mask256, 0); +#elif defined(__SSE3__) + const __m128i mask128 = _mm_lddqu_si128((const float *)mask); +#else + const __m128i mask128 = _mm_loadu_si128((const __m128i *)mask); +#endif + if (msg_len >= 16) { + do { +#if defined(__SSE3__) + __m128i v = _mm_lddqu_si128((const __m128i *)msg); +#else + __m128i v = _mm_loadu_si128((const __m128i *)msg); +#endif + _mm_storeu_si128((__m128i *)msg, _mm_xor_si128(v, mask128)); - msg += 16; - } - msg_len = (size_t)(msg_end - msg); + msg += 16; + msg_len -= 16; + } while (msg_len >= 16); } #endif if (sizeof(void *) == 8) { - const uint64_t mask64 = (uint64_t)mask32 << 32 | (uint64_t)mask32; - const size_t len64 = msg_len / 8; - for (size_t i = 0; i < len64; i++) { - uint64_t v = string_as_uint64(msg); - v ^= mask64; - msg = mempcpy(msg, &v, sizeof(v)); + if (msg_len >= 8) { +#if defined(__SSE_4_1__) + /* We're far away enough from the AVX2 path that it's + * probably better to use mask128 instead of mask256 + * here. */ + const __int64 mask64 = _mm_extract_epi64(mask128, 0); +#else + const uint32_t mask32 = string_as_uint32(mask); + const uint64_t mask64 = (uint64_t)mask32 << 32 | (uint64_t)mask32; +#endif + do { + uint64_t v = string_as_uint64(msg); + v ^= (uint64_t)mask64; + msg = mempcpy(msg, &v, sizeof(v)); + msg_len -= 8; + } while (msg_len >= 8); } } - const size_t len32 = (size_t)((msg_end - msg) / 4); - for (size_t i = 0; i < len32; i++) { - uint32_t v = string_as_uint32(msg); - v ^= (uint32_t)mask32; - msg = mempcpy(msg, &v, sizeof(v)); + if (msg_len >= 4) { + const uint32_t mask32 = string_as_uint32(mask); + do { + uint32_t v = string_as_uint32(msg); + v ^= (uint32_t)mask32; + msg = mempcpy(msg, &v, sizeof(v)); + msg_len -= 4; + } while (msg_len >= 4); } - switch (msg_end - msg) { + switch (msg_len) { case 3: msg[2] ^= mask[2]; /* fallthrough */ case 2: msg[1] ^= mask[1]; /* fallthrough */ case 1: msg[0] ^= mask[0]; + break; + default: + __builtin_unreachable(); } +#undef MASK32_SET } static void send_websocket_pong(struct lwan_request *request, uint16_t header) From f3e40361e2569864881be757287c36f6e82426a6 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 28 Apr 2024 11:05:37 -0700 Subject: [PATCH 2209/2505] No need to reload the mask if we can extract it from the SSE path --- src/lib/lwan-websocket.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index 603b44ebf..9641aa997 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -213,7 +213,11 @@ static void unmask(char *msg, size_t msg_len, char mask[static 4]) } if (msg_len >= 4) { +#if defined(__SSE_4_1__) + const uint32_t mask32 = _mm_extract_epi32(mask128, 0); +#else const uint32_t mask32 = string_as_uint32(mask); +#endif do { uint32_t v = string_as_uint32(msg); v ^= (uint32_t)mask32; From 00a4c536e9ab42cd41936a9e9ffbdd5ccff4220f Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 28 Apr 2024 13:20:23 -0700 Subject: [PATCH 2210/2505] More simplifications to the websocket unmasking code --- src/lib/lwan-websocket.c | 52 ++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index 9641aa997..626df961d 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -157,14 +157,9 @@ static void unmask(char *msg, size_t msg_len, char mask[static 4]) #if defined(__AVX2__) const __m256i mask256 = _mm256_castps_si256(_mm256_broadcast_ss((const float *)mask)); - if (msg_len >= 32) { - do { - __m256i v = _mm256_lddqu_si256((const __m256i *)msg); - _mm256_storeu_si256((__m256i *)msg, _mm256_xor_si256(v, mask256)); - - msg += 32; - msg_len -= 32; - } while (msg_len >= 32); + for (; msg_len >= 32; msg_len -= 32, msg += 32) { + __m256i v = _mm256_lddqu_si256((const __m256i *)msg); + _mm256_storeu_si256((__m256i *)msg, _mm256_xor_si256(v, mask256)); } #endif @@ -176,40 +171,33 @@ static void unmask(char *msg, size_t msg_len, char mask[static 4]) #else const __m128i mask128 = _mm_loadu_si128((const __m128i *)mask); #endif - if (msg_len >= 16) { - do { + for (; msg_len >= 16; msg_len -= 16, msg += 16) { #if defined(__SSE3__) - __m128i v = _mm_lddqu_si128((const __m128i *)msg); + __m128i v = _mm_lddqu_si128((const __m128i *)msg); #else - __m128i v = _mm_loadu_si128((const __m128i *)msg); + __m128i v = _mm_loadu_si128((const __m128i *)msg); #endif - _mm_storeu_si128((__m128i *)msg, _mm_xor_si128(v, mask128)); - - msg += 16; - msg_len -= 16; - } while (msg_len >= 16); + _mm_storeu_si128((__m128i *)msg, _mm_xor_si128(v, mask128)); } #endif - if (sizeof(void *) == 8) { - if (msg_len >= 8) { + if (sizeof(void *) == 8 && msg_len >= 8) { #if defined(__SSE_4_1__) - /* We're far away enough from the AVX2 path that it's - * probably better to use mask128 instead of mask256 - * here. */ - const __int64 mask64 = _mm_extract_epi64(mask128, 0); + /* We're far away enough from the AVX2 path that it's + * probably better to use mask128 instead of mask256 + * here. */ + const __int64 mask64 = _mm_extract_epi64(mask128, 0); #else - const uint32_t mask32 = string_as_uint32(mask); - const uint64_t mask64 = (uint64_t)mask32 << 32 | (uint64_t)mask32; + const uint32_t mask32 = string_as_uint32(mask); + const uint64_t mask64 = (uint64_t)mask32 << 32 | (uint64_t)mask32; #endif - do { - uint64_t v = string_as_uint64(msg); - v ^= (uint64_t)mask64; - msg = mempcpy(msg, &v, sizeof(v)); - msg_len -= 8; - } while (msg_len >= 8); - } + do { + uint64_t v = string_as_uint64(msg); + v ^= (uint64_t)mask64; + msg = mempcpy(msg, &v, sizeof(v)); + msg_len -= 8; + } while (msg_len >= 8); } if (msg_len >= 4) { From 14384f5055d98a10873daf6515d4ba598822c5af Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 28 Apr 2024 13:29:56 -0700 Subject: [PATCH 2211/2505] Try harder to not reload the mask --- src/lib/lwan-websocket.c | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index 626df961d..3fb91bd59 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -182,33 +182,42 @@ static void unmask(char *msg, size_t msg_len, char mask[static 4]) } #endif - if (sizeof(void *) == 8 && msg_len >= 8) { +#if __SIZEOF_POINTER__ == 8 + #if defined(__SSE_4_1__) - /* We're far away enough from the AVX2 path that it's - * probably better to use mask128 instead of mask256 - * here. */ - const __int64 mask64 = _mm_extract_epi64(mask128, 0); + /* We're far away enough from the AVX2 path that it's + * probably better to use mask128 instead of mask256 + * here. */ + const uint64_t mask64 = _mm_extract_epi64(mask128, 0); #else - const uint32_t mask32 = string_as_uint32(mask); - const uint64_t mask64 = (uint64_t)mask32 << 32 | (uint64_t)mask32; + const uint32_t mask32 = string_as_uint32(mask); + const uint64_t mask64 = (uint64_t)mask32 << 32 | (uint64_t)mask32; +#define HAS_MASK32 #endif + + if (msg_len >= 8) { do { uint64_t v = string_as_uint64(msg); - v ^= (uint64_t)mask64; + v ^= mask64; msg = mempcpy(msg, &v, sizeof(v)); msg_len -= 8; } while (msg_len >= 8); } +#endif if (msg_len >= 4) { -#if defined(__SSE_4_1__) +#if defined(HAS_MASK32) + /* do nothing */ +#elif __SIZEOF_POINTER__ == 8 + const uint32_t mask32 = (uint32_t)mask64; +#elif defined(__SSE_4_1__) const uint32_t mask32 = _mm_extract_epi32(mask128, 0); #else const uint32_t mask32 = string_as_uint32(mask); #endif do { uint32_t v = string_as_uint32(msg); - v ^= (uint32_t)mask32; + v ^= mask32; msg = mempcpy(msg, &v, sizeof(v)); msg_len -= 4; } while (msg_len >= 4); @@ -225,7 +234,7 @@ static void unmask(char *msg, size_t msg_len, char mask[static 4]) default: __builtin_unreachable(); } -#undef MASK32_SET +#undef HAS_MASK32 } static void send_websocket_pong(struct lwan_request *request, uint16_t header) From a3bdbfc59c6545e4b3fd04ebaea1be583e6098ec Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 28 Apr 2024 19:01:22 -0700 Subject: [PATCH 2212/2505] Factor out bitptr adjustment Slightly cleans up peek_byte() --- src/lib/lwan-h2-huffman.c | 10 ++++------ src/scripts/gentables.py | 7 ++++--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/lib/lwan-h2-huffman.c b/src/lib/lwan-h2-huffman.c index 81816edf7..5265ba33f 100644 --- a/src/lib/lwan-h2-huffman.c +++ b/src/lib/lwan-h2-huffman.c @@ -276,20 +276,18 @@ struct bit_reader { static inline uint8_t peek_byte(struct bit_reader *reader) { if (reader->bitcount < 8) { + const uint64_t adjust = reader->bitcount + (reader->bitcount & 1); if (reader->total_bitcount >= 64) { reader->bitbuf |= read64be(reader->bitptr) >> reader->bitcount; - reader->bitptr += - (63 - reader->bitcount + (reader->bitcount & 1)) >> 3; + reader->bitptr += (63 - adjust) >> 3; reader->bitcount |= 56; } else if (reader->total_bitcount >= 32) { reader->bitbuf |= read32be(reader->bitptr) >> reader->bitcount; - reader->bitptr += - (31 - reader->bitcount + (reader->bitcount & 1)) >> 3; + reader->bitptr += (31 - adjust) >> 3; reader->bitcount |= 24; } else { reader->bitbuf |= *reader->bitptr >> reader->bitcount; - reader->bitptr += - (7 - reader->bitcount + (reader->bitcount & 1)) >> 3; + reader->bitptr += (7 - adjust) >> 3; reader->bitcount |= 8; } } diff --git a/src/scripts/gentables.py b/src/scripts/gentables.py index 2642bfb1c..f6a1cc87e 100755 --- a/src/scripts/gentables.py +++ b/src/scripts/gentables.py @@ -182,17 +182,18 @@ def generate_level(level, next_table): static inline uint8_t peek_byte(struct bit_reader *reader) { if (reader->bitcount < 8) { + const uint64_t adjust = reader->bitcount + (reader->bitcount & 1); if (reader->total_bitcount >= 64) { reader->bitbuf |= read64be(reader->bitptr) >> reader->bitcount; - reader->bitptr += (63 - reader->bitcount + (reader->bitcount & 1)) >> 3; + reader->bitptr += (63 - adjust) >> 3; reader->bitcount |= 56; } else if (reader->total_bitcount >= 32) { reader->bitbuf |= read32be(reader->bitptr) >> reader->bitcount; - reader->bitptr += (31 - reader->bitcount + (reader->bitcount & 1)) >> 3; + reader->bitptr += (31 - adjust) >> 3; reader->bitcount |= 24; } else { reader->bitbuf |= *reader->bitptr >> reader->bitcount; - reader->bitptr += (7 - reader->bitcount + (reader->bitcount & 1)) >> 3; + reader->bitptr += (7 - adjust) >> 3; reader->bitcount |= 8; } } From 2718dc235a8f81e7b82756a3be96a1acbf018f11 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 28 Apr 2024 19:11:25 -0700 Subject: [PATCH 2213/2505] Make bin2hex work on C libraries without constructor attribute support --- src/bin/tools/bin2hex.c | 49 ++++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/src/bin/tools/bin2hex.c b/src/bin/tools/bin2hex.c index 60cf6b9bf..92da6396b 100644 --- a/src/bin/tools/bin2hex.c +++ b/src/bin/tools/bin2hex.c @@ -28,6 +28,14 @@ #include "lwan.h" +static int constructor_attr_supported = 0; + +__attribute__((constructor)) +static void initialize_constructor_attr_supported(void) +{ + constructor_attr_supported = 1; +} + static int bin2hex_mmap(const char *path, const char *identifier) { int fd = open(path, O_RDONLY | O_CLOEXEC); @@ -96,12 +104,15 @@ static int bin2hex(const char *path, const char *identifier) printf("\n/* Contents of %s available through %s_value */\n", path, identifier); - printf("#if defined(__GNUC__) || defined(__clang__)\n"); - r |= bin2hex_incbin(path, identifier); - printf("#else\n"); - r |= bin2hex_mmap(path, identifier); - printf("#endif\n\n"); - + if (constructor_attr_supported) { + printf("#if defined(__GNUC__) || defined(__clang__)\n"); + r |= bin2hex_incbin(path, identifier); + printf("#else\n"); + r |= bin2hex_mmap(path, identifier); + printf("#endif\n\n"); + } else { + r |= bin2hex_mmap(path, identifier); + } return r; } @@ -135,19 +146,21 @@ int main(int argc, char *argv[]) } } - printf("#if defined(__GNUC__) || defined(__clang__)\n"); - printf("__attribute__((constructor (101))) static void\n"); - printf("initialize_bin2hex_%016lx(void)\n", (uintptr_t)argv); - printf("{\n"); - for (arg = 1; arg < argc; arg += 2) { - const char *identifier = argv[arg + 1]; - - printf(" %s_value = (struct lwan_value) {.value = (char *)%s_start, " - ".len = (size_t)(%s_end - %s_start)};\n", - identifier, identifier, identifier, identifier); + if (constructor_attr_supported) { + printf("#if defined(__GNUC__) || defined(__clang__)\n"); + printf("__attribute__((constructor (101))) static void\n"); + printf("initialize_bin2hex_%016lx(void)\n", (uintptr_t)argv); + printf("{\n"); + for (arg = 1; arg < argc; arg += 2) { + const char *identifier = argv[arg + 1]; + + printf(" %s_value = (struct lwan_value) {.value = (char *)%s_start, " + ".len = (size_t)(%s_end - %s_start)};\n", + identifier, identifier, identifier, identifier); + } + printf("}\n"); + printf("#endif\n"); } - printf("}\n"); - printf("#endif\n"); return 0; } From ce4161cef11101beb92831bd75ba4b56ec15a599 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 28 Apr 2024 19:59:00 -0700 Subject: [PATCH 2214/2505] Better compliance to RFC7230/RFC3875 when building CGI headers --- src/lib/lwan-private.h | 1 + src/lib/lwan-request.c | 3 +- src/lib/lwan-tables.c | 144 +++++++++++++++++++++++------------------ 3 files changed, 84 insertions(+), 64 deletions(-) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index c27cf3001..8486889f3 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -133,6 +133,7 @@ uint8_t lwan_char_isxdigit(char ch) __attribute__((pure)); uint8_t lwan_char_isdigit(char ch) __attribute__((pure)); uint8_t lwan_char_isalpha(char ch) __attribute__((pure)); uint8_t lwan_char_isalnum(char ch) __attribute__((pure)); +uint8_t lwan_char_iscgiheader(char ch) __attribute__((pure)); static ALWAYS_INLINE __attribute__((pure)) size_t lwan_nextpow2(size_t number) { diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 482fd9f56..b5ad925b0 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -2254,11 +2254,10 @@ void lwan_request_foreach_header_for_cgi(struct lwan_request *request, if (r < 0 || r >= (int)sizeof(header_name)) continue; - /* FIXME: RFC7230/RFC3875 compliance */ for (char *p = header_name; *p; p++) { if (lwan_char_isalpha(*p)) *p &= ~0x20; - else if (!lwan_char_isdigit(*p)) + else if (!lwan_char_iscgiheader(*p)) *p = '_'; } diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index 354dda431..c810daaa7 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -196,6 +196,7 @@ enum { CHAR_PROP_HEX = 1 << 1, CHAR_PROP_DIG = 1 << 2, CHAR_PROP_ALPHA = 1 << 3, + CHAR_PROP_CGI_HEADER = 1 << 4, }; static const uint8_t char_prop_tbl[256] = { @@ -203,68 +204,82 @@ static const uint8_t char_prop_tbl[256] = { ['\t'] = CHAR_PROP_SPACE, ['\n'] = CHAR_PROP_SPACE, ['\r'] = CHAR_PROP_SPACE, - ['0'] = CHAR_PROP_HEX | CHAR_PROP_DIG, - ['1'] = CHAR_PROP_HEX | CHAR_PROP_DIG, - ['2'] = CHAR_PROP_HEX | CHAR_PROP_DIG, - ['3'] = CHAR_PROP_HEX | CHAR_PROP_DIG, - ['4'] = CHAR_PROP_HEX | CHAR_PROP_DIG, - ['5'] = CHAR_PROP_HEX | CHAR_PROP_DIG, - ['6'] = CHAR_PROP_HEX | CHAR_PROP_DIG, - ['7'] = CHAR_PROP_HEX | CHAR_PROP_DIG, - ['8'] = CHAR_PROP_HEX | CHAR_PROP_DIG, - ['9'] = CHAR_PROP_HEX | CHAR_PROP_DIG, - ['a'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA, - ['b'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA, - ['c'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA, - ['d'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA, - ['e'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA, - ['f'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA, - ['g'] = CHAR_PROP_ALPHA, - ['h'] = CHAR_PROP_ALPHA, - ['i'] = CHAR_PROP_ALPHA, - ['j'] = CHAR_PROP_ALPHA, - ['k'] = CHAR_PROP_ALPHA, - ['l'] = CHAR_PROP_ALPHA, - ['m'] = CHAR_PROP_ALPHA, - ['n'] = CHAR_PROP_ALPHA, - ['o'] = CHAR_PROP_ALPHA, - ['p'] = CHAR_PROP_ALPHA, - ['q'] = CHAR_PROP_ALPHA, - ['r'] = CHAR_PROP_ALPHA, - ['s'] = CHAR_PROP_ALPHA, - ['t'] = CHAR_PROP_ALPHA, - ['u'] = CHAR_PROP_ALPHA, - ['v'] = CHAR_PROP_ALPHA, - ['w'] = CHAR_PROP_ALPHA, - ['x'] = CHAR_PROP_ALPHA, - ['y'] = CHAR_PROP_ALPHA, - ['z'] = CHAR_PROP_ALPHA, - ['A'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA, - ['B'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA, - ['C'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA, - ['D'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA, - ['E'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA, - ['F'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA, - ['G'] = CHAR_PROP_ALPHA, - ['H'] = CHAR_PROP_ALPHA, - ['I'] = CHAR_PROP_ALPHA, - ['J'] = CHAR_PROP_ALPHA, - ['K'] = CHAR_PROP_ALPHA, - ['L'] = CHAR_PROP_ALPHA, - ['M'] = CHAR_PROP_ALPHA, - ['N'] = CHAR_PROP_ALPHA, - ['O'] = CHAR_PROP_ALPHA, - ['P'] = CHAR_PROP_ALPHA, - ['Q'] = CHAR_PROP_ALPHA, - ['R'] = CHAR_PROP_ALPHA, - ['S'] = CHAR_PROP_ALPHA, - ['T'] = CHAR_PROP_ALPHA, - ['U'] = CHAR_PROP_ALPHA, - ['V'] = CHAR_PROP_ALPHA, - ['W'] = CHAR_PROP_ALPHA, - ['X'] = CHAR_PROP_ALPHA, - ['Y'] = CHAR_PROP_ALPHA, - ['Z'] = CHAR_PROP_ALPHA, + ['0'] = CHAR_PROP_HEX | CHAR_PROP_DIG | CHAR_PROP_CGI_HEADER, + ['1'] = CHAR_PROP_HEX | CHAR_PROP_DIG | CHAR_PROP_CGI_HEADER, + ['2'] = CHAR_PROP_HEX | CHAR_PROP_DIG | CHAR_PROP_CGI_HEADER, + ['3'] = CHAR_PROP_HEX | CHAR_PROP_DIG | CHAR_PROP_CGI_HEADER, + ['4'] = CHAR_PROP_HEX | CHAR_PROP_DIG | CHAR_PROP_CGI_HEADER, + ['5'] = CHAR_PROP_HEX | CHAR_PROP_DIG | CHAR_PROP_CGI_HEADER, + ['6'] = CHAR_PROP_HEX | CHAR_PROP_DIG | CHAR_PROP_CGI_HEADER, + ['7'] = CHAR_PROP_HEX | CHAR_PROP_DIG | CHAR_PROP_CGI_HEADER, + ['8'] = CHAR_PROP_HEX | CHAR_PROP_DIG | CHAR_PROP_CGI_HEADER, + ['9'] = CHAR_PROP_HEX | CHAR_PROP_DIG | CHAR_PROP_CGI_HEADER, + ['a'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['b'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['c'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['d'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['e'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['f'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['g'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['h'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['i'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['j'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['k'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['l'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['m'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['n'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['o'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['p'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['q'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['r'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['s'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['t'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['u'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['v'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['w'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['x'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['y'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['z'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['A'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['B'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['C'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['D'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['E'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['F'] = CHAR_PROP_HEX | CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['G'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['H'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['I'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['J'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['K'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['L'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['M'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['N'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['O'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['P'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['Q'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['R'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['S'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['T'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['U'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['V'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['W'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['X'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['Y'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['Z'] = CHAR_PROP_ALPHA | CHAR_PROP_CGI_HEADER, + ['!'] = CHAR_PROP_CGI_HEADER, + ['#'] = CHAR_PROP_CGI_HEADER, + ['$'] = CHAR_PROP_CGI_HEADER, + ['%'] = CHAR_PROP_CGI_HEADER, + ['&'] = CHAR_PROP_CGI_HEADER, + ['\''] = CHAR_PROP_CGI_HEADER, + ['*'] = CHAR_PROP_CGI_HEADER, + ['+'] = CHAR_PROP_CGI_HEADER, + ['.'] = CHAR_PROP_CGI_HEADER, + ['^'] = CHAR_PROP_CGI_HEADER, + ['_'] = CHAR_PROP_CGI_HEADER, + ['`'] = CHAR_PROP_CGI_HEADER, + ['|'] = CHAR_PROP_CGI_HEADER, + ['~'] = CHAR_PROP_CGI_HEADER, }; ALWAYS_INLINE uint8_t lwan_char_isspace(char ch) @@ -272,6 +287,11 @@ ALWAYS_INLINE uint8_t lwan_char_isspace(char ch) return char_prop_tbl[(unsigned char)ch] & CHAR_PROP_SPACE; } +ALWAYS_INLINE uint8_t lwan_char_iscgiheader(char ch) +{ + return char_prop_tbl[(unsigned char)ch] & CHAR_PROP_CGI_HEADER; +} + ALWAYS_INLINE uint8_t lwan_char_isxdigit(char ch) { return char_prop_tbl[(unsigned char)ch] & CHAR_PROP_HEX; From d56d5882e6b5b8205cdeec7d547c78ce7125d5ff Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 28 Apr 2024 20:04:49 -0700 Subject: [PATCH 2215/2505] FastCGI module requires chunked encoding So error out if the request is HTTP/1.0. --- src/lib/lwan-mod-fastcgi.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index 13339f284..7fa351edc 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -423,6 +423,13 @@ static void reset_additional_header(void *data) static enum lwan_http_status try_initiating_chunked_response(struct lwan_request *request) { + if (request->flags & REQUEST_IS_HTTP_1_0) { + /* Chunked encoding is not supported in HTTP/1.0. We don't have a + * way to buffer the responses in this module yet, so return an + * error here. */ + return HTTP_NOT_IMPLEMENTED; + } + struct lwan_response *response = &request->response; char *header_start[N_HEADER_START]; char *next_request; From 2878321ffcb432f7a88309dd892619fdf18ef54f Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 29 Apr 2024 19:36:51 -0700 Subject: [PATCH 2216/2505] Print which sanitizer Lwan has been built with in `--help' --- src/bin/lwan/main.c | 12 ++++++++++++ src/cmake/TrySanitizer.cmake | 3 +++ src/cmake/lwan-build-config.h.cmake | 5 +++++ 3 files changed, 20 insertions(+) diff --git a/src/bin/lwan/main.c b/src/bin/lwan/main.c index e5592aedb..b1265f90d 100644 --- a/src/bin/lwan/main.c +++ b/src/bin/lwan/main.c @@ -108,6 +108,18 @@ print_build_time_configuration(void) printf(" syslog"); #endif +#if defined(LWAN_HAVE_UNDEFINED_SANITIZER) + printf(" ubsan"); +#endif + +#if defined(LWAN_HAVE_ADDRESS_SANITIZER) + printf(" asan"); +#endif + +#if defined(LWAN_HAVE_THREAD_SANITIZER) + printf(" tsan"); +#endif + printf(".\n"); } diff --git a/src/cmake/TrySanitizer.cmake b/src/cmake/TrySanitizer.cmake index 5d86cf0db..6b0a3e905 100644 --- a/src/cmake/TrySanitizer.cmake +++ b/src/cmake/TrySanitizer.cmake @@ -12,6 +12,9 @@ macro(try_sanitizer _type) if (HAVE_SANITIZER) message(STATUS "Building with ${_type} sanitizer") set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} ${SANITIZER_FLAG}") + + string(TOUPPER ${_type} SANITIZER_NAME) + set(LWAN_HAVE_${SANITIZER_NAME}_SANITIZER 1) endif () unset(HAVE_SANITIZER) diff --git a/src/cmake/lwan-build-config.h.cmake b/src/cmake/lwan-build-config.h.cmake index e70b3e139..ce6a7d531 100644 --- a/src/cmake/lwan-build-config.h.cmake +++ b/src/cmake/lwan-build-config.h.cmake @@ -71,3 +71,8 @@ /* Valgrind support for coroutines */ #cmakedefine LWAN_HAVE_VALGRIND + +/* Sanitizer */ +#cmakedefine LWAN_HAVE_UNDEFINED_SANITIZER +#cmakedefine LWAN_HAVE_ADDRESS_SANITIZER +#cmakedefine LWAN_HAVE_THREAD_SANITIZER From 2507adea8ebfbcfb313d5d0fa6ef36cf6b2d990e Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 29 Apr 2024 19:38:02 -0700 Subject: [PATCH 2217/2505] Show if Lwan has been built in debugging mode in `--help' --- src/bin/lwan/main.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/bin/lwan/main.c b/src/bin/lwan/main.c index b1265f90d..0ff6f0f56 100644 --- a/src/bin/lwan/main.c +++ b/src/bin/lwan/main.c @@ -120,6 +120,10 @@ print_build_time_configuration(void) printf(" tsan"); #endif +#if !defined(NDEBUG) + printf(" debug"); +#endif + printf(".\n"); } From b099506d1a39a3637e2b01498459e82b9c8669f5 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 29 Apr 2024 19:38:36 -0700 Subject: [PATCH 2218/2505] Tell whether Lwan is using libucontext or the builtin coro switcher --- src/bin/lwan/main.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/bin/lwan/main.c b/src/bin/lwan/main.c index 0ff6f0f56..46f7550c0 100644 --- a/src/bin/lwan/main.c +++ b/src/bin/lwan/main.c @@ -85,7 +85,9 @@ print_build_time_configuration(void) #endif #if defined(LWAN_HAVE_LIBUCONTEXT) - printf(" libucontext"); + printf(" libucontext-coroutine"); +#else + printf(" builtin-coroutine"); #endif #if defined(LWAN_HAVE_EPOLL) From bdaf9d498efd971b044679ef90a5e8951c9b4f71 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 7 May 2024 17:46:44 -0700 Subject: [PATCH 2219/2505] Add .mailmap file --- .mailmap | 1 + 1 file changed, 1 insertion(+) create mode 100644 .mailmap diff --git a/.mailmap b/.mailmap new file mode 100644 index 000000000..ec27b915d --- /dev/null +++ b/.mailmap @@ -0,0 +1 @@ +L. Pereira \ No newline at end of file From 10e6865c40ebe86007172b3f013931b5407c090d Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 8 May 2024 18:20:15 -0700 Subject: [PATCH 2220/2505] Revert "Factor out bitptr adjustment" This reverts commit a3bdbfc59c6545e4b3fd04ebaea1be583e6098ec. --- src/lib/lwan-h2-huffman.c | 10 ++++++---- src/scripts/gentables.py | 7 +++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/lib/lwan-h2-huffman.c b/src/lib/lwan-h2-huffman.c index 5265ba33f..81816edf7 100644 --- a/src/lib/lwan-h2-huffman.c +++ b/src/lib/lwan-h2-huffman.c @@ -276,18 +276,20 @@ struct bit_reader { static inline uint8_t peek_byte(struct bit_reader *reader) { if (reader->bitcount < 8) { - const uint64_t adjust = reader->bitcount + (reader->bitcount & 1); if (reader->total_bitcount >= 64) { reader->bitbuf |= read64be(reader->bitptr) >> reader->bitcount; - reader->bitptr += (63 - adjust) >> 3; + reader->bitptr += + (63 - reader->bitcount + (reader->bitcount & 1)) >> 3; reader->bitcount |= 56; } else if (reader->total_bitcount >= 32) { reader->bitbuf |= read32be(reader->bitptr) >> reader->bitcount; - reader->bitptr += (31 - adjust) >> 3; + reader->bitptr += + (31 - reader->bitcount + (reader->bitcount & 1)) >> 3; reader->bitcount |= 24; } else { reader->bitbuf |= *reader->bitptr >> reader->bitcount; - reader->bitptr += (7 - adjust) >> 3; + reader->bitptr += + (7 - reader->bitcount + (reader->bitcount & 1)) >> 3; reader->bitcount |= 8; } } diff --git a/src/scripts/gentables.py b/src/scripts/gentables.py index f6a1cc87e..2642bfb1c 100755 --- a/src/scripts/gentables.py +++ b/src/scripts/gentables.py @@ -182,18 +182,17 @@ def generate_level(level, next_table): static inline uint8_t peek_byte(struct bit_reader *reader) { if (reader->bitcount < 8) { - const uint64_t adjust = reader->bitcount + (reader->bitcount & 1); if (reader->total_bitcount >= 64) { reader->bitbuf |= read64be(reader->bitptr) >> reader->bitcount; - reader->bitptr += (63 - adjust) >> 3; + reader->bitptr += (63 - reader->bitcount + (reader->bitcount & 1)) >> 3; reader->bitcount |= 56; } else if (reader->total_bitcount >= 32) { reader->bitbuf |= read32be(reader->bitptr) >> reader->bitcount; - reader->bitptr += (31 - adjust) >> 3; + reader->bitptr += (31 - reader->bitcount + (reader->bitcount & 1)) >> 3; reader->bitcount |= 24; } else { reader->bitbuf |= *reader->bitptr >> reader->bitcount; - reader->bitptr += (7 - adjust) >> 3; + reader->bitptr += (7 - reader->bitcount + (reader->bitcount & 1)) >> 3; reader->bitcount |= 8; } } From 7d2ae74fb5102ac162dce633c4ce28b6f0dad0d5 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 8 May 2024 18:23:27 -0700 Subject: [PATCH 2221/2505] Fix crash in websocket frame unmasking Also simplify this a bit further. --- src/lib/lwan-websocket.c | 82 +++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 43 deletions(-) diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index 3fb91bd59..4f5b6d022 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -150,52 +150,58 @@ static size_t get_frame_length(struct lwan_request *request, uint16_t header) static void unmask(char *msg, size_t msg_len, char mask[static 4]) { + uint32_t mask32; + /* TODO: handle alignment of `msg` to use (at least) NT loads * as we're rewriting msg anyway. (NT writes aren't that * useful as the unmasked value will be used right after.) */ #if defined(__AVX2__) - const __m256i mask256 = - _mm256_castps_si256(_mm256_broadcast_ss((const float *)mask)); - for (; msg_len >= 32; msg_len -= 32, msg += 32) { - __m256i v = _mm256_lddqu_si256((const __m256i *)msg); - _mm256_storeu_si256((__m256i *)msg, _mm256_xor_si256(v, mask256)); - } -#endif - -#if defined(__SSE2__) -#if defined(__AVX2__) - const __m128i mask128 = _mm256_extracti128_si256(mask256, 0); -#elif defined(__SSE3__) - const __m128i mask128 = _mm_lddqu_si128((const float *)mask); -#else - const __m128i mask128 = _mm_loadu_si128((const __m128i *)mask); -#endif - for (; msg_len >= 16; msg_len -= 16, msg += 16) { -#if defined(__SSE3__) - __m128i v = _mm_lddqu_si128((const __m128i *)msg); -#else - __m128i v = _mm_loadu_si128((const __m128i *)msg); -#endif + if (msg_len >= 32) { + const __m256i mask256 = + _mm256_castps_si256(_mm256_broadcast_ss((const float *)mask)); + do { + const __m256i v = _mm256_lddqu_si256((const __m256i *)msg); + _mm256_storeu_si256((__m256i *)msg, _mm256_xor_si256(v, mask256)); + msg += 32; + msg_len -= 32; + } while (msg_len >= 32); + + if (msg_len >= 16) { + const __m128i mask128 = _mm256_extracti128_si256(mask256, 0); + const __m128i v = _mm_lddqu_si128((const __m128i *)msg); + _mm_storeu_si128((__m128i *)msg, _mm_xor_si128(v, mask128)); + msg += 16; + msg_len -= 16; + } - _mm_storeu_si128((__m128i *)msg, _mm_xor_si128(v, mask128)); + mask32 = (uint32_t)_mm256_extract_epi32(mask256, 0); + } else { + mask32 = string_as_uint32(mask); } -#endif +#elif defined(__SSE3__) + if (msg_len >= 16) { + const __m128i mask128 = _mm_lddqu_si128((const float *)mask); -#if __SIZEOF_POINTER__ == 8 + do { + const __m128i v = _mm_lddqu_si128((const __m128i *)msg); + _mm_storeu_si128((__m128i *)msg, _mm_xor_si128(v, mask128)); + msg += 16; + msg_len -= 16; + } while (msg_len >= 16); -#if defined(__SSE_4_1__) - /* We're far away enough from the AVX2 path that it's - * probably better to use mask128 instead of mask256 - * here. */ - const uint64_t mask64 = _mm_extract_epi64(mask128, 0); + mask32 = _mm_extract_epi32(mask128, 0); + } else { + mask32 = string_as_uint32(mask); + } #else - const uint32_t mask32 = string_as_uint32(mask); - const uint64_t mask64 = (uint64_t)mask32 << 32 | (uint64_t)mask32; -#define HAS_MASK32 + mask32 = string_as_uint32(mask); #endif +#if __SIZEOF_POINTER__ == 8 if (msg_len >= 8) { + const uint64_t mask64 = (uint64_t)mask32 << 32 | (uint64_t)mask32; + do { uint64_t v = string_as_uint64(msg); v ^= mask64; @@ -206,15 +212,6 @@ static void unmask(char *msg, size_t msg_len, char mask[static 4]) #endif if (msg_len >= 4) { -#if defined(HAS_MASK32) - /* do nothing */ -#elif __SIZEOF_POINTER__ == 8 - const uint32_t mask32 = (uint32_t)mask64; -#elif defined(__SSE_4_1__) - const uint32_t mask32 = _mm_extract_epi32(mask128, 0); -#else - const uint32_t mask32 = string_as_uint32(mask); -#endif do { uint32_t v = string_as_uint32(msg); v ^= mask32; @@ -234,7 +231,6 @@ static void unmask(char *msg, size_t msg_len, char mask[static 4]) default: __builtin_unreachable(); } -#undef HAS_MASK32 } static void send_websocket_pong(struct lwan_request *request, uint16_t header) From 6cc861e36567c64612a12588f72501513d5774d9 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 8 May 2024 18:30:20 -0700 Subject: [PATCH 2222/2505] Allow cache item creation callback to take a creation context pointer This simplifies a lot of code, especially the smolsite and pastebin samples! --- src/lib/lwan-cache.c | 29 +++++++++--- src/lib/lwan-cache.h | 11 +++-- src/lib/lwan-http-authorize.c | 3 +- src/lib/lwan-mod-fastcgi.c | 3 +- src/lib/lwan-mod-lua.c | 5 +- src/lib/lwan-mod-serve-files.c | 7 ++- src/samples/freegeoip/freegeoip.c | 11 +++-- src/samples/pastebin/main.c | 47 ++---------------- src/samples/smolsite/main.c | 68 +++++---------------------- src/samples/techempower/techempower.c | 5 +- 10 files changed, 69 insertions(+), 120 deletions(-) diff --git a/src/lib/lwan-cache.c b/src/lib/lwan-cache.c index 5bcd0e5d7..d514e6ad7 100644 --- a/src/lib/lwan-cache.c +++ b/src/lib/lwan-cache.c @@ -190,8 +190,9 @@ void cache_destroy(struct cache *cache) free(cache); } -struct cache_entry *cache_get_and_ref_entry(struct cache *cache, - const void *key, int *error) +struct cache_entry *cache_get_and_ref_entry_with_ctx(struct cache *cache, + const void *key, void *create_ctx, + int *error) { struct cache_entry *entry; char *key_copy; @@ -245,7 +246,7 @@ struct cache_entry *cache_get_and_ref_entry(struct cache *cache, } } - entry = cache->cb.create_entry(key, cache->cb.context); + entry = cache->cb.create_entry(key, cache->cb.context, create_ctx); if (UNLIKELY(!entry)) { *error = ECANCELED; cache->key.free(key_copy); @@ -299,6 +300,12 @@ struct cache_entry *cache_get_and_ref_entry(struct cache *cache, return entry; } +ALWAYS_INLINE struct cache_entry * +cache_get_and_ref_entry(struct cache *cache, const void *key, int *error) +{ + return cache_get_and_ref_entry_with_ctx(cache, key, NULL, error); +} + void cache_entry_unref(struct cache *cache, struct cache_entry *entry) { assert(entry); @@ -434,9 +441,10 @@ static void cache_entry_unref_defer(void *data1, void *data2) cache_entry_unref((struct cache *)data1, (struct cache_entry *)data2); } -struct cache_entry *cache_coro_get_and_ref_entry(struct cache *cache, - struct coro *coro, - const void *key) +struct cache_entry *cache_coro_get_and_ref_entry_with_ctx(struct cache *cache, + struct coro *coro, + const void *key, + void *create_ctx) { /* If a cache is read-only, cache_get_and_ref_entry() should be * used directly. */ @@ -444,7 +452,8 @@ struct cache_entry *cache_coro_get_and_ref_entry(struct cache *cache, for (int tries = GET_AND_REF_TRIES; tries; tries--) { int error; - struct cache_entry *ce = cache_get_and_ref_entry(cache, key, &error); + struct cache_entry *ce = + cache_get_and_ref_entry_with_ctx(cache, key, &error, create_ctx); if (LIKELY(ce)) { /* @@ -469,6 +478,12 @@ struct cache_entry *cache_coro_get_and_ref_entry(struct cache *cache, return NULL; } +ALWAYS_INLINE struct cache_entry *cache_coro_get_and_ref_entry( + struct cache *cache, struct coro *coro, const void *key) +{ + return cache_coro_get_and_ref_entry_with_ctx(cache, coro, key, NULL); +} + void cache_make_read_only(struct cache *cache) { cache->flags |= READ_ONLY; diff --git a/src/lib/lwan-cache.h b/src/lib/lwan-cache.h index 066bdfc1a..4956c73d2 100644 --- a/src/lib/lwan-cache.h +++ b/src/lib/lwan-cache.h @@ -33,9 +33,10 @@ struct cache_entry { }; typedef struct cache_entry *(*cache_create_entry_cb)(const void *key, - void *context); + void *cache_ctx, + void *create_ctx); typedef void (*cache_destroy_entry_cb)(struct cache_entry *entry, - void *context); + void *cache_ctx); typedef struct hash *(*hash_create_func_cb)(void (*)(void *), void (*)(void *)); struct cache; @@ -53,8 +54,12 @@ void cache_destroy(struct cache *cache); struct cache_entry *cache_get_and_ref_entry(struct cache *cache, const void *key, int *error); -void cache_entry_unref(struct cache *cache, struct cache_entry *entry); +struct cache_entry *cache_get_and_ref_entry_with_ctx(struct cache *cache, + const void *key, void *create_ctx, int *error); struct cache_entry *cache_coro_get_and_ref_entry(struct cache *cache, struct coro *coro, const void *key); +struct cache_entry *cache_coro_get_and_ref_entry_with_ctx(struct cache *cache, + struct coro *coro, const void *key, void *create_ctx); +void cache_entry_unref(struct cache *cache, struct cache_entry *entry); void cache_make_read_only(struct cache *cache); diff --git a/src/lib/lwan-http-authorize.c b/src/lib/lwan-http-authorize.c index 99e561b8f..cae2a97fd 100644 --- a/src/lib/lwan-http-authorize.c +++ b/src/lib/lwan-http-authorize.c @@ -45,7 +45,8 @@ static void zero_and_free(void *str) } static struct cache_entry * -create_realm_file(const void *key, void *context __attribute__((unused))) +create_realm_file(const void *key, void *context __attribute__((unused)), + void *create_contex __attribute__((unused))) { struct realm_password_file_t *rpf = malloc(sizeof(*rpf)); const struct config_line *l; diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index 7fa351edc..05ba4d8ce 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -162,7 +162,8 @@ add_int_param(struct lwan_strbuf *strbuf, const char *key, ssize_t value) return add_param_len(strbuf, key, strlen(key), p, len); } -static struct cache_entry *create_script_name(const void *keyptr, void *context) +static struct cache_entry * +create_script_name(const void *keyptr, void *context, void *create_contex) { struct private_data *pd = context; struct script_name_cache_entry *entry; diff --git a/src/lib/lwan-mod-lua.c b/src/lib/lwan-mod-lua.c index e9778a804..ed79555f4 100644 --- a/src/lib/lwan-mod-lua.c +++ b/src/lib/lwan-mod-lua.c @@ -48,9 +48,10 @@ struct lwan_lua_state { }; static struct cache_entry *state_create(const void *key __attribute__((unused)), - void *context) + void *cache_ctx, + void *create_ctx __attribute__((unused))) { - struct lwan_lua_priv *priv = context; + struct lwan_lua_priv *priv = cache_ctx; struct lwan_lua_state *state = malloc(sizeof(*state)); if (UNLIKELY(!state)) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 5fc7b959b..8baafcb37 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -798,9 +798,12 @@ static void destroy_cache_entry(struct cache_entry *entry, free(fce); } -static struct cache_entry *create_cache_entry(const void *key, void *context) +static struct cache_entry *create_cache_entry(const void *key, + void *cache_ctx, + void *create_ctx + __attribute__((unused))) { - struct serve_files_priv *priv = context; + struct serve_files_priv *priv = cache_ctx; struct file_cache_entry *fce; struct stat st; const struct cache_funcs *funcs; diff --git a/src/samples/freegeoip/freegeoip.c b/src/samples/freegeoip/freegeoip.c index 5bd96330e..b398c88af 100644 --- a/src/samples/freegeoip/freegeoip.c +++ b/src/samples/freegeoip/freegeoip.c @@ -234,7 +234,8 @@ static ALWAYS_INLINE char *text_column_helper(sqlite3_stmt *stmt, int ind) } static struct cache_entry *create_ipinfo(const void *key, - void *context __attribute__((unused))) + void *cache_ctx __attribute__((unused)), + void *create_ctx __attribute__((unused))) { sqlite3_stmt *stmt; struct ip_info *ip_info = NULL; @@ -291,10 +292,10 @@ static struct cache_entry *create_ipinfo(const void *key, } #if QUERIES_PER_HOUR != 0 -static struct cache_entry *create_query_limit(const void *key - __attribute__((unused)), - void *context - __attribute__((unused))) +static struct cache_entry * +create_query_limit(const void *key __attribute__((unused)), + void *cache_ctx __attribute__((unused)), + void *create_ctx __attribute__((unused))) { struct query_limit *entry = malloc(sizeof(*entry)); if (LIKELY(entry)) diff --git a/src/samples/pastebin/main.c b/src/samples/pastebin/main.c index 60911015f..5074e3c85 100644 --- a/src/samples/pastebin/main.c +++ b/src/samples/pastebin/main.c @@ -39,31 +39,10 @@ struct paste { char value[]; }; -static struct hash *pending_pastes(void) +static struct cache_entry * +create_paste(const void *key, void *cache_ctx, void *create_ctx) { - /* This is kind of a hack: we can't have just a single thread-local - * for the current thread's pending paste because a coroutine might - * yield while trying to obtain an item from the pastes cache, which - * would override that value. Store these in a thread-local hash - * table instead, which can be consulted by the create_paste() function. - * Items are removed from this table in a defer handler. */ - static __thread struct hash *pending_pastes; - - if (!pending_pastes) { - pending_pastes = hash_int64_new(NULL, NULL); - if (!pending_pastes) { - lwan_status_critical( - "Could not allocate pending pastes hash table"); - } - } - - return pending_pastes; -} - -static struct cache_entry *create_paste(const void *key, void *context) -{ - const struct lwan_value *body = - hash_find(pending_pastes(), (const void *)key); + const struct lwan_value *body = create_ctx; size_t alloc_size; if (!body) @@ -86,11 +65,6 @@ static void destroy_paste(struct cache_entry *entry, void *context) free(entry); } -static void remove_from_pending(void *data) -{ - hash_del(pending_pastes(), data); -} - static enum lwan_http_status post_paste(struct lwan_request *request, struct lwan_response *response) { @@ -106,19 +80,8 @@ static enum lwan_http_status post_paste(struct lwan_request *request, key = (void *)(uintptr_t)lwan_random_uint64(); } while (!key); - switch (hash_add_unique(pending_pastes(), key, body)) { - case -EEXIST: - continue; - case 0: - break; - default: - return HTTP_UNAVAILABLE; - } - - coro_defer(request->conn->coro, remove_from_pending, key); - - struct cache_entry *paste = - cache_coro_get_and_ref_entry(pastes, request->conn->coro, key); + struct cache_entry *paste = cache_coro_get_and_ref_entry_with_ctx( + pastes, request->conn->coro, key, (void *)body); if (paste) { const char *host_hdr = lwan_request_get_host(request); diff --git a/src/samples/smolsite/main.c b/src/samples/smolsite/main.c index a608306a2..168640292 100644 --- a/src/samples/smolsite/main.c +++ b/src/samples/smolsite/main.c @@ -95,26 +95,6 @@ static void calc_hash(struct lwan_value value, digest[17], digest[18], digest[19]); } -static struct hash *pending_sites(void) -{ - /* This is kind of a hack: we can't have just a single thread-local - * for the current thread's pending site because a coroutine might - * yield while trying to obtain an item from the sites cache, which - * would override that value. Store these in a thread-local hash - * table instead, which can be consulted by the create_site() function. - * Items are removed from this table in a defer handler. */ - static __thread struct hash *pending_sites; - - if (!pending_sites) { - pending_sites = hash_str_new(free, NULL); - if (!pending_sites) { - lwan_status_critical("Could not allocate pending sites hash table"); - } - } - - return pending_sites; -} - static int file_cb( JZFile *zip, int idx, JZFileHeader *header, char *filename, void *user_data) { @@ -206,11 +186,11 @@ static bool generate_qr_code_gif(const char *b64, struct lwan_strbuf *output) return true; } -static struct cache_entry *create_site(const void *key, void *context) +static struct cache_entry * +create_site(const void *key, void *context, void *create_ctx) { struct lwan_strbuf qr_code = LWAN_STRBUF_STATIC_INIT; - const struct lwan_value *base64_encoded = - hash_find(pending_sites(), (const void *)key); + const struct lwan_value *base64_encoded = create_ctx; unsigned char *decoded = NULL; size_t decoded_len; @@ -235,7 +215,8 @@ static struct cache_entry *create_site(const void *key, void *context) generate_qr_code_gif((const char *)base64_encoded->value, &qr_code); site->qr_code = qr_code; - site->zipped = (struct lwan_value) {.value = (char *)decoded, .len = decoded_len}; + site->zipped = + (struct lwan_value){.value = (char *)decoded, .len = decoded_len}; FILE *zip_mem = fmemopen(decoded, decoded_len, "rb"); if (!zip_mem) @@ -284,12 +265,6 @@ static void destroy_site(struct cache_entry *entry, void *context) free(site); } -static void remove_from_pending_defer(void *data) -{ - char *key = data; - hash_del(pending_sites(), key); -} - LWAN_HANDLER_ROUTE(view_root, "/") { char digest_str[41]; @@ -301,10 +276,10 @@ LWAN_HANDLER_ROUTE(view_root, "/") {"Cache-Control", "no-cache, max-age=0, private, no-transform"}, {}, }; - response->headers = coro_memdup(request->conn->coro, - redir_headers, + response->headers = coro_memdup(request->conn->coro, redir_headers, sizeof(redir_headers)); - return response->headers ? HTTP_TEMPORARY_REDIRECT : HTTP_INTERNAL_ERROR; + return response->headers ? HTTP_TEMPORARY_REDIRECT + : HTTP_INTERNAL_ERROR; } /* Lwan gives us a percent-decoded URL, but '+' is part of the Base64 @@ -314,29 +289,10 @@ LWAN_HANDLER_ROUTE(view_root, "/") calc_hash(request->url, digest_str); - site = (struct site *)cache_coro_get_and_ref_entry( - sites, request->conn->coro, digest_str); - if (!site) { - char *key = strdup(digest_str); - if (!key) { - lwan_status_debug("a"); - return HTTP_INTERNAL_ERROR; - } - - if (UNLIKELY(hash_add_unique(pending_sites(), key, &request->url))) { - lwan_status_debug("b"); - return HTTP_INTERNAL_ERROR; - } - - coro_defer(request->conn->coro, remove_from_pending_defer, key); - - site = (struct site *)cache_coro_get_and_ref_entry( - sites, request->conn->coro, key); - if (UNLIKELY(!site)) { - lwan_status_debug("c"); - return HTTP_INTERNAL_ERROR; -} - } + site = (struct site *)cache_coro_get_and_ref_entry_with_ctx( + sites, request->conn->coro, digest_str, &request->url); + if (!site) + return HTTP_INTERNAL_ERROR; response->mime_type = "text/html; charset=utf-8"; diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index 4782874ed..f94223625 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -283,7 +283,10 @@ struct db_json_cached { struct db_json db_json; }; -static struct cache_entry *cached_queries_new(const void *keyptr, void *context) +static struct cache_entry *cached_queries_new(const void *keyptr, + void *context, + void *create_ctx + __attribute__((unused))) { struct db_json_cached *entry; struct db_stmt *stmt; From 945fd7807126fd525d5672140f0725ce84233f0b Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 8 May 2024 18:31:34 -0700 Subject: [PATCH 2223/2505] Add notification file descriptor to pubsub subscription This uses eventfd if available, otherwise uses a pipe. --- CMakeLists.txt | 1 + src/cmake/lwan-build-config.h.cmake | 1 + src/lib/lwan-pubsub.c | 63 ++++++++++++++++++++++++++++- src/lib/lwan-pubsub.h | 2 + 4 files changed, 66 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f4054c1eb..39f6dc64c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -175,6 +175,7 @@ set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) check_function_exists(get_current_dir_name LWAN_HAVE_GET_CURRENT_DIR_NAME) check_symbol_exists(reallocarray stdlib.h LWAN_HAVE_REALLOCARRAY) +check_symbol_exists(eventfd sys/eventfd.h LWAN_HAVE_EVENTFD) check_function_exists(mempcpy LWAN_HAVE_MEMPCPY) check_function_exists(memrchr LWAN_HAVE_MEMRCHR) check_function_exists(pipe2 LWAN_HAVE_PIPE2) diff --git a/src/cmake/lwan-build-config.h.cmake b/src/cmake/lwan-build-config.h.cmake index ce6a7d531..6b50d454f 100644 --- a/src/cmake/lwan-build-config.h.cmake +++ b/src/cmake/lwan-build-config.h.cmake @@ -48,6 +48,7 @@ #cmakedefine LWAN_HAVE_SO_INCOMING_CPU #cmakedefine LWAN_HAVE_SYSLOG #cmakedefine LWAN_HAVE_STPCPY +#cmakedefine LWAN_HAVE_EVENTFD /* Compiler builtins for specific CPU instruction support */ #cmakedefine LWAN_HAVE_BUILTIN_CLZLL diff --git a/src/lib/lwan-pubsub.c b/src/lib/lwan-pubsub.c index a8b46e269..7a1c83087 100644 --- a/src/lib/lwan-pubsub.c +++ b/src/lib/lwan-pubsub.c @@ -19,9 +19,16 @@ */ #define _GNU_SOURCE +#include +#include #include #include -#include + +#if defined(LWAN_HAVE_EVENTFD) +#include +#else +#include +#endif #include "list.h" #include "ringbuffer.h" @@ -49,6 +56,8 @@ struct lwan_pubsub_subscriber { pthread_mutex_t lock; struct list_head msg_refs; + + int event_fd[2]; }; static void lwan_pubsub_queue_init(struct lwan_pubsub_subscriber *sub) @@ -196,6 +205,22 @@ static bool lwan_pubsub_publish_value(struct lwan_pubsub_topic *topic, ATOMIC_DEC(msg->refcount); } pthread_mutex_unlock(&sub->lock); + + if (sub->event_fd[1] < 0) { + continue; + } + while (true) { + ssize_t written = + write(sub->event_fd[1], &(uint64_t){1}, sizeof(uint64_t)); + + if (LIKELY(written == (ssize_t)sizeof(uint64_t))) + break; + + if (UNLIKELY(written < 0)) { + if (errno == EINTR) + continue; + } + } } pthread_mutex_unlock(&topic->lock); @@ -250,6 +275,9 @@ lwan_pubsub_subscribe(struct lwan_pubsub_topic *topic) if (!sub) return NULL; + sub->event_fd[0] = -1; + sub->event_fd[1] = -1; + pthread_mutex_init(&sub->lock, NULL); lwan_pubsub_queue_init(sub); @@ -268,6 +296,11 @@ struct lwan_pubsub_msg *lwan_pubsub_consume(struct lwan_pubsub_subscriber *sub) msg = lwan_pubsub_queue_get(sub); pthread_mutex_unlock(&sub->lock); + if (msg && sub->event_fd[0] >= 0) { + uint64_t discard; + LWAN_NO_DISCARD(read(sub->event_fd[0], &discard, sizeof(uint64_t))); + } + return msg; } @@ -289,6 +322,14 @@ static void lwan_pubsub_unsubscribe_internal(struct lwan_pubsub_topic *topic, pthread_mutex_unlock(&sub->lock); pthread_mutex_destroy(&sub->lock); + + if (sub->event_fd[0] != sub->event_fd[1]) { + close(sub->event_fd[0]); + close(sub->event_fd[1]); + } else if (LIKELY(sub->event_fd[0] >= 0)) { + close(sub->event_fd[0]); + } + free(sub); } @@ -302,3 +343,23 @@ const struct lwan_value *lwan_pubsub_msg_value(const struct lwan_pubsub_msg *msg { return &msg->value; } + +int lwan_pubsub_get_notification_fd(struct lwan_pubsub_subscriber *sub) +{ + if (sub->event_fd[0] < 0) { +#if defined(LWAN_HAVE_EVENTFD) + int efd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK | EFD_SEMAPHORE); + if (efd < 0) { + return -1; + } + + sub->event_fd[0] = sub->event_fd[1] = efd; +#else + if (pipe2(sub->event_fd, O_CLOEXEC | O_NONBLOCK) < 0) { + return -1; + } +#endif + } + + return sub->event_fd[0]; +} diff --git a/src/lib/lwan-pubsub.h b/src/lib/lwan-pubsub.h index cb33f7724..c3e1c78fb 100644 --- a/src/lib/lwan-pubsub.h +++ b/src/lib/lwan-pubsub.h @@ -44,3 +44,5 @@ void lwan_pubsub_unsubscribe(struct lwan_pubsub_topic *topic, struct lwan_pubsub_msg *lwan_pubsub_consume(struct lwan_pubsub_subscriber *sub); const struct lwan_value *lwan_pubsub_msg_value(const struct lwan_pubsub_msg *msg); void lwan_pubsub_msg_done(struct lwan_pubsub_msg *msg); + +int lwan_pubsub_get_notification_fd(struct lwan_pubsub_subscriber *sub); From b530ed6e7f3744788967670ff0aa3c6abaa18461 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 8 May 2024 18:36:41 -0700 Subject: [PATCH 2224/2505] Allow async/await on multiple file descriptors Provide two new public APIs: lwan_request_awaitv_any() and lwan_request_awaitv_all(), which, respectively, will await for an operation on at least one of the awaited file descriptors, returning the one that unblocked the coroutine, and for all the awaited file descriptors. The APIs are experimental but you can already see how much it improves the chat implementation of the websockets sample: now, instead of having to poll both the websocket and the pub/sub subscription, and wait a few milliseconds, it now instantaneously wakes up when there's data in either one of them, and processes only what has data. The chat now feels like a proper chat app (well, within reason for that crude app, but you get the idea). (As a side effect: we now send websocket pings periodically.) There's a lot to clean up here, but I'm tired and this will be done eventually. --- src/lib/liblwan.sym | 1 + src/lib/lwan-private.h | 2 + src/lib/lwan-request.c | 32 ------ src/lib/lwan-strbuf.c | 1 + src/lib/lwan-thread.c | 190 ++++++++++++++++++++++++++++++++--- src/lib/lwan-tq.c | 9 +- src/lib/lwan-websocket.c | 115 +++++++++++++++++---- src/lib/lwan.h | 15 ++- src/samples/websocket/main.c | 49 ++++----- 9 files changed, 323 insertions(+), 91 deletions(-) diff --git a/src/lib/liblwan.sym b/src/lib/liblwan.sym index 5b5227d6d..d0f59d554 100644 --- a/src/lib/liblwan.sym +++ b/src/lib/liblwan.sym @@ -73,6 +73,7 @@ global: lwan_handler_info_*; lwan_request_await_*; + lwan_request_awaitv_*; lwan_request_async_*; lwan_straitjacket_enforce*; diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 8486889f3..ed87c88a8 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -283,3 +283,5 @@ void lwan_request_foreach_header_for_cgi(struct lwan_request *request, size_t value_len, void *user_data), void *user_data); + +bool lwan_send_websocket_ping_for_tq(struct lwan_connection *conn); diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index b5ad925b0..424c1c9f6 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -2083,38 +2083,6 @@ __attribute__((used)) int fuzz_parse_http_request(const uint8_t *data, } #endif -static inline int64_t -make_async_yield_value(int fd, enum lwan_connection_coro_yield event) -{ - return (int64_t)(((uint64_t)fd << 32 | event)); -} - -static inline struct lwan_connection *async_await_fd( - struct coro *coro, int fd, enum lwan_connection_coro_yield events) -{ - assert(events >= CONN_CORO_ASYNC_AWAIT_READ && - events <= CONN_CORO_ASYNC_AWAIT_READ_WRITE); - - int64_t from_coro = coro_yield(coro, make_async_yield_value(fd, events)); - return (struct lwan_connection *)(intptr_t)from_coro; -} - -struct lwan_connection *lwan_request_await_read(struct lwan_request *r, int fd) -{ - return async_await_fd(r->conn->coro, fd, CONN_CORO_ASYNC_AWAIT_READ); -} - -struct lwan_connection *lwan_request_await_write(struct lwan_request *r, int fd) -{ - return async_await_fd(r->conn->coro, fd, CONN_CORO_ASYNC_AWAIT_WRITE); -} - -struct lwan_connection *lwan_request_await_read_write(struct lwan_request *r, - int fd) -{ - return async_await_fd(r->conn->coro, fd, CONN_CORO_ASYNC_AWAIT_READ_WRITE); -} - ssize_t lwan_request_async_read_flags( struct lwan_request *request, int fd, void *buf, size_t len, int flags) { diff --git a/src/lib/lwan-strbuf.c b/src/lib/lwan-strbuf.c index 29772969a..3678ff61a 100644 --- a/src/lib/lwan-strbuf.c +++ b/src/lib/lwan-strbuf.c @@ -27,6 +27,7 @@ #include #include #include +#include #include "lwan-private.h" diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 42078c3aa..6525e2c6f 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -559,7 +559,7 @@ conn_flags_to_epoll_events(enum lwan_connection_flags flags) return EPOLL_EVENTS(flags); } -static void update_epoll_flags(const struct timeout_queue *tq, +static void update_epoll_flags(const struct lwan *lwan, struct lwan_connection *conn, int epoll_fd, enum lwan_connection_coro_yield yield_result) @@ -609,7 +609,7 @@ static void update_epoll_flags(const struct timeout_queue *tq, struct epoll_event event = {.events = conn_flags_to_epoll_events(conn->flags), .data.ptr = conn}; - int fd = lwan_connection_get_fd(tq->lwan, conn); + int fd = lwan_connection_get_fd(lwan, conn); if (UNLIKELY(epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &event) < 0)) lwan_status_perror("epoll_ctl"); @@ -619,7 +619,11 @@ static void unasync_await_conn(void *data1, void *data2) { struct lwan_connection *async_fd_conn = data1; - async_fd_conn->flags &= ~(CONN_ASYNC_AWAIT | CONN_HUNG_UP); + async_fd_conn->flags &= + ~(CONN_ASYNC_AWAIT | CONN_HUNG_UP | CONN_ASYNC_AWAIT_MULTIPLE); + assert(async_fd_conn->parent); + async_fd_conn->parent->flags &= ~CONN_ASYNC_AWAIT_MULTIPLE; + async_fd_conn->thread = data2; /* If this file descriptor number is used again in the future as an HTTP @@ -635,9 +639,9 @@ static void unasync_await_conn(void *data1, void *data2) } static enum lwan_connection_coro_yield -resume_async(const struct timeout_queue *tq, +resume_async(const struct lwan *l, enum lwan_connection_coro_yield yield_result, - int64_t from_coro, + int await_fd, struct lwan_connection *conn, int epoll_fd) { @@ -646,7 +650,6 @@ resume_async(const struct timeout_queue *tq, [CONN_CORO_ASYNC_AWAIT_WRITE] = CONN_EVENTS_WRITE, [CONN_CORO_ASYNC_AWAIT_READ_WRITE] = CONN_EVENTS_READ_WRITE, }; - int await_fd = (int)((uint64_t)from_coro >> 32); enum lwan_connection_flags flags; int op; @@ -656,7 +659,7 @@ resume_async(const struct timeout_queue *tq, flags = to_connection_flags[yield_result]; - struct lwan_connection *await_fd_conn = &tq->lwan->conns[await_fd]; + struct lwan_connection *await_fd_conn = &l->conns[await_fd]; if (LIKELY(await_fd_conn->flags & CONN_ASYNC_AWAIT)) { if (LIKELY((await_fd_conn->flags & CONN_EVENTS_MASK) == flags)) return CONN_CORO_SUSPEND; @@ -697,6 +700,168 @@ resume_async(const struct timeout_queue *tq, return CONN_CORO_ABORT; } +struct flag_update { + unsigned int num_awaiting; + enum lwan_connection_coro_yield request_conn_yield; +}; + +static struct flag_update +update_flags_for_async_awaitv(struct lwan_request *r, struct lwan *l, va_list ap) +{ + int epoll_fd = r->conn->thread->epoll_fd; + struct flag_update update = {.num_awaiting = 0, + .request_conn_yield = CONN_CORO_YIELD}; + + while (true) { + int await_fd = va_arg(ap, int); + if (await_fd < 0) { + return update; + } + + enum lwan_connection_coro_yield events = + va_arg(ap, enum lwan_connection_coro_yield); + if (UNLIKELY(events < CONN_CORO_ASYNC_AWAIT_READ || + events > CONN_CORO_ASYNC_AWAIT_READ_WRITE)) { + lwan_status_error("awaitv() called with invalid events"); + coro_yield(r->conn->coro, CONN_CORO_ABORT); + __builtin_unreachable(); + } + + struct lwan_connection *conn = &l->conns[await_fd]; + + if (UNLIKELY(conn->flags & CONN_ASYNC_AWAIT_MULTIPLE)) { + lwan_status_debug("ignoring second awaitv call on same fd: %d", + await_fd); + continue; + } + + conn->flags |= CONN_ASYNC_AWAIT_MULTIPLE; + update.num_awaiting++; + + if (await_fd == r->fd) { + static const enum lwan_connection_coro_yield to_request_yield[] = { + [CONN_CORO_ASYNC_AWAIT_READ] = CONN_CORO_WANT_READ, + [CONN_CORO_ASYNC_AWAIT_WRITE] = CONN_CORO_WANT_WRITE, + [CONN_CORO_ASYNC_AWAIT_READ_WRITE] = CONN_CORO_WANT_READ_WRITE, + }; + + update.request_conn_yield = to_request_yield[events]; + continue; + } + + events = resume_async(l, events, await_fd, r->conn, epoll_fd); + if (UNLIKELY(events == CONN_CORO_ABORT)) { + lwan_status_error("could not register fd for async operation"); + coro_yield(r->conn->coro, CONN_CORO_ABORT); + __builtin_unreachable(); + } + } +} + +static void reset_conn_async_await_multiple_flag(struct lwan_connection *conns, + va_list ap) +{ + while (true) { + int await_fd = va_arg(ap, int); + if (await_fd < 0) + return; + + struct lwan_connection *conn = &conns[await_fd]; + conn->flags &= ~CONN_ASYNC_AWAIT_MULTIPLE; + + LWAN_NO_DISCARD(va_arg(ap, enum lwan_connection_coro_yield)); + } +} + +int lwan_request_awaitv_any(struct lwan_request *r, ...) +{ + struct lwan *l = r->conn->thread->lwan; + va_list ap; + + va_start(ap, r); + reset_conn_async_await_multiple_flag(l->conns, ap); + va_end(ap); + + va_start(ap, r); + struct flag_update update = update_flags_for_async_awaitv(r, l, ap); + va_end(ap); + + while (true) { + int64_t v = coro_yield(r->conn->coro, update.request_conn_yield); + struct lwan_connection *conn = (struct lwan_connection *)(uintptr_t)v; + + if (conn->flags & CONN_ASYNC_AWAIT_MULTIPLE) { + va_start(ap, r); + reset_conn_async_await_multiple_flag(l->conns, ap); + va_end(ap); + + return lwan_connection_get_fd(l, conn); + } + } +} + +void lwan_request_awaitv_all(struct lwan_request *r, ...) +{ + struct lwan *l = r->conn->thread->lwan; + va_list ap; + + va_start(ap, r); + reset_conn_async_await_multiple_flag(l->conns, ap); + va_end(ap); + + va_start(ap, r); + struct flag_update update = update_flags_for_async_awaitv(r, l, ap); + va_end(ap); + + while (update.num_awaiting) { + int64_t v = coro_yield(r->conn->coro, update.request_conn_yield); + struct lwan_connection *conn = (struct lwan_connection *)(uintptr_t)v; + + if (conn->flags & CONN_ASYNC_AWAIT_MULTIPLE) { + conn->flags &= ~CONN_ASYNC_AWAIT_MULTIPLE; + update.num_awaiting--; + } + } +} + +static inline int64_t +make_async_yield_value(int fd, enum lwan_connection_coro_yield event) +{ + assert(event >= CONN_CORO_ASYNC_AWAIT_READ && + event <= CONN_CORO_ASYNC_AWAIT_READ_WRITE); + + return (int64_t)(((uint64_t)fd << 32 | event)); +} + +static inline int async_await_fd(struct lwan_connection *conn, + int fd, + enum lwan_connection_coro_yield events) +{ + int64_t yield_value = make_async_yield_value(fd, events); + int64_t from_coro = coro_yield(conn->coro, yield_value); + struct lwan_connection *conn_from_coro = + (struct lwan_connection *)(intptr_t)from_coro; + + assert(conn_from_coro->flags & CONN_ASYNC_AWAIT); + + return lwan_connection_get_fd(conn->thread->lwan, conn_from_coro); +} + +inline int lwan_request_await_read(struct lwan_request *r, int fd) +{ + return async_await_fd(r->conn, fd, CONN_CORO_ASYNC_AWAIT_READ); +} + +inline int lwan_request_await_write(struct lwan_request *r, int fd) +{ + return async_await_fd(r->conn, fd, CONN_CORO_ASYNC_AWAIT_WRITE); +} + +inline int lwan_request_await_read_write(struct lwan_request *r, int fd) +{ + return async_await_fd(r->conn, fd, CONN_CORO_ASYNC_AWAIT_READ_WRITE); +} + static ALWAYS_INLINE void resume_coro(struct timeout_queue *tq, struct lwan_connection *conn_to_resume, struct lwan_connection *conn_to_yield, @@ -710,14 +875,15 @@ static ALWAYS_INLINE void resume_coro(struct timeout_queue *tq, enum lwan_connection_coro_yield yield_result = from_coro & 0xffffffff; if (UNLIKELY(yield_result >= CONN_CORO_ASYNC)) { - yield_result = - resume_async(tq, yield_result, from_coro, conn_to_resume, epoll_fd); + int await_fd = (int)((uint64_t)from_coro >> 32); + yield_result = resume_async(tq->lwan, yield_result, await_fd, + conn_to_resume, epoll_fd); } if (UNLIKELY(yield_result == CONN_CORO_ABORT)) { timeout_queue_expire(tq, conn_to_resume); } else { - update_epoll_flags(tq, conn_to_resume, epoll_fd, yield_result); + update_epoll_flags(tq->lwan, conn_to_resume, epoll_fd, yield_result); timeout_queue_move_to_last(tq, conn_to_resume); } } @@ -787,7 +953,7 @@ static bool process_pending_timers(struct timeout_queue *tq, } request = container_of(timeout, struct lwan_request, timeout); - update_epoll_flags(tq, request->conn, epoll_fd, CONN_CORO_RESUME); + update_epoll_flags(tq->lwan, request->conn, epoll_fd, CONN_CORO_RESUME); } if (should_expire_timers) { @@ -1452,7 +1618,7 @@ void lwan_thread_init(struct lwan *l) for (unsigned int i = 0; i < l->thread.count; i++) { struct lwan_thread *thread; - + if (schedtbl) { /* For SO_ATTACH_REUSEPORT_CBPF to work with the program * we provide the kernel, sockets have to be added to the diff --git a/src/lib/lwan-tq.c b/src/lib/lwan-tq.c index 4f085049b..ce0542c0e 100644 --- a/src/lib/lwan-tq.c +++ b/src/lib/lwan-tq.c @@ -112,7 +112,14 @@ void timeout_queue_expire_waiting(struct timeout_queue *tq) if (conn->time_to_expire > tq->current_time) return; - timeout_queue_expire(tq, conn); + if (LIKELY(!(conn->flags & CONN_IS_WEBSOCKET))) { + timeout_queue_expire(tq, conn); + } else { + if (LIKELY(lwan_send_websocket_ping_for_tq(conn))) + timeout_queue_move_to_last(tq, conn); + else + timeout_queue_expire(tq, conn); + } } /* Timeout queue exhausted: reset epoch */ diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index 4f5b6d022..e6b598063 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -55,12 +55,16 @@ enum ws_opcode { WS_OPCODE_INVALID = 16, }; -static void write_websocket_frame(struct lwan_request *request, - unsigned char header_byte, - char *msg, - size_t len) +#define WS_MASKED 0x80 + +static ALWAYS_INLINE bool +write_websocket_frame_full(struct lwan_request *request, + unsigned char header_byte, + char *msg, + size_t len, + bool use_coro) { - uint8_t frame[10] = { header_byte }; + uint8_t frame[10] = {header_byte}; size_t frame_len; if (len <= 125) { @@ -82,14 +86,51 @@ static void write_websocket_frame(struct lwan_request *request, {.iov_base = msg, .iov_len = len}, }; - lwan_writev(request, vec, N_ELEMENTS(vec)); + if (LIKELY(use_coro)) { + lwan_writev(request, vec, N_ELEMENTS(vec)); + return true; + } + + size_t total_written = 0; + int curr_iov = 0; + for (int try = 0; try < 10; try++) { + ssize_t written = writev(request->fd, &vec[curr_iov], + (int)N_ELEMENTS(vec) - curr_iov); + if (written < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + return false; + } + + total_written += (size_t)written; + while (curr_iov < (int)N_ELEMENTS(vec) && + written >= (ssize_t)vec[curr_iov].iov_len) { + written -= (ssize_t)vec[curr_iov].iov_len; + curr_iov++; + } + if (curr_iov == (int)N_ELEMENTS(vec)) + return true; + + vec[curr_iov].iov_base = (char *)vec[curr_iov].iov_base + written; + vec[curr_iov].iov_len -= (size_t)written; + } + + return false; +} + +static bool write_websocket_frame(struct lwan_request *request, + unsigned char header_byte, + char *msg, + size_t len) +{ + return write_websocket_frame_full(request, header_byte, msg, len, true); } static inline void lwan_response_websocket_write(struct lwan_request *request, unsigned char op) { size_t len = lwan_strbuf_get_length(request->response.buffer); char *msg = lwan_strbuf_get_buffer(request->response.buffer); - unsigned char header = 0x80 | op; + unsigned char header = WS_MASKED | op; if (!(request->conn->flags & CONN_IS_WEBSOCKET)) return; @@ -233,18 +274,20 @@ static void unmask(char *msg, size_t msg_len, char mask[static 4]) } } -static void send_websocket_pong(struct lwan_request *request, uint16_t header) +static void +ping_pong(struct lwan_request *request, uint16_t header, enum ws_opcode opcode) { const size_t len = header & 0x7f; char msg[128]; char mask[4]; - assert(header & 0x80); + assert(header & WS_MASKED); + assert(opcode == WS_OPCODE_PING || opcode == WS_OPCODE_PONG); if (UNLIKELY(len > 125)) { - lwan_status_debug("Received PING opcode with length %zu." + lwan_status_debug("Received %s frame with length %zu." "Max is 125. Aborting connection.", - len); + opcode == WS_OPCODE_PING ? "PING" : "PONG", len); coro_yield(request->conn->coro, CONN_CORO_ABORT); __builtin_unreachable(); } @@ -254,10 +297,48 @@ static void send_websocket_pong(struct lwan_request *request, uint16_t header) {.iov_base = msg, .iov_len = len}, }; - lwan_readv(request, vec, N_ELEMENTS(vec)); - unmask(msg, len, mask); + if (opcode == WS_OPCODE_PING) { + lwan_readv(request, vec, N_ELEMENTS(vec)); + unmask(msg, len, mask); + write_websocket_frame(request, WS_MASKED | WS_OPCODE_PONG, msg, len); + } else { + /* From MDN: "You might also get a pong without ever sending a ping; + * ignore this if it happens." */ + + /* FIXME: should we care about the contents of PONG packets? */ + /* FIXME: should we have a lwan_recvmsg() too that takes an iovec? */ + const size_t total_len = vec[0].iov_len + vec[1].iov_len; + if (LIKELY(total_len < sizeof(msg))) { + lwan_recv(request, msg, total_len, MSG_TRUNC); + } else { + lwan_recv(request, vec[0].iov_base, vec[0].iov_len, MSG_TRUNC); + lwan_recv(request, vec[1].iov_base, vec[1].iov_len, MSG_TRUNC); + } + } +} - write_websocket_frame(request, 0x80 | WS_OPCODE_PONG, msg, len); +bool lwan_send_websocket_ping_for_tq(struct lwan_connection *conn) +{ + uint32_t mask32 = (uint32_t)lwan_random_uint64(); + char mask[sizeof(mask32)]; + struct timespec payload; + + memcpy(mask, &mask32, sizeof(mask32)); + + if (UNLIKELY(clock_gettime(monotonic_clock_id, &payload) < 0)) + return false; + + unmask((char *)&payload, sizeof(payload), mask); + + /* use_coro is set to false here because this function is called outside + * a connection coroutine and the I/O wrappers might yield, which of course + * wouldn't work */ + struct lwan_request req = { + .conn = conn, + .fd = lwan_connection_get_fd(conn->thread->lwan, conn), + }; + return write_websocket_frame_full(&req, WS_MASKED | WS_OPCODE_PING, + (char *)&payload, sizeof(payload), false); } int lwan_response_websocket_read_hint(struct lwan_request *request, size_t size_hint) @@ -286,7 +367,7 @@ int lwan_response_websocket_read_hint(struct lwan_request *request, size_t size_ coro_yield(request->conn->coro, CONN_CORO_ABORT); __builtin_unreachable(); } - if (UNLIKELY(!(header & 0x80))) { + if (UNLIKELY(!(header & WS_MASKED))) { lwan_status_debug("Client sent an unmasked WebSockets frame, aborting"); coro_yield(request->conn->coro, CONN_CORO_ABORT); __builtin_unreachable(); @@ -312,11 +393,11 @@ int lwan_response_websocket_read_hint(struct lwan_request *request, size_t size_ request->conn->flags &= ~CONN_IS_WEBSOCKET; break; + case WS_OPCODE_PONG: case WS_OPCODE_PING: - send_websocket_pong(request, header); + ping_pong(request, header, opcode); goto next_frame; - case WS_OPCODE_PONG: case WS_OPCODE_RSVD_1 ... WS_OPCODE_RSVD_5: case WS_OPCODE_RSVD_CONTROL_1 ... WS_OPCODE_RSVD_CONTROL_5: case WS_OPCODE_INVALID: diff --git a/src/lib/lwan.h b/src/lib/lwan.h index fb5ccb0a5..d20a7690a 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -300,6 +300,11 @@ enum lwan_connection_flags { * can deal with this fact. */ CONN_HUNG_UP = 1 << 12, + /* Used to both implement lwan_request_awaitv_all() correctly, and to + * ensure that spurious resumes from fds that weren't in the multiple + * await call won't return to the request handler. */ + CONN_ASYNC_AWAIT_MULTIPLE = 1 << 13, + CONN_FLAG_LAST = CONN_HUNG_UP, }; @@ -666,11 +671,11 @@ void lwan_response_websocket_write_binary(struct lwan_request *request); int lwan_response_websocket_read(struct lwan_request *request); int lwan_response_websocket_read_hint(struct lwan_request *request, size_t size_hint); -struct lwan_connection *lwan_request_await_read(struct lwan_request *r, int fd); -struct lwan_connection *lwan_request_await_write(struct lwan_request *r, - int fd); -struct lwan_connection *lwan_request_await_read_write(struct lwan_request *r, - int fd); +int lwan_request_await_read(struct lwan_request *r, int fd); +int lwan_request_await_write(struct lwan_request *r, int fd); +int lwan_request_await_read_write(struct lwan_request *r, int fd); +int lwan_request_awaitv_any(struct lwan_request *r, ...); +void lwan_request_awaitv_all(struct lwan_request *r, ...); ssize_t lwan_request_async_read(struct lwan_request *r, int fd, void *buf, size_t len); ssize_t lwan_request_async_read_flags(struct lwan_request *request, int fd, void *buf, size_t len, int flags); ssize_t lwan_request_async_write(struct lwan_request *r, int fd, const void *buf, size_t len); diff --git a/src/samples/websocket/main.c b/src/samples/websocket/main.c index d8b84930c..64ccdd644 100644 --- a/src/samples/websocket/main.c +++ b/src/samples/websocket/main.c @@ -123,12 +123,12 @@ static void pub_depart_message(void *data1, void *data2) LWAN_HANDLER_ROUTE(ws_chat, "/ws-chat") { + struct lwan *lwan = request->conn->thread->lwan; struct lwan_pubsub_subscriber *sub; struct lwan_pubsub_msg *msg; enum lwan_http_status status; static int total_user_count; int user_id; - uint64_t sleep_time = 1000; sub = lwan_pubsub_subscribe(chat); if (!sub) @@ -149,13 +149,17 @@ LWAN_HANDLER_ROUTE(ws_chat, "/ws-chat") (void *)(intptr_t)user_id); lwan_pubsub_publishf(chat, "*** User%d has joined the chat!\n", user_id); + const int websocket_fd = request->fd; + const int sub_fd = lwan_pubsub_get_notification_fd(sub); while (true) { - switch (lwan_response_websocket_read(request)) { - case ENOTCONN: /* read() called before connection is websocket */ - case ECONNRESET: /* Client closed the connection */ + int resumed_fd = lwan_request_awaitv_any( + request, websocket_fd, CONN_CORO_ASYNC_AWAIT_READ, sub_fd, + CONN_CORO_ASYNC_AWAIT_READ, -1); + + if (lwan->conns[resumed_fd].flags & CONN_HUNG_UP) goto out; - case EAGAIN: /* Nothing is available from other clients */ + if (resumed_fd == sub_fd) { while ((msg = lwan_pubsub_consume(sub))) { const struct lwan_value *value = lwan_pubsub_msg_value(msg); @@ -167,26 +171,23 @@ LWAN_HANDLER_ROUTE(ws_chat, "/ws-chat") lwan_pubsub_msg_done(msg); lwan_response_websocket_write_text(request); - sleep_time = 500; } - - lwan_request_sleep(request, sleep_time); - - /* We're receiving a lot of messages, wait up to 1s (500ms in the loop - * above, and 500ms in the increment below). Otherwise, wait 500ms every - * time we return from lwan_request_sleep() until we reach 8s. This way, - * if a chat is pretty busy, we'll have a lag of at least 1s -- which is - * probably fine; if it's not busy, we can sleep a bit more and conserve - * some resources. */ - if (sleep_time <= 8000) - sleep_time += 500; - break; - - case 0: /* We got something! Copy it to echo it back */ - lwan_pubsub_publishf(chat, "User%d: %.*s\n", user_id, - (int)lwan_strbuf_get_length(response->buffer), - lwan_strbuf_get_buffer(response->buffer)); - break; + } else if (resumed_fd == websocket_fd) { + switch (lwan_response_websocket_read(request)) { + case ENOTCONN: /* read() called before connection is websocket */ + case ECONNRESET: /* Client closed the connection */ + goto out; + + case 0: /* We got something! Copy it to echo it back */ + lwan_pubsub_publishf( + chat, "User%d: %.*s\n", user_id, + (int)lwan_strbuf_get_length(response->buffer), + lwan_strbuf_get_buffer(response->buffer)); + } + } else { + lwan_status_error( + "lwan_request_awaitv_any() returned %d, but waiting on it", + resumed_fd); } } From 92b4ec7822ecc7fd540cc2869a0c035d4e17ee3d Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 8 May 2024 21:48:14 -0700 Subject: [PATCH 2225/2505] Simplify update_flags_for_async_awaitv() Move call to reset_conn_async_await_multiple_flag() to the mentioned function, so that the awaitv_all() and awaitv_any() functions don't need to do it themselves. Also, no need to clear the flag in the awaitv_any() case when returning from coro_yield(). --- src/lib/lwan-thread.c | 52 +++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 6525e2c6f..35b447c1b 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -705,6 +705,26 @@ struct flag_update { enum lwan_connection_coro_yield request_conn_yield; }; +static void reset_conn_async_await_multiple_flag(struct lwan_connection *conns, + va_list ap_orig) +{ + va_list ap; + + va_copy(ap, ap_orig); + + while (true) { + int await_fd = va_arg(ap, int); + if (await_fd < 0) { + va_end(ap); + break; + } + + conns[await_fd].flags &= ~CONN_ASYNC_AWAIT_MULTIPLE; + + LWAN_NO_DISCARD(va_arg(ap, enum lwan_connection_coro_yield)); + } +} + static struct flag_update update_flags_for_async_awaitv(struct lwan_request *r, struct lwan *l, va_list ap) { @@ -712,6 +732,8 @@ update_flags_for_async_awaitv(struct lwan_request *r, struct lwan *l, va_list ap struct flag_update update = {.num_awaiting = 0, .request_conn_yield = CONN_CORO_YIELD}; + reset_conn_async_await_multiple_flag(l->conns, ap); + while (true) { int await_fd = va_arg(ap, int); if (await_fd < 0) { @@ -758,30 +780,11 @@ update_flags_for_async_awaitv(struct lwan_request *r, struct lwan *l, va_list ap } } -static void reset_conn_async_await_multiple_flag(struct lwan_connection *conns, - va_list ap) -{ - while (true) { - int await_fd = va_arg(ap, int); - if (await_fd < 0) - return; - - struct lwan_connection *conn = &conns[await_fd]; - conn->flags &= ~CONN_ASYNC_AWAIT_MULTIPLE; - - LWAN_NO_DISCARD(va_arg(ap, enum lwan_connection_coro_yield)); - } -} - int lwan_request_awaitv_any(struct lwan_request *r, ...) { struct lwan *l = r->conn->thread->lwan; va_list ap; - va_start(ap, r); - reset_conn_async_await_multiple_flag(l->conns, ap); - va_end(ap); - va_start(ap, r); struct flag_update update = update_flags_for_async_awaitv(r, l, ap); va_end(ap); @@ -790,13 +793,8 @@ int lwan_request_awaitv_any(struct lwan_request *r, ...) int64_t v = coro_yield(r->conn->coro, update.request_conn_yield); struct lwan_connection *conn = (struct lwan_connection *)(uintptr_t)v; - if (conn->flags & CONN_ASYNC_AWAIT_MULTIPLE) { - va_start(ap, r); - reset_conn_async_await_multiple_flag(l->conns, ap); - va_end(ap); - + if (conn->flags & CONN_ASYNC_AWAIT_MULTIPLE) return lwan_connection_get_fd(l, conn); - } } } @@ -805,10 +803,6 @@ void lwan_request_awaitv_all(struct lwan_request *r, ...) struct lwan *l = r->conn->thread->lwan; va_list ap; - va_start(ap, r); - reset_conn_async_await_multiple_flag(l->conns, ap); - va_end(ap); - va_start(ap, r); struct flag_update update = update_flags_for_async_awaitv(r, l, ap); va_end(ap); From c26a3985690466a91d7cdd6ca1f26652f1a2d2f8 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 8 May 2024 21:55:29 -0700 Subject: [PATCH 2226/2505] s/CONN_ASYNC_AWAIT_MULTIPLE/CONN_ASYNC_AWAITV/g Makes this more consistent with the API names I ended up using. --- src/lib/lwan-thread.c | 22 +++++++++++----------- src/lib/lwan.h | 18 +++++++++--------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 35b447c1b..844e53954 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -620,9 +620,9 @@ static void unasync_await_conn(void *data1, void *data2) struct lwan_connection *async_fd_conn = data1; async_fd_conn->flags &= - ~(CONN_ASYNC_AWAIT | CONN_HUNG_UP | CONN_ASYNC_AWAIT_MULTIPLE); + ~(CONN_ASYNC_AWAIT | CONN_HUNG_UP | CONN_ASYNC_AWAITV); assert(async_fd_conn->parent); - async_fd_conn->parent->flags &= ~CONN_ASYNC_AWAIT_MULTIPLE; + async_fd_conn->parent->flags &= ~CONN_ASYNC_AWAITV; async_fd_conn->thread = data2; @@ -705,8 +705,8 @@ struct flag_update { enum lwan_connection_coro_yield request_conn_yield; }; -static void reset_conn_async_await_multiple_flag(struct lwan_connection *conns, - va_list ap_orig) +static void reset_conn_async_awaitv_flag(struct lwan_connection *conns, + va_list ap_orig) { va_list ap; @@ -719,7 +719,7 @@ static void reset_conn_async_await_multiple_flag(struct lwan_connection *conns, break; } - conns[await_fd].flags &= ~CONN_ASYNC_AWAIT_MULTIPLE; + conns[await_fd].flags &= ~CONN_ASYNC_AWAITV; LWAN_NO_DISCARD(va_arg(ap, enum lwan_connection_coro_yield)); } @@ -732,7 +732,7 @@ update_flags_for_async_awaitv(struct lwan_request *r, struct lwan *l, va_list ap struct flag_update update = {.num_awaiting = 0, .request_conn_yield = CONN_CORO_YIELD}; - reset_conn_async_await_multiple_flag(l->conns, ap); + reset_conn_async_awaitv_flag(l->conns, ap); while (true) { int await_fd = va_arg(ap, int); @@ -751,13 +751,13 @@ update_flags_for_async_awaitv(struct lwan_request *r, struct lwan *l, va_list ap struct lwan_connection *conn = &l->conns[await_fd]; - if (UNLIKELY(conn->flags & CONN_ASYNC_AWAIT_MULTIPLE)) { + if (UNLIKELY(conn->flags & CONN_ASYNC_AWAITV)) { lwan_status_debug("ignoring second awaitv call on same fd: %d", await_fd); continue; } - conn->flags |= CONN_ASYNC_AWAIT_MULTIPLE; + conn->flags |= CONN_ASYNC_AWAITV; update.num_awaiting++; if (await_fd == r->fd) { @@ -793,7 +793,7 @@ int lwan_request_awaitv_any(struct lwan_request *r, ...) int64_t v = coro_yield(r->conn->coro, update.request_conn_yield); struct lwan_connection *conn = (struct lwan_connection *)(uintptr_t)v; - if (conn->flags & CONN_ASYNC_AWAIT_MULTIPLE) + if (conn->flags & CONN_ASYNC_AWAITV) return lwan_connection_get_fd(l, conn); } } @@ -811,8 +811,8 @@ void lwan_request_awaitv_all(struct lwan_request *r, ...) int64_t v = coro_yield(r->conn->coro, update.request_conn_yield); struct lwan_connection *conn = (struct lwan_connection *)(uintptr_t)v; - if (conn->flags & CONN_ASYNC_AWAIT_MULTIPLE) { - conn->flags &= ~CONN_ASYNC_AWAIT_MULTIPLE; + if (conn->flags & CONN_ASYNC_AWAITV) { + conn->flags &= ~CONN_ASYNC_AWAITV; update.num_awaiting--; } } diff --git a/src/lib/lwan.h b/src/lib/lwan.h index d20a7690a..5198446d5 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -286,24 +286,24 @@ enum lwan_connection_flags { * whenever associated client connection is closed. */ CONN_ASYNC_AWAIT = 1 << 8, - CONN_SENT_CONNECTION_HEADER = 1 << 9, + /* Used to both implement lwan_request_awaitv_all() correctly, and to + * ensure that spurious resumes from fds that weren't in the multiple + * await call won't return to the request handler. */ + CONN_ASYNC_AWAITV = 1 << 9, + + CONN_SENT_CONNECTION_HEADER = 1 << 10, /* Is this a TLS connection? */ - CONN_TLS = 1 << 10, + CONN_TLS = 1 << 11, /* Both are used to know if an epoll event pertains to a listener rather * than a client. */ - CONN_LISTENER = 1 << 11, + CONN_LISTENER = 1 << 12, /* Only valid when CONN_ASYNC_AWAIT is set. Set on file descriptors that * got (EPOLLHUP|EPOLLRDHUP) events from epoll so that request handlers * can deal with this fact. */ - CONN_HUNG_UP = 1 << 12, - - /* Used to both implement lwan_request_awaitv_all() correctly, and to - * ensure that spurious resumes from fds that weren't in the multiple - * await call won't return to the request handler. */ - CONN_ASYNC_AWAIT_MULTIPLE = 1 << 13, + CONN_HUNG_UP = 1 << 13, CONN_FLAG_LAST = CONN_HUNG_UP, }; From c26a6bafea9cc47b887233c13e2ddb44b363e126 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 8 May 2024 22:23:43 -0700 Subject: [PATCH 2227/2505] Ensure lwan_connection_get_fd() is always inlined --- src/lib/lwan-request.c | 6 ------ src/lib/lwan.h | 7 +++++++ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 424c1c9f6..455f0c5d3 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1788,12 +1788,6 @@ const char *lwan_request_get_host(struct lwan_request *request) return helper->host.len ? helper->host.value : NULL; } -ALWAYS_INLINE int -lwan_connection_get_fd(const struct lwan *lwan, const struct lwan_connection *conn) -{ - return (int)(intptr_t)(conn - lwan->conns); -} - const char * lwan_request_get_remote_address_and_port(struct lwan_request *request, char buffer[static INET6_ADDRSTRLEN], diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 5198446d5..75dc6c22e 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -686,6 +686,13 @@ ssize_t lwan_request_async_writev(struct lwan_request *request, void lwan_straitjacket_enforce(const struct lwan_straitjacket *sj); +static ALWAYS_INLINE int +lwan_connection_get_fd(const struct lwan *lwan, + const struct lwan_connection *conn) +{ + return (int)(intptr_t)(conn - lwan->conns); +} + #if defined(__cplusplus) } #endif From 0289a1f42b28db61a2f6d8f4b4da563c0ac9cba8 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 8 May 2024 22:53:09 -0700 Subject: [PATCH 2228/2505] Fix mask loading websocket unmasking function (SSE3 path) --- src/lib/lwan-websocket.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index e6b598063..d699e4dc9 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -222,7 +222,8 @@ static void unmask(char *msg, size_t msg_len, char mask[static 4]) } #elif defined(__SSE3__) if (msg_len >= 16) { - const __m128i mask128 = _mm_lddqu_si128((const float *)mask); + const __m128i mask128 = + _mm_castps_si128(_mm_load_ps1((const float *)mask)); do { const __m128i v = _mm_lddqu_si128((const __m128i *)msg); From 6ea930099c3060b0a6f3372cacb570d65779ce4a Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 9 May 2024 08:50:53 -0700 Subject: [PATCH 2229/2505] Revert "TAILQ_CONCAT() will initialize the head for the second list" This reverts commit 72ae74c1868576abafa90d2afcb5e57a1ed280b5. Turns out this wasn't necessary! :) --- src/lib/timeout.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/lib/timeout.c b/src/lib/timeout.c index 5f82d0e03..c069aae80 100644 --- a/src/lib/timeout.c +++ b/src/lib/timeout.c @@ -311,10 +311,7 @@ void timeouts_update(struct timeouts *T, abstime_t curtime) while (pending & T->pending[wheel]) { /* ctz input cannot be zero: loop condition. */ int slot = ctz(pending & T->pending[wheel]); - list_append_list(&todo, &T->wheel[wheel][slot]); - list_head_init(&T->wheel[wheel][slot]); - T->pending[wheel] &= ~(UINT64_C(1) << slot); } From 9646dfdb831ab6f1baa29003cce77d9f20c1eda2 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 9 May 2024 18:13:08 -0700 Subject: [PATCH 2230/2505] Fix implementation of cache_coro_get_and_ref_entry_with_ctx() The error/create_ctx pointers were swapped. Found fuzzing with Mayhem (thanks!). --- src/lib/lwan-cache.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-cache.c b/src/lib/lwan-cache.c index d514e6ad7..3e1ce6152 100644 --- a/src/lib/lwan-cache.c +++ b/src/lib/lwan-cache.c @@ -453,7 +453,7 @@ struct cache_entry *cache_coro_get_and_ref_entry_with_ctx(struct cache *cache, for (int tries = GET_AND_REF_TRIES; tries; tries--) { int error; struct cache_entry *ce = - cache_get_and_ref_entry_with_ctx(cache, key, &error, create_ctx); + cache_get_and_ref_entry_with_ctx(cache, key, create_ctx, &error); if (LIKELY(ce)) { /* From 96bace85b0c81f592b43e8c81887444339af757e Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 9 May 2024 18:14:25 -0700 Subject: [PATCH 2231/2505] Move lwan_connection_get_fd() to private.h There was a prototype in private.h, so move the static inline implementation from the public.h there instead. --- src/lib/lwan-private.h | 9 ++++++--- src/lib/lwan.h | 7 ------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index ed87c88a8..724d1c5e8 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -260,9 +260,12 @@ const char *lwan_http_status_as_string_with_code(enum lwan_http_status status) const char *lwan_http_status_as_descriptive_string(enum lwan_http_status status) __attribute__((const)) __attribute__((warn_unused_result)); -int lwan_connection_get_fd(const struct lwan *lwan, - const struct lwan_connection *conn) - __attribute__((pure)) __attribute__((warn_unused_result)); +static ALWAYS_INLINE __attribute__((pure, warn_unused_result)) int +lwan_connection_get_fd(const struct lwan *lwan, + const struct lwan_connection *conn) +{ + return (int)(intptr_t)(conn - lwan->conns); +} int lwan_format_rfc_time(const time_t in, char out LWAN_ARRAY_PARAM(30)); int lwan_parse_rfc_time(const char in LWAN_ARRAY_PARAM(30), time_t *out); diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 75dc6c22e..5198446d5 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -686,13 +686,6 @@ ssize_t lwan_request_async_writev(struct lwan_request *request, void lwan_straitjacket_enforce(const struct lwan_straitjacket *sj); -static ALWAYS_INLINE int -lwan_connection_get_fd(const struct lwan *lwan, - const struct lwan_connection *conn) -{ - return (int)(intptr_t)(conn - lwan->conns); -} - #if defined(__cplusplus) } #endif From 6e713a54ab91455dd28bdf5a8b50d8b162f21ccb Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 9 May 2024 18:15:09 -0700 Subject: [PATCH 2232/2505] Use a rwlock for pub/sub publishers --- src/lib/lwan-pubsub.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/lib/lwan-pubsub.c b/src/lib/lwan-pubsub.c index 7a1c83087..e46a73ee5 100644 --- a/src/lib/lwan-pubsub.c +++ b/src/lib/lwan-pubsub.c @@ -36,7 +36,7 @@ struct lwan_pubsub_topic { struct list_head subscribers; - pthread_mutex_t lock; + pthread_rwlock_t lock; }; struct lwan_pubsub_msg { @@ -153,7 +153,7 @@ struct lwan_pubsub_topic *lwan_pubsub_new_topic(void) return NULL; list_head_init(&topic->subscribers); - pthread_mutex_init(&topic->lock, NULL); + pthread_rwlock_init(&topic->lock, NULL); return topic; } @@ -162,12 +162,12 @@ void lwan_pubsub_free_topic(struct lwan_pubsub_topic *topic) { struct lwan_pubsub_subscriber *iter, *next; - pthread_mutex_lock(&topic->lock); + pthread_rwlock_wrlock(&topic->lock); list_for_each_safe (&topic->subscribers, iter, next, subscriber) lwan_pubsub_unsubscribe_internal(topic, iter, false); - pthread_mutex_unlock(&topic->lock); + pthread_rwlock_unlock(&topic->lock); - pthread_mutex_destroy(&topic->lock); + pthread_rwlock_destroy(&topic->lock); free(topic); } @@ -195,10 +195,11 @@ static bool lwan_pubsub_publish_value(struct lwan_pubsub_topic *topic, msg->refcount = 1; msg->value = value; - pthread_mutex_lock(&topic->lock); + pthread_rwlock_rdlock(&topic->lock); list_for_each (&topic->subscribers, sub, subscriber) { ATOMIC_INC(msg->refcount); + /* FIXME: use trylock and a local queue to try again? */ pthread_mutex_lock(&sub->lock); if (!lwan_pubsub_queue_put(sub, msg)) { lwan_status_warning("Couldn't enqueue message, dropping"); @@ -222,7 +223,7 @@ static bool lwan_pubsub_publish_value(struct lwan_pubsub_topic *topic, } } } - pthread_mutex_unlock(&topic->lock); + pthread_rwlock_unlock(&topic->lock); lwan_pubsub_msg_done(msg); @@ -281,9 +282,9 @@ lwan_pubsub_subscribe(struct lwan_pubsub_topic *topic) pthread_mutex_init(&sub->lock, NULL); lwan_pubsub_queue_init(sub); - pthread_mutex_lock(&topic->lock); + pthread_rwlock_wrlock(&topic->lock); list_add(&topic->subscribers, &sub->subscriber); - pthread_mutex_unlock(&topic->lock); + pthread_rwlock_unlock(&topic->lock); return sub; } @@ -311,10 +312,10 @@ static void lwan_pubsub_unsubscribe_internal(struct lwan_pubsub_topic *topic, struct lwan_pubsub_msg *iter; if (take_topic_lock) - pthread_mutex_lock(&topic->lock); + pthread_rwlock_wrlock(&topic->lock); list_del(&sub->subscriber); if (take_topic_lock) - pthread_mutex_unlock(&topic->lock); + pthread_rwlock_unlock(&topic->lock); pthread_mutex_lock(&sub->lock); while ((iter = lwan_pubsub_queue_get(sub))) From 55ccf94cfafa1e4eef41356a8750e471084088fe Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 9 May 2024 18:15:30 -0700 Subject: [PATCH 2233/2505] Fix timeout queue corruption when connections expired --- src/lib/lwan-request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 455f0c5d3..67039cc05 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1841,7 +1841,7 @@ lwan_request_get_remote_address(struct lwan_request *request, static void remove_sleep(void *data1, void *data2) { static const enum lwan_connection_flags suspended_sleep = - CONN_SUSPENDED | CONN_HAS_REMOVE_SLEEP_DEFER; + CONN_SUSPENDED_MASK | CONN_HAS_REMOVE_SLEEP_DEFER; struct timeouts *wheel = data1; struct timeout *timeout = data2; struct lwan_request *request = From 9b3bdf531e6c8b6ff211f90e75c3a2c90d146c79 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 9 May 2024 18:16:20 -0700 Subject: [PATCH 2234/2505] Set TCP_NODELAY in all the listening sockets Maybe this should help latency a bit? I don't have my testing setup anymore, so, eh. --- src/lib/lwan-socket.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/lwan-socket.c b/src/lib/lwan-socket.c index 700254f5c..87072f183 100644 --- a/src/lib/lwan-socket.c +++ b/src/lib/lwan-socket.c @@ -245,6 +245,8 @@ static int set_socket_options(const struct lwan *l, int fd) SET_SOCKET_OPTION(SOL_SOCKET, SO_LINGER, (&(struct linger){.l_onoff = 1, .l_linger = 1})); + SET_SOCKET_OPTION_MAY_FAIL(SOL_TCP, TCP_NODELAY, (int[]){1}); + #ifdef __linux__ #ifndef TCP_FASTOPEN From f8b41ed1c5214061549cb16b98f9d60a6efa83e0 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 9 May 2024 19:31:08 -0700 Subject: [PATCH 2235/2505] Avoid a mod in the status lookup perfect hash table --- src/bin/tools/statuslookupgen.c | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/bin/tools/statuslookupgen.c b/src/bin/tools/statuslookupgen.c index 5a73892a0..37070a747 100644 --- a/src/bin/tools/statuslookupgen.c +++ b/src/bin/tools/statuslookupgen.c @@ -13,6 +13,11 @@ static inline uint32_t rotate(uint32_t v, int n) return v << (32 - n) | v >> n; } +static inline uint32_t map_0_to_n(uint32_t value, uint32_t n) +{ + return (uint32_t)(((uint64_t)value * (uint64_t)n) >> 32); +} + int main(void) { uint32_t max_key = 0; @@ -49,7 +54,8 @@ int main(void) int set_bits = 0; for (int key = 0; key < N_KEYS; key++) { - uint32_t k = rotate((uint32_t)keys[key] - subtract, rot) % mod; + uint32_t k = map_0_to_n( + rotate((uint32_t)keys[key] - subtract, rot), mod); if (set & 1ull<> %d)) %% %d];\n", 32 - best_rot, best_rot, best_mod); + printf(" const uint32_t hash = (k << %d) | (k >> %d);\n", 32 - best_rot, best_rot); + printf(" const char *ret = table[(uint32_t)(((uint64_t)hash * (uint64_t)%d) >> 32)];\n", best_mod); printf(" assert((uint32_t)(ret[2] - '0') == ((uint32_t)status %% 10));\n"); printf(" assert((uint32_t)(ret[1] - '0') == ((uint32_t)(status / 10) %% 10));\n"); printf(" assert((uint32_t)(ret[0] - '0') == ((uint32_t)(status / 100) %% 10));\n"); printf(" return ret;\n"); printf("}\n"); - } From 292d7acd96886e82559638103d7e4bda8092c9c2 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 9 May 2024 23:37:29 -0700 Subject: [PATCH 2236/2505] Reduce memory reads when looking up MIME type from extension Since, by construction, the extensions are stored sorted and sequentially in memory, if we're comparing only the strings found in the blob generated by mimegen, we can compare only the pointers; otherwise, fall back to two 64-bit reads, replacing the indirect call to strncmp(). The extensions are now stored in big-endian rather than in host-endian, making it possible to compare things properly. As a result, Brotli liked the input data a bit more and was able to save a bit over 50 bytes; it's not much, but, hey, savings are savings. --- src/bin/tools/mimegen.c | 30 ++++++++++++------------------ src/lib/lwan-tables.c | 34 +++++++++++++++++++++++++--------- 2 files changed, 37 insertions(+), 27 deletions(-) diff --git a/src/bin/tools/mimegen.c b/src/bin/tools/mimegen.c index 96156b9aa..354ae85de 100644 --- a/src/bin/tools/mimegen.c +++ b/src/bin/tools/mimegen.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -67,20 +68,9 @@ output_append_full(struct output *output, const char *str, size_t str_len) return 0; } -static int output_append_padded(struct output *output, const char *str) +static int output_append_u64(struct output *output, uint64_t value) { - size_t str_len = strlen(str); - - assert(str_len <= 8); - - int r = output_append_full(output, str, str_len); - if (r < 0) - return r; - - if (str_len != 8) - return output_append_full(output, "\0\0\0\0\0\0\0\0", 8 - str_len); - - return 0; + return output_append_full(output, (char *)&value, 8); } static int output_append(struct output *output, const char *str) @@ -316,14 +306,18 @@ int main(int argc, char *argv[]) return 1; } for (i = 0; i < hash_get_count(ext_mime); i++) { - char ext_lower[9] = {0}; + uint64_t ext_lower = 0; - strncpy(ext_lower, exts[i], 8); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstringop-truncation" + /* See lwan_determine_mime_type_for_file_name() in lwan-tables.c */ + strncpy((char *)&ext_lower, exts[i], 8); +#pragma GCC diagnostic pop - for (char *p = ext_lower; *p; p++) - *p &= ~0x20; + ext_lower &= ~0x2020202020202020ull; + ext_lower = htobe64(ext_lower); - if (output_append_padded(&output, ext_lower) < 0) { + if (output_append_u64(&output, ext_lower) < 0) { fprintf(stderr, "Could not append to output\n"); fclose(fp); return 1; diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index c810daaa7..3c2f64461 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -120,13 +120,29 @@ LWAN_SELF_TEST(status_codes) #undef ASSERT_STATUS } -static int -compare_mime_entry(const void *a, const void *b) +static int compare_mime_entry(const void *a, const void *b) { - const char *exta = (const char *)a; - const char *extb = (const char *)b; + static const uintptr_t begin = (uintptr_t)uncompressed_mime_entries; + static const uintptr_t end = begin + 8 * MIME_ENTRIES; + const uintptr_t pa = (uintptr_t)a; + const uintptr_t pb = (uintptr_t)b; + uint64_t exta; + uint64_t extb; + + if (end - pa >= begin && end - pb >= begin) { + /* If both keys are within the uncompressed mime entries range, then + * we don't need to load from memory, just compare the pointers: they're + * all stored sequentially in memory by construction. */ + exta = pa; + extb = pb; + } else { + /* These are stored in big-endian so the comparison below works + * as expected. */ + exta = string_as_uint64((const char *)a); + extb = string_as_uint64((const char *)b); + } - return strncmp(exta, extb, 8); + return (exta > extb) - (exta < extb); } const char * @@ -147,19 +163,19 @@ lwan_determine_mime_type_for_file_name(const char *file_name) } if (LIKELY(*last_dot)) { - uint64_t key; + uint64_t key = 0; const unsigned char *extension; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wstringop-truncation" /* Data is stored with NULs on strings up to 7 chars, and no NULs * for 8-char strings, because that's implicit. So truncation is - * intentional here: comparison in compare_mime_entry() uses - * strncmp(..., 8), so even if NUL isn't present, it'll stop at the - * right place. */ + * intentional here: comparison in compare_mime_entry() always loads + * 8 bytes per extension. */ strncpy((char *)&key, last_dot + 1, 8); #pragma GCC diagnostic pop key &= ~0x2020202020202020ull; + key = htobe64(key); extension = bsearch(&key, uncompressed_mime_entries, MIME_ENTRIES, 8, compare_mime_entry); From 53423117c49700783cca214ae3827d74db6f4386 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 9 May 2024 23:41:15 -0700 Subject: [PATCH 2237/2505] Self-test lwan_char_isspace() too, using memchr() --- src/lib/lwan-tables.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index 3c2f64461..a5c9de327 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -336,7 +336,6 @@ LWAN_SELF_TEST(compare_with_ctype) assert(!!isdigit((char)i) == !!lwan_char_isdigit((char)i)); assert(!!isalpha((char)i) == !!lwan_char_isalpha((char)i)); assert(!!isalnum((char)i) == !!lwan_char_isalnum((char)i)); - - /* isspace() and lwan_char_isspace() differs on purpose */ + assert(!!memchr(" \t\n\r", i, 4) == !!lwan_char_isspace((char)i)); } } From 2771ad5db0ca9d4874f6d45500f07f23383e091a Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 12 May 2024 13:54:43 -0700 Subject: [PATCH 2238/2505] Revert mime-table optimization, partially Since bsearch() only compares the key itself with the other values from the array, this optimization would never work, as compare_mime_entry() would never be called with pointers from the mime extension table. --- src/lib/lwan-tables.c | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index a5c9de327..6a63508fd 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -122,25 +122,8 @@ LWAN_SELF_TEST(status_codes) static int compare_mime_entry(const void *a, const void *b) { - static const uintptr_t begin = (uintptr_t)uncompressed_mime_entries; - static const uintptr_t end = begin + 8 * MIME_ENTRIES; - const uintptr_t pa = (uintptr_t)a; - const uintptr_t pb = (uintptr_t)b; - uint64_t exta; - uint64_t extb; - - if (end - pa >= begin && end - pb >= begin) { - /* If both keys are within the uncompressed mime entries range, then - * we don't need to load from memory, just compare the pointers: they're - * all stored sequentially in memory by construction. */ - exta = pa; - extb = pb; - } else { - /* These are stored in big-endian so the comparison below works - * as expected. */ - exta = string_as_uint64((const char *)a); - extb = string_as_uint64((const char *)b); - } + const uint64_t exta = string_as_uint64((const char *)a); + const uint64_t extb = string_as_uint64((const char *)b); return (exta > extb) - (exta < extb); } From f9eedda18f89616fdc349d8a96c7ed2754f78e21 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 13 May 2024 20:23:23 -0700 Subject: [PATCH 2239/2505] Update SignalR implementation sample to use pub/sub and awaitv This cleans up this old sample (that's not currently built as the JS part is missing!) quite a bit, by using things that we didn't have back when it was written. This is so much nicer now! --- src/samples/chatr/main.c | 322 +++++++++++++++++---------------------- 1 file changed, 137 insertions(+), 185 deletions(-) diff --git a/src/samples/chatr/main.c b/src/samples/chatr/main.c index fd2385e56..7bcf06f07 100644 --- a/src/samples/chatr/main.c +++ b/src/samples/chatr/main.c @@ -18,42 +18,29 @@ * USA. */ -#include #include +#include -#include "lwan.h" +#include "../techempower/json.h" #include "hash.h" +#include "lwan.h" #include "ringbuffer.h" -#include "../techempower/json.h" -struct sync_map { - struct hash *table; - pthread_mutex_t mutex; -}; +struct hub { + struct lwan_pub_sub_topic *topic; -#define SYNC_MAP_INITIALIZER(free_key_func_, free_value_func_) \ - (struct sync_map) \ - { \ - .mutex = PTHREAD_MUTEX_INITIALIZER, \ - .table = hash_str_new(free_key_func_, free_value_func_) \ - } - -DEFINE_RING_BUFFER_TYPE(msg_ring_buffer, char *, 32) -struct msg_ring { - struct msg_ring_buffer rb; - pthread_mutex_t mutex; + pthread_rwlock_t clients_lock; + struct hash *clients; }; -#define MSG_RING_INITIALIZER \ - (struct msg_ring) { .mutex = PTHREAD_MUTEX_INITIALIZER } - struct available_transport { const char *transport; const char *transferFormats[4]; size_t numTransferFormats; }; static const struct json_obj_descr available_transport_descr[] = { - JSON_OBJECT_DESCR_PRIM(struct available_transport, transport, JSON_TOK_STRING), + JSON_OBJECT_DESCR_PRIM( + struct available_transport, transport, JSON_TOK_STRING), JSON_OBJECT_DESCR_ARRAY(struct available_transport, transferFormats, 1, @@ -67,7 +54,8 @@ struct negotiate_response { size_t numAvailableTransports; }; static const struct json_obj_descr negotiate_response_descr[] = { - JSON_OBJ_DESCR_PRIM(struct negotiate_response, connectionId, JSON_TOK_STRING), + JSON_OBJ_DESCR_PRIM( + struct negotiate_response, connectionId, JSON_TOK_STRING), JSON_OBJ_DESCR_OBJ_ARRAY(struct negotiate_response, availableTransports, 4, @@ -101,7 +89,8 @@ struct invocation_message { }; static const struct json_obj_descr invocation_message[] = { JSON_OBJ_DESCR_PRIM(struct invocation_message, target, JSON_TOK_STRING), - JSON_OBJ_DESCR_PRIM(struct invocation_message, invocationId, JSON_TOK_STRING), + JSON_OBJ_DESCR_PRIM( + struct invocation_message, invocationId, JSON_TOK_STRING), JSON_OBJ_DESCR_ARRAY(struct invocation_message, arguments, 10, @@ -118,99 +107,37 @@ struct completion_message { }; static const struct json_obj_descr invocation_message[] = { JSON_OBJ_DESCR_PRIM(struct completion_message, type, JSON_TOK_NUMBER), - JSON_OBJ_DESCR_PRIM(struct completion_message, invocationId, JSON_TOK_STRING), + JSON_OBJ_DESCR_PRIM( + struct completion_message, invocationId, JSON_TOK_STRING), JSON_OBJ_DESCR_PRIM(struct completion_message, result, JSON_TOK_STRING), JSON_OBJ_DESCR_PRIM(struct completion_message, error, JSON_TOK_STRING), }; -static bool msg_ring_try_put(struct msg_ring *ring, const char *msg) -{ - /* FIXME: find a way to use coro_strdup() here? should make cleanup easier */ - char *copy = strdup(msg); - bool ret; - - pthread_mutex_lock(&sync_map->mutex); - ret = msg_ring_buffer_try_put(&ring->rb, copy); - if (!ret) - free(copy); - pthread_mutex_unlock(&sync_map->mutex); - - return ret; -} - -static void msg_ring_consume(struct msg_ring *ring, - bool (*iter_func)(char *msg, void *data), - void *data) -{ - char *msg; - - pthread_mutex_lock(&ring->mutex); - - while ((msg = msg_ring_buffer_get_ptr_or_null(&ring->rb))) { - bool cont = iter(msg, data); - - free(msg); - if (!cont) - break; - } - - pthread_mutex_unlock(&ring->mutex); -} - -static bool free_ring_msg(char *msg, void *data) +static const char *subscribe_and_get_conn_id(struct hub *hub) { - free(msg); - return true; -} + lwan_pubsub_subscription *sub = lwan_pubsub_subscribe(hub->topic); -static void msg_ring_free(struct msg_ring *ring) -{ - msg_ring_consume(ring, free_ring_msg, NULL); - pthread_mutex_destroy(&ring->mutex); -} + if (!sub) + return NULL; -static int sync_map_add(struct sync_map *sync_map, const char *key, const void *value) -{ - int ret; - - pthread_mutex_lock(&sync_map->mutex); - ret = hash_add(sync_map->table, key, value); - pthread_mutex_unlock(&sync_map->mutex); - - return ret; -} + pthread_rwlock_wrlock(&hub->clients_hash); + while (true) { + const uint64_t id[] = {lwan_random_uint64(), lwan_random_uint64()}; + char *base64_id = base64_encode((char *)id, sizeof(id), NULL); -static void -sync_map_range(struct sync_map *sync_map, - bool (*iter_func)(const char *key, void *value, void *data), - void *data) -{ - struct hash_iter iter; - const void *key; - void *value; - - pthread_mutex_lock(&sync_map->mutex); - hash_iter_init(&sync_map->table, &iter); - while (hash_iter_next(&iter, &key, &value)) { - if (!iter_func(key, value, data)) - break; + switch (hash_add_unique(hub->clients, base64_id, sub)) { + case -EEXIST: + free(base64_id); + continue; + case 0: + pthread_rwlock_unlock(&hub->clients_hash); + return base64_id; + default: + pthread_rwlock_unlock(&sub->clients_hash); + lwan_pubsub_unsubscribe(sub); + return NULL; + } } - pthread_mutex_unlock(&sync_map->mutex); -} - -static const char *get_connection_id(char connection_id[static 17]) -{ - /* FIXME: use a better PRNG */ - /* FIXME: maybe base64? */ - static const char alphabet[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwvyz01234567890"; - - for (int i = 0; i < 16; i++) - connection_id[i] = alphabet[rand() % (sizeof(alphabet) - 1)]; - - connection_id[16] = '\0'; - - return connection_id; } static int append_to_strbuf(const char *bytes, size_t len, void *data) @@ -222,24 +149,24 @@ static int append_to_strbuf(const char *bytes, size_t len, void *data) LWAN_HANDLER(negotiate) { - char connection_id[17]; + struct hub *hub = data; if (lwan_request_get_method(request) != REQUEST_METHOD_POST) return HTTP_BAD_REQUEST; struct negotiate_response response = { - .connectionId = get_connection_id(connection_id), - .availableTransports = - (struct available_transport[]){ - { - .transport : "WebSockets", - .transferFormats : (const char *[]){"Text", "Binary"}, - .numTransferFormats : 2, - }, - }, + .connectionId = subscribe_and_get_conn_id(hub), + .availableTransports = (struct available_transport[]){{ + .transport : "WebSockets", + .transferFormats : (const char *[]){"Text", "Binary"}, + .numTransferFormats : 2 + }}, .numAvailableTransports = 1, }; + if (!response.connectionId) + return HTTP_INTERNAL_ERROR; + if (json_obj_encode_full(negotiate_response_descr, ARRAY_SIZE(negotiate_response_descr), &response, append_to_strbuf, response->buffer, false) != 0) @@ -293,8 +220,9 @@ static int send_json(struct lwan_response *request, static bool send_error(struct lwan_request *request, const char *error) { - lwan_strbuf_set_printf(request->response->buffer, - "{\"error\":\"%s\"}\x1e", error); + /* FIXME: use proper json here because error might need escaping! */ + lwan_strbuf_set_printf(request->response->buffer, "{\"error\":\"%s\"}\x1e", + error); lwan_response_websocket_send(request); return false; } @@ -332,27 +260,20 @@ static void handle_ping(struct lwan_request *request) lwan_response_websocket_send(request); } -static bool broadcast_msg(const void *key, void *value, void *data) -{ - struct msg_ring *messages = value; - - if (message->numArguments == 1) - return msg_ring_try_put(messages, message->arguments[0]); - - return false; -} - static struct completion_message handle_invocation_send(struct lwan_request *request, struct invocation_message *message, - struct sync_map *clients) + struct lwan_pubsub_topic *topic) { if (message->numArguments == 0) return (struct completion_message){.error = "No arguments were passed"}; - sync_map_range(clients, broadcast_msg, NULL); + if (!lwan_pubsub_publish(topic, message, sizeof(*message))) + return (struct completion_messdage{.error = "Could not publish message"}; return (struct completion_message){ + /* FIXME: memory allocated by coro_printf() is only freed when + * coroutine finishes! */ .result = coro_printf(request->conn->coro, "Got your message with %d arguments", message->numArguments), @@ -375,7 +296,7 @@ static bool send_completion_response(struct lwan_request *request, } static bool handle_invocation(struct lwan_request *request, - struct sync_map *clients) + struct lwan_pub_sub_topic *topic) { struct invocation_message message; int ret = parse_json(request->response, invocation_message_descr, @@ -393,32 +314,18 @@ static bool handle_invocation(struct lwan_request *request, if (streq(message.target, "send")) { return send_completion_response( request, message.invocationId, - handle_invocation_send(request, &message, clients)); + handle_invocation_send(request, &message, topic)); } return send_error(request, "Unknown target"); } -static bool hub_msg_ring_send_message(char *msg, void *data) -{ - struct lwan_request *request = data; - struct invocation_message invocation = { - .type = 1, - .target = "send", - .arguments[0] = msg, - .numArguments = 1, - }; - - return send_json(response, invocation_message_descr, - ARRAY_SIZE(invocation_message_descr), &invocation) == 0; -} - static void parse_hub_msg(struct lwan_request *request, - struct sync_map *clients) + struct lwan_pub_sub_topic *topic) { struct message message; - int ret = parse_json(response, message_descr, - ARRAY_SIZE(message_descr), &message); + int ret = parse_json(response, message_descr, ARRAY_SIZE(message_descr), + &message); if (ret < 0) return; if (!(ret & 1 << 0)) /* `type` not present, ignore */ @@ -426,66 +333,99 @@ static void parse_hub_msg(struct lwan_request *request, switch (message.type) { case 1: - return handle_invocation(request, clients); + return handle_invocation(request, topic); case 6: return handle_ping(request); } } +static void unsubscribe_client(void *data) +{ + struct lwan_pubsub_subscription *sub = data; + lwan_pubsub_unsubscribe(sub); +} + +static void mark_msg_as_done(void *data) +{ + struct lwan_pub_sub_msg *msg = data; + lwan_pubsub_msg_done(msg); +} + static enum lwan_http_status hub_connection_handler(struct lwan_request *request, struct lwan_response *response, const char *connection_id, void *data) { - struct sync_map *clients = data; - struct msg_ring msg_ring = MSG_RING_INITIALIZER; + struct hub *hub = data; + struct lwan_pub_subscriber *subscriber; - if (sync_map_add(clients, connection_id, &msg_ring) != 0) { - send_error(request, "Could not register client ID"); - msg_ring_free(messages); - return HTTP_INTERNAL_ERROR; - } - coro_defer2(request->conn->coro, remove_client, clients, connection_id); - coro_defer(request->conn->coro, msg_ring_free, messages); + pthread_rwlock_rdlock(&hub->clients_lock); + subscriber = hash_find(hub->clients, connection_id); + pthread_rwlock_unlock(&hub->clients_lock); + if (!subscriber) + return HTTP_BAD_REQUEST; + + coro_defer(request->conn->coro, unsubscribe_client, subscription); + const int websocket_fd = request->fd; + const int sub_fd = lwan_pubsub_get_notification_fd(sub); while (true) { - switch (lwan_response_websocket_read(request)) { - case ENOTCONN: - /* Shouldn't happen because if we get here, this connection - * has been upgraded to a websocket. */ - return HTTP_INTERNAL_ERROR; + int resumed_fd = lwan_request_awaitv_any( + request, websocket_fd, CONN_CORO_ASYNC_AWAIT_READ, sub_fd, + CONN_CORO_ASYNC_AWAIT_READ, -1); - case ECONNRESET: - /* Connection has been closed by the peer. */ + if (lwan->conns[resumed_fd].flags & CONN_HUNG_UP) return HTTP_UNAVAILABLE; - case EAGAIN: - msg_ring_consume(&msg_ring, hub_msg_ring_send_message, request); - - /* FIXME: ideally, websocket_read() should have a timeout; this is - * just a workaround */ - lwan_request_sleep(1000); - break; - - case 0: - parse_hub_msg(request, clients); - break; + if (resumed_fd == websocket_fd) { + switch (lwan_response_websocket_read(request)) { + case ENOTCONN: + case ECONNRESET: + return HTTP_UNAVAILABLE; + + case 0: + parse_hub_msg(request, topic); + break; + } + } else if (resumed_fd == sub_fd) { + struct lwan_pubsub_msg *msg; + + while ((msg = lwan_pubsub_consume(sub))) { + const struct lwan_value *value = lwan_pubsub_msg_value(msg); + struct invocation_message invocation = { + .type = 1, + .target = "send", + .arguments[0] = value->value, + .numArguments = 1, + }; + int64_t done_defer = + coro_defer(request->conn->coro, mark_msg_as_done, msg); + if (send_json(response, invocation_message_descr, + ARRAY_SIZE(invocation_message_descr), + &invocation) != 0) { + return HTTP_UNAVAILABLE; + } + coro_defer_fire_and_disarm(request->conn->coro, done_defer); + } } } } LWAN_HANDLER(chat) { - static const char handshake_response[] = "{}\x1e"; + struct hub *hub = data; const char *connection_id; if (lwan_request_websocket_upgrade(request) != HTTP_SWITCHING_PROTOCOLS) return HTTP_BAD_REQUEST; connection_id = lwan_request_get_query_param(request, "id"); - if (!connecton_id || *connection_id == '\0') - connection_id = get_connection_id(coro_malloc(request->conn->coro, 17)); + if (!connecton_id || *connection_id == '\0') { + connection_id = subscribe_and_get_conn_id(hub); + if (!connection_id) + return HTTP_INTERNAL_ERROR; + } if (!process_handshake(request, response)) return HTTP_BAD_REQUEST; @@ -495,10 +435,22 @@ LWAN_HANDLER(chat) int main(void) { - struct sync_map clients = SYNC_MAP_INITIALIZER(NULL, NULL); + struct hub hub = { + .topic = lwan_pubsub_new_topic(), + .clients_lock = PTHREAD_RWLOCK_INITIALIZER, + .clients = hash_str_new(free, NULL), + }; + + if (!hub.topic) + lwan_status_critical("Could not create pubsub topic"); + if (!hub.clients) + lwan_status_critical("Could not create clients hash table"); + const struct lwan_url_map default_map[] = { - {.prefix = "/chat", .handler = LWAN_HANDLER_REF(chat), .data = &clients}, - {.prefix = "/chat/negotiate", .handler = LWAN_HANDLER_REF(negotiate)}, + {.prefix = "/chat", .handler = LWAN_HANDLER_REF(chat), .data = &hub}, + {.prefix = "/chat/negotiate", + .handler = LWAN_HANDLER_REF(negotiate), + .data = &hub}, {.prefix = "/", .module = SERVE_FILES("wwwroot")}, {}, }; From 5d771762e3cce5aaebb3e8bc2ab7f582ed6032ef Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 13 May 2024 20:25:26 -0700 Subject: [PATCH 2240/2505] Use a branchless binary search for extension<->MIME-Types This uses the excellent branchless binary search by Orlon Peters[1], based on the Malte Skarupke's version[2] of Leonard E Shar's version of a binary search. It's a fascinating implementation that ends up becoming just a very tight loop using CMOV instructions, and finishing with another CMOV instruction. It's so clean that I removed the fast path using STRING_SWITCH as that's not necessary anymore. A nice side effect of this change is that the string "application/octet-stream" doesn't appear in the binary anymore (except in the debug version due to assertions), as it's now part of the compressed blob that mimegen generates. A good segue to this commit would be porting the other usage of bsearch() to this implementation. [1] https://orlp.net/blog/bitwise-binary-search/ [2] https://probablydance.com/2023/04/27/beautiful-branchless-binary-search/ --- src/bin/tools/mimegen.c | 59 ++++++++++++++++++++++------------------- src/lib/lwan-tables.c | 47 ++++++++++++-------------------- 2 files changed, 48 insertions(+), 58 deletions(-) diff --git a/src/bin/tools/mimegen.c b/src/bin/tools/mimegen.c index 354ae85de..68291d049 100644 --- a/src/bin/tools/mimegen.c +++ b/src/bin/tools/mimegen.c @@ -161,29 +161,6 @@ static char *compress_output(const struct output *output, size_t *outlen) return compressed; } -static bool is_builtin_ext(const char *ext) -{ - /* STRING_SWITCH_L() is not used here to not bring in lwan.h */ - /* FIXME: maybe use an X-macro to keep in sync with lwan-tables.c? */ - if (strcaseequal_neutral(ext, "css")) - return true; - if (strcaseequal_neutral(ext, "gif")) - return true; - if (strcaseequal_neutral(ext, "htm")) - return true; - if (strcaseequal_neutral(ext, "html")) - return true; - if (strcaseequal_neutral(ext, "jpg")) - return true; - if (strcaseequal_neutral(ext, "js")) - return true; - if (strcaseequal_neutral(ext, "png")) - return true; - if (strcaseequal_neutral(ext, "txt")) - return true; - return false; -} - int main(int argc, char *argv[]) { /* 32k is sufficient for the provided mime.types, but we can reallocate @@ -258,11 +235,6 @@ int main(int argc, char *argv[]) ext[8] = '\0'; } - /* Lwan has a fast-path for some common extensions, so don't bundle them - * in this table if not really needed. */ - if (is_builtin_ext(ext)) - continue; - k = strdup(ext); v = strdup(mime_type); @@ -286,6 +258,22 @@ int main(int argc, char *argv[]) } } + { + char *k = strdup("bin"); + char *v = strdup("application/octet-stream"); + if (!k || !v) { + fprintf(stderr, "Could not allocate memory\n"); + fclose(fp); + return 1; + } + int r = hash_add_unique(ext_mime, k, v); + if (r != 0 && r != -EEXIST) { + fprintf(stderr, "Could not add fallback mime entry\n"); + fclose(fp); + return 1; + } + } + /* Get sorted list of extensions. */ exts = calloc(hash_get_count(ext_mime), sizeof(char *)); if (!exts) { @@ -305,6 +293,7 @@ int main(int argc, char *argv[]) fclose(fp); return 1; } + ssize_t bin_index = -1; for (i = 0; i < hash_get_count(ext_mime); i++) { uint64_t ext_lower = 0; @@ -322,6 +311,9 @@ int main(int argc, char *argv[]) fclose(fp); return 1; } + + if (bin_index < 0 && streq(exts[i], "bin")) + bin_index = (ssize_t)i; } for (i = 0; i < hash_get_count(ext_mime); i++) { if (output_append(&output, hash_find(ext_mime, exts[i])) < 0) { @@ -331,6 +323,12 @@ int main(int argc, char *argv[]) } } + if (bin_index < 0) { + fprintf(stderr, "Could not find fallback item after sorting!\n"); + fclose(fp); + return 1; + } + /* Compress blob. */ compressed = compress_output(&output, &compressed_size); if (!compressed) { @@ -349,10 +347,15 @@ int main(int argc, char *argv[]) #else printf("/* Compressed with zlib (deflate) */\n"); #endif + + unsigned int entries_floor = 1u << (31 - __builtin_clz(hash_get_count(ext_mime))); + printf("#pragma once\n"); printf("#define MIME_UNCOMPRESSED_LEN %zu\n", output.used); printf("#define MIME_COMPRESSED_LEN %lu\n", compressed_size); printf("#define MIME_ENTRIES %d\n", hash_get_count(ext_mime)); + printf("#define MIME_ENTRIES_FLOOR %d\n", entries_floor); + printf("#define MIME_ENTRY_FALLBACK %ld\n", bin_index); printf("static const unsigned char mime_entries_compressed[] = {\n"); for (i = 1; compressed_size; compressed_size--, i++) printf("0x%02x,%c", compressed[i - 1] & 0xff, " \n"[i % 13 == 0]); diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index 6a63508fd..bcbc2d159 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -36,6 +36,7 @@ static unsigned char uncompressed_mime_entries[MIME_UNCOMPRESSED_LEN]; static char *mime_types[MIME_ENTRIES]; +static uint64_t *mime_extensions; static bool mime_entries_initialized = false; void lwan_tables_shutdown(void) @@ -86,6 +87,7 @@ void lwan_tables_init(void) mime_types[i] = (char *)ptr; ptr += strlen((const char *)ptr) + 1; } + mime_extensions = (uint64_t *)uncompressed_mime_entries; mime_entries_initialized = true; @@ -120,34 +122,25 @@ LWAN_SELF_TEST(status_codes) #undef ASSERT_STATUS } -static int compare_mime_entry(const void *a, const void *b) +static ALWAYS_INLINE const char *bsearch_mime_type(uint64_t ext) { - const uint64_t exta = string_as_uint64((const char *)a); - const uint64_t extb = string_as_uint64((const char *)b); - - return (exta > extb) - (exta < extb); + /* Based on https://orlp.net/blog/bitwise-binary-search/ */ + int64_t b = ext > mime_extensions[MIME_ENTRIES / 2] + ? MIME_ENTRIES - MIME_ENTRIES_FLOOR + : -1; + for (uint64_t bit = MIME_ENTRIES_FLOOR >> 1; bit != 0; bit >>= 1) { + if (ext > mime_extensions[b + (int64_t)bit]) + b += (int64_t)bit; + } + return mime_types[mime_extensions[b + 1] == ext ? b + 1 + : MIME_ENTRY_FALLBACK]; } -const char * -lwan_determine_mime_type_for_file_name(const char *file_name) +const char *lwan_determine_mime_type_for_file_name(const char *file_name) { char *last_dot = strrchr(file_name, '.'); - if (UNLIKELY(!last_dot)) - goto fallback; - - STRING_SWITCH_L(last_dot) { - case STR4_INT_L('.','c','s','s'): return "text/css"; - case STR4_INT_L('.','g','i','f'): return "image/gif"; - case STR4_INT_L('.','h','t','m'): return "text/html"; - case STR4_INT_L('.','j','p','g'): return "image/jpeg"; - case STR4_INT_L('.','j','s',' '): return "text/javascript"; - case STR4_INT_L('.','p','n','g'): return "image/png"; - case STR4_INT_L('.','t','x','t'): return "text/plain"; - } - - if (LIKELY(*last_dot)) { + if (LIKELY(last_dot && *last_dot)) { uint64_t key = 0; - const unsigned char *extension; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wstringop-truncation" @@ -157,17 +150,11 @@ lwan_determine_mime_type_for_file_name(const char *file_name) * 8 bytes per extension. */ strncpy((char *)&key, last_dot + 1, 8); #pragma GCC diagnostic pop - key &= ~0x2020202020202020ull; - key = htobe64(key); - extension = bsearch(&key, uncompressed_mime_entries, MIME_ENTRIES, 8, - compare_mime_entry); - if (LIKELY(extension)) - return mime_types[(extension - uncompressed_mime_entries) / 8]; + return bsearch_mime_type(htobe64(key & ~0x2020202020202020ull)); } -fallback: - return "application/octet-stream"; + return mime_types[MIME_ENTRY_FALLBACK]; } #include "lookup-http-status.h" /* genrated by statuslookupgen */ From b6a95c8afb64ce05dd1608572ce390c780684764 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 15 May 2024 07:07:10 -0700 Subject: [PATCH 2241/2505] Slight cleanup in timeout_queue_expire_waiting() --- src/lib/lwan-tq.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan-tq.c b/src/lib/lwan-tq.c index ce0542c0e..20e778886 100644 --- a/src/lib/lwan-tq.c +++ b/src/lib/lwan-tq.c @@ -112,14 +112,14 @@ void timeout_queue_expire_waiting(struct timeout_queue *tq) if (conn->time_to_expire > tq->current_time) return; - if (LIKELY(!(conn->flags & CONN_IS_WEBSOCKET))) { - timeout_queue_expire(tq, conn); - } else { - if (LIKELY(lwan_send_websocket_ping_for_tq(conn))) + if (conn->flags & CONN_IS_WEBSOCKET) { + if (LIKELY(lwan_send_websocket_ping_for_tq(conn))) { timeout_queue_move_to_last(tq, conn); - else - timeout_queue_expire(tq, conn); + continue; + } } + + timeout_queue_expire(tq, conn); } /* Timeout queue exhausted: reset epoch */ From 802cf500ca9662157948e9783c7956ab7e6341ad Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 15 May 2024 07:07:52 -0700 Subject: [PATCH 2242/2505] Reindent lwan-websocket.c --- src/lib/lwan-websocket.c | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index d699e4dc9..62e269ff9 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -126,7 +126,8 @@ static bool write_websocket_frame(struct lwan_request *request, return write_websocket_frame_full(request, header_byte, msg, len, true); } -static inline void lwan_response_websocket_write(struct lwan_request *request, unsigned char op) +static inline void lwan_response_websocket_write(struct lwan_request *request, + unsigned char op) { size_t len = lwan_strbuf_get_length(request->response.buffer); char *msg = lwan_strbuf_get_buffer(request->response.buffer); @@ -159,8 +160,8 @@ static size_t get_frame_length(struct lwan_request *request, uint16_t header) len = (uint64_t)ntohs((uint16_t)len); if (len < 0x7e) { - lwan_status_warning("Can't use 16-bit encoding for frame length of %zu", - len); + lwan_status_warning( + "Can't use 16-bit encoding for frame length of %zu", len); coro_yield(request->conn->coro, CONN_CORO_ABORT); __builtin_unreachable(); } @@ -171,14 +172,13 @@ static size_t get_frame_length(struct lwan_request *request, uint16_t header) len = be64toh(len); if (UNLIKELY(len > SSIZE_MAX)) { - lwan_status_warning("Frame length of %zu won't fit a ssize_t", - len); + lwan_status_warning("Frame length of %zu won't fit a ssize_t", len); coro_yield(request->conn->coro, CONN_CORO_ABORT); __builtin_unreachable(); } if (UNLIKELY(len <= 0xffff)) { - lwan_status_warning("Can't use 64-bit encoding for frame length of %zu", - len); + lwan_status_warning( + "Can't use 64-bit encoding for frame length of %zu", len); coro_yield(request->conn->coro, CONN_CORO_ABORT); __builtin_unreachable(); } @@ -342,7 +342,8 @@ bool lwan_send_websocket_ping_for_tq(struct lwan_connection *conn) (char *)&payload, sizeof(payload), false); } -int lwan_response_websocket_read_hint(struct lwan_request *request, size_t size_hint) +int lwan_response_websocket_read_hint(struct lwan_request *request, + size_t size_hint) { enum ws_opcode opcode = WS_OPCODE_INVALID; enum ws_opcode last_opcode; @@ -357,13 +358,15 @@ int lwan_response_websocket_read_hint(struct lwan_request *request, size_t size_ next_frame: last_opcode = opcode; - if (!lwan_recv(request, &header, sizeof(header), continuation ? 0 : MSG_DONTWAIT)) + if (!lwan_recv(request, &header, sizeof(header), + continuation ? 0 : MSG_DONTWAIT)) return EAGAIN; header = htons(header); continuation = false; if (UNLIKELY(header & 0x7000)) { - lwan_status_debug("RSV1...RSV3 has non-zero value %d, aborting", header & 0x7000); + lwan_status_debug("RSV1...RSV3 has non-zero value %d, aborting", + header & 0x7000); /* No extensions are supported yet, so fail connection per RFC6455. */ coro_yield(request->conn->coro, CONN_CORO_ABORT); __builtin_unreachable(); @@ -402,7 +405,8 @@ int lwan_response_websocket_read_hint(struct lwan_request *request, size_t size_ case WS_OPCODE_RSVD_1 ... WS_OPCODE_RSVD_5: case WS_OPCODE_RSVD_CONTROL_1 ... WS_OPCODE_RSVD_CONTROL_5: case WS_OPCODE_INVALID: - /* RFC6455: ...the receiving endpoint MUST _Fail the WebSocket Connection_ */ + /* RFC6455: ...the receiving endpoint MUST _Fail the WebSocket + * Connection_ */ coro_yield(request->conn->coro, CONN_CORO_ABORT); __builtin_unreachable(); } @@ -431,10 +435,10 @@ int lwan_response_websocket_read_hint(struct lwan_request *request, size_t size_ inline int lwan_response_websocket_read(struct lwan_request *request) { /* Ensure that a rogue client won't keep increasing the memory usage in an - * uncontrolled manner by curbing the backing store to 1KB at most by default. - * If an application expects messages to be larger than 1024 bytes on average, - * they can call lwan_response_websocket_read_hint() directly with a larger - * value to avoid malloc chatter (things should still work, but will be - * slightly more inefficient). */ + * uncontrolled manner by curbing the backing store to 1KB at most by + * default. If an application expects messages to be larger than 1024 bytes + * on average, they can call lwan_response_websocket_read_hint() directly + * with a larger value to avoid malloc chatter (things should still work, + * but will be slightly more inefficient). */ return lwan_response_websocket_read_hint(request, 1024); } From 3508980882634495633e6fffc86ed918e8bd5510 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 15 May 2024 21:03:50 -0700 Subject: [PATCH 2243/2505] Simplify async/await paths This removes quite a bit of complexity from how async/await works: * The connection_coro_yield enum doesn't have any CONN_CORO_ASYNC_AWAIT_* value anymore, because the CONN_CORO_WANT_* values are used instead. * epoll flags are now set using the same function that awaitv uses, and that resume_coro() used to use, so there's no need to pack the file descriptor and the interest in the coroutine yield value anymore. * resume_coro() is now simplified, not needing to do any work related to async/await anymore, as that's now performed by the async_await_fd() auxiliary function: so now you only pay the (cheap!) price if you're using this feature. In addition, signaling if an awaited file descriptor was hung up is done differently, via the return value of the async/await functions. --- src/lib/lwan-thread.c | 211 +++++++++++++++++++---------------- src/lib/lwan.h | 12 +- src/samples/websocket/main.c | 122 +++++++++++++------- 3 files changed, 192 insertions(+), 153 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 844e53954..9afba606d 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -639,23 +639,23 @@ static void unasync_await_conn(void *data1, void *data2) } static enum lwan_connection_coro_yield -resume_async(const struct lwan *l, - enum lwan_connection_coro_yield yield_result, - int await_fd, - struct lwan_connection *conn, - int epoll_fd) +prepare_await(const struct lwan *l, + enum lwan_connection_coro_yield yield_result, + int await_fd, + struct lwan_connection *conn, + int epoll_fd) { static const enum lwan_connection_flags to_connection_flags[] = { - [CONN_CORO_ASYNC_AWAIT_READ] = CONN_EVENTS_READ, - [CONN_CORO_ASYNC_AWAIT_WRITE] = CONN_EVENTS_WRITE, - [CONN_CORO_ASYNC_AWAIT_READ_WRITE] = CONN_EVENTS_READ_WRITE, + [CONN_CORO_WANT_READ] = CONN_EVENTS_READ, + [CONN_CORO_WANT_WRITE] = CONN_EVENTS_WRITE, + [CONN_CORO_WANT_READ_WRITE] = CONN_EVENTS_READ_WRITE, }; enum lwan_connection_flags flags; int op; assert(await_fd >= 0); - assert(yield_result >= CONN_CORO_ASYNC_AWAIT_READ && - yield_result <= CONN_CORO_ASYNC_AWAIT_READ_WRITE); + assert(yield_result >= CONN_CORO_WANT_READ && + yield_result <= CONN_CORO_WANT_READ_WRITE); flags = to_connection_flags[yield_result]; @@ -700,57 +700,47 @@ resume_async(const struct lwan *l, return CONN_CORO_ABORT; } -struct flag_update { - unsigned int num_awaiting; - enum lwan_connection_coro_yield request_conn_yield; -}; - -static void reset_conn_async_awaitv_flag(struct lwan_connection *conns, - va_list ap_orig) +static void clear_awaitv_flags(struct lwan_connection *conns, va_list ap_orig) { va_list ap; va_copy(ap, ap_orig); - - while (true) { - int await_fd = va_arg(ap, int); - if (await_fd < 0) { - va_end(ap); - break; - } - - conns[await_fd].flags &= ~CONN_ASYNC_AWAITV; - + for (int fd = va_arg(ap, int); fd >= 0; fd = va_arg(ap, int)) { + conns[fd].flags &= ~CONN_ASYNC_AWAITV; LWAN_NO_DISCARD(va_arg(ap, enum lwan_connection_coro_yield)); } + va_end(ap); } -static struct flag_update -update_flags_for_async_awaitv(struct lwan_request *r, struct lwan *l, va_list ap) +struct awaitv_state { + unsigned int num_awaiting; + enum lwan_connection_coro_yield request_conn_yield; +}; + +static int prepare_awaitv(struct lwan_request *r, + struct lwan *l, + va_list ap, + struct awaitv_state *state) { int epoll_fd = r->conn->thread->epoll_fd; - struct flag_update update = {.num_awaiting = 0, - .request_conn_yield = CONN_CORO_YIELD}; - reset_conn_async_awaitv_flag(l->conns, ap); + *state = (struct awaitv_state){ + .num_awaiting = 0, + .request_conn_yield = CONN_CORO_YIELD, + }; - while (true) { - int await_fd = va_arg(ap, int); - if (await_fd < 0) { - return update; - } + clear_awaitv_flags(l->conns, ap); + for (int await_fd = va_arg(ap, int); await_fd >= 0; + await_fd = va_arg(ap, int)) { + struct lwan_connection *conn = &l->conns[await_fd]; enum lwan_connection_coro_yield events = va_arg(ap, enum lwan_connection_coro_yield); - if (UNLIKELY(events < CONN_CORO_ASYNC_AWAIT_READ || - events > CONN_CORO_ASYNC_AWAIT_READ_WRITE)) { - lwan_status_error("awaitv() called with invalid events"); - coro_yield(r->conn->coro, CONN_CORO_ABORT); - __builtin_unreachable(); - } - - struct lwan_connection *conn = &l->conns[await_fd]; + if (UNLIKELY(events < CONN_CORO_WANT_READ || + events > CONN_CORO_WANT_READ_WRITE)) { + return -EINVAL; + } if (UNLIKELY(conn->flags & CONN_ASYNC_AWAITV)) { lwan_status_debug("ignoring second awaitv call on same fd: %d", await_fd); @@ -758,102 +748,133 @@ update_flags_for_async_awaitv(struct lwan_request *r, struct lwan *l, va_list ap } conn->flags |= CONN_ASYNC_AWAITV; - update.num_awaiting++; + state->num_awaiting++; if (await_fd == r->fd) { - static const enum lwan_connection_coro_yield to_request_yield[] = { - [CONN_CORO_ASYNC_AWAIT_READ] = CONN_CORO_WANT_READ, - [CONN_CORO_ASYNC_AWAIT_WRITE] = CONN_CORO_WANT_WRITE, - [CONN_CORO_ASYNC_AWAIT_READ_WRITE] = CONN_CORO_WANT_READ_WRITE, - }; - - update.request_conn_yield = to_request_yield[events]; + state->request_conn_yield = events; continue; } - - events = resume_async(l, events, await_fd, r->conn, epoll_fd); - if (UNLIKELY(events == CONN_CORO_ABORT)) { + if (UNLIKELY(prepare_await(l, events, await_fd, r->conn, epoll_fd) == + CONN_CORO_ABORT)) { lwan_status_error("could not register fd for async operation"); - coro_yield(r->conn->coro, CONN_CORO_ABORT); - __builtin_unreachable(); + return -EIO; } } + + return 0; } int lwan_request_awaitv_any(struct lwan_request *r, ...) { struct lwan *l = r->conn->thread->lwan; + struct awaitv_state state; va_list ap; va_start(ap, r); - struct flag_update update = update_flags_for_async_awaitv(r, l, ap); + int ret = prepare_awaitv(r, l, ap, &state); va_end(ap); + if (UNLIKELY(ret < 0)) { + errno = -ret; + lwan_status_critical_perror("prepare_awaitv()"); + coro_yield(r->conn->coro, CONN_CORO_ABORT); + __builtin_unreachable(); + } + while (true) { - int64_t v = coro_yield(r->conn->coro, update.request_conn_yield); + int64_t v = coro_yield(r->conn->coro, state.request_conn_yield); struct lwan_connection *conn = (struct lwan_connection *)(uintptr_t)v; - if (conn->flags & CONN_ASYNC_AWAITV) - return lwan_connection_get_fd(l, conn); + if (conn->flags & CONN_ASYNC_AWAITV) { + /* Ensure flags are unset in case awaitv_any() is called with + * a different set of file descriptors. */ + va_start(ap, r); + clear_awaitv_flags(l->conns, ap); + va_end(ap); + + int fd = lwan_connection_get_fd(l, conn); + return UNLIKELY(conn->flags & CONN_HUNG_UP) ? -fd : fd; + } } } -void lwan_request_awaitv_all(struct lwan_request *r, ...) +int lwan_request_awaitv_all(struct lwan_request *r, ...) { struct lwan *l = r->conn->thread->lwan; + struct awaitv_state state; va_list ap; va_start(ap, r); - struct flag_update update = update_flags_for_async_awaitv(r, l, ap); + int ret = prepare_awaitv(r, l, ap, &state); va_end(ap); - while (update.num_awaiting) { - int64_t v = coro_yield(r->conn->coro, update.request_conn_yield); + if (UNLIKELY(ret < 0)) { + errno = -ret; + lwan_status_critical_perror("prepare_awaitv()"); + coro_yield(r->conn->coro, CONN_CORO_ABORT); + __builtin_unreachable(); + } + + for (ret = 0; state.num_awaiting;) { + int64_t v = coro_yield(r->conn->coro, state.request_conn_yield); struct lwan_connection *conn = (struct lwan_connection *)(uintptr_t)v; if (conn->flags & CONN_ASYNC_AWAITV) { conn->flags &= ~CONN_ASYNC_AWAITV; - update.num_awaiting--; + + if (UNLIKELY(conn->flags & CONN_HUNG_UP)) { + /* Ensure flags are unset in case awaitv_any() is called with + * a different set of file descriptors. */ + va_start(ap, r); + clear_awaitv_flags(l->conns, ap); + va_end(ap); + + return lwan_connection_get_fd(l, conn); + } + + state.num_awaiting--; } } -} - -static inline int64_t -make_async_yield_value(int fd, enum lwan_connection_coro_yield event) -{ - assert(event >= CONN_CORO_ASYNC_AWAIT_READ && - event <= CONN_CORO_ASYNC_AWAIT_READ_WRITE); - return (int64_t)(((uint64_t)fd << 32 | event)); + return -1; } static inline int async_await_fd(struct lwan_connection *conn, int fd, enum lwan_connection_coro_yield events) { - int64_t yield_value = make_async_yield_value(fd, events); - int64_t from_coro = coro_yield(conn->coro, yield_value); - struct lwan_connection *conn_from_coro = - (struct lwan_connection *)(intptr_t)from_coro; + struct lwan_thread *thread = conn->thread; + struct lwan *lwan = thread->lwan; + enum lwan_connection_coro_yield yield = + prepare_await(lwan, events, fd, conn, thread->epoll_fd); - assert(conn_from_coro->flags & CONN_ASYNC_AWAIT); + if (LIKELY(yield == CONN_CORO_SUSPEND)) { + int64_t v = coro_yield(conn->coro, yield); - return lwan_connection_get_fd(conn->thread->lwan, conn_from_coro); + fd = + lwan_connection_get_fd(lwan, (struct lwan_connection *)(intptr_t)v); + + return UNLIKELY(conn->flags & CONN_HUNG_UP) ? -fd : fd; + } + + lwan_status_critical_perror("prepare_await(%d)", fd); + coro_yield(conn->coro, CONN_CORO_ABORT); + __builtin_unreachable(); } -inline int lwan_request_await_read(struct lwan_request *r, int fd) +int lwan_request_await_read(struct lwan_request *r, int fd) { - return async_await_fd(r->conn, fd, CONN_CORO_ASYNC_AWAIT_READ); + return async_await_fd(r->conn, fd, CONN_CORO_WANT_READ); } -inline int lwan_request_await_write(struct lwan_request *r, int fd) +int lwan_request_await_write(struct lwan_request *r, int fd) { - return async_await_fd(r->conn, fd, CONN_CORO_ASYNC_AWAIT_WRITE); + return async_await_fd(r->conn, fd, CONN_CORO_WANT_WRITE); } -inline int lwan_request_await_read_write(struct lwan_request *r, int fd) +int lwan_request_await_read_write(struct lwan_request *r, int fd) { - return async_await_fd(r->conn, fd, CONN_CORO_ASYNC_AWAIT_READ_WRITE); + return async_await_fd(r->conn, fd, CONN_CORO_WANT_READ_WRITE); } static ALWAYS_INLINE void resume_coro(struct timeout_queue *tq, @@ -864,20 +885,12 @@ static ALWAYS_INLINE void resume_coro(struct timeout_queue *tq, assert(conn_to_resume->coro); assert(conn_to_yield->coro); - int64_t from_coro = coro_resume_value(conn_to_resume->coro, - (int64_t)(intptr_t)conn_to_yield); - enum lwan_connection_coro_yield yield_result = from_coro & 0xffffffff; - - if (UNLIKELY(yield_result >= CONN_CORO_ASYNC)) { - int await_fd = (int)((uint64_t)from_coro >> 32); - yield_result = resume_async(tq->lwan, yield_result, await_fd, - conn_to_resume, epoll_fd); - } - - if (UNLIKELY(yield_result == CONN_CORO_ABORT)) { + enum lwan_connection_coro_yield from_coro = coro_resume_value( + conn_to_resume->coro, (int64_t)(intptr_t)conn_to_yield); + if (UNLIKELY(from_coro == CONN_CORO_ABORT)) { timeout_queue_expire(tq, conn_to_resume); } else { - update_epoll_flags(tq->lwan, conn_to_resume, epoll_fd, yield_result); + update_epoll_flags(tq->lwan, conn_to_resume, epoll_fd, from_coro); timeout_queue_move_to_last(tq, conn_to_resume); } } diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 5198446d5..afe42f74b 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -334,17 +334,7 @@ enum lwan_connection_coro_yield { CONN_CORO_SUSPEND, CONN_CORO_RESUME, - /* Group async stuff together to make it easier to check if a connection - * coroutine is yielding because of async reasons. */ - CONN_CORO_ASYNC_AWAIT_READ, - CONN_CORO_ASYNC_AWAIT_WRITE, - CONN_CORO_ASYNC_AWAIT_READ_WRITE, - CONN_CORO_MAX, - - /* Private API used by the async/await mechanism. Shouldn't be used - * by handlers. */ - CONN_CORO_ASYNC = CONN_CORO_ASYNC_AWAIT_READ, }; struct lwan_key_value { @@ -675,7 +665,7 @@ int lwan_request_await_read(struct lwan_request *r, int fd); int lwan_request_await_write(struct lwan_request *r, int fd); int lwan_request_await_read_write(struct lwan_request *r, int fd); int lwan_request_awaitv_any(struct lwan_request *r, ...); -void lwan_request_awaitv_all(struct lwan_request *r, ...); +int lwan_request_awaitv_all(struct lwan_request *r, ...); ssize_t lwan_request_async_read(struct lwan_request *r, int fd, void *buf, size_t len); ssize_t lwan_request_async_read_flags(struct lwan_request *request, int fd, void *buf, size_t len, int flags); ssize_t lwan_request_async_write(struct lwan_request *r, int fd, const void *buf, size_t len); diff --git a/src/samples/websocket/main.c b/src/samples/websocket/main.c index 64ccdd644..dc4a1a03d 100644 --- a/src/samples/websocket/main.c +++ b/src/samples/websocket/main.c @@ -123,7 +123,6 @@ static void pub_depart_message(void *data1, void *data2) LWAN_HANDLER_ROUTE(ws_chat, "/ws-chat") { - struct lwan *lwan = request->conn->thread->lwan; struct lwan_pubsub_subscriber *sub; struct lwan_pubsub_msg *msg; enum lwan_http_status status; @@ -152,12 +151,9 @@ LWAN_HANDLER_ROUTE(ws_chat, "/ws-chat") const int websocket_fd = request->fd; const int sub_fd = lwan_pubsub_get_notification_fd(sub); while (true) { - int resumed_fd = lwan_request_awaitv_any( - request, websocket_fd, CONN_CORO_ASYNC_AWAIT_READ, sub_fd, - CONN_CORO_ASYNC_AWAIT_READ, -1); - - if (lwan->conns[resumed_fd].flags & CONN_HUNG_UP) - goto out; + int resumed_fd = + lwan_request_awaitv_any(request, websocket_fd, CONN_CORO_WANT_READ, + sub_fd, CONN_CORO_WANT_READ, -1); if (resumed_fd == sub_fd) { while ((msg = lwan_pubsub_consume(sub))) { @@ -184,10 +180,11 @@ LWAN_HANDLER_ROUTE(ws_chat, "/ws-chat") (int)lwan_strbuf_get_length(response->buffer), lwan_strbuf_get_buffer(response->buffer)); } + } else if (resumed_fd < 0) { + lwan_status_error("error from fd %d", -resumed_fd); + goto out; } else { - lwan_status_error( - "lwan_request_awaitv_any() returned %d, but waiting on it", - resumed_fd); + lwan_status_warning("not awaiting on fd %d, ignoring", resumed_fd); } } @@ -204,68 +201,107 @@ LWAN_HANDLER_ROUTE(index, "/") "\n" " \n" " \n" " \n" " \n" "

Lwan WebSocket demo!

\n" - "

Send-only sample: server is writing this continuously:

\n" - "

Disconnected

\n" + "

Send-only sample: server is writing this " + "continuously:

\n" + "

Disconnected

\n" "

Echo server sample:

\n" - "

\n" - "

Server said this:

Disconnected

\n" + "

\n" + "

Server said this:

Disconnected

\n" "

Chat sample:

\n" - " Send message:

\n" - " \n" + " Send message:

\n" + " \n" " \n" ""; From 6d12dd827709e8ee7485371d6cdf9d56961c1ee6 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 15 May 2024 22:28:39 -0700 Subject: [PATCH 2244/2505] Don't allocate memory when performing hpack huffman decoding Use a ring buffer instead. Hopefully this should increase the fuzzing throughput. --- src/bin/fuzz/h2_huffman_fuzzer.cc | 11 +- src/lib/lwan-h2-huffman.c | 110 +++++++++++-------- src/lib/ringbuffer.h | 22 ++++ src/scripts/gentables.py | 172 ++++++++++++++++++++---------- 4 files changed, 205 insertions(+), 110 deletions(-) diff --git a/src/bin/fuzz/h2_huffman_fuzzer.cc b/src/bin/fuzz/h2_huffman_fuzzer.cc index 270ae7a5e..ef0de8106 100644 --- a/src/bin/fuzz/h2_huffman_fuzzer.cc +++ b/src/bin/fuzz/h2_huffman_fuzzer.cc @@ -2,16 +2,11 @@ #include extern "C" { -uint8_t *lwan_h2_huffman_decode_for_fuzzing(const uint8_t *input, - size_t input_len); +bool lwan_h2_huffman_decode_for_fuzzing(const uint8_t *input, + size_t input_len); int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { - uint8_t *decoded = lwan_h2_huffman_decode_for_fuzzing(data, size); - if (decoded) { - free(decoded); - return 0; - } - return 1; + return lwan_h2_huffman_decode_for_fuzzing(data, size) == true; } } diff --git a/src/lib/lwan-h2-huffman.c b/src/lib/lwan-h2-huffman.c index 81816edf7..adeae37ea 100644 --- a/src/lib/lwan-h2-huffman.c +++ b/src/lib/lwan-h2-huffman.c @@ -305,86 +305,97 @@ static inline bool consume(struct bit_reader *reader, int count) return reader->total_bitcount > 0; } -static inline size_t output_size(size_t input_size) +DEFINE_RING_BUFFER_TYPE(uint8_ring_buffer, uint8_t, 64) + +struct lwan_h2_huffman_decoder { + struct bit_reader bit_reader; + struct uint8_ring_buffer buffer; +}; + +void lwan_h2_huffman_init(struct lwan_h2_huffman_decoder *huff, + const uint8_t *input, + size_t input_len) { - /* Smallest input is 5 bits which produces 8 bits. Scaling that to 8 bits, - * we get 12.8 bits of output per 8 bits of input. */ - return (input_size * 128) / 10; + huff->bit_reader = (struct bit_reader){ + .bitptr = input, + .total_bitcount = (int64_t)input_len * 8, + }; + uint8_ring_buffer_init(&huff->buffer); } -uint8_t *lwan_h2_huffman_decode_for_fuzzing(const uint8_t *input, - size_t input_len) +ssize_t lwan_h2_huffman_next(struct lwan_h2_huffman_decoder *huff) { - uint8_t *output = malloc(output_size(input_len)); - uint8_t *ret = output; - struct bit_reader bit_reader = {.bitptr = input, - .total_bitcount = (int64_t)input_len * 8}; + struct bit_reader *reader = &huff->bit_reader; + struct uint8_ring_buffer *buffer = &huff->buffer; - while (bit_reader.total_bitcount > 7) { - uint8_t peeked_byte = peek_byte(&bit_reader); + while (reader->total_bitcount > 7) { + if (uint8_ring_buffer_full(buffer)) + goto done; + + uint8_t peeked_byte = peek_byte(reader); if (LIKELY(level0[peeked_byte].num_bits)) { - *output++ = level0[peeked_byte].symbol; - consume(&bit_reader, level0[peeked_byte].num_bits); + uint8_ring_buffer_put_copy(buffer, level0[peeked_byte].symbol); + consume(reader, level0[peeked_byte].num_bits); assert(bit_reader.total_bitcount >= 0); continue; } - if (!consume(&bit_reader, 8)) - goto fail; + if (!consume(reader, 8)) + return -1; const struct h2_huffman_code *level1 = next_level0(peeked_byte); - peeked_byte = peek_byte(&bit_reader); + peeked_byte = peek_byte(reader); if (level1[peeked_byte].num_bits) { - *output++ = level1[peeked_byte].symbol; - if (!consume(&bit_reader, level1[peeked_byte].num_bits)) - goto fail; + uint8_ring_buffer_put_copy(buffer, level1[peeked_byte].symbol); + if (!consume(reader, level1[peeked_byte].num_bits)) + return -1; continue; } - if (!consume(&bit_reader, 8)) - goto fail; + if (!consume(reader, 8)) + return -1; const struct h2_huffman_code *level2 = next_level1(peeked_byte); - peeked_byte = peek_byte(&bit_reader); + peeked_byte = peek_byte(reader); if (level2[peeked_byte].num_bits) { - *output++ = level2[peeked_byte].symbol; - if (!consume(&bit_reader, level2[peeked_byte].num_bits)) - goto fail; + uint8_ring_buffer_put_copy(buffer, level2[peeked_byte].symbol); + if (!consume(reader, level2[peeked_byte].num_bits)) + return -1; continue; } - if (!consume(&bit_reader, 8)) + if (!consume(reader, 8)) goto fail; const struct h2_huffman_code *level3 = next_level2(peeked_byte); if (LIKELY(level3)) { - peeked_byte = peek_byte(&bit_reader); + peeked_byte = peek_byte(reader); if (level3[peeked_byte].num_bits < 0) { /* EOS found */ - return ret; + goto done; } if (LIKELY(level3[peeked_byte].num_bits)) { - *output++ = level3[peeked_byte].symbol; - if (!consume(&bit_reader, level3[peeked_byte].num_bits)) - goto fail; + uint8_ring_buffer_put_copy(buffer, level3[peeked_byte].symbol); + if (!consume(reader, level3[peeked_byte].num_bits)) + return -1; continue; } } - goto fail; + return -1; } /* FIXME: ensure we're not promoting types unnecessarily here */ - if (bit_reader.total_bitcount) { - const uint8_t peeked_byte = peek_byte(&bit_reader); + if (reader->total_bitcount) { + const uint8_t peeked_byte = peek_byte(reader); const uint8_t eos_prefix = ((1 << bit_reader.total_bitcount) - 1) << (8 - bit_reader.total_bitcount); if ((peeked_byte & eos_prefix) == eos_prefix) goto done; - if (level0[peeked_byte].num_bits == (int8_t)bit_reader.total_bitcount) { - *output = level0[peeked_byte].symbol; + if (level0[peeked_byte].num_bits == (int8_t)reader->total_bitcount) { + uint8_ring_buffer_put_copy(buffer, level0[peeked_byte].symbol); goto done; } @@ -393,15 +404,28 @@ uint8_t *lwan_h2_huffman_decode_for_fuzzing(const uint8_t *input, * - Incomplete sequence * - Has overlong padding */ - goto fail; + return -1; } done: - return ret; - -fail: - free(ret); - return NULL; + return (ssize_t)uint8_ring_buffer_size(buffer); } +bool lwan_h2_huffman_decode_for_fuzzing(const uint8_t *input, size_t input_len) +{ + struct lwan_h2_huffman_decoder decoder; + + lwan_h2_huffman_init(&decoder, input, input_len); + + while (true) { + ssize_t n_decoded = lwan_h2_huffman_next(&decoder); + + if (UNLIKELY(n_decoded < 0)) + return false; + if (n_decoded < 64) + return true; + + uint8_ring_buffer_init(&decoder->buffer); + } +} #endif diff --git a/src/lib/ringbuffer.h b/src/lib/ringbuffer.h index 1bece7c5e..7a5ecaf8e 100644 --- a/src/lib/ringbuffer.h +++ b/src/lib/ringbuffer.h @@ -76,6 +76,13 @@ memcpy(&rb->array[type_name_##_mask(rb->write++)], e, sizeof(*e)); \ } \ \ + __attribute__((unused)) static inline void type_name_##_put_copy( \ + struct type_name_ *rb, const element_type_ e) \ + { \ + assert(!type_name_##_full(rb)); \ + rb->array[type_name_##_mask(rb->write++)] = e; \ + } \ + \ __attribute__((unused)) static inline bool type_name_##_try_put( \ struct type_name_ *rb, const element_type_ *e) \ { \ @@ -93,6 +100,21 @@ return rb->array[type_name_##_mask(rb->read++)]; \ } \ \ + __attribute__((unused)) static inline void type_name_##_consume( \ + struct type_name_ *rb, element_type_ *buffer, uint32_t entries) \ + { \ + assert(type_name_##_size(rb) >= entries); \ + const uint32_t mask = type_name_##_mask(rb->read); \ + if (mask && entries > mask) { \ + memcpy(buffer, &rb->array[rb->read], \ + sizeof(*buffer) * (entries - mask)); \ + memcpy(buffer + mask, &rb->array[0], sizeof(*buffer) * mask); \ + } else { \ + memcpy(buffer, &rb->array[rb->read], sizeof(*buffer) * entries); \ + } \ + rb->read += entries; \ + } \ + \ __attribute__((unused)) static inline element_type_ *type_name_##_get_ptr( \ struct type_name_ *rb) \ { \ diff --git a/src/scripts/gentables.py b/src/scripts/gentables.py index 2642bfb1c..195a496a0 100755 --- a/src/scripts/gentables.py +++ b/src/scripts/gentables.py @@ -209,83 +209,97 @@ def generate_level(level, next_table): } """) - print("""static inline size_t output_size(size_t input_size) { - /* Smallest input is 5 bits which produces 8 bits. Scaling that to 8 bits, we - * get 12.8 bits of output per 8 bits of input. */ - return (input_size * 128) / 10; -}""") + print("""DEFINE_RING_BUFFER_TYPE(uint8_ring_buffer, uint8_t, 64) - print("""uint8_t *h2_huffman_decode(const uint8_t *input, size_t input_len) +struct lwan_h2_huffman_decoder { + struct bit_reader bit_reader; + struct uint8_ring_buffer buffer; +}; + +void lwan_h2_huffman_init(struct lwan_h2_huffman_decoder *huff, + const uint8_t *input, + size_t input_len) { - uint8_t *output = malloc(output_size(input_len)); - uint8_t *ret = output; - struct bit_reader bit_reader = {.bitptr = input, - .total_bitcount = (int64_t)input_len * 8}; + huff->bit_reader = (struct bit_reader){ + .bitptr = input, + .total_bitcount = (int64_t)input_len * 8, + }; + uint8_ring_buffer_init(&huff->buffer); +} - while (bit_reader.total_bitcount > 7) { - uint8_t peeked_byte = peek_byte(&bit_reader); +ssize_t lwan_h2_huffman_next(struct lwan_h2_huffman_decoder *huff) +{ + struct bit_reader *reader = &huff->bit_reader; + struct uint8_ring_buffer *buffer = &huff->buffer; + + while (reader->total_bitcount > 7) { + if (uint8_ring_buffer_full(buffer)) + goto done; + + uint8_t peeked_byte = peek_byte(reader); if (LIKELY(level0[peeked_byte].num_bits)) { - *output++ = level0[peeked_byte].symbol; - consume(&bit_reader, level0[peeked_byte].num_bits); + uint8_ring_buffer_put_copy(buffer, level0[peeked_byte].symbol); + consume(reader, level0[peeked_byte].num_bits); + assert(bit_reader.total_bitcount >= 0); continue; } - if (!consume(&bit_reader, 8)) - goto fail; + if (!consume(reader, 8)) + return -1; const struct h2_huffman_code *level1 = next_level0(peeked_byte); - peeked_byte = peek_byte(&bit_reader); + peeked_byte = peek_byte(reader); if (level1[peeked_byte].num_bits) { - *output++ = level1[peeked_byte].symbol; - if (!consume(&bit_reader, level1[peeked_byte].num_bits)) - goto fail; + uint8_ring_buffer_put_copy(buffer, level1[peeked_byte].symbol); + if (!consume(reader, level1[peeked_byte].num_bits)) + return -1; continue; } - if (!consume(&bit_reader, 8)) - goto fail; + if (!consume(reader, 8)) + return -1; const struct h2_huffman_code *level2 = next_level1(peeked_byte); - peeked_byte = peek_byte(&bit_reader); + peeked_byte = peek_byte(reader); if (level2[peeked_byte].num_bits) { - *output++ = level2[peeked_byte].symbol; - if (!consume(&bit_reader, level2[peeked_byte].num_bits)) - goto fail; + uint8_ring_buffer_put_copy(buffer, level2[peeked_byte].symbol); + if (!consume(reader, level2[peeked_byte].num_bits)) + return -1; continue; } - if (!consume(&bit_reader, 8)) + if (!consume(reader, 8)) goto fail; const struct h2_huffman_code *level3 = next_level2(peeked_byte); if (LIKELY(level3)) { - peeked_byte = peek_byte(&bit_reader); - if (UNLIKELY(level3[peeked_byte].num_bits < 0)) { + peeked_byte = peek_byte(reader); + if (level3[peeked_byte].num_bits < 0) { /* EOS found */ - return ret; + goto done; } if (LIKELY(level3[peeked_byte].num_bits)) { - *output++ = level3[peeked_byte].symbol; - if (!consume(&bit_reader, level3[peeked_byte].num_bits)) - goto fail; + uint8_ring_buffer_put_copy(buffer, level3[peeked_byte].symbol); + if (!consume(reader, level3[peeked_byte].num_bits)) + return -1; continue; } } - goto fail; + return -1; } /* FIXME: ensure we're not promoting types unnecessarily here */ - if (bit_reader.total_bitcount) { - const uint8_t peeked_byte = peek_byte(&bit_reader); + if (reader->total_bitcount) { + const uint8_t peeked_byte = peek_byte(reader); const uint8_t eos_prefix = ((1 << bit_reader.total_bitcount) - 1) << (8 - bit_reader.total_bitcount); if ((peeked_byte & eos_prefix) == eos_prefix) goto done; - if (level0[peeked_byte].num_bits == (int8_t)bit_reader.total_bitcount) { - *output = level0[peeked_byte].symbol; + if (level0[peeked_byte].num_bits == (int8_t)reader->total_bitcount) { + uint8_ring_buffer_put_copy(buffer, level0[peeked_byte].symbol); goto done; } @@ -294,30 +308,68 @@ def generate_level(level, next_table): * - Incomplete sequence * - Has overlong padding */ - goto fail; + return -1; } done: - return ret; + return (ssize_t)uint8_ring_buffer_size(buffer); +} + +bool lwan_h2_huffman_decode_for_fuzzing(const uint8_t *input, size_t input_len) +{ + struct lwan_h2_huffman_decoder decoder; -fail: - free(ret); - return NULL; + lwan_h2_huffman_init(&decoder, input, input_len); + + while (true) { + ssize_t n_decoded = lwan_h2_huffman_next(&decoder); + + if (UNLIKELY(n_decoded < 0)) + return false; + if (n_decoded < 64) + return true; + + uint8_ring_buffer_init(&decoder->buffer); + } +} + +bool test_decoder(unsigned char input[], size_t input_size, const char expected[], size_t expected_size) +{ + struct lwan_h2_huffman_decoder decoder; + const char *expected_ptr = expected; + + lwan_h2_huffman_init(&decoder, input, input_size); + + while (true) { + ssize_t n_decoded = lwan_h2_huffman_next(&decoder); + if (n_decoded < 0) + return false; + if (n_decoded == 0 && expected_size == 0) + return true; + if (n_decoded == 0 && expected_size != 0) + return false; + while (n_decoded && expected_size) { + uint8_t expected = *expected_ptr; + uint8_t got = uint8_ring_buffer_get(&decoder->buffer); + expected_ptr++; + expected_size--; + n_decoded--; + if (expected != got) { + fprintf(stderr, "expected %d, got %d\n", expected, got); + return false; + } + } + } } -int main(int argc, char *argv[]) { +int main(int argc, char *argv[]) +{ /* "litespeed" */ - unsigned char litespeed_huff[128] = {0xce, 0x64, 0x97, 0x75, 0x65, 0x2c, 0x9f}; - unsigned char *decoded; - - decoded = h2_huffman_decode(litespeed_huff, 7); - if (!decoded) { - puts("could not decode"); + unsigned char litespeed_huff[] = {0xce, 0x64, 0x97, 0x75, 0x65, 0x2c, 0x9f}; + if (!test_decoder(litespeed_huff, sizeof(litespeed_huff), "LiteSpeed", + sizeof("LiteSpeed") - 1)) { return 1; } - printf("%s\\n", !strcmp(decoded, "LiteSpeed") ? "pass!" : "fail!"); - printf("decoded: '%s'\\n", decoded); - free(decoded); unsigned char x_fb_debug[128] = { 0xa7, 0x06, 0xa7, 0x63, 0x97, 0xc6, 0x1d, 0xc9, 0xbb, 0xa3, 0xc6, 0x5e, @@ -327,13 +379,15 @@ def generate_level(level, next_table): 0x8b, 0xac, 0x7f, 0xef, 0x65, 0x5d, 0x9f, 0x8c, 0x9d, 0x3c, 0x72, 0x8f, 0xc5, 0xfd, 0x9e, 0xd0, 0x51, 0xb1, 0xdf, 0x46, 0xc8, 0x20, }; - decoded = h2_huffman_decode(x_fb_debug, 6*12-2); - if (!decoded) { - puts("could not decode"); + unsigned char x_fb_debug_decoded[] = + "mEO7bfwFStBMwJWfW4pmg2XL25AswjrVlfcfYbxkcS2ssduZmiKoipMH9XwoTGkb+" + "Qnq9bcjwWbwDQzsea/vMQ=="; + if (!test_decoder(x_fb_debug, sizeof(x_fb_debug), x_fb_debug_decoded, + sizeof(x_fb_debug_decoded) - 1)) { return 1; } - printf("%s\\n", !strcmp(decoded, "mEO7bfwFStBMwJWfW4pmg2XL25AswjrVlfcfYbxkcS2ssduZmiKoipMH9XwoTGkb+Qnq9bcjwWbwDQzsea/vMQ==") ? "pass!" : "fail!"); - printf("decoded: '%s'\\n", decoded); - free(decoded); + + puts("passed!"); + return 0; } """) From b001a9042e398f007c6ce66d4ac569a19db2071a Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 15 May 2024 22:40:32 -0700 Subject: [PATCH 2245/2505] Better handle error when writing to pub/sub subscriber eventfd --- src/lib/lwan-pubsub.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-pubsub.c b/src/lib/lwan-pubsub.c index e46a73ee5..6bdcad399 100644 --- a/src/lib/lwan-pubsub.c +++ b/src/lib/lwan-pubsub.c @@ -218,8 +218,10 @@ static bool lwan_pubsub_publish_value(struct lwan_pubsub_topic *topic, break; if (UNLIKELY(written < 0)) { - if (errno == EINTR) + if (errno == EINTR || errno == EAGAIN) continue; + lwan_status_perror("write to eventfd failed, ignoring"); + break; } } } From d9a48781b20881d2cc38179cf1bf9f1eaaec1785 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 15 May 2024 22:41:02 -0700 Subject: [PATCH 2246/2505] Fix compilation warning in ringbuffer type --- src/lib/ringbuffer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/ringbuffer.h b/src/lib/ringbuffer.h index 7a5ecaf8e..045032f05 100644 --- a/src/lib/ringbuffer.h +++ b/src/lib/ringbuffer.h @@ -77,7 +77,7 @@ } \ \ __attribute__((unused)) static inline void type_name_##_put_copy( \ - struct type_name_ *rb, const element_type_ e) \ + struct type_name_ *rb, element_type_ e) \ { \ assert(!type_name_##_full(rb)); \ rb->array[type_name_##_mask(rb->write++)] = e; \ From b4f896694ed019478d8c20e9bc97de226effb83e Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 15 May 2024 22:58:52 -0700 Subject: [PATCH 2247/2505] lwan_status_critical_perror() shouldn't be used in steady state! --- src/lib/lwan-thread.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 9afba606d..d74c983e5 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -776,7 +776,7 @@ int lwan_request_awaitv_any(struct lwan_request *r, ...) if (UNLIKELY(ret < 0)) { errno = -ret; - lwan_status_critical_perror("prepare_awaitv()"); + lwan_status_perror("prepare_awaitv()"); coro_yield(r->conn->coro, CONN_CORO_ABORT); __builtin_unreachable(); } @@ -810,7 +810,7 @@ int lwan_request_awaitv_all(struct lwan_request *r, ...) if (UNLIKELY(ret < 0)) { errno = -ret; - lwan_status_critical_perror("prepare_awaitv()"); + lwan_status_perror("prepare_awaitv()"); coro_yield(r->conn->coro, CONN_CORO_ABORT); __builtin_unreachable(); } @@ -857,7 +857,7 @@ static inline int async_await_fd(struct lwan_connection *conn, return UNLIKELY(conn->flags & CONN_HUNG_UP) ? -fd : fd; } - lwan_status_critical_perror("prepare_await(%d)", fd); + lwan_status_perror("prepare_await(%d)", fd); coro_yield(conn->coro, CONN_CORO_ABORT); __builtin_unreachable(); } From bcf9dbc9cb8596c500619e52788eb00bc755461e Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 16 May 2024 08:27:02 -0700 Subject: [PATCH 2248/2505] Re-seed PRNG if lwan_random_uint64() returns 0 when getting req id --- src/lib/lwan-thread.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index d74c983e5..232c133b4 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -113,7 +113,7 @@ static void lwan_random_seed_prng_for_thread(const struct lwan_thread *t) uint64_t lwan_random_uint64() { - static uint64_t value; + static uint64_t value = 1; return ATOMIC_INC(value); } @@ -142,9 +142,15 @@ uint64_t lwan_request_get_id(struct lwan_request *request) { struct lwan_request_parser_helper *helper = request->helper; - if (helper->request_id == 0) + if (helper->request_id == 0) { helper->request_id = lwan_random_uint64(); + if (UNLIKELY(helper->request_id == 0)) { + lwan_random_seed_prng_for_thread(request->conn->thread); + return lwan_request_get_id(request); + } + } + return helper->request_id; } From 335698594829637a1b0bc91790c50e9bf178ec72 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 17 May 2024 18:17:43 -0700 Subject: [PATCH 2249/2505] Better bubble errors to async/await operations --- src/lib/lwan-request.c | 23 ++++++++++------- src/lib/lwan-thread.c | 58 ++++++++++++++++++------------------------ 2 files changed, 39 insertions(+), 42 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 67039cc05..c76fdc523 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -2086,11 +2086,13 @@ ssize_t lwan_request_async_read_flags( if (r < 0) { switch (errno) { case EWOULDBLOCK: - lwan_request_await_read(request, fd); + r = lwan_request_await_read(request, fd); + if (UNLIKELY(r < 0)) + return (int)r; /* Fallthrough */ case EINTR: continue; - case EPIPE: + default: return -errno; } } @@ -2118,11 +2120,13 @@ ssize_t lwan_request_async_write(struct lwan_request *request, if (r < 0) { switch (errno) { case EWOULDBLOCK: - lwan_request_await_write(request, fd); + r = lwan_request_await_write(request, fd); + if (UNLIKELY(r < 0)) + return (int)r; /* Fallthrough */ case EINTR: continue; - case EPIPE: + default: return -errno; } } @@ -2142,6 +2146,7 @@ ssize_t lwan_request_async_writev(struct lwan_request *request, for (int tries = 10; tries;) { const int remaining_len = (int)(iov_count - curr_iov); ssize_t written; + int r; if (remaining_len == 1) { const struct iovec *vec = &iov[curr_iov]; @@ -2159,7 +2164,7 @@ ssize_t lwan_request_async_writev(struct lwan_request *request, case EINTR: goto try_again; default: - goto out; + return -errno; } } @@ -2178,12 +2183,12 @@ ssize_t lwan_request_async_writev(struct lwan_request *request, iov[curr_iov].iov_len -= (size_t)written; try_again: - lwan_request_await_write(request, fd); + r = lwan_request_await_write(request, fd); + if (UNLIKELY(r < 0)) + return r; } -out: - coro_yield(request->conn->coro, CONN_CORO_ABORT); - __builtin_unreachable(); + return -ETIMEDOUT; } void lwan_request_foreach_header_for_cgi(struct lwan_request *request, diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 232c133b4..f2bd69aa9 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -644,12 +644,11 @@ static void unasync_await_conn(void *data1, void *data2) async_fd_conn->next = -1; } -static enum lwan_connection_coro_yield -prepare_await(const struct lwan *l, - enum lwan_connection_coro_yield yield_result, - int await_fd, - struct lwan_connection *conn, - int epoll_fd) +static int prepare_await(const struct lwan *l, + enum lwan_connection_coro_yield yield_result, + int await_fd, + struct lwan_connection *conn, + int epoll_fd) { static const enum lwan_connection_flags to_connection_flags[] = { [CONN_CORO_WANT_READ] = CONN_EVENTS_READ, @@ -668,7 +667,7 @@ prepare_await(const struct lwan *l, struct lwan_connection *await_fd_conn = &l->conns[await_fd]; if (LIKELY(await_fd_conn->flags & CONN_ASYNC_AWAIT)) { if (LIKELY((await_fd_conn->flags & CONN_EVENTS_MASK) == flags)) - return CONN_CORO_SUSPEND; + return 0; op = EPOLL_CTL_MOD; } else { @@ -700,10 +699,10 @@ prepare_await(const struct lwan *l, if (LIKELY(!epoll_ctl(epoll_fd, op, await_fd, &event))) { await_fd_conn->flags &= ~CONN_EVENTS_MASK; await_fd_conn->flags |= flags; - return CONN_CORO_SUSPEND; + return 0; } - return CONN_CORO_ABORT; + return -errno; } static void clear_awaitv_flags(struct lwan_connection *conns, va_list ap_orig) @@ -760,10 +759,12 @@ static int prepare_awaitv(struct lwan_request *r, state->request_conn_yield = events; continue; } - if (UNLIKELY(prepare_await(l, events, await_fd, r->conn, epoll_fd) == - CONN_CORO_ABORT)) { - lwan_status_error("could not register fd for async operation"); - return -EIO; + + int ret = prepare_await(l, events, await_fd, r->conn, epoll_fd); + if (UNLIKELY(ret < 0)) { + errno = -ret; + lwan_status_perror("prepare_await(%d)", await_fd); + return ret; } } @@ -814,12 +815,8 @@ int lwan_request_awaitv_all(struct lwan_request *r, ...) int ret = prepare_awaitv(r, l, ap, &state); va_end(ap); - if (UNLIKELY(ret < 0)) { - errno = -ret; - lwan_status_perror("prepare_awaitv()"); - coro_yield(r->conn->coro, CONN_CORO_ABORT); - __builtin_unreachable(); - } + if (UNLIKELY(ret < 0)) + return ret; for (ret = 0; state.num_awaiting;) { int64_t v = coro_yield(r->conn->coro, state.request_conn_yield); @@ -842,7 +839,7 @@ int lwan_request_awaitv_all(struct lwan_request *r, ...) } } - return -1; + return -EISCONN; } static inline int async_await_fd(struct lwan_connection *conn, @@ -851,21 +848,16 @@ static inline int async_await_fd(struct lwan_connection *conn, { struct lwan_thread *thread = conn->thread; struct lwan *lwan = thread->lwan; - enum lwan_connection_coro_yield yield = - prepare_await(lwan, events, fd, conn, thread->epoll_fd); - - if (LIKELY(yield == CONN_CORO_SUSPEND)) { - int64_t v = coro_yield(conn->coro, yield); + int r = prepare_await(lwan, events, fd, conn, thread->epoll_fd); - fd = - lwan_connection_get_fd(lwan, (struct lwan_connection *)(intptr_t)v); + if (UNLIKELY(r < 0)) + return r; - return UNLIKELY(conn->flags & CONN_HUNG_UP) ? -fd : fd; - } - - lwan_status_perror("prepare_await(%d)", fd); - coro_yield(conn->coro, CONN_CORO_ABORT); - __builtin_unreachable(); + conn = (struct lwan_connection *)(intptr_t)coro_yield(conn->coro, + CONN_CORO_SUSPEND); + return UNLIKELY(conn->flags & CONN_HUNG_UP) + ? -ECONNRESET + : lwan_connection_get_fd(lwan, conn); } int lwan_request_await_read(struct lwan_request *r, int fd) From 71dbc357e0ff636ada358f3683bf9248c2123a14 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 17 May 2024 18:27:25 -0700 Subject: [PATCH 2250/2505] Abort coroutine if setting main epoll flags fail --- src/lib/lwan-thread.c | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index f2bd69aa9..515ada9aa 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -565,10 +565,10 @@ conn_flags_to_epoll_events(enum lwan_connection_flags flags) return EPOLL_EVENTS(flags); } -static void update_epoll_flags(const struct lwan *lwan, - struct lwan_connection *conn, - int epoll_fd, - enum lwan_connection_coro_yield yield_result) +static int update_epoll_flags(const struct lwan *lwan, + struct lwan_connection *conn, + int epoll_fd, + enum lwan_connection_coro_yield yield_result) { static const enum lwan_connection_flags or_mask[CONN_CORO_MAX] = { [CONN_CORO_YIELD] = 0, @@ -611,14 +611,13 @@ static void update_epoll_flags(const struct lwan *lwan, assert((conn->flags & CONN_TLS) == (prev_flags & CONN_TLS)); if (LWAN_EVENTS(conn->flags) == LWAN_EVENTS(prev_flags)) - return; + return 0; struct epoll_event event = {.events = conn_flags_to_epoll_events(conn->flags), .data.ptr = conn}; int fd = lwan_connection_get_fd(lwan, conn); - if (UNLIKELY(epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &event) < 0)) - lwan_status_perror("epoll_ctl"); + return epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &event); } static void unasync_await_conn(void *data1, void *data2) @@ -885,12 +884,16 @@ static ALWAYS_INLINE void resume_coro(struct timeout_queue *tq, enum lwan_connection_coro_yield from_coro = coro_resume_value( conn_to_resume->coro, (int64_t)(intptr_t)conn_to_yield); - if (UNLIKELY(from_coro == CONN_CORO_ABORT)) { - timeout_queue_expire(tq, conn_to_resume); - } else { - update_epoll_flags(tq->lwan, conn_to_resume, epoll_fd, from_coro); - timeout_queue_move_to_last(tq, conn_to_resume); + if (LIKELY(from_coro != CONN_CORO_ABORT)) { + int r = + update_epoll_flags(tq->lwan, conn_to_resume, epoll_fd, from_coro); + if (LIKELY(!r)) { + timeout_queue_move_to_last(tq, conn_to_resume); + return; + } } + + timeout_queue_expire(tq, conn_to_resume); } static void update_date_cache(struct lwan_thread *thread) @@ -950,15 +953,18 @@ static bool process_pending_timers(struct timeout_queue *tq, bool should_expire_timers = false; while ((timeout = timeouts_get(t->wheel))) { - struct lwan_request *request; - if (timeout == &tq->timeout) { should_expire_timers = true; continue; } - request = container_of(timeout, struct lwan_request, timeout); - update_epoll_flags(tq->lwan, request->conn, epoll_fd, CONN_CORO_RESUME); + struct lwan_request *request = + container_of(timeout, struct lwan_request, timeout); + int r = update_epoll_flags(tq->lwan, request->conn, epoll_fd, + CONN_CORO_RESUME); + if (UNLIKELY(r < 0)) { + timeout_queue_expire(tq, request->conn); + } } if (should_expire_timers) { From 334173f8e8ff6acb54af6570113f50cfc6f99246 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 17 May 2024 19:22:11 -0700 Subject: [PATCH 2251/2505] Use FNV1a hash instead of Murmur3 It's much simpler, and in some cases can even be inlined. --- src/bin/tools/CMakeLists.txt | 2 - src/lib/CMakeLists.txt | 1 - src/lib/hash.c | 32 ++-- src/lib/hash.h | 28 +++ src/lib/lwan-thread.c | 6 +- src/lib/murmur3.c | 354 ----------------------------------- src/lib/murmur3.h | 16 -- 7 files changed, 46 insertions(+), 393 deletions(-) delete mode 100644 src/lib/murmur3.c delete mode 100644 src/lib/murmur3.h diff --git a/src/bin/tools/CMakeLists.txt b/src/bin/tools/CMakeLists.txt index c5d19b73c..54f83496d 100644 --- a/src/bin/tools/CMakeLists.txt +++ b/src/bin/tools/CMakeLists.txt @@ -5,7 +5,6 @@ else () add_executable(mimegen mimegen.c ${CMAKE_SOURCE_DIR}/src/lib/hash.c - ${CMAKE_SOURCE_DIR}/src/lib/murmur3.c ${CMAKE_SOURCE_DIR}/src/lib/missing.c ) if (LWAN_HAVE_BROTLI) @@ -37,7 +36,6 @@ else () ${CMAKE_SOURCE_DIR}/src/lib/lwan-strbuf.c ${CMAKE_SOURCE_DIR}/src/lib/missing.c ${CMAKE_SOURCE_DIR}/src/lib/hash.c - ${CMAKE_SOURCE_DIR}/src/lib/murmur3.c ) add_executable(weighttp weighttp.c) diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index 62d992c08..e9fccf9e0 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -39,7 +39,6 @@ set(SOURCES missing.c missing-pthread.c missing-epoll.c - murmur3.c patterns.c realpathat.c sd-daemon.c diff --git a/src/lib/hash.c b/src/lib/hash.c index 46c474668..5ee9d9ec0 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -34,7 +34,6 @@ #include "lwan-private.h" #include "hash.h" -#include "murmur3.h" struct hash_bucket { void **keys; @@ -81,11 +80,12 @@ struct hash_entry { static_assert((MIN_BUCKETS & (MIN_BUCKETS - 1)) == 0, "Bucket size is power of 2"); +static inline unsigned int hash_fnv1a_32(const void *keyptr); static inline unsigned int hash_int_shift_mult(const void *keyptr); static inline unsigned int hash_int64_shift_mult(const void *keyptr); static unsigned int odd_constant = DEFAULT_ODD_CONSTANT; -static unsigned (*hash_str)(const void *key) = murmur3_simple; +static unsigned (*hash_str)(const void *key) = hash_fnv1a_32; static unsigned (*hash_int)(const void *key) = hash_int_shift_mult; static unsigned (*hash_int64)(const void *key) = hash_int64_shift_mult; @@ -115,28 +115,21 @@ static bool resize_bucket(struct hash_bucket *bucket, unsigned int new_size) return false; } +static inline unsigned int hash_fnv1a_32(const void *keyptr) +{ + return fnv1a_32(keyptr, strlen(keyptr)); +} + static inline unsigned int hash_int_shift_mult(const void *keyptr) { - /* http://www.concentric.net/~Ttwang/tech/inthash.htm */ unsigned int key = (unsigned int)(long)keyptr; - unsigned int c2 = odd_constant; - - key = (key ^ 61) ^ (key >> 16); - key += key << 3; - key ^= key >> 4; - key *= c2; - key ^= key >> 15; - return key; + return fnv1a_32(&key, sizeof(key)); } static inline unsigned int hash_int64_shift_mult(const void *keyptr) { const uint64_t key = (uint64_t)(uintptr_t)keyptr; - uint32_t key_low = (uint32_t)(key & 0xffffffff); - uint32_t key_high = (uint32_t)(key >> 32); - - return hash_int_shift_mult((void *)(uintptr_t)key_low) ^ - hash_int_shift_mult((void *)(uintptr_t)key_high); + return fnv1a_32(&key, sizeof(key)); } #if defined(LWAN_HAVE_BUILTIN_CPU_INIT) && defined(LWAN_HAVE_BUILTIN_IA32_CRC32) @@ -200,6 +193,9 @@ static inline unsigned int hash_int64_crc32(const void *keyptr) #endif +uint64_t fnv1a_64_seed = 0xcbf29ce484222325ull; +uint32_t fnv1a_32_seed = 0x811c9dc5u; + __attribute__((constructor(65535))) static void initialize_odd_constant(void) { /* This constant is randomized in order to mitigate the DDoS attack @@ -207,7 +203,9 @@ __attribute__((constructor(65535))) static void initialize_odd_constant(void) if (lwan_getentropy(&odd_constant, sizeof(odd_constant), 0) < 0) odd_constant = DEFAULT_ODD_CONSTANT; odd_constant |= 1; - murmur3_set_seed(odd_constant); + + fnv1a_64_seed = fnv1a_64(&odd_constant, sizeof(odd_constant)); + fnv1a_32_seed = fnv1a_32(&odd_constant, sizeof(odd_constant)); #if defined(LWAN_HAVE_BUILTIN_CPU_INIT) && defined(LWAN_HAVE_BUILTIN_IA32_CRC32) __builtin_cpu_init(); diff --git a/src/lib/hash.h b/src/lib/hash.h index f68e8ef47..bec709e55 100644 --- a/src/lib/hash.h +++ b/src/lib/hash.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -30,3 +31,30 @@ void hash_iter_init(const struct hash *hash, struct hash_iter *iter); bool hash_iter_next(struct hash_iter *iter, const void **key, const void **value); + +extern uint64_t fnv1a_64_seed; +extern uint32_t fnv1a_32_seed; + +static inline uint64_t fnv1a_64(const void *buffer, size_t len) +{ + const unsigned char *data = (unsigned char *)buffer; + uint64_t hash; + + for (hash = fnv1a_64_seed; len--; data++) { + hash = (hash ^ *data) * 0x100000001b3ul; + } + + return hash; +} + +static inline uint32_t fnv1a_32(const void *buffer, size_t len) +{ + const unsigned char *data = (unsigned char *)buffer; + uint32_t hash; + + for (hash = fnv1a_32_seed; len--; data++) { + hash = (hash ^ *data) * 0x1000193u; + } + + return hash; +} diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 515ada9aa..ede38e7a2 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -47,7 +47,6 @@ #endif #include "list.h" -#include "murmur3.h" #include "lwan-private.h" #include "lwan-tq.h" @@ -124,9 +123,10 @@ static void lwan_random_seed_prng_for_thread(const struct lwan_thread *t) { if (lwan_getentropy(&lehmer64_state, sizeof(lehmer64_state), 0) < 0) { lwan_status_warning("Couldn't get proper entropy for PRNG, using fallback seed"); - lehmer64_state |= murmur3_fmix64((uint64_t)(uintptr_t)t); + uintptr_t ptr = (uintptr_t)t; + lehmer64_state |= fnv1a_64(&ptr, sizeof(ptr)); lehmer64_state <<= 64; - lehmer64_state |= murmur3_fmix64((uint64_t)t->epoll_fd); + lehmer64_state |= fnv1a_64(&t->epoll_fd, sizeof(t->epoll_fd)); } } diff --git a/src/lib/murmur3.c b/src/lib/murmur3.c deleted file mode 100644 index 18beec759..000000000 --- a/src/lib/murmur3.c +++ /dev/null @@ -1,354 +0,0 @@ -//----------------------------------------------------------------------------- -// MurmurHash3 was written by Austin Appleby, and is placed in the public -// domain. The author hereby disclaims copyright to this source code. - -// Note - The x86 and x64 versions do _not_ produce the same results, as the -// algorithms are optimized for their respective platforms. You can still -// compile and run any of them on any platform, but your performance with the -// non-native version will be less than optimal. - -#include -#include - -#include "murmur3.h" - -//----------------------------------------------------------------------------- -// Platform-specific functions and macros - -#ifdef __GNUC__ -#define FORCE_INLINE __attribute__((always_inline)) inline -#else /* */ -#define FORCE_INLINE -#endif /* */ - -#ifndef __x86_64__ -static FORCE_INLINE uint32_t rotl32(uint32_t x, int8_t r) -{ - return (x << r) | (x >> (32 - r)); -} -#endif - -static FORCE_INLINE uint64_t rotl64(uint64_t x, int8_t r) -{ - return (x << r) | (x >> (64 - r)); -} - -#define BIG_CONSTANT(x) (x##LLU) - -static uint32_t seed_value = 0xdeadbeef; - -//----------------------------------------------------------------------------- -// Finalization mix - force all bits of a hash block to avalanche -#ifndef __x86_64__ -static FORCE_INLINE uint32_t fmix32(uint32_t h) -{ - h ^= h >> 16; - h *= 0x85ebca6b; - h ^= h >> 13; - h *= 0xc2b2ae35; - h ^= h >> 16; - return h; -} -#endif - -//---------- -FORCE_INLINE uint64_t murmur3_fmix64(uint64_t k) -{ - k ^= k >> 33; - k *= BIG_CONSTANT(0xff51afd7ed558ccd); - k ^= k >> 33; - k *= BIG_CONSTANT(0xc4ceb9fe1a85ec53); - k ^= k >> 33; - return k; -} - - -//----------------------------------------------------------------------------- -#ifndef __x86_64__ -FORCE_INLINE static void -MurmurHash3_x86_32(const void *key, size_t len, uint32_t seed, void *out) -{ - const uint8_t *data = (const uint8_t *)key; - const size_t nblocks = len / 4; - size_t i; - uint32_t h1 = seed; - uint32_t c1 = 0xcc9e2d51; - uint32_t c2 = 0x1b873593; - - //---------- - // body - const uint32_t *blocks = (const uint32_t *)(data + nblocks * 4); - for (i = -nblocks; i; i++) { - uint32_t k1; - - memcpy(&k1, blocks + i, sizeof(k1)); - - k1 *= c1; - k1 = rotl32(k1, 15); - k1 *= c2; - h1 ^= k1; - h1 = rotl32(h1, 13); - h1 = h1 * 5 + 0xe6546b64; - } - - //---------- - // tail - const uint8_t *tail = (const uint8_t *)(data + nblocks * 4); - uint32_t k1 = 0; - switch (len & 3) { - case 3: - k1 ^= (uint32_t)tail[2] << 16; - /* fallthrough */ - case 2: - k1 ^= (uint32_t)tail[1] << 8; - /* fallthrough */ - case 1: - k1 ^= (uint32_t)tail[0]; - k1 *= c1; - k1 = rotl32(k1, 15); - k1 *= c2; - h1 ^= k1; - }; - - //---------- - // finalization - h1 ^= (uint32_t)len; - h1 = fmix32(h1); - *(uint32_t *) out = h1; -} - - -//----------------------------------------------------------------------------- -FORCE_INLINE static void MurmurHash3_x86_128 (const void *key, const size_t len, - uint32_t seed, void *out) -{ - size_t i; - const uint8_t * data = (const uint8_t*)key; - const size_t nblocks = len / 16; - - uint32_t h1 = seed; - uint32_t h2 = seed; - uint32_t h3 = seed; - uint32_t h4 = seed; - - const uint32_t c1 = 0x239b961b; - const uint32_t c2 = 0xab0e9789; - const uint32_t c3 = 0x38b34ae5; - const uint32_t c4 = 0xa1e38b93; - - //---------- - // body - - const uint32_t * blocks = (const uint32_t *)(data + nblocks*16); - - for(i = -nblocks; i; i++) { - uint32_t k1, k2, k3, k4; - - memcpy(&k1, blocks + i * 4 + 0, sizeof(k1)); - memcpy(&k2, blocks + i * 4 + 1, sizeof(k2)); - memcpy(&k3, blocks + i * 4 + 2, sizeof(k3)); - memcpy(&k4, blocks + i * 4 + 3, sizeof(k4)); - - k1 *= c1; k1 = rotl32(k1,15); k1 *= c2; h1 ^= k1; - h1 = rotl32(h1,19); h1 += h2; h1 = h1*5+0x561ccd1b; - k2 *= c2; k2 = rotl32(k2,16); k2 *= c3; h2 ^= k2; - h2 = rotl32(h2,17); h2 += h3; h2 = h2*5+0x0bcaa747; - k3 *= c3; k3 = rotl32(k3,17); k3 *= c4; h3 ^= k3; - h3 = rotl32(h3,15); h3 += h4; h3 = h3*5+0x96cd1c35; - k4 *= c4; k4 = rotl32(k4,18); k4 *= c1; h4 ^= k4; - h4 = rotl32(h4,13); h4 += h1; h4 = h4*5+0x32ac3b17; - } - - //---------- - // tail - - const uint8_t * tail = (const uint8_t*)(data + nblocks*16); - - uint32_t k1 = 0; - uint32_t k2 = 0; - uint32_t k3 = 0; - uint32_t k4 = 0; - - switch(len & 15) { - case 15: k4 ^= (uint32_t)tail[14] << 16; /* fallthrough */ - case 14: k4 ^= (uint32_t)tail[13] << 8; /* fallthrough */ - case 13: k4 ^= (uint32_t)tail[12] << 0; - k4 *= c4; k4 = rotl32(k4,18); k4 *= c1; h4 ^= k4; - /* fallthrough */ - case 12: k3 ^= (uint32_t)tail[11] << 24; /* fallthrough */ - case 11: k3 ^= (uint32_t)tail[10] << 16; /* fallthrough */ - case 10: k3 ^= (uint32_t)tail[ 9] << 8; /* fallthrough */ - case 9: k3 ^= (uint32_t)tail[ 8] << 0; - k3 *= c3; k3 = rotl32(k3,17); k3 *= c4; h3 ^= k3; - /* fallthrough */ - case 8: k2 ^= (uint32_t)tail[ 7] << 24; /* fallthrough */ - case 7: k2 ^= (uint32_t)tail[ 6] << 16; /* fallthrough */ - case 6: k2 ^= (uint32_t)tail[ 5] << 8; /* fallthrough */ - case 5: k2 ^= (uint32_t)tail[ 4] << 0; - k2 *= c2; k2 = rotl32(k2,16); k2 *= c3; h2 ^= k2; - /* fallthrough */ - case 4: k1 ^= (uint32_t)tail[ 3] << 24; /* fallthrough */ - case 3: k1 ^= (uint32_t)tail[ 2] << 16; /* fallthrough */ - case 2: k1 ^= (uint32_t)tail[ 1] << 8; /* fallthrough */ - case 1: k1 ^= (uint32_t)tail[ 0] << 0; - k1 *= c1; k1 = rotl32(k1,15); k1 *= c2; h1 ^= k1; - } - - //---------- - // finalization - - h1 ^= (uint32_t)len; h2 ^= (uint32_t)len; - h3 ^= (uint32_t)len; h4 ^= (uint32_t)len; - - h1 += h2; h1 += h3; h1 += h4; - h2 += h1; h3 += h1; h4 += h1; - - h1 = fmix32(h1); - h2 = fmix32(h2); - h3 = fmix32(h3); - h4 = fmix32(h4); - - h1 += h2; h1 += h3; h1 += h4; - h2 += h1; h3 += h1; h4 += h1; - - ((uint32_t*)out)[0] = h1; - ((uint32_t*)out)[1] = h2; - ((uint32_t*)out)[2] = h3; - ((uint32_t*)out)[3] = h4; -} -#endif - -//----------------------------------------------------------------------------- -FORCE_INLINE static void -MurmurHash3_x64_128(const void *key, const size_t len, const uint32_t seed, - void *out) -{ - const uint8_t *data = (const uint8_t *)key; - const size_t nblocks = len / 16; - size_t i; - uint64_t h1 = seed; - uint64_t h2 = seed; - uint64_t c1 = BIG_CONSTANT(0x87c37b91114253d5); - uint64_t c2 = BIG_CONSTANT(0x4cf5ad432745937f); - - //---------- - // body - const uint64_t *blocks = (const uint64_t *)(data); - for (i = 0; i < nblocks; i++) { - uint64_t k1, k2; - - memcpy(&k1, blocks + i * 2 + 0, sizeof(k1)); - memcpy(&k2, blocks + i * 2 + 1, sizeof(k2)); - - k1 *= c1; - k1 = rotl64(k1, 31); - k1 *= c2; - h1 ^= k1; - h1 = rotl64(h1, 27); - h1 += h2; - h1 = h1 * 5 + 0x52dce729; - k2 *= c2; - k2 = rotl64(k2, 33); - k2 *= c1; - h2 ^= k2; - h2 = rotl64(h2, 31); - h2 += h1; - h2 = h2 * 5 + 0x38495ab5; - } - - //---------- - // tail - const uint8_t *tail = (const uint8_t *)(data + nblocks * 16); - uint64_t k1 = 0; - uint64_t k2 = 0; - switch (len & 15) { - case 15: - k2 ^= (uint64_t) (tail[14]) << 48; /* fallthrough */ - case 14: - k2 ^= (uint64_t) (tail[13]) << 40; /* fallthrough */ - case 13: - k2 ^= (uint64_t) (tail[12]) << 32; /* fallthrough */ - case 12: - k2 ^= (uint64_t) (tail[11]) << 24; /* fallthrough */ - case 11: - k2 ^= (uint64_t) (tail[10]) << 16; /* fallthrough */ - case 10: - k2 ^= (uint64_t) (tail[9]) << 8; /* fallthrough */ - case 9: - k2 ^= (uint64_t) (tail[8]) << 0; - k2 *= c2; - k2 = rotl64(k2, 33); - k2 *= c1; - h2 ^= k2; - /* fallthrough */ - case 8: - k1 ^= (uint64_t) (tail[7]) << 56; - /* fallthrough */ - case 7: - k1 ^= (uint64_t) (tail[6]) << 48; - /* fallthrough */ - case 6: - k1 ^= (uint64_t) (tail[5]) << 40; - /* fallthrough */ - case 5: - k1 ^= (uint64_t) (tail[4]) << 32; - /* fallthrough */ - case 4: - k1 ^= (uint64_t) (tail[3]) << 24; - /* fallthrough */ - case 3: - k1 ^= (uint64_t) (tail[2]) << 16; - /* fallthrough */ - case 2: - k1 ^= (uint64_t) (tail[1]) << 8; - /* fallthrough */ - case 1: - k1 ^= (uint64_t) (tail[0]) << 0; - k1 *= c1; - k1 = rotl64(k1, 31); - k1 *= c2; - h1 ^= k1; - }; - - //---------- - // finalization - h1 ^= (uint64_t)len; - h2 ^= (uint64_t)len; - h1 += h2; - h2 += h1; - h1 = murmur3_fmix64(h1); - h2 = murmur3_fmix64(h2); - h1 += h2; - h2 += h1; - ((uint64_t *) out)[0] = h1; - ((uint64_t *) out)[1] = h2; -} - - -//----------------------------------------------------------------------------- -unsigned int -murmur3_simple(const void *keyptr) -{ - size_t len = strlen((char *)keyptr); -#ifdef __x86_64__ - uint64_t hash[2]; - MurmurHash3_x64_128(keyptr, len, seed_value, hash); - return (unsigned int)hash[1]; -#else - if (len <= 16) { - unsigned int hash; - MurmurHash3_x86_32(keyptr, len, seed_value, &hash); - return hash; - } - - unsigned int hash[4]; - MurmurHash3_x86_128(keyptr, len, seed_value, hash); - return hash[3]; -#endif -} - -void -murmur3_set_seed(const uint32_t seed) -{ - seed_value = seed; -} diff --git a/src/lib/murmur3.h b/src/lib/murmur3.h deleted file mode 100644 index 314f5ceaf..000000000 --- a/src/lib/murmur3.h +++ /dev/null @@ -1,16 +0,0 @@ -//----------------------------------------------------------------------------- -// MurmurHash3 was written by Austin Appleby, and is placed in the -// public domain. The author hereby disclaims copyright to this source -// code. - -#pragma once - -#include - -//----------------------------------------------------------------------------- - -uint64_t murmur3_fmix64(uint64_t k); -void murmur3_set_seed(const uint32_t seed); -unsigned int murmur3_simple(const void *key); - -//----------------------------------------------------------------------------- From 330da1a375eee0d3b8e518d4a4eb96f6f9e97dda Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 17 May 2024 19:35:48 -0700 Subject: [PATCH 2252/2505] Rename internal hash funcs to match the current algorithms --- src/lib/hash.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/hash.c b/src/lib/hash.c index 5ee9d9ec0..cd1b3ca5e 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -81,13 +81,13 @@ static_assert((MIN_BUCKETS & (MIN_BUCKETS - 1)) == 0, "Bucket size is power of 2"); static inline unsigned int hash_fnv1a_32(const void *keyptr); -static inline unsigned int hash_int_shift_mult(const void *keyptr); -static inline unsigned int hash_int64_shift_mult(const void *keyptr); +static inline unsigned int hash_int_32(const void *keyptr); +static inline unsigned int hash_int_64(const void *keyptr); static unsigned int odd_constant = DEFAULT_ODD_CONSTANT; static unsigned (*hash_str)(const void *key) = hash_fnv1a_32; -static unsigned (*hash_int)(const void *key) = hash_int_shift_mult; -static unsigned (*hash_int64)(const void *key) = hash_int64_shift_mult; +static unsigned (*hash_int)(const void *key) = hash_int_32; +static unsigned (*hash_int64)(const void *key) = hash_int_64; static bool resize_bucket(struct hash_bucket *bucket, unsigned int new_size) { @@ -120,13 +120,13 @@ static inline unsigned int hash_fnv1a_32(const void *keyptr) return fnv1a_32(keyptr, strlen(keyptr)); } -static inline unsigned int hash_int_shift_mult(const void *keyptr) +static inline unsigned int hash_int_32(const void *keyptr) { unsigned int key = (unsigned int)(long)keyptr; return fnv1a_32(&key, sizeof(key)); } -static inline unsigned int hash_int64_shift_mult(const void *keyptr) +static inline unsigned int hash_int_64(const void *keyptr) { const uint64_t key = (uint64_t)(uintptr_t)keyptr; return fnv1a_32(&key, sizeof(key)); From eb3bba826cb9391add9d63486a3ba14c98566e44 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 17 May 2024 21:42:38 -0700 Subject: [PATCH 2253/2505] Don't build with -fno-pie I don't know what I had in my head when I enabled this option. --- CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 39f6dc64c..39fa1560a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -272,8 +272,6 @@ else () LWAN_HAVE_READ_ONLY_GOT) enable_c_flag_if_avail(-fno-plt CMAKE_C_FLAGS LWAN_HAVE_NO_PLT) - enable_c_flag_if_avail(-no-pie CMAKE_C_FLAGS - LWAN_HAVE_NO_PIE) enable_c_flag_if_avail(-Wl,-z,noexecstack CMAKE_EXE_LINKER_FLAGS LWAN_HAVE_NOEXEC_STACK) endif () From 659eeae4e7624b6c45e9257d7ee8ef5bd2c54010 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 17 May 2024 21:43:26 -0700 Subject: [PATCH 2254/2505] Remove pragmas to disable -Wstringop-truncation GCC doesn't seem to have this warning group there. That particular usage is fine and there's a comment explaining why. --- src/bin/tools/mimegen.c | 3 --- src/lib/lwan-tables.c | 3 --- 2 files changed, 6 deletions(-) diff --git a/src/bin/tools/mimegen.c b/src/bin/tools/mimegen.c index 68291d049..a58f60dd8 100644 --- a/src/bin/tools/mimegen.c +++ b/src/bin/tools/mimegen.c @@ -297,11 +297,8 @@ int main(int argc, char *argv[]) for (i = 0; i < hash_get_count(ext_mime); i++) { uint64_t ext_lower = 0; -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wstringop-truncation" /* See lwan_determine_mime_type_for_file_name() in lwan-tables.c */ strncpy((char *)&ext_lower, exts[i], 8); -#pragma GCC diagnostic pop ext_lower &= ~0x2020202020202020ull; ext_lower = htobe64(ext_lower); diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index bcbc2d159..1cf3c5c38 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -142,14 +142,11 @@ const char *lwan_determine_mime_type_for_file_name(const char *file_name) if (LIKELY(last_dot && *last_dot)) { uint64_t key = 0; -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wstringop-truncation" /* Data is stored with NULs on strings up to 7 chars, and no NULs * for 8-char strings, because that's implicit. So truncation is * intentional here: comparison in compare_mime_entry() always loads * 8 bytes per extension. */ strncpy((char *)&key, last_dot + 1, 8); -#pragma GCC diagnostic pop return bsearch_mime_type(htobe64(key & ~0x2020202020202020ull)); } From a43011ef22dee5d7450d01cc812c11cc16821211 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 17 May 2024 21:44:32 -0700 Subject: [PATCH 2255/2505] Remove unused variable --- src/lib/lwan-websocket.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index 62e269ff9..c3e26de88 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -91,7 +91,6 @@ write_websocket_frame_full(struct lwan_request *request, return true; } - size_t total_written = 0; int curr_iov = 0; for (int try = 0; try < 10; try++) { ssize_t written = writev(request->fd, &vec[curr_iov], @@ -102,7 +101,6 @@ write_websocket_frame_full(struct lwan_request *request, return false; } - total_written += (size_t)written; while (curr_iov < (int)N_ELEMENTS(vec) && written >= (ssize_t)vec[curr_iov].iov_len) { written -= (ssize_t)vec[curr_iov].iov_len; From b407781827f75286d46272faef8c2b51f61a48d7 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 17 May 2024 21:47:02 -0700 Subject: [PATCH 2256/2505] Silence warnings about pointer<->int conversions --- src/lib/lwan-mod-response.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-mod-response.c b/src/lib/lwan-mod-response.c index 159325ca2..0fa2ef870 100644 --- a/src/lib/lwan-mod-response.c +++ b/src/lib/lwan-mod-response.c @@ -29,7 +29,7 @@ response_handle_request(struct lwan_request *request __attribute__((unused)), struct lwan_response *response __attribute__((unused)), void *instance) { - return (enum lwan_http_status)instance; + return (enum lwan_http_status)(intptr_t)instance; } static void *response_create(const char *prefix __attribute__((unused)), @@ -45,7 +45,7 @@ static void *response_create(const char *prefix __attribute__((unused)), return NULL; } - return (void *)settings->code; + return (void *)(uintptr_t)settings->code; } static void *response_create_from_hash(const char *prefix, From e380a94fedeec7a71841e37eec0f7b0ef992dd5f Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 17 May 2024 21:50:09 -0700 Subject: [PATCH 2257/2505] Silence warning in lwan-threads.c New GCC, new warnings. Sigh. --- src/lib/lwan-thread.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index ede38e7a2..ecdda9f86 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -561,7 +561,8 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, static ALWAYS_INLINE uint32_t conn_flags_to_epoll_events(enum lwan_connection_flags flags) { - assert((EPOLL_EVENTS(flags) & ~(EPOLLIN | EPOLLOUT | EPOLLRDHUP)) == 0); + assert((EPOLL_EVENTS(flags) & + (uint32_t) ~(EPOLLIN | EPOLLOUT | EPOLLRDHUP)) == 0); return EPOLL_EVENTS(flags); } @@ -882,11 +883,12 @@ static ALWAYS_INLINE void resume_coro(struct timeout_queue *tq, assert(conn_to_resume->coro); assert(conn_to_yield->coro); - enum lwan_connection_coro_yield from_coro = coro_resume_value( - conn_to_resume->coro, (int64_t)(intptr_t)conn_to_yield); + int64_t from_coro = coro_resume_value(conn_to_resume->coro, + (int64_t)(intptr_t)conn_to_yield); if (LIKELY(from_coro != CONN_CORO_ABORT)) { - int r = - update_epoll_flags(tq->lwan, conn_to_resume, epoll_fd, from_coro); + int r = update_epoll_flags( + tq->lwan, conn_to_resume, epoll_fd, + (enum lwan_connection_coro_yield)(uint32_t)from_coro); if (LIKELY(!r)) { timeout_queue_move_to_last(tq, conn_to_resume); return; From 547292781a36fb454536bcc3e1c7c58bc4a71744 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 17 May 2024 21:41:51 -0700 Subject: [PATCH 2258/2505] Cleanup how the initial hash value is seeded --- src/bin/tools/CMakeLists.txt | 1 + src/lib/hash.c | 48 +++++++++++++++++++++++++----------- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/src/bin/tools/CMakeLists.txt b/src/bin/tools/CMakeLists.txt index 54f83496d..4a047e210 100644 --- a/src/bin/tools/CMakeLists.txt +++ b/src/bin/tools/CMakeLists.txt @@ -6,6 +6,7 @@ else () mimegen.c ${CMAKE_SOURCE_DIR}/src/lib/hash.c ${CMAKE_SOURCE_DIR}/src/lib/missing.c + ${CMAKE_SOURCE_DIR}/src/lib/lwan-status.c ) if (LWAN_HAVE_BROTLI) message(STATUS "Using Brotli for mimegen") diff --git a/src/lib/hash.c b/src/lib/hash.c index cd1b3ca5e..b871690b7 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -80,11 +80,22 @@ struct hash_entry { static_assert((MIN_BUCKETS & (MIN_BUCKETS - 1)) == 0, "Bucket size is power of 2"); +#define DEFAULT_FNV1A_64_SEED 0xcbf29ce484222325ull +#define DEFAULT_FNV1A_32_SEED 0x811c9dc5u + +uint64_t fnv1a_64_seed = DEFAULT_FNV1A_64_SEED; +uint32_t fnv1a_32_seed = DEFAULT_FNV1A_32_SEED; + +#define ASSERT_SEED_INITIALIZED() \ + do { \ + assert(fnv1a_64_seed != DEFAULT_FNV1A_64_SEED); \ + assert(fnv1a_32_seed != DEFAULT_FNV1A_32_SEED); \ + } while (0) + static inline unsigned int hash_fnv1a_32(const void *keyptr); static inline unsigned int hash_int_32(const void *keyptr); static inline unsigned int hash_int_64(const void *keyptr); -static unsigned int odd_constant = DEFAULT_ODD_CONSTANT; static unsigned (*hash_str)(const void *key) = hash_fnv1a_32; static unsigned (*hash_int)(const void *key) = hash_int_32; static unsigned (*hash_int64)(const void *key) = hash_int_64; @@ -135,10 +146,12 @@ static inline unsigned int hash_int_64(const void *keyptr) #if defined(LWAN_HAVE_BUILTIN_CPU_INIT) && defined(LWAN_HAVE_BUILTIN_IA32_CRC32) static inline unsigned int hash_str_crc32(const void *keyptr) { - unsigned int hash = odd_constant; + unsigned int hash = fnv1a_32_seed; const char *key = keyptr; size_t len = strlen(key); + ASSERT_SEED_INITIALIZED(); + #if __x86_64__ while (len >= sizeof(uint64_t)) { uint64_t data; @@ -171,20 +184,24 @@ static inline unsigned int hash_str_crc32(const void *keyptr) static inline unsigned int hash_int_crc32(const void *keyptr) { - return __builtin_ia32_crc32si(odd_constant, + ASSERT_SEED_INITIALIZED(); + + return __builtin_ia32_crc32si(fnv1a_32_seed, (unsigned int)(uintptr_t)keyptr); } static inline unsigned int hash_int64_crc32(const void *keyptr) { + ASSERT_SEED_INITIALIZED(); + #ifdef __x86_64__ - return (unsigned int)__builtin_ia32_crc32di(odd_constant, + return (unsigned int)__builtin_ia32_crc32di(fnv1a_32_seed, (uint64_t)(uintptr_t)keyptr); #else const uint64_t key = (uint64_t)(uintptr_t)keyptr; uint32_t crc; - crc = __builtin_ia32_crc32si(odd_constant, (uint32_t)(key & 0xffffffff)); + crc = __builtin_ia32_crc32si(fnv1a_32_seed, (uint32_t)(key & 0xffffffff)); crc = __builtin_ia32_crc32si(crc, (uint32_t)(key >> 32)); return crc; @@ -193,19 +210,20 @@ static inline unsigned int hash_int64_crc32(const void *keyptr) #endif -uint64_t fnv1a_64_seed = 0xcbf29ce484222325ull; -uint32_t fnv1a_32_seed = 0x811c9dc5u; - -__attribute__((constructor(65535))) static void initialize_odd_constant(void) +__attribute__((constructor(65535))) static void initialize_fnv1a_seed(void) { - /* This constant is randomized in order to mitigate the DDoS attack + uint8_t entropy[128]; + + /* The seeds are randomized in order to mitigate the DDoS attack * described by Crosby and Wallach in UsenixSec2003. */ - if (lwan_getentropy(&odd_constant, sizeof(odd_constant), 0) < 0) - odd_constant = DEFAULT_ODD_CONSTANT; - odd_constant |= 1; + if (UNLIKELY(lwan_getentropy(entropy, sizeof(entropy), 0) < 0)) { + lwan_status_perror("Could not initialize FNV1a seed"); + __builtin_unreachable(); + } - fnv1a_64_seed = fnv1a_64(&odd_constant, sizeof(odd_constant)); - fnv1a_32_seed = fnv1a_32(&odd_constant, sizeof(odd_constant)); + fnv1a_64_seed = fnv1a_64(entropy, sizeof(entropy)); + fnv1a_32_seed = fnv1a_32(entropy, sizeof(entropy)); + lwan_always_bzero(entropy, sizeof(entropy)); #if defined(LWAN_HAVE_BUILTIN_CPU_INIT) && defined(LWAN_HAVE_BUILTIN_IA32_CRC32) __builtin_cpu_init(); From d2d91b595cd44d91ee3bd718ecf4751642e4f16e Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 17 May 2024 22:08:55 -0700 Subject: [PATCH 2259/2505] Ensure the library doesn't initialize if hash can't be randomized --- src/lib/hash.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/hash.c b/src/lib/hash.c index b871690b7..9c27fc34d 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -217,7 +217,7 @@ __attribute__((constructor(65535))) static void initialize_fnv1a_seed(void) /* The seeds are randomized in order to mitigate the DDoS attack * described by Crosby and Wallach in UsenixSec2003. */ if (UNLIKELY(lwan_getentropy(entropy, sizeof(entropy), 0) < 0)) { - lwan_status_perror("Could not initialize FNV1a seed"); + lwan_status_critical_perror("Could not initialize FNV1a seed"); __builtin_unreachable(); } From a7c6400134ccf3bbd4bf1e684bcbc4574e4ef4a4 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 17 May 2024 22:12:53 -0700 Subject: [PATCH 2260/2505] Fiz OSS-fuzz build --- src/lib/lwan-h2-huffman.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/lwan-h2-huffman.c b/src/lib/lwan-h2-huffman.c index adeae37ea..22bd1324d 100644 --- a/src/lib/lwan-h2-huffman.c +++ b/src/lib/lwan-h2-huffman.c @@ -34,6 +34,8 @@ #include #include +#include "ringbuffer.h" + #define LIKELY(x) x #define UNLIKELY(x) x From a5d2e74bc7e63cba7d93cdd04f346ee5033348f3 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 18 May 2024 13:27:27 -0700 Subject: [PATCH 2261/2505] Another round of h2 huffman build fixes --- src/lib/lwan-h2-huffman.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-h2-huffman.c b/src/lib/lwan-h2-huffman.c index 22bd1324d..dd8b358f4 100644 --- a/src/lib/lwan-h2-huffman.c +++ b/src/lib/lwan-h2-huffman.c @@ -338,7 +338,7 @@ ssize_t lwan_h2_huffman_next(struct lwan_h2_huffman_decoder *huff) if (LIKELY(level0[peeked_byte].num_bits)) { uint8_ring_buffer_put_copy(buffer, level0[peeked_byte].symbol); consume(reader, level0[peeked_byte].num_bits); - assert(bit_reader.total_bitcount >= 0); + assert(reader->total_bitcount >= 0); continue; } @@ -390,8 +390,8 @@ ssize_t lwan_h2_huffman_next(struct lwan_h2_huffman_decoder *huff) /* FIXME: ensure we're not promoting types unnecessarily here */ if (reader->total_bitcount) { const uint8_t peeked_byte = peek_byte(reader); - const uint8_t eos_prefix = ((1 << bit_reader.total_bitcount) - 1) - << (8 - bit_reader.total_bitcount); + const uint8_t eos_prefix = ((1 << reader->total_bitcount) - 1) + << (8 - reader->total_bitcount); if ((peeked_byte & eos_prefix) == eos_prefix) goto done; @@ -427,7 +427,7 @@ bool lwan_h2_huffman_decode_for_fuzzing(const uint8_t *input, size_t input_len) if (n_decoded < 64) return true; - uint8_ring_buffer_init(&decoder->buffer); + uint8_ring_buffer_init(&decoder.buffer); } } #endif From 5925c3f8acc7fa64e431f1415b9e0933b6479338 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 18 May 2024 13:55:59 -0700 Subject: [PATCH 2262/2505] Fix CMake warnings --- CMakeLists.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 39fa1560a..e475eb196 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.20) project(lwan C) set(PROJECT_DESCRIPTION "Scalable, high performance, experimental web server") message(STATUS "Running CMake for ${PROJECT_NAME} (${PROJECT_DESCRIPTION})") @@ -388,17 +388,17 @@ install(FILES "${CMAKE_BINARY_DIR}/${PROJECT_NAME}.pc" DESTINATION lib/pkgconfig # # Set up testsuite and benchmark targets # -find_package(PythonInterp 3) -if (LUA_FOUND AND PYTHONINTERP_FOUND) +find_package(Python3 COMPONENTS Interpreter) +if (LUA_FOUND AND Python3_Interpreter_FOUND) add_custom_target(testsuite - COMMAND ${PYTHON_EXECUTABLE} + COMMAND ${Python3_EXECUTABLE} ${PROJECT_SOURCE_DIR}/src/scripts/testsuite.py -v ${CMAKE_BINARY_DIR} DEPENDS testrunner techempower WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMENT "Running test suite.") add_custom_target(benchmark - COMMAND ${PYTHON_EXECUTABLE} + COMMAND ${Python3_EXECUTABLE} ${PROJECT_SOURCE_DIR}/src/scripts/benchmark.py ${CMAKE_BINARY_DIR}/src/bin/testrunner/testrunner DEPENDS testrunner From 8455a528c3486013c123fcfb16514561866ecf80 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 18 May 2024 13:56:09 -0700 Subject: [PATCH 2263/2505] Limit the scope of the FNV1a seed externs --- src/lib/hash.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib/hash.h b/src/lib/hash.h index bec709e55..edec94134 100644 --- a/src/lib/hash.h +++ b/src/lib/hash.h @@ -32,12 +32,10 @@ bool hash_iter_next(struct hash_iter *iter, const void **key, const void **value); -extern uint64_t fnv1a_64_seed; -extern uint32_t fnv1a_32_seed; - static inline uint64_t fnv1a_64(const void *buffer, size_t len) { const unsigned char *data = (unsigned char *)buffer; + extern uint64_t fnv1a_64_seed; uint64_t hash; for (hash = fnv1a_64_seed; len--; data++) { @@ -50,6 +48,7 @@ static inline uint64_t fnv1a_64(const void *buffer, size_t len) static inline uint32_t fnv1a_32(const void *buffer, size_t len) { const unsigned char *data = (unsigned char *)buffer; + extern uint32_t fnv1a_32_seed; uint32_t hash; for (hash = fnv1a_32_seed; len--; data++) { From 58dae658b507e95933f7e9cd8361b9ea2feb05e5 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 18 May 2024 18:07:45 -0700 Subject: [PATCH 2264/2505] Revert "Remove pragmas to disable -Wstringop-truncation" I was compiling with Clang, and not GCC. Put the warning back in! This reverts commit 659eeae4e7624b6c45e9257d7ee8ef5bd2c54010. --- src/bin/tools/mimegen.c | 3 +++ src/lib/lwan-tables.c | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/bin/tools/mimegen.c b/src/bin/tools/mimegen.c index a58f60dd8..68291d049 100644 --- a/src/bin/tools/mimegen.c +++ b/src/bin/tools/mimegen.c @@ -297,8 +297,11 @@ int main(int argc, char *argv[]) for (i = 0; i < hash_get_count(ext_mime); i++) { uint64_t ext_lower = 0; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstringop-truncation" /* See lwan_determine_mime_type_for_file_name() in lwan-tables.c */ strncpy((char *)&ext_lower, exts[i], 8); +#pragma GCC diagnostic pop ext_lower &= ~0x2020202020202020ull; ext_lower = htobe64(ext_lower); diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index 1cf3c5c38..bcbc2d159 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -142,11 +142,14 @@ const char *lwan_determine_mime_type_for_file_name(const char *file_name) if (LIKELY(last_dot && *last_dot)) { uint64_t key = 0; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstringop-truncation" /* Data is stored with NULs on strings up to 7 chars, and no NULs * for 8-char strings, because that's implicit. So truncation is * intentional here: comparison in compare_mime_entry() always loads * 8 bytes per extension. */ strncpy((char *)&key, last_dot + 1, 8); +#pragma GCC diagnostic pop return bsearch_mime_type(htobe64(key & ~0x2020202020202020ull)); } From 2ab645498cb6ee480f5198d3c1157c72fa21611c Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 18 May 2024 13:58:22 -0700 Subject: [PATCH 2265/2505] Make benchmark target depend on weighttp --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e475eb196..52af98d8e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -401,7 +401,7 @@ if (LUA_FOUND AND Python3_Interpreter_FOUND) COMMAND ${Python3_EXECUTABLE} ${PROJECT_SOURCE_DIR}/src/scripts/benchmark.py ${CMAKE_BINARY_DIR}/src/bin/testrunner/testrunner - DEPENDS testrunner + DEPENDS testrunner weighttp WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMENT "Running benchmark.") endif() From 6a636114aefec5ea506f24f162607d69687aca7c Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 18 May 2024 18:08:45 -0700 Subject: [PATCH 2266/2505] Partially update the benchmark script It's still not working, but it's not a Python2 script anymore. --- src/scripts/benchmark.py | 48 +++++++++++++++++----------------------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/src/scripts/benchmark.py b/src/scripts/benchmark.py index ce25642ef..71a21e2dd 100755 --- a/src/scripts/benchmark.py +++ b/src/scripts/benchmark.py @@ -1,15 +1,12 @@ #!/usr/bin/python -from __future__ import print_function - -import commands import json import os import subprocess import sys import time -LWAN_PATH = './build/testrunner/testrunner' +LWAN_PATH = './src/bin/testrunner/testrunner' for arg in sys.argv[1:]: if not arg.startswith('-') and os.path.exists(arg): LWAN_PATH = arg @@ -33,25 +30,25 @@ def clearstderrline(): def weighttp(url, n_threads, n_connections, n_requests, keep_alive): - keep_alive = '-k' if keep_alive else '' - command = 'weighttp %(keep_alive)s ' \ - '-t %(n_threads)d ' \ - '-c %(n_connections)d ' \ - '-n %(n_requests)d ' \ - '-j ' \ - '%(url)s 2> /dev/null' % locals() - clearstderrline() - sys.stderr.write('*** %s\r' % command) - - output = commands.getoutput(command) - - return json.loads(output) - - -def weighttp_has_json_output(): - output = commands.getoutput('weighttp -j') - return not 'unknown option: -j' in output + sys.stderr.write(f'*** Running weighttp on {url}: threads: {n_threads} ' \ + f'conns: {n_connections} reqs: {n_requests} ' \ + f'{"keep-alive" if keep_alive else ""} \r') + + command = [ + './src/bin/tools/weighttp', + '-k' if keep_alive else '', + '-t', n_threads, + '-c', n_connections, + '-n', n_requests, + '-j', + url + ] + print(command) + output = subprocess.run(command, capture_output=True) + output.check_returncode() + + return json.loads(output.stdout) def steprange(initial, final, steps=10): @@ -151,11 +148,6 @@ def log(self, keep_alive, n_connections, rps, kbps, _2xx, _3xx, _4xx, _5xx): if __name__ == '__main__': - if not weighttp_has_json_output(): - print('This script requires a special version of weighttp which supports JSON') - print('output. Get it at http://github.com/lpereira/weighttp') - sys.exit(1) - plot = cmdlineboolarg('--plot') xkcd = cmdlineboolarg('--xkcd') n_threads = cmdlineintarg('--threads', 2) @@ -183,7 +175,7 @@ def log(self, keep_alive, n_connections, rps, kbps, _2xx, _3xx, _4xx, _5xx): status = results['status_codes'] output.log(keep_alive, n_connections, results['reqs_per_sec'], - results['kbyte_per_sec'], status['2xx'], status['3xx'], + results['kBps_per_sec'], status['2xx'], status['3xx'], status['4xx'], status['5xx']) sleepwithstatus('Waiting for keepalive connection timeout', keep_alive_timeout * 1.1) From d87e7407c13e27773c7b51b30879e23d43279723 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 19 May 2024 00:33:47 -0700 Subject: [PATCH 2267/2505] Use LWAN_EVENTS() macro to check if prepare_wait() should do anything --- src/lib/lwan-thread.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index ecdda9f86..0474f232a 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -617,7 +617,6 @@ static int update_epoll_flags(const struct lwan *lwan, struct epoll_event event = {.events = conn_flags_to_epoll_events(conn->flags), .data.ptr = conn}; int fd = lwan_connection_get_fd(lwan, conn); - return epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &event); } @@ -666,8 +665,9 @@ static int prepare_await(const struct lwan *l, struct lwan_connection *await_fd_conn = &l->conns[await_fd]; if (LIKELY(await_fd_conn->flags & CONN_ASYNC_AWAIT)) { - if (LIKELY((await_fd_conn->flags & CONN_EVENTS_MASK) == flags)) + if (LIKELY(LWAN_EVENTS(await_fd_conn->flags) == LWAN_EVENTS(flags))) { return 0; + } op = EPOLL_CTL_MOD; } else { From bba7388b0aaff9d5644fe1c954a8540a447ad209 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 19 May 2024 00:34:16 -0700 Subject: [PATCH 2268/2505] Ensure single-fd awaits actually return the requested fd --- src/lib/lwan-thread.c | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 0474f232a..1a4b5d1d2 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -842,37 +842,47 @@ int lwan_request_awaitv_all(struct lwan_request *r, ...) return -EISCONN; } -static inline int async_await_fd(struct lwan_connection *conn, +static inline int async_await_fd(struct lwan_request *request, int fd, enum lwan_connection_coro_yield events) { - struct lwan_thread *thread = conn->thread; + struct lwan_thread *thread = request->conn->thread; struct lwan *lwan = thread->lwan; - int r = prepare_await(lwan, events, fd, conn, thread->epoll_fd); + struct lwan_connection *awaited = &lwan->conns[fd]; + if (request->conn == awaited) { + coro_yield(request->conn->coro, events); + return fd; + } + + int r = prepare_await(lwan, events, fd, request->conn, thread->epoll_fd); if (UNLIKELY(r < 0)) return r; - conn = (struct lwan_connection *)(intptr_t)coro_yield(conn->coro, - CONN_CORO_SUSPEND); - return UNLIKELY(conn->flags & CONN_HUNG_UP) - ? -ECONNRESET - : lwan_connection_get_fd(lwan, conn); + while (true) { + int64_t from_coro = coro_yield(request->conn->coro, CONN_CORO_SUSPEND); + + if ((struct lwan_connection *)(intptr_t)from_coro == awaited) { + return UNLIKELY(request->conn->flags & CONN_HUNG_UP) + ? -ECONNRESET + : lwan_connection_get_fd(lwan, awaited); + } + } } int lwan_request_await_read(struct lwan_request *r, int fd) { - return async_await_fd(r->conn, fd, CONN_CORO_WANT_READ); + return async_await_fd(r, fd, CONN_CORO_WANT_READ); } int lwan_request_await_write(struct lwan_request *r, int fd) { - return async_await_fd(r->conn, fd, CONN_CORO_WANT_WRITE); + return async_await_fd(r, fd, CONN_CORO_WANT_WRITE); } int lwan_request_await_read_write(struct lwan_request *r, int fd) { - return async_await_fd(r->conn, fd, CONN_CORO_WANT_READ_WRITE); + return async_await_fd(r, fd, CONN_CORO_WANT_READ_WRITE); } static ALWAYS_INLINE void resume_coro(struct timeout_queue *tq, From d5fe08983f9a57f28904c79f13e5b884ce6ace7e Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 19 May 2024 08:13:57 -0700 Subject: [PATCH 2269/2505] Unify I/O wrappers (should work with request FDs and other FDs!) --- src/lib/lwan-io-wrappers.c | 155 ++++++++++++++++++---------------- src/lib/lwan-io-wrappers.h | 99 +++++++++++++++++++--- src/lib/lwan-mod-fastcgi.c | 50 ++++++----- src/lib/lwan-request.c | 114 ------------------------- src/lib/lwan.h | 7 -- src/samples/asyncawait/main.c | 3 +- 6 files changed, 199 insertions(+), 229 deletions(-) diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index 5745b3a65..b1ec0a563 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -32,7 +32,7 @@ static const int MAX_FAILED_TRIES = 5; ssize_t -lwan_writev(struct lwan_request *request, struct iovec *iov, int iov_count) +lwan_writev_fd(struct lwan_request *request, int fd, struct iovec *iov, int iov_count) { ssize_t total_written = 0; int curr_iov = 0; @@ -44,14 +44,14 @@ lwan_writev(struct lwan_request *request, struct iovec *iov, int iov_count) if (remaining_len == 1) { const struct iovec *vec = &iov[curr_iov]; - return lwan_send(request, vec->iov_base, vec->iov_len, flags); + return lwan_send_fd(request, fd, vec->iov_base, vec->iov_len, flags); } struct msghdr hdr = { .msg_iov = iov + curr_iov, .msg_iovlen = (size_t)remaining_len, }; - written = sendmsg(request->fd, &hdr, flags); + written = sendmsg(fd, &hdr, flags); if (UNLIKELY(written < 0)) { /* FIXME: Consider short writes as another try as well? */ @@ -62,7 +62,7 @@ lwan_writev(struct lwan_request *request, struct iovec *iov, int iov_count) case EINTR: goto try_again; default: - goto out; + return -errno; } } @@ -81,23 +81,20 @@ lwan_writev(struct lwan_request *request, struct iovec *iov, int iov_count) iov[curr_iov].iov_len -= (size_t)written; try_again: - coro_yield(request->conn->coro, CONN_CORO_WANT_WRITE); + lwan_request_await_read(request, fd); } -out: - coro_yield(request->conn->coro, CONN_CORO_ABORT); - __builtin_unreachable(); + return -ETIMEDOUT; } ssize_t -lwan_readv(struct lwan_request *request, struct iovec *iov, int iov_count) +lwan_readv_fd(struct lwan_request *request, int fd, struct iovec *iov, int iov_count) { ssize_t total_bytes_read = 0; int curr_iov = 0; for (int tries = MAX_FAILED_TRIES; tries;) { - ssize_t bytes_read = - readv(request->fd, iov + curr_iov, iov_count - curr_iov); + ssize_t bytes_read = readv(fd, iov + curr_iov, iov_count - curr_iov); if (UNLIKELY(bytes_read < 0)) { /* FIXME: Consider short reads as another try as well? */ tries--; @@ -107,7 +104,7 @@ lwan_readv(struct lwan_request *request, struct iovec *iov, int iov_count) case EINTR: goto try_again; default: - goto out; + return -errno; } } @@ -126,18 +123,17 @@ lwan_readv(struct lwan_request *request, struct iovec *iov, int iov_count) iov[curr_iov].iov_len -= (size_t)bytes_read; try_again: - coro_yield(request->conn->coro, CONN_CORO_WANT_READ); + lwan_request_await_read(request, fd); } -out: - coro_yield(request->conn->coro, CONN_CORO_ABORT); - __builtin_unreachable(); + return -ETIMEDOUT; } -ssize_t lwan_send(struct lwan_request *request, - const void *buf, - size_t count, - int flags) +ssize_t lwan_send_fd(struct lwan_request *request, + int fd, + const void *buf, + size_t count, + int flags) { ssize_t total_sent = 0; @@ -145,7 +141,7 @@ ssize_t lwan_send(struct lwan_request *request, flags |= MSG_MORE; for (int tries = MAX_FAILED_TRIES; tries;) { - ssize_t written = send(request->fd, buf, count, flags); + ssize_t written = send(fd, buf, count, flags); if (UNLIKELY(written < 0)) { tries--; @@ -154,7 +150,7 @@ ssize_t lwan_send(struct lwan_request *request, case EINTR: goto try_again; default: - goto out; + return -errno; } } @@ -165,21 +161,19 @@ ssize_t lwan_send(struct lwan_request *request, buf = (char *)buf + written; try_again: - coro_yield(request->conn->coro, CONN_CORO_WANT_WRITE); + lwan_request_await_write(request, fd); } -out: - coro_yield(request->conn->coro, CONN_CORO_ABORT); - __builtin_unreachable(); + return -ETIMEDOUT; } ssize_t -lwan_recv(struct lwan_request *request, void *buf, size_t count, int flags) +lwan_recv_fd(struct lwan_request *request, int fd, void *buf, size_t count, int flags) { ssize_t total_recv = 0; for (int tries = MAX_FAILED_TRIES; tries;) { - ssize_t recvd = recv(request->fd, buf, count, flags); + ssize_t recvd = recv(fd, buf, count, flags); if (UNLIKELY(recvd < 0)) { tries--; @@ -191,7 +185,7 @@ lwan_recv(struct lwan_request *request, void *buf, size_t count, int flags) case EINTR: goto try_again; default: - goto out; + return -errno; } } @@ -202,21 +196,20 @@ lwan_recv(struct lwan_request *request, void *buf, size_t count, int flags) buf = (char *)buf + recvd; try_again: - coro_yield(request->conn->coro, CONN_CORO_WANT_READ); + lwan_request_await_read(request, fd); } -out: - coro_yield(request->conn->coro, CONN_CORO_ABORT); - __builtin_unreachable(); + return -ETIMEDOUT; } #if defined(__linux__) -void lwan_sendfile(struct lwan_request *request, - int in_fd, - off_t offset, - size_t count, - const char *header, - size_t header_len) +int lwan_sendfile_fd(struct lwan_request *request, + int out_fd, + int in_fd, + off_t offset, + size_t count, + const char *header, + size_t header_len) { /* Clamp each chunk to 2^21 bytes[1] to balance throughput and * scalability. This used to be capped to 2^14 bytes, as that's the @@ -227,47 +220,51 @@ void lwan_sendfile(struct lwan_request *request, * sent using MSG_MORE. Subsequent chunks are sized 2^21 bytes. (Do * this regardless of this connection being TLS or not for simplicity.) * - * [1] https://www.kernel.org/doc/html/v5.12/networking/tls.html#sending-tls-application-data + * [1] + * https://www.kernel.org/doc/html/v5.12/networking/tls.html#sending-tls-application-data * [2] https://github.com/lpereira/lwan/issues/334 */ size_t chunk_size = LWAN_MIN(count, (1ul << 21) - header_len); size_t to_be_written = count; + ssize_t r; assert(header_len < (1ul << 21)); - lwan_send(request, header, header_len, MSG_MORE); + r = lwan_send_fd(request, out_fd, header, header_len, MSG_MORE); + if (r < 0) + return (int)r; while (true) { - ssize_t written = sendfile(request->fd, in_fd, &offset, chunk_size); + ssize_t written = sendfile(out_fd, in_fd, &offset, chunk_size); if (written < 0) { switch (errno) { case EAGAIN: case EINTR: goto try_again; default: - coro_yield(request->conn->coro, CONN_CORO_ABORT); - __builtin_unreachable(); + return -errno; } } to_be_written -= (size_t)written; if (!to_be_written) - break; + return 0; chunk_size = LWAN_MIN(to_be_written, 1ul << 21); lwan_readahead_queue(in_fd, offset, chunk_size); try_again: - coro_yield(request->conn->coro, CONN_CORO_WANT_WRITE); + lwan_request_await_write(request, out_fd); } } #elif defined(__FreeBSD__) || defined(__APPLE__) -void lwan_sendfile(struct lwan_request *request, - int in_fd, - off_t offset, - size_t count, - const char *header, - size_t header_len) +int lwan_sendfile(struct lwan_request *request, + int out_fd, + int in_fd, + off_t offset, + size_t count, + const char *header, + size_t header_len) { struct sf_hdtr headers = {.headers = (struct iovec[]){{.iov_base = (void *)header, @@ -277,19 +274,19 @@ void lwan_sendfile(struct lwan_request *request, if (!count) { /* FreeBSD's sendfile() won't send the headers when count is 0. Why? */ - return (void)lwan_writev(request, headers.headers, headers.hdr_cnt); + return lwan_writev_fd(request, out_fd, headers.headers, + headers.hdr_cnt); } while (true) { int r; #ifdef __APPLE__ - r = sendfile(in_fd, request->fd, offset, &sbytes, &headers, 0); + r = sendfile(in_fd, out_fd, offset, &sbytes, &headers, 0); #else - r = sendfile(in_fd, request->fd, offset, count, &headers, &sbytes, + r = sendfile(in_fd, out_fd, offset, count, &headers, &sbytes, SF_MNOWAIT); #endif - if (UNLIKELY(r < 0)) { switch (errno) { case EAGAIN: @@ -297,21 +294,20 @@ void lwan_sendfile(struct lwan_request *request, case EINTR: goto try_again; default: - coro_yield(request->conn->coro, CONN_CORO_ABORT); - __builtin_unreachable(); + return -errno; } } count -= (size_t)sbytes; if (!count) - break; + return 0; try_again: - coro_yield(request->conn->coro, CONN_CORO_WANT_WRITE); + lwan_request_await_write(request, out_fd); } } #else -static size_t try_pread_file(struct lwan_request *request, +static ssize_t try_pread_file(struct lwan_request *request, int fd, void *buffer, size_t len, @@ -332,7 +328,7 @@ static size_t try_pread_file(struct lwan_request *request, coro_yield(request->conn->coro, CONN_CORO_YIELD); continue; default: - break; + return -errno; } } @@ -344,25 +340,36 @@ static size_t try_pread_file(struct lwan_request *request, offset += r; } - coro_yield(request->conn->coro, CONN_CORO_ABORT); - __builtin_unreachable(); + return -ETIMEDOUT; } -void lwan_sendfile(struct lwan_request *request, - int in_fd, - off_t offset, - size_t count, - const char *header, - size_t header_len) +int lwan_sendfile_fd(struct lwan_request *request, + int out_fd, + int in_fd, + off_t offset, + size_t count, + const char *header, + size_t header_len) { unsigned char buffer[512]; + ssize_t r; - lwan_send(request, header, header_len, MSG_MORE); + r = lwan_send_fd(request, out_fd, header, header_len, MSG_MORE); + if (UNLIKELY(r < 0)) { + return (int)r; + } while (count) { - size_t bytes_read = try_pread_file( - request, in_fd, buffer, LWAN_MIN(count, sizeof(buffer)), offset); - lwan_send(request, buffer, bytes_read, 0); + r = try_pread_file(request, in_fd, buffer, + LWAN_MIN(count, sizeof(buffer)), offset); + if (UNLIKELY(r < 0)) + return (int)r; + + size_t bytes_read = (size_t)r; + r = lwan_send_fd(request, out_fd, buffer, bytes_read, 0); + if (UNLIKELY(r < 0)) + return (int)r; + count -= bytes_read; offset += bytes_read; } diff --git a/src/lib/lwan-io-wrappers.h b/src/lib/lwan-io-wrappers.h index d86f23e96..8130a50b8 100644 --- a/src/lib/lwan-io-wrappers.h +++ b/src/lib/lwan-io-wrappers.h @@ -24,15 +24,90 @@ #include "lwan.h" -ssize_t lwan_writev(struct lwan_request *request, struct iovec *iov, - int iovcnt); -ssize_t lwan_send(struct lwan_request *request, const void *buf, size_t count, - int flags); -void lwan_sendfile(struct lwan_request *request, int in_fd, - off_t offset, size_t count, - const char *header, size_t header_len); - -ssize_t lwan_recv(struct lwan_request *request, - void *buf, size_t count, int flags); -ssize_t lwan_readv(struct lwan_request *request, - struct iovec *iov, int iov_count); +ssize_t lwan_writev_fd(struct lwan_request *request, + int fd, + struct iovec *iov, + int iovcnt); +ssize_t lwan_send_fd(struct lwan_request *request, + int fd, + const void *buf, + size_t count, + int flags); +int lwan_sendfile_fd(struct lwan_request *request, + int out_fd, + int in_fd, + off_t offset, + size_t count, + const char *header, + size_t header_len); +ssize_t lwan_recv_fd(struct lwan_request *request, + int fd, + void *buf, + size_t count, + int flags); +ssize_t lwan_readv_fd(struct lwan_request *request, + int fd, + struct iovec *iov, + int iov_count); + +static inline ssize_t +lwan_writev(struct lwan_request *request, struct iovec *iov, int iovcnt) +{ + ssize_t r = lwan_writev_fd(request, request->fd, iov, iovcnt); + if (UNLIKELY(r < 0)) { + coro_yield(request->conn->coro, CONN_CORO_ABORT); + __builtin_unreachable(); + } + return r; +} + +static inline ssize_t lwan_send(struct lwan_request *request, + const void *buf, + size_t count, + int flags) +{ + ssize_t r = lwan_send_fd(request, request->fd, buf, count, flags); + if (UNLIKELY(r < 0)) { + coro_yield(request->conn->coro, CONN_CORO_ABORT); + __builtin_unreachable(); + } + return r; +} + +static inline int lwan_sendfile(struct lwan_request *request, + int in_fd, + off_t offset, + size_t count, + const char *header, + size_t header_len) +{ + int r = lwan_sendfile_fd(request, request->fd, in_fd, offset, count, header, + header_len); + if (UNLIKELY(r < 0)) { + coro_yield(request->conn->coro, CONN_CORO_ABORT); + __builtin_unreachable(); + } + return r; +} + +static inline ssize_t +lwan_recv(struct lwan_request *request, void *buf, size_t count, int flags) +{ + ssize_t r = lwan_recv_fd(request, request->fd, buf, count, flags); + if (UNLIKELY(r < 0)) { + coro_yield(request->conn->coro, CONN_CORO_ABORT); + __builtin_unreachable(); + } + return r; +} + +static inline ssize_t +lwan_readv(struct lwan_request *request, struct iovec *iov, int iov_count) +{ + ssize_t r = lwan_readv_fd(request, request->fd, iov, iov_count); + if (UNLIKELY(r < 0)) { + coro_yield(request->conn->coro, CONN_CORO_ABORT); + __builtin_unreachable(); + } + return r; +} diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index 05ba4d8ce..b53b5d1aa 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -46,6 +46,7 @@ #include "patterns.h" #include "realpathat.h" #include "lwan-cache.h" +#include "lwan-io-wrappers.h" #include "lwan-mod-fastcgi.h" #include "lwan-strbuf.h" @@ -327,7 +328,7 @@ handle_stdout(struct lwan_request *request, const struct record *record, int fd) return false; while (to_read) { - ssize_t r = lwan_request_async_read(request, fd, buffer, to_read); + ssize_t r = lwan_recv_fd(request, fd, buffer, to_read, 0); if (r < 0) return false; @@ -338,8 +339,10 @@ handle_stdout(struct lwan_request *request, const struct record *record, int fd) if (record->len_padding) { char padding[256]; - lwan_request_async_read_flags(request, fd, padding, - (size_t)record->len_padding, MSG_TRUNC); + if (lwan_send_fd(request, fd, padding, (size_t)record->len_padding, + MSG_TRUNC) < 0) { + return false; + } } return true; @@ -354,10 +357,11 @@ handle_stderr(struct lwan_request *request, const struct record *record, int fd) if (!buffer) return false; - coro_deferred buffer_free_defer = coro_defer(request->conn->coro, free, buffer); + coro_deferred buffer_free_defer = + coro_defer(request->conn->coro, free, buffer); for (char *p = buffer; to_read;) { - ssize_t r = lwan_request_async_read(request, fd, p, to_read); + ssize_t r = lwan_recv_fd(request, fd, p, to_read, 0); if (r < 0) return false; @@ -366,16 +370,17 @@ handle_stderr(struct lwan_request *request, const struct record *record, int fd) to_read -= (size_t)r; } - lwan_status_error("FastCGI stderr output: %.*s", - (int)record->len_content, buffer); + lwan_status_error("FastCGI stderr output: %.*s", (int)record->len_content, + buffer); coro_defer_fire_and_disarm(request->conn->coro, buffer_free_defer); if (record->len_padding) { char padding[256]; - lwan_request_async_read_flags(request, fd, padding, - (size_t)record->len_padding, - MSG_TRUNC); + if (lwan_recv_fd(request, fd, padding, (size_t)record->len_padding, + MSG_TRUNC) < 0) { + return false; + } } return true; @@ -402,8 +407,8 @@ static bool discard_unknown_record(struct lwan_request *request, while (to_read) { ssize_t r; - r = lwan_request_async_read_flags( - request, fd, buffer, LWAN_MIN(sizeof(buffer), to_read), MSG_TRUNC); + r = lwan_recv_fd(request, fd, buffer, LWAN_MIN(sizeof(buffer), to_read), + MSG_TRUNC); if (r < 0) return false; @@ -574,9 +579,11 @@ static bool build_stdin_records(struct lwan_request *request, *iovec = (struct iovec){.iov_base = buffer, .iov_len = block_size}; if (iovec_array_len(iovec_array) == LWAN_ARRAY_INCREMENT) { - lwan_request_async_writev(request, fcgi_fd, - iovec_array_get_array(iovec_array), - (int)iovec_array_len(iovec_array)); + if (lwan_writev_fd(request, fcgi_fd, + iovec_array_get_array(iovec_array), + (int)iovec_array_len(iovec_array)) < 0) { + return false; + } iovec_array_reset(iovec_array); record_array_reset(record_array); } @@ -631,8 +638,8 @@ static enum lwan_http_status send_request(struct private_data *pd, .begin_request = {.version = 1, .type = FASTCGI_TYPE_BEGIN_REQUEST, .id = htons(1), - .len_content = htons( - (uint16_t)sizeof(struct begin_request_body))}, + .len_content = htons((uint16_t)sizeof( + struct begin_request_body))}, .begin_request_body = {.role = htons(FASTCGI_ROLE_RESPONDER)}, .begin_params = {.version = 1, .type = FASTCGI_TYPE_PARAMS, @@ -675,9 +682,10 @@ static enum lwan_http_status send_request(struct private_data *pd, .iov_len = sizeof(struct record), }; - lwan_request_async_writev(request, fcgi_fd, - iovec_array_get_array(&iovec_array), - (int)iovec_array_len(&iovec_array)); + if (lwan_writev_fd(request, fcgi_fd, iovec_array_get_array(&iovec_array), + (int)iovec_array_len(&iovec_array)) < 0) { + return HTTP_INTERNAL_ERROR; + } iovec_array_reset(&iovec_array); record_array_reset(&record_array); @@ -769,7 +777,7 @@ fastcgi_handle_request(struct lwan_request *request, struct record record; ssize_t r; - r = lwan_request_async_read(request, fcgi_fd, &record, sizeof(record)); + r = lwan_recv_fd(request, fcgi_fd, &record, sizeof(record), 0); if (r < 0) return HTTP_UNAVAILABLE; if (r != (ssize_t)sizeof(record)) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index c76fdc523..182da7d0b 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -2077,120 +2077,6 @@ __attribute__((used)) int fuzz_parse_http_request(const uint8_t *data, } #endif -ssize_t lwan_request_async_read_flags( - struct lwan_request *request, int fd, void *buf, size_t len, int flags) -{ - while (true) { - ssize_t r = recv(fd, buf, len, MSG_DONTWAIT | MSG_NOSIGNAL | flags); - - if (r < 0) { - switch (errno) { - case EWOULDBLOCK: - r = lwan_request_await_read(request, fd); - if (UNLIKELY(r < 0)) - return (int)r; - /* Fallthrough */ - case EINTR: - continue; - default: - return -errno; - } - } - - return r; - } -} - -ssize_t lwan_request_async_read(struct lwan_request *request, - int fd, - void *buf, - size_t len) -{ - return lwan_request_async_read_flags(request, fd, buf, len, 0); -} - -ssize_t lwan_request_async_write(struct lwan_request *request, - int fd, - const void *buf, - size_t len) -{ - while (true) { - ssize_t r = send(fd, buf, len, MSG_DONTWAIT|MSG_NOSIGNAL); - - if (r < 0) { - switch (errno) { - case EWOULDBLOCK: - r = lwan_request_await_write(request, fd); - if (UNLIKELY(r < 0)) - return (int)r; - /* Fallthrough */ - case EINTR: - continue; - default: - return -errno; - } - } - - return r; - } -} - -ssize_t lwan_request_async_writev(struct lwan_request *request, - int fd, - struct iovec *iov, - int iov_count) -{ - ssize_t total_written = 0; - int curr_iov = 0; - - for (int tries = 10; tries;) { - const int remaining_len = (int)(iov_count - curr_iov); - ssize_t written; - int r; - - if (remaining_len == 1) { - const struct iovec *vec = &iov[curr_iov]; - return lwan_request_async_write(request, fd, vec->iov_base, - vec->iov_len); - } - - written = writev(fd, iov + curr_iov, remaining_len); - if (UNLIKELY(written < 0)) { - /* FIXME: Consider short writes as another try as well? */ - tries--; - - switch (errno) { - case EAGAIN: - case EINTR: - goto try_again; - default: - return -errno; - } - } - - total_written += written; - - while (curr_iov < iov_count && - written >= (ssize_t)iov[curr_iov].iov_len) { - written -= (ssize_t)iov[curr_iov].iov_len; - curr_iov++; - } - - if (curr_iov == iov_count) - return total_written; - - iov[curr_iov].iov_base = (char *)iov[curr_iov].iov_base + written; - iov[curr_iov].iov_len -= (size_t)written; - - try_again: - r = lwan_request_await_write(request, fd); - if (UNLIKELY(r < 0)) - return r; - } - - return -ETIMEDOUT; -} - void lwan_request_foreach_header_for_cgi(struct lwan_request *request, void (*cb)(const char *header_name, size_t header_len, diff --git a/src/lib/lwan.h b/src/lib/lwan.h index afe42f74b..c7cc1eaf5 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -666,13 +666,6 @@ int lwan_request_await_write(struct lwan_request *r, int fd); int lwan_request_await_read_write(struct lwan_request *r, int fd); int lwan_request_awaitv_any(struct lwan_request *r, ...); int lwan_request_awaitv_all(struct lwan_request *r, ...); -ssize_t lwan_request_async_read(struct lwan_request *r, int fd, void *buf, size_t len); -ssize_t lwan_request_async_read_flags(struct lwan_request *request, int fd, void *buf, size_t len, int flags); -ssize_t lwan_request_async_write(struct lwan_request *r, int fd, const void *buf, size_t len); -ssize_t lwan_request_async_writev(struct lwan_request *request, - int fd, - struct iovec *iov, - int iov_count); void lwan_straitjacket_enforce(const struct lwan_straitjacket *sj); diff --git a/src/samples/asyncawait/main.c b/src/samples/asyncawait/main.c index 65e70a86a..caebfaf3c 100644 --- a/src/samples/asyncawait/main.c +++ b/src/samples/asyncawait/main.c @@ -26,6 +26,7 @@ #include #include "lwan.h" +#include "lwan-io-wrappers.h" static void close_socket(void *data) { close((int)(intptr_t)data); } @@ -48,7 +49,7 @@ LWAN_HANDLER_ROUTE(asyncawait, "/") while (true) { char buffer[128]; - ssize_t r = lwan_request_async_read(request, fd, buffer, sizeof(buffer)); + ssize_t r = lwan_recv_fd(request, fd, buffer, sizeof(buffer), 0); lwan_strbuf_set_static(response->buffer, buffer, (size_t)r); lwan_response_send_chunk(request); From d2a4bcc55b5c014aa49e61b502051cd187411e10 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 19 May 2024 11:17:24 -0700 Subject: [PATCH 2270/2505] Ensure lwan_sendfile_fd() fallback returns 0 on success --- src/lib/lwan-io-wrappers.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index b1ec0a563..279ce3442 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -373,5 +373,7 @@ int lwan_sendfile_fd(struct lwan_request *request, count -= bytes_read; offset += bytes_read; } + + return 0; } #endif From 22d6b5de8276f1c6f957a3d700502452e4b28791 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 19 May 2024 11:20:11 -0700 Subject: [PATCH 2271/2505] Fix reception of fastcgi stdout frame padding after d5fe08 --- src/lib/lwan-mod-fastcgi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index b53b5d1aa..a01d6ba5f 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -339,7 +339,7 @@ handle_stdout(struct lwan_request *request, const struct record *record, int fd) if (record->len_padding) { char padding[256]; - if (lwan_send_fd(request, fd, padding, (size_t)record->len_padding, + if (lwan_recv_fd(request, fd, padding, (size_t)record->len_padding, MSG_TRUNC) < 0) { return false; } From 3ca46344908d3b24c052a10acf2b0ecc44b1c86e Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 19 May 2024 11:29:03 -0700 Subject: [PATCH 2272/2505] Change "Worker thread ... starting" message to debug level No need to print this in release builds! --- src/lib/lwan-thread.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 1a4b5d1d2..9dec2c9fb 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -1177,12 +1177,12 @@ static void *thread_io_loop(void *data) struct timeout_queue tq; if (t->cpu == UINT_MAX) { - lwan_status_info("Worker thread #%zd starting", - t - t->lwan->thread.threads + 1); + lwan_status_debug("Worker thread #%zd starting", + t - t->lwan->thread.threads + 1); } else { - lwan_status_info("Worker thread #%zd starting on CPU %d", - t - t->lwan->thread.threads + 1, - t->cpu); + lwan_status_debug("Worker thread #%zd starting on CPU %d", + t - t->lwan->thread.threads + 1, + t->cpu); } lwan_set_thread_name("worker"); From 6be4831f570fcb8d59464f9c2b1758b5931d3ed7 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 19 May 2024 11:34:12 -0700 Subject: [PATCH 2273/2505] Valgrind instrumentation is only enabled in debug builds --- src/bin/lwan/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/lwan/main.c b/src/bin/lwan/main.c index 46f7550c0..dd86a20ef 100644 --- a/src/bin/lwan/main.c +++ b/src/bin/lwan/main.c @@ -102,7 +102,7 @@ print_build_time_configuration(void) printf(" sockopt-reuseport-incoming-cpu"); #endif -#if defined(LWAN_HAVE_VALGRIND) +#if !defined(NDEBUG) && defined(LWAN_HAVE_VALGRIND) printf(" valgrind"); #endif From 61e16cc82143ed1fa2f834cc8581e9cc38744430 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 19 May 2024 15:39:54 -0700 Subject: [PATCH 2274/2505] Review error handling in I/O wrappers Some of these were subtly broken. Make them hopefully less broken(?) --- src/lib/lwan-io-wrappers.c | 166 ++++++++++++++++++------------------- 1 file changed, 83 insertions(+), 83 deletions(-) diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index 279ce3442..c1dc8fbf5 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -31,8 +31,10 @@ static const int MAX_FAILED_TRIES = 5; -ssize_t -lwan_writev_fd(struct lwan_request *request, int fd, struct iovec *iov, int iov_count) +ssize_t lwan_writev_fd(struct lwan_request *request, + int fd, + struct iovec *iov, + int iov_count) { ssize_t total_written = 0; int curr_iov = 0; @@ -44,7 +46,8 @@ lwan_writev_fd(struct lwan_request *request, int fd, struct iovec *iov, int iov_ if (remaining_len == 1) { const struct iovec *vec = &iov[curr_iov]; - return lwan_send_fd(request, fd, vec->iov_base, vec->iov_len, flags); + return lwan_send_fd(request, fd, vec->iov_base, vec->iov_len, + flags); } struct msghdr hdr = { @@ -60,35 +63,36 @@ lwan_writev_fd(struct lwan_request *request, int fd, struct iovec *iov, int iov_ switch (errno) { case EAGAIN: case EINTR: - goto try_again; + break; default: return -errno; } - } + } else { + total_written += written; - total_written += written; - - while (curr_iov < iov_count && - written >= (ssize_t)iov[curr_iov].iov_len) { - written -= (ssize_t)iov[curr_iov].iov_len; - curr_iov++; - } + while (curr_iov < iov_count && + written >= (ssize_t)iov[curr_iov].iov_len) { + written -= (ssize_t)iov[curr_iov].iov_len; + curr_iov++; + } - if (curr_iov == iov_count) - return total_written; + iov[curr_iov].iov_base = (char *)iov[curr_iov].iov_base + written; + iov[curr_iov].iov_len -= (size_t)written; - iov[curr_iov].iov_base = (char *)iov[curr_iov].iov_base + written; - iov[curr_iov].iov_len -= (size_t)written; + if (curr_iov == iov_count && iov[curr_iov].iov_len == 0) + return total_written; + } - try_again: lwan_request_await_read(request, fd); } return -ETIMEDOUT; } -ssize_t -lwan_readv_fd(struct lwan_request *request, int fd, struct iovec *iov, int iov_count) +ssize_t lwan_readv_fd(struct lwan_request *request, + int fd, + struct iovec *iov, + int iov_count) { ssize_t total_bytes_read = 0; int curr_iov = 0; @@ -102,27 +106,27 @@ lwan_readv_fd(struct lwan_request *request, int fd, struct iovec *iov, int iov_c switch (errno) { case EAGAIN: case EINTR: - goto try_again; + break; default: return -errno; } - } + } else { + total_bytes_read += bytes_read; - total_bytes_read += bytes_read; - - while (curr_iov < iov_count && - bytes_read >= (ssize_t)iov[curr_iov].iov_len) { - bytes_read -= (ssize_t)iov[curr_iov].iov_len; - curr_iov++; - } + while (curr_iov < iov_count && + bytes_read >= (ssize_t)iov[curr_iov].iov_len) { + bytes_read -= (ssize_t)iov[curr_iov].iov_len; + curr_iov++; + } - if (curr_iov == iov_count) - return total_bytes_read; + iov[curr_iov].iov_base = + (char *)iov[curr_iov].iov_base + bytes_read; + iov[curr_iov].iov_len -= (size_t)bytes_read; - iov[curr_iov].iov_base = (char *)iov[curr_iov].iov_base + bytes_read; - iov[curr_iov].iov_len -= (size_t)bytes_read; + if (curr_iov == iov_count && iov[curr_iov].iov_len == 0) + return total_bytes_read; + } - try_again: lwan_request_await_read(request, fd); } @@ -135,32 +139,30 @@ ssize_t lwan_send_fd(struct lwan_request *request, size_t count, int flags) { - ssize_t total_sent = 0; + size_t to_send = count; if (request->conn->flags & CONN_CORK) flags |= MSG_MORE; for (int tries = MAX_FAILED_TRIES; tries;) { - ssize_t written = send(fd, buf, count, flags); + ssize_t written = send(fd, buf, to_send, flags); if (UNLIKELY(written < 0)) { tries--; switch (errno) { case EAGAIN: case EINTR: - goto try_again; + break; default: return -errno; } - } - - total_sent += written; - if ((size_t)total_sent == count) - return total_sent; - if ((size_t)total_sent < count) + } else { + to_send -= (size_t)written; + if (!to_send) + return count; buf = (char *)buf + written; + } - try_again: lwan_request_await_write(request, fd); } @@ -170,32 +172,29 @@ ssize_t lwan_send_fd(struct lwan_request *request, ssize_t lwan_recv_fd(struct lwan_request *request, int fd, void *buf, size_t count, int flags) { - ssize_t total_recv = 0; + size_t to_recv = count; for (int tries = MAX_FAILED_TRIES; tries;) { - ssize_t recvd = recv(fd, buf, count, flags); + ssize_t recvd = recv(fd, buf, to_recv, flags); if (UNLIKELY(recvd < 0)) { tries--; switch (errno) { + case EINTR: case EAGAIN: if (flags & MSG_DONTWAIT) - return total_recv; - /* Fallthrough */ - case EINTR: - goto try_again; + return count - to_recv; + break; default: return -errno; } - } - - total_recv += recvd; - if ((size_t)total_recv == count) - return total_recv; - if ((size_t)total_recv < count) + } else { + to_recv -= (size_t)recvd; + if (!to_recv) + return count; buf = (char *)buf + recvd; + } - try_again: lwan_request_await_read(request, fd); } @@ -220,8 +219,7 @@ int lwan_sendfile_fd(struct lwan_request *request, * sent using MSG_MORE. Subsequent chunks are sized 2^21 bytes. (Do * this regardless of this connection being TLS or not for simplicity.) * - * [1] - * https://www.kernel.org/doc/html/v5.12/networking/tls.html#sending-tls-application-data + * [1] https://www.kernel.org/doc/html/v5.12/networking/tls.html#sending-tls-application-data * [2] https://github.com/lpereira/lwan/issues/334 */ size_t chunk_size = LWAN_MIN(count, (1ul << 21) - header_len); @@ -236,24 +234,23 @@ int lwan_sendfile_fd(struct lwan_request *request, while (true) { ssize_t written = sendfile(out_fd, in_fd, &offset, chunk_size); - if (written < 0) { + if (UNLIKELY(written < 0)) { switch (errno) { case EAGAIN: case EINTR: - goto try_again; + break; default: return -errno; } - } - - to_be_written -= (size_t)written; - if (!to_be_written) - return 0; + } else { + to_be_written -= (size_t)written; + if (!to_be_written) + return 0; - chunk_size = LWAN_MIN(to_be_written, 1ul << 21); - lwan_readahead_queue(in_fd, offset, chunk_size); + chunk_size = LWAN_MIN(to_be_written, 1ul << 21); + lwan_readahead_queue(in_fd, offset, chunk_size); + } - try_again: lwan_request_await_write(request, out_fd); } } @@ -292,39 +289,41 @@ int lwan_sendfile(struct lwan_request *request, case EAGAIN: case EBUSY: case EINTR: - goto try_again; + break; default: return -errno; } + } else { + count -= (size_t)sbytes; + if (!count) + return 0; } - count -= (size_t)sbytes; - if (!count) - return 0; - - try_again: lwan_request_await_write(request, out_fd); } } #else static ssize_t try_pread_file(struct lwan_request *request, - int fd, - void *buffer, - size_t len, - off_t offset) + int fd, + void *buffer, + size_t len, + off_t offset) { size_t total_read = 0; for (int tries = MAX_FAILED_TRIES; tries;) { ssize_t r = pread(fd, buffer, len, offset); + if (r == 0) { + return total_read; + } if (UNLIKELY(r < 0)) { tries--; switch (errno) { case EAGAIN: case EINTR: - /* fd is a file, re-read -- but give other coros some time, too */ + /* fd is a file, re-read -- but give other coros some time */ coro_yield(request->conn->coro, CONN_CORO_YIELD); continue; default: @@ -333,9 +332,9 @@ static ssize_t try_pread_file(struct lwan_request *request, } total_read += (size_t)r; - - if (r == 0 || total_read == len) + if (total_read == len) { return total_read; + } offset += r; } @@ -362,11 +361,12 @@ int lwan_sendfile_fd(struct lwan_request *request, while (count) { r = try_pread_file(request, in_fd, buffer, LWAN_MIN(count, sizeof(buffer)), offset); - if (UNLIKELY(r < 0)) + if (UNLIKELY(r <= 0)) return (int)r; size_t bytes_read = (size_t)r; - r = lwan_send_fd(request, out_fd, buffer, bytes_read, 0); + r = lwan_send_fd(request, out_fd, buffer, bytes_read, + bytes_read < sizeof(buffer) ? 0 : MSG_MORE); if (UNLIKELY(r < 0)) return (int)r; From 07745c394a09468292103340e3511f6140b44030 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 19 May 2024 15:47:39 -0700 Subject: [PATCH 2275/2505] Remove unused constant --- src/lib/hash.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib/hash.c b/src/lib/hash.c index 9c27fc34d..6b765741e 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -75,8 +75,6 @@ struct hash_entry { * curb wasteful allocations */ #define STEPS 4 -#define DEFAULT_ODD_CONSTANT 0x27d4eb2d - static_assert((MIN_BUCKETS & (MIN_BUCKETS - 1)) == 0, "Bucket size is power of 2"); From 5bad7810b7f0cf3f85f8408c1afc66b7581018c2 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 19 May 2024 22:45:44 -0700 Subject: [PATCH 2276/2505] Fix compilation warnings --- src/lib/lwan-io-wrappers.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index c1dc8fbf5..c6cea18f0 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -159,7 +159,7 @@ ssize_t lwan_send_fd(struct lwan_request *request, } else { to_send -= (size_t)written; if (!to_send) - return count; + return (ssize_t)count; buf = (char *)buf + written; } @@ -183,7 +183,7 @@ lwan_recv_fd(struct lwan_request *request, int fd, void *buf, size_t count, int case EINTR: case EAGAIN: if (flags & MSG_DONTWAIT) - return count - to_recv; + return -EAGAIN; break; default: return -errno; @@ -191,7 +191,7 @@ lwan_recv_fd(struct lwan_request *request, int fd, void *buf, size_t count, int } else { to_recv -= (size_t)recvd; if (!to_recv) - return count; + return (ssize_t)count; buf = (char *)buf + recvd; } From 7eab5a7b866285764a94471c1ad9eee7c1862ba2 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 19 May 2024 22:53:36 -0700 Subject: [PATCH 2277/2505] Don't check last iovec length in writev()/readv() wrappers (This is still subtly broken, but without this, things go haywire.) --- src/lib/lwan-io-wrappers.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index c6cea18f0..1ee0382eb 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -79,7 +79,7 @@ ssize_t lwan_writev_fd(struct lwan_request *request, iov[curr_iov].iov_base = (char *)iov[curr_iov].iov_base + written; iov[curr_iov].iov_len -= (size_t)written; - if (curr_iov == iov_count && iov[curr_iov].iov_len == 0) + if (curr_iov == iov_count) return total_written; } @@ -123,7 +123,7 @@ ssize_t lwan_readv_fd(struct lwan_request *request, (char *)iov[curr_iov].iov_base + bytes_read; iov[curr_iov].iov_len -= (size_t)bytes_read; - if (curr_iov == iov_count && iov[curr_iov].iov_len == 0) + if (curr_iov == iov_count) return total_bytes_read; } From 0347646b78a15a5b2d175fdb2ca5e49c11c5651b Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 19 May 2024 23:39:17 -0700 Subject: [PATCH 2278/2505] Put some guards to avoid issues when handling of short reads/writes --- src/lib/lwan-io-wrappers.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index 1ee0382eb..8a6fcb0d5 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -67,6 +67,8 @@ ssize_t lwan_writev_fd(struct lwan_request *request, default: return -errno; } + } else if (written == 0) { + return total_written; } else { total_written += written; @@ -110,6 +112,8 @@ ssize_t lwan_readv_fd(struct lwan_request *request, default: return -errno; } + } else if (bytes_read == 0) { + return total_bytes_read; } else { total_bytes_read += bytes_read; From 5520530af91c81d3eb209e5fe13650242076fa1c Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 19 May 2024 23:40:02 -0700 Subject: [PATCH 2279/2505] Use recvmsg() instead of readv() --- src/lib/lwan-io-wrappers.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index 8a6fcb0d5..86db36e2b 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -100,7 +100,18 @@ ssize_t lwan_readv_fd(struct lwan_request *request, int curr_iov = 0; for (int tries = MAX_FAILED_TRIES; tries;) { - ssize_t bytes_read = readv(fd, iov + curr_iov, iov_count - curr_iov); + const int remaining_len = (int)(iov_count - curr_iov); + + if (remaining_len == 1) { + const struct iovec *vec = &iov[curr_iov]; + return lwan_recv_fd(request, fd, vec->iov_base, vec->iov_len, 0); + } + + struct msghdr hdr = { + .msg_iov = iov + curr_iov, + .msg_iovlen = (size_t)remaining_len, + }; + ssize_t bytes_read = recvmsg(fd, &hdr, 0); if (UNLIKELY(bytes_read < 0)) { /* FIXME: Consider short reads as another try as well? */ tries--; From 25e6eacc7a5fcf356fbb8bb0861b0509b5280063 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 20 May 2024 23:14:36 -0700 Subject: [PATCH 2280/2505] Get rid of the hash table in the kqueue epoll implementation This will get rid of a malloc()/free() every time epoll_wait() is called on a BSD system, but still coalesce the filters/and flags as before. Untested/uncompiled. --- src/lib/missing-epoll.c | 69 +++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 37 deletions(-) diff --git a/src/lib/missing-epoll.c b/src/lib/missing-epoll.c index c9e33a1c5..e0b0f64b4 100644 --- a/src/lib/missing-epoll.c +++ b/src/lib/missing-epoll.c @@ -34,8 +34,6 @@ #include #include -#include "hash.h" - int epoll_create1(int flags) { #if defined(LWAN_HAVE_KQUEUE1) @@ -115,63 +113,60 @@ static struct timespec *to_timespec(struct timespec *t, int ms) return t; } +static int kevent_ident_cmp(const void *ptr0, const void *ptr1) +{ + struct kevent *ev0 = ptr0; + struct kevent *ev1 = ptr1; + return (ev0->ident > ev1->ident) - (ev0->ident < ev1->ident); +} + int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) { struct epoll_event *ev = events; struct kevent evs[maxevents]; struct timespec tmspec; - struct hash *coalesce; int i, r; - coalesce = hash_int_new(NULL, NULL); - if (UNLIKELY(!coalesce)) - return -1; - r = kevent(epfd, NULL, 0, evs, maxevents, to_timespec(&tmspec, timeout)); if (UNLIKELY(r < 0)) { - hash_unref(coalesce); return -1; } - for (i = 0; i < r; i++) { - struct kevent *kev = &evs[i]; - uint32_t mask = (uint32_t)(uintptr_t)hash_find( - coalesce, (void *)(intptr_t)evs[i].ident); - - if (kev->flags & EV_ERROR) - mask |= EPOLLERR; - if (kev->flags & EV_EOF) - mask |= EPOLLRDHUP; - - if (kev->filter == EVFILT_READ) - mask |= EPOLLIN; - else if (kev->filter == EVFILT_WRITE && evs[i].udata != &epoll_no_event_marker) - mask |= EPOLLOUT; - - hash_add(coalesce, (void *)(intptr_t)evs[i].ident, - (void *)(uintptr_t)mask); - } + qsort(evs, (size_t)r, sizeof(struct kevent), kevent_ident_cmp); + int last = -1; for (i = 0; i < r; i++) { - void *maskptr; - - maskptr = hash_find(coalesce, (void *)(intptr_t)evs[i].ident); - if (maskptr) { - struct kevent *kev = &evs[i]; + struct kevent *kev = &evs[i]; - if (kev->udata == &epoll_no_event_marker) - continue; + if (kev->udata == &epoll_no_event_marker) { + continue; + } - ev->data.ptr = kev->udata; - ev->events = (uint32_t)(uintptr_t)maskptr; + if (last < 0) { + ev->mask = 0; + } else if (kev->ident != last) { ev++; + ev->mask = 0; + } + + if (kev->flags & EV_ERROR) { + ev->events |= EPOLLERR; + } + if (kev->flags & EV_EOF) { + ev->events |= EPOLLRDHUP; + } + if (kev->filter == EVFILT_READ) { + ev->events |= EPOLLIN; + } else if (kev->filter == EVFILT_WRITE) { + ev->events |= EPOLLOUT; } + ev->data.ptr = kev->udata; + + last = kev->ident; } - hash_unref(coalesce); return (int)(intptr_t)(ev - events); } #elif !defined(LWAN_HAVE_EPOLL) #error epoll() not implemented for this platform #endif - From 0fd4c7e28435fc10fe01610d7ea7e1b98741b5a1 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 21 May 2024 19:46:58 -0700 Subject: [PATCH 2281/2505] CONN_HUNG_UP should be checked in the awaited conn! --- src/lib/lwan-thread.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 9dec2c9fb..23ef932d6 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -863,7 +863,7 @@ static inline int async_await_fd(struct lwan_request *request, int64_t from_coro = coro_yield(request->conn->coro, CONN_CORO_SUSPEND); if ((struct lwan_connection *)(intptr_t)from_coro == awaited) { - return UNLIKELY(request->conn->flags & CONN_HUNG_UP) + return UNLIKELY(awaited->flags & CONN_HUNG_UP) ? -ECONNRESET : lwan_connection_get_fd(lwan, awaited); } From a04eaa2f256143a6265b5bb092026c9eafdd6b73 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 21 May 2024 19:47:30 -0700 Subject: [PATCH 2282/2505] Cleanup async_await_fd() a bit more (And make it so that the coro_yield() will also check if the connection that caused the coroutine to be resumed was in fact the one we were interested in.) --- src/lib/lwan-thread.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 23ef932d6..014069dd8 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -850,17 +850,17 @@ static inline int async_await_fd(struct lwan_request *request, struct lwan *lwan = thread->lwan; struct lwan_connection *awaited = &lwan->conns[fd]; - if (request->conn == awaited) { - coro_yield(request->conn->coro, events); - return fd; - } + if (request->conn != awaited) { + int r = + prepare_await(lwan, events, fd, request->conn, thread->epoll_fd); + if (UNLIKELY(r < 0)) + return r; - int r = prepare_await(lwan, events, fd, request->conn, thread->epoll_fd); - if (UNLIKELY(r < 0)) - return r; + events = CONN_CORO_SUSPEND; + } while (true) { - int64_t from_coro = coro_yield(request->conn->coro, CONN_CORO_SUSPEND); + int64_t from_coro = coro_yield(request->conn->coro, events); if ((struct lwan_connection *)(intptr_t)from_coro == awaited) { return UNLIKELY(awaited->flags & CONN_HUNG_UP) From c04da079f8bb4dbabe4e3b88cb6371ca93d77201 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 21 May 2024 19:48:26 -0700 Subject: [PATCH 2283/2505] Fixes to the new epoll-in-kqueue implementation The code handling artificial events (udata = &epoll_no_event_marker, and filter = EVFILT_WRITE) was broken after the removal of the hash table. Still untested. --- src/lib/missing-epoll.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/lib/missing-epoll.c b/src/lib/missing-epoll.c index e0b0f64b4..ac2a5d091 100644 --- a/src/lib/missing-epoll.c +++ b/src/lib/missing-epoll.c @@ -74,6 +74,9 @@ int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) } else if (event->events & EPOLLOUT) { events = EVFILT_WRITE; } else { + /* kqueue needs an event filter to track a file descriptor, + * but epoll doesn't. So create a fake one here and check for + * it when converting from kevents to epoll_events. */ events = EVFILT_WRITE; udata = &epoll_no_event_marker; } @@ -138,15 +141,12 @@ int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) for (i = 0; i < r; i++) { struct kevent *kev = &evs[i]; - if (kev->udata == &epoll_no_event_marker) { - continue; - } + if (kev->ident != last) { + if (last >= 0) + ev++; - if (last < 0) { - ev->mask = 0; - } else if (kev->ident != last) { - ev++; ev->mask = 0; + ev->data.ptr = kev->udata; } if (kev->flags & EV_ERROR) { @@ -157,10 +157,10 @@ int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) } if (kev->filter == EVFILT_READ) { ev->events |= EPOLLIN; - } else if (kev->filter == EVFILT_WRITE) { + } else if (kev->filter == EVFILT_WRITE && + kev->udata != &epoll_no_event_marker) { ev->events |= EPOLLOUT; } - ev->data.ptr = kev->udata; last = kev->ident; } From d8ed81e961799fb7b8c303afa1db428307c83d50 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 21 May 2024 19:49:47 -0700 Subject: [PATCH 2284/2505] Copy va_list when awaiting multiple fds only if needed --- src/lib/lwan-thread.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 014069dd8..044adb4fd 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -705,16 +705,15 @@ static int prepare_await(const struct lwan *l, return -errno; } -static void clear_awaitv_flags(struct lwan_connection *conns, va_list ap_orig) -{ - va_list ap; +#define FOR_EACH_FD(name_) \ + for (int name_ = va_arg(ap, int); name_ >= 0; name_ = va_arg(ap, int)) - va_copy(ap, ap_orig); - for (int fd = va_arg(ap, int); fd >= 0; fd = va_arg(ap, int)) { +static void clear_awaitv_flags(struct lwan_connection *conns, va_list ap) +{ + FOR_EACH_FD(fd) { conns[fd].flags &= ~CONN_ASYNC_AWAITV; LWAN_NO_DISCARD(va_arg(ap, enum lwan_connection_coro_yield)); } - va_end(ap); } struct awaitv_state { @@ -734,10 +733,14 @@ static int prepare_awaitv(struct lwan_request *r, .request_conn_yield = CONN_CORO_YIELD, }; - clear_awaitv_flags(l->conns, ap); + { + va_list copy_to_clear; + va_copy(ap, copy_to_clear); + clear_awaitv_flags(l->conns, copy_to_clear); + va_end(copy_to_clear); + } - for (int await_fd = va_arg(ap, int); await_fd >= 0; - await_fd = va_arg(ap, int)) { + FOR_EACH_FD(await_fd) { struct lwan_connection *conn = &l->conns[await_fd]; enum lwan_connection_coro_yield events = va_arg(ap, enum lwan_connection_coro_yield); @@ -771,6 +774,8 @@ static int prepare_awaitv(struct lwan_request *r, return 0; } +#undef FOR_EACH_FD + int lwan_request_awaitv_any(struct lwan_request *r, ...) { struct lwan *l = r->conn->thread->lwan; From 38ebd48dbfa792665cb55f4b40a2ccd90c739e71 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 21 May 2024 19:52:07 -0700 Subject: [PATCH 2285/2505] Ensure fallback sendfile() implementation yields every now and then --- src/lib/lwan-io-wrappers.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index 86db36e2b..5eb09b539 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -329,9 +329,6 @@ static ssize_t try_pread_file(struct lwan_request *request, for (int tries = MAX_FAILED_TRIES; tries;) { ssize_t r = pread(fd, buffer, len, offset); - if (r == 0) { - return total_read; - } if (UNLIKELY(r < 0)) { tries--; @@ -366,6 +363,7 @@ int lwan_sendfile_fd(struct lwan_request *request, size_t header_len) { unsigned char buffer[512]; + unsigned int blocks_sent = 0; ssize_t r; r = lwan_send_fd(request, out_fd, header, header_len, MSG_MORE); @@ -387,6 +385,11 @@ int lwan_sendfile_fd(struct lwan_request *request, count -= bytes_read; offset += bytes_read; + + blocks_sent++; + if (blocks_sent & 3 == 0) { + coro_yield(request->conn->coro, CONN_CORO_WRITE); + } } return 0; From 6cb080d583eeaa2cd0080611cd7d8de44996e50f Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 22 May 2024 08:32:22 -0700 Subject: [PATCH 2286/2505] More fixes to the epoll-on-kqueue implementation I really need to install a BSD on a VM again to test these things. --- src/lib/missing-epoll.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/missing-epoll.c b/src/lib/missing-epoll.c index ac2a5d091..85835c714 100644 --- a/src/lib/missing-epoll.c +++ b/src/lib/missing-epoll.c @@ -118,8 +118,8 @@ static struct timespec *to_timespec(struct timespec *t, int ms) static int kevent_ident_cmp(const void *ptr0, const void *ptr1) { - struct kevent *ev0 = ptr0; - struct kevent *ev1 = ptr1; + const struct kevent *ev0 = ptr0; + const struct kevent *ev1 = ptr1; return (ev0->ident > ev1->ident) - (ev0->ident < ev1->ident); } @@ -145,7 +145,7 @@ int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) if (last >= 0) ev++; - ev->mask = 0; + ev->events = 0; ev->data.ptr = kev->udata; } From 04e7d8763d9497c60c3a4be612e6c51e4ec9dac2 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 22 May 2024 18:44:50 -0700 Subject: [PATCH 2287/2505] Streamline calculation of buffer space for the terminating NUL Also, ensure that none of those calculations can overflow. --- src/lib/lwan-strbuf.c | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/src/lib/lwan-strbuf.c b/src/lib/lwan-strbuf.c index 3678ff61a..5d4f8a111 100644 --- a/src/lib/lwan-strbuf.c +++ b/src/lib/lwan-strbuf.c @@ -57,8 +57,12 @@ bool grow_buffer_if_needed_internal(struct lwan_strbuf *s, size_t size) if (s->flags & BUFFER_FIXED) return size < s->capacity; + /* Ensure we always have space for the NUL character! */ + if (UNLIKELY(__builtin_add_overflow(size, 1, &size))) + return false; + if (!(s->flags & BUFFER_MALLOCD)) { - const size_t aligned_size = align_size(LWAN_MAX(size + 1, s->used)); + const size_t aligned_size = align_size(LWAN_MAX(size, s->used)); if (UNLIKELY(!aligned_size)) return false; @@ -78,7 +82,7 @@ bool grow_buffer_if_needed_internal(struct lwan_strbuf *s, size_t size) if (UNLIKELY(s->capacity < size)) { char *buffer; - const size_t aligned_size = align_size(size + 1); + const size_t aligned_size = align_size(size); if (UNLIKELY(!aligned_size)) return false; @@ -173,11 +177,15 @@ struct lwan_strbuf *lwan_strbuf_new_with_size(size_t size) struct lwan_strbuf *lwan_strbuf_new_with_fixed_buffer(size_t size) { - struct lwan_strbuf *s = malloc(sizeof(*s) + size + 1); + struct lwan_strbuf *s; + size_t alloc_size; + + if (UNLIKELY(__builtin_add_overflow(sizeof(*s) + 1, size, &alloc_size))) + return NULL; + s = malloc(alloc_size); if (UNLIKELY(!lwan_strbuf_init_with_fixed_buffer(s, s + 1, size))) { free(s); - return NULL; } @@ -223,7 +231,10 @@ void lwan_strbuf_free(struct lwan_strbuf *s) bool lwan_strbuf_append_char(struct lwan_strbuf *s, const char c) { - if (UNLIKELY(!grow_buffer_if_needed(s, s->used + 2))) + size_t grow_size; + if (UNLIKELY(__builtin_add_overflow(s->used, 1, &grow_size))) + return false; + if (UNLIKELY(!grow_buffer_if_needed(s, grow_size))) return false; s->buffer[s->used++] = c; @@ -234,7 +245,10 @@ bool lwan_strbuf_append_char(struct lwan_strbuf *s, const char c) bool lwan_strbuf_append_str(struct lwan_strbuf *s1, const char *s2, size_t sz) { - if (UNLIKELY(!grow_buffer_if_needed(s1, s1->used + sz + 2))) + size_t grow_size; + if (UNLIKELY(__builtin_add_overflow(s1->used, sz, &grow_size))) + return false; + if (UNLIKELY(!grow_buffer_if_needed(s1, grow_size))) return false; memcpy(s1->buffer + s1->used, s2, sz); @@ -258,7 +272,7 @@ bool lwan_strbuf_set_static(struct lwan_strbuf *s1, const char *s2, size_t sz) bool lwan_strbuf_set(struct lwan_strbuf *s1, const char *s2, size_t sz) { - if (UNLIKELY(!grow_buffer_if_needed(s1, sz + 1))) + if (UNLIKELY(!grow_buffer_if_needed(s1, sz))) return false; memcpy(s1->buffer, s2, sz); @@ -322,14 +336,14 @@ bool lwan_strbuf_append_printf(struct lwan_strbuf *s, const char *fmt, ...) bool lwan_strbuf_grow_to(struct lwan_strbuf *s, size_t new_size) { - return grow_buffer_if_needed(s, new_size + 1); + return grow_buffer_if_needed(s, new_size); } bool lwan_strbuf_grow_by(struct lwan_strbuf *s, size_t offset) { size_t new_size; - if (__builtin_add_overflow(offset, s->used, &new_size)) + if (UNLIKELY(__builtin_add_overflow(offset, s->used, &new_size))) return false; return lwan_strbuf_grow_to(s, new_size); @@ -380,10 +394,7 @@ bool lwan_strbuf_init_from_file(struct lwan_strbuf *s, const char *path) if (UNLIKELY(fstat(fd, &st) < 0)) goto error_close; - size_t min_buf_size; - if (UNLIKELY(__builtin_add_overflow(st.st_size, 1, &min_buf_size))) - goto error_close; - if (UNLIKELY(!lwan_strbuf_init_with_size(s, min_buf_size))) + if (UNLIKELY(!lwan_strbuf_init_with_size(s, (size_t)st.st_size))) goto error_close; s->used = (size_t)st.st_size; From 12a77f5d958b5fb7b9d5c555981f9122b3670486 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 22 May 2024 18:49:08 -0700 Subject: [PATCH 2288/2505] Simplify lwan_determine_mime_type_for_file_name() even further --- src/bin/tools/mimegen.c | 1 + src/lib/lwan-tables.c | 20 ++++++++------------ 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/bin/tools/mimegen.c b/src/bin/tools/mimegen.c index 68291d049..264eb6847 100644 --- a/src/bin/tools/mimegen.c +++ b/src/bin/tools/mimegen.c @@ -356,6 +356,7 @@ int main(int argc, char *argv[]) printf("#define MIME_ENTRIES %d\n", hash_get_count(ext_mime)); printf("#define MIME_ENTRIES_FLOOR %d\n", entries_floor); printf("#define MIME_ENTRY_FALLBACK %ld\n", bin_index); + printf("#define MIME_EXT_FALLBACK \".%s\"\n", exts[bin_index]); printf("static const unsigned char mime_entries_compressed[] = {\n"); for (i = 1; compressed_size; compressed_size--, i++) printf("0x%02x,%c", compressed[i - 1] & 0xff, " \n"[i % 13 == 0]); diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index bcbc2d159..9e675b6f5 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -138,23 +138,19 @@ static ALWAYS_INLINE const char *bsearch_mime_type(uint64_t ext) const char *lwan_determine_mime_type_for_file_name(const char *file_name) { - char *last_dot = strrchr(file_name, '.'); - if (LIKELY(last_dot && *last_dot)) { - uint64_t key = 0; + const char *last_dot = strrchr(file_name, '.') ?: MIME_EXT_FALLBACK; + uint64_t key = 0; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wstringop-truncation" - /* Data is stored with NULs on strings up to 7 chars, and no NULs - * for 8-char strings, because that's implicit. So truncation is - * intentional here: comparison in compare_mime_entry() always loads - * 8 bytes per extension. */ - strncpy((char *)&key, last_dot + 1, 8); + /* Data is stored with NULs on strings up to 7 chars, and + * no NULs for 8-char strings, because that's implicit. + * So truncation is intentional here: comparisons in + * bsearch_mime_type() always loads keys as uint64_ts. */ + strncpy((char *)&key, last_dot + 1, 8); #pragma GCC diagnostic pop - return bsearch_mime_type(htobe64(key & ~0x2020202020202020ull)); - } - - return mime_types[MIME_ENTRY_FALLBACK]; + return bsearch_mime_type(htobe64(key & ~0x2020202020202020ull)); } #include "lookup-http-status.h" /* genrated by statuslookupgen */ From 4f96154acf12385fc912783f9084d7fb52958c82 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 22 May 2024 19:01:06 -0700 Subject: [PATCH 2289/2505] Add more self-tests for extension lookups (Check for one of the first and one of the last extensions.) --- src/lib/lwan-tables.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/lwan-tables.c b/src/lib/lwan-tables.c index 9e675b6f5..503735ec2 100644 --- a/src/lib/lwan-tables.c +++ b/src/lib/lwan-tables.c @@ -106,6 +106,10 @@ void lwan_tables_init(void) "text/javascript")); assert(streq(lwan_determine_mime_type_for_file_name(".BZ2"), "application/x-bzip2")); + assert(streq(lwan_determine_mime_type_for_file_name(".z1"), + "application/x-zmachine")); + assert(streq(lwan_determine_mime_type_for_file_name(".asm"), + "text/x-asm")); } LWAN_SELF_TEST(status_codes) From b3730cc2e62eed19c77466759d3a3cf4b98ad495 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 22 May 2024 21:31:31 -0700 Subject: [PATCH 2290/2505] Simplify handling of websocket PING/PONG frames --- src/lib/lwan-websocket.c | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index c3e26de88..cdfe09dd5 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -277,8 +277,7 @@ static void ping_pong(struct lwan_request *request, uint16_t header, enum ws_opcode opcode) { const size_t len = header & 0x7f; - char msg[128]; - char mask[4]; + char msg[4 + 128]; assert(header & WS_MASKED); assert(opcode == WS_OPCODE_PING || opcode == WS_OPCODE_PONG); @@ -291,28 +290,16 @@ ping_pong(struct lwan_request *request, uint16_t header, enum ws_opcode opcode) __builtin_unreachable(); } - struct iovec vec[] = { - {.iov_base = mask, .iov_len = sizeof(mask)}, - {.iov_base = msg, .iov_len = len}, - }; - if (opcode == WS_OPCODE_PING) { - lwan_readv(request, vec, N_ELEMENTS(vec)); - unmask(msg, len, mask); + lwan_recv(request, msg, len + 4, 0); + unmask(msg + 4, len, msg); write_websocket_frame(request, WS_MASKED | WS_OPCODE_PONG, msg, len); } else { /* From MDN: "You might also get a pong without ever sending a ping; * ignore this if it happens." */ /* FIXME: should we care about the contents of PONG packets? */ - /* FIXME: should we have a lwan_recvmsg() too that takes an iovec? */ - const size_t total_len = vec[0].iov_len + vec[1].iov_len; - if (LIKELY(total_len < sizeof(msg))) { - lwan_recv(request, msg, total_len, MSG_TRUNC); - } else { - lwan_recv(request, vec[0].iov_base, vec[0].iov_len, MSG_TRUNC); - lwan_recv(request, vec[1].iov_base, vec[1].iov_len, MSG_TRUNC); - } + lwan_recv(request, msg, len + 4, MSG_TRUNC); } } From 15e0a49eb49a95a7601bbc10897aeba82dafbe3b Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 22 May 2024 21:33:58 -0700 Subject: [PATCH 2291/2505] Revert "Copy va_list when awaiting multiple fds only if needed" This change was causing a segmentation fault. I'll investigate some other day; for now, revert this. This reverts commit d8ed81e961799fb7b8c303afa1db428307c83d50. --- src/lib/lwan-thread.c | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 044adb4fd..014069dd8 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -705,15 +705,16 @@ static int prepare_await(const struct lwan *l, return -errno; } -#define FOR_EACH_FD(name_) \ - for (int name_ = va_arg(ap, int); name_ >= 0; name_ = va_arg(ap, int)) - -static void clear_awaitv_flags(struct lwan_connection *conns, va_list ap) +static void clear_awaitv_flags(struct lwan_connection *conns, va_list ap_orig) { - FOR_EACH_FD(fd) { + va_list ap; + + va_copy(ap, ap_orig); + for (int fd = va_arg(ap, int); fd >= 0; fd = va_arg(ap, int)) { conns[fd].flags &= ~CONN_ASYNC_AWAITV; LWAN_NO_DISCARD(va_arg(ap, enum lwan_connection_coro_yield)); } + va_end(ap); } struct awaitv_state { @@ -733,14 +734,10 @@ static int prepare_awaitv(struct lwan_request *r, .request_conn_yield = CONN_CORO_YIELD, }; - { - va_list copy_to_clear; - va_copy(ap, copy_to_clear); - clear_awaitv_flags(l->conns, copy_to_clear); - va_end(copy_to_clear); - } + clear_awaitv_flags(l->conns, ap); - FOR_EACH_FD(await_fd) { + for (int await_fd = va_arg(ap, int); await_fd >= 0; + await_fd = va_arg(ap, int)) { struct lwan_connection *conn = &l->conns[await_fd]; enum lwan_connection_coro_yield events = va_arg(ap, enum lwan_connection_coro_yield); @@ -774,8 +771,6 @@ static int prepare_awaitv(struct lwan_request *r, return 0; } -#undef FOR_EACH_FD - int lwan_request_awaitv_any(struct lwan_request *r, ...) { struct lwan *l = r->conn->thread->lwan; From b379e1351156e7acd3d7d15376f9ea3e3d03b5d9 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 22 May 2024 21:42:04 -0700 Subject: [PATCH 2292/2505] Fix handling of MSG_DONTWAIT after unifying I/O stuff --- src/lib/lwan-io-wrappers.h | 15 +++++++-------- src/lib/lwan-websocket.c | 8 +++++--- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/lib/lwan-io-wrappers.h b/src/lib/lwan-io-wrappers.h index 8130a50b8..94e44df28 100644 --- a/src/lib/lwan-io-wrappers.h +++ b/src/lib/lwan-io-wrappers.h @@ -14,13 +14,15 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #pragma once -#include +#include #include +#include #include "lwan.h" @@ -40,11 +42,8 @@ int lwan_sendfile_fd(struct lwan_request *request, size_t count, const char *header, size_t header_len); -ssize_t lwan_recv_fd(struct lwan_request *request, - int fd, - void *buf, - size_t count, - int flags); +ssize_t lwan_recv_fd( + struct lwan_request *request, int fd, void *buf, size_t count, int flags); ssize_t lwan_readv_fd(struct lwan_request *request, int fd, struct iovec *iov, @@ -94,7 +93,7 @@ static inline ssize_t lwan_recv(struct lwan_request *request, void *buf, size_t count, int flags) { ssize_t r = lwan_recv_fd(request, request->fd, buf, count, flags); - if (UNLIKELY(r < 0)) { + if (UNLIKELY(r < 0 && !(flags & MSG_NOSIGNAL))) { coro_yield(request->conn->coro, CONN_CORO_ABORT); __builtin_unreachable(); } diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index cdfe09dd5..e6ea64b98 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -343,9 +343,11 @@ int lwan_response_websocket_read_hint(struct lwan_request *request, next_frame: last_opcode = opcode; - if (!lwan_recv(request, &header, sizeof(header), - continuation ? 0 : MSG_DONTWAIT)) - return EAGAIN; + ssize_t r = lwan_recv(request, &header, sizeof(header), + MSG_DONTWAIT | MSG_NOSIGNAL); + if (r < 0) { + return (int)-r; + } header = htons(header); continuation = false; From 842d0011292325b264c07449475f0de0cb312688 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 23 May 2024 08:43:32 -0700 Subject: [PATCH 2293/2505] More fixes to I/O unification stuff --- src/lib/lwan-io-wrappers.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index 5eb09b539..6084f0a3d 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -78,11 +78,11 @@ ssize_t lwan_writev_fd(struct lwan_request *request, curr_iov++; } - iov[curr_iov].iov_base = (char *)iov[curr_iov].iov_base + written; - iov[curr_iov].iov_len -= (size_t)written; - if (curr_iov == iov_count) return total_written; + + iov[curr_iov].iov_base = (char *)iov[curr_iov].iov_base + written; + iov[curr_iov].iov_len -= (size_t)written; } lwan_request_await_read(request, fd); @@ -134,12 +134,12 @@ ssize_t lwan_readv_fd(struct lwan_request *request, curr_iov++; } + if (curr_iov == iov_count) + return total_bytes_read; + iov[curr_iov].iov_base = (char *)iov[curr_iov].iov_base + bytes_read; iov[curr_iov].iov_len -= (size_t)bytes_read; - - if (curr_iov == iov_count) - return total_bytes_read; } lwan_request_await_read(request, fd); From a51cd04eafdf1384e68fbc94908942298bfd3b32 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 24 May 2024 18:17:01 -0700 Subject: [PATCH 2294/2505] Fix build errors in HTTP/2 Huffman decoder fuzzer --- src/lib/lwan-h2-huffman.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-h2-huffman.c b/src/lib/lwan-h2-huffman.c index dd8b358f4..1da6d65d8 100644 --- a/src/lib/lwan-h2-huffman.c +++ b/src/lib/lwan-h2-huffman.c @@ -367,7 +367,7 @@ ssize_t lwan_h2_huffman_next(struct lwan_h2_huffman_decoder *huff) } if (!consume(reader, 8)) - goto fail; + return -1; const struct h2_huffman_code *level3 = next_level2(peeked_byte); if (LIKELY(level3)) { @@ -390,8 +390,9 @@ ssize_t lwan_h2_huffman_next(struct lwan_h2_huffman_decoder *huff) /* FIXME: ensure we're not promoting types unnecessarily here */ if (reader->total_bitcount) { const uint8_t peeked_byte = peek_byte(reader); - const uint8_t eos_prefix = ((1 << reader->total_bitcount) - 1) - << (8 - reader->total_bitcount); + const uint8_t eos_prefix = + (uint8_t)(((1u << reader->total_bitcount) - 1u) + << (8u - reader->total_bitcount)); if ((peeked_byte & eos_prefix) == eos_prefix) goto done; From fd90f95c905db5683a58e2efbb0e5c001c1092d1 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 24 May 2024 18:22:03 -0700 Subject: [PATCH 2295/2505] Fix some build warnings with Clang --- CMakeLists.txt | 1 + src/lib/lwan-pubsub.c | 3 +-- src/lib/lwan-request.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 52af98d8e..3cc74c2d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -315,6 +315,7 @@ enable_warning_if_supported(-Wformat-overflow) enable_warning_if_supported(-Wlogical-not-parentheses) enable_warning_if_supported(-Wlogical-op) enable_warning_if_supported(-Wno-override-init) +enable_warning_if_supported(-Wno-unknown-warning-option) enable_warning_if_supported(-Wno-unused-parameter) enable_warning_if_supported(-Wrestrict) enable_warning_if_supported(-Wstringop-overflow) diff --git a/src/lib/lwan-pubsub.c b/src/lib/lwan-pubsub.c index 6bdcad399..6761cc0c4 100644 --- a/src/lib/lwan-pubsub.c +++ b/src/lib/lwan-pubsub.c @@ -23,11 +23,10 @@ #include #include #include +#include #if defined(LWAN_HAVE_EVENTFD) #include -#else -#include #endif #include "list.h" diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 182da7d0b..6438f67c3 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -930,7 +930,7 @@ static void save_to_corpus_for_fuzzing(struct lwan_value buffer) } buffer_copy.value += r; - buffer_copy.len -= r; + buffer_copy.len -= (size_t)r; } close(fd); From 488445e78864c12e5eb2e35606b62b296b2c68bf Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 24 May 2024 18:22:22 -0700 Subject: [PATCH 2296/2505] helper->header_start shouldn't have its storage in parser helper --- src/lib/lwan-private.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 724d1c5e8..2f1ce966c 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -46,8 +46,8 @@ struct lwan_request_parser_helper { struct lwan_key_value_array cookies, query_params, post_params; - char *header_start[N_HEADER_START]; /* Headers: n: start, n+1: end */ - size_t n_header_start; /* len(header_start) */ + char **header_start; /* Headers: n: start, n+1: end */ + size_t n_header_start; /* len(header_start) */ struct { /* If-Modified-Since: */ struct lwan_value raw; From ae46e88d6c174ac7d119630076161b64e4c864d7 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 24 May 2024 18:32:59 -0700 Subject: [PATCH 2297/2505] Reuse storage for header_start --- src/lib/lwan-thread.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 014069dd8..bdd94af8c 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -451,6 +451,7 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, /* NOTE: This function should not return; coro_yield should be used * instead. This ensures the storage for `strbuf` is alive when the * coroutine ends and lwan_strbuf_free() is called. */ + char *header_start[N_HEADER_START]; struct lwan_connection *conn = data; struct lwan *lwan = conn->thread->lwan; int fd = lwan_connection_get_fd(lwan, conn); @@ -511,6 +512,7 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, .buffer = &buffer, .next_request = next_request, .error_when_n_packets = error_when_n_packets, + .header_start = header_start, }; struct lwan_request request = {.conn = conn, .global_response_headers = &lwan->headers, From 2d231f42ec3bc48f695b558c888d7323c02350d6 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 24 May 2024 18:58:47 -0700 Subject: [PATCH 2298/2505] Fixes to find_pct_or_plus() --- src/lib/lwan-request.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 6438f67c3..0f703af16 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -293,14 +293,11 @@ static char *find_pct_or_plus(const char *input) uint64_t m = LWAN_MAX(has_plus, has_pct); if (has_zero && LWAN_MAX(has_zero, m) == has_zero) { - switch (__builtin_ctzll(has_zero) / 8) { - case 1 ... 3: + const int zero_pos = __builtin_ctzll(has_zero) / 8; + if (zero_pos >= 1 && zero_pos <= 3) goto check_small; - case 4 ... 7: + if (zero_pos >= 4 && zero_pos <= 7) goto check_at_least_four; - default: - return NULL; - } } if (m) { From 1eeecb744b2259da738fca3c7ee99d0d5b6e27a4 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 24 May 2024 19:06:30 -0700 Subject: [PATCH 2299/2505] Fix off-by-one when looking for terminating NUL in find_pct_or_plus() --- src/lib/lwan-request.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 0f703af16..7824e446b 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -293,11 +293,9 @@ static char *find_pct_or_plus(const char *input) uint64_t m = LWAN_MAX(has_plus, has_pct); if (has_zero && LWAN_MAX(has_zero, m) == has_zero) { - const int zero_pos = __builtin_ctzll(has_zero) / 8; - if (zero_pos >= 1 && zero_pos <= 3) + if (__builtin_ctzll(has_zero) / 8 < 4) goto check_small; - if (zero_pos >= 4 && zero_pos <= 7) - goto check_at_least_four; + goto check_at_least_four; } if (m) { From e1442a5a9f464402ec1b7c9d00671e78aaae53a3 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 25 May 2024 09:23:44 -0700 Subject: [PATCH 2300/2505] Fix reading of POST bodies --- src/lib/lwan-request.c | 6 ++++-- src/lib/lwan.c | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 7824e446b..7c367a969 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1288,8 +1288,10 @@ get_remaining_body_data_length(struct lwan_request *request, return HTTP_BAD_REQUEST; if (UNLIKELY((size_t)parsed_size >= max_size)) return HTTP_TOO_LARGE; - if (UNLIKELY(!parsed_size)) + if (UNLIKELY(!parsed_size)) { + *total = *have = 0; return HTTP_OK; + } *total = (size_t)parsed_size; @@ -1336,7 +1338,7 @@ static int read_body_data(struct lwan_request *request) status = get_remaining_body_data_length(request, max_data_size, &total, &have); - if (status != HTTP_PARTIAL_CONTENT) + if (status != HTTP_PARTIAL_CONTENT && status != HTTP_OK) return -(int)status; new_buffer = diff --git a/src/lib/lwan.c b/src/lib/lwan.c index ec6c0cf5f..bcbc82192 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -462,7 +462,8 @@ void lwan_detect_url_map(struct lwan *l) iter->name, iter->route); const struct lwan_url_map map = {.prefix = iter->route, - .handler = iter->handler}; + .handler = iter->handler, + .flags = HANDLER_PARSE_MASK}; register_url_map(l, &map); } } From 575d56b9355f065dec2c37496f5f7f8b65297cbb Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 26 May 2024 08:22:01 -0700 Subject: [PATCH 2301/2505] Fix assertion while filling H2 decoded ring buffer Thanks to OSS-Fuzz: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=69232 --- src/lib/lwan-h2-huffman.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/lib/lwan-h2-huffman.c b/src/lib/lwan-h2-huffman.c index 1da6d65d8..79f4bb839 100644 --- a/src/lib/lwan-h2-huffman.c +++ b/src/lib/lwan-h2-huffman.c @@ -331,12 +331,11 @@ ssize_t lwan_h2_huffman_next(struct lwan_h2_huffman_decoder *huff) struct uint8_ring_buffer *buffer = &huff->buffer; while (reader->total_bitcount > 7) { - if (uint8_ring_buffer_full(buffer)) - goto done; - uint8_t peeked_byte = peek_byte(reader); if (LIKELY(level0[peeked_byte].num_bits)) { - uint8_ring_buffer_put_copy(buffer, level0[peeked_byte].symbol); + if (!uint8_ring_buffer_try_put_copy(buffer, + level0[peeked_byte].symbol)) + goto done; consume(reader, level0[peeked_byte].num_bits); assert(reader->total_bitcount >= 0); continue; @@ -348,7 +347,9 @@ ssize_t lwan_h2_huffman_next(struct lwan_h2_huffman_decoder *huff) const struct h2_huffman_code *level1 = next_level0(peeked_byte); peeked_byte = peek_byte(reader); if (level1[peeked_byte].num_bits) { - uint8_ring_buffer_put_copy(buffer, level1[peeked_byte].symbol); + if (!uint8_ring_buffer_try_put_copy(buffer, + level1[peeked_byte].symbol)) + goto done; if (!consume(reader, level1[peeked_byte].num_bits)) return -1; continue; @@ -360,7 +361,9 @@ ssize_t lwan_h2_huffman_next(struct lwan_h2_huffman_decoder *huff) const struct h2_huffman_code *level2 = next_level1(peeked_byte); peeked_byte = peek_byte(reader); if (level2[peeked_byte].num_bits) { - uint8_ring_buffer_put_copy(buffer, level2[peeked_byte].symbol); + if (!uint8_ring_buffer_try_put_copy(buffer, + level2[peeked_byte].symbol)) + goto done; if (!consume(reader, level2[peeked_byte].num_bits)) return -1; continue; @@ -377,7 +380,9 @@ ssize_t lwan_h2_huffman_next(struct lwan_h2_huffman_decoder *huff) goto done; } if (LIKELY(level3[peeked_byte].num_bits)) { - uint8_ring_buffer_put_copy(buffer, level3[peeked_byte].symbol); + if (!uint8_ring_buffer_try_put_copy(buffer, + level3[peeked_byte].symbol)) + goto done; if (!consume(reader, level3[peeked_byte].num_bits)) return -1; continue; @@ -398,7 +403,7 @@ ssize_t lwan_h2_huffman_next(struct lwan_h2_huffman_decoder *huff) goto done; if (level0[peeked_byte].num_bits == (int8_t)reader->total_bitcount) { - uint8_ring_buffer_put_copy(buffer, level0[peeked_byte].symbol); + uint8_ring_buffer_try_put_copy(buffer, level0[peeked_byte].symbol); goto done; } From ff5bd0b005b8a25822cb88263b893e476c873973 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 26 May 2024 10:19:14 -0700 Subject: [PATCH 2302/2505] Actually implement the try_put_copy() ringbuffer method --- src/lib/ringbuffer.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/lib/ringbuffer.h b/src/lib/ringbuffer.h index 045032f05..9741b54e8 100644 --- a/src/lib/ringbuffer.h +++ b/src/lib/ringbuffer.h @@ -83,6 +83,15 @@ rb->array[type_name_##_mask(rb->write++)] = e; \ } \ \ + __attribute__((unused)) static inline bool type_name_##_try_put_copy( \ + struct type_name_ *rb, element_type_ e) \ + { \ + if (type_name_##_full(rb)) \ + return false; \ + \ + rb->array[type_name_##_mask(rb->write++)] = e; \ + } \ + \ __attribute__((unused)) static inline bool type_name_##_try_put( \ struct type_name_ *rb, const element_type_ *e) \ { \ From b302fd313be74a8ab07d454c0f3a63cf771e817e Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 27 May 2024 14:23:04 -0700 Subject: [PATCH 2303/2505] Fix reading of body data --- src/lib/lwan-request.c | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 7c367a969..0574410fb 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1289,23 +1289,23 @@ get_remaining_body_data_length(struct lwan_request *request, if (UNLIKELY((size_t)parsed_size >= max_size)) return HTTP_TOO_LARGE; if (UNLIKELY(!parsed_size)) { + helper->next_request = ""; *total = *have = 0; - return HTTP_OK; - } - - *total = (size_t)parsed_size; + } else { + *total = (size_t)parsed_size; - if (!helper->next_request) { - *have = 0; - return HTTP_PARTIAL_CONTENT; - } + if (!helper->next_request) { + *have = 0; + return HTTP_PARTIAL_CONTENT; + } - char *buffer_end = helper->buffer->value + helper->buffer->len; + char *buffer_end = helper->buffer->value + helper->buffer->len; - *have = (size_t)(buffer_end - helper->next_request); + *have = (size_t)(buffer_end - helper->next_request); - if (*have < *total) - return HTTP_PARTIAL_CONTENT; + if (*have < *total) + return HTTP_PARTIAL_CONTENT; + } helper->body_data.value = helper->next_request; helper->body_data.len = *total; @@ -1338,7 +1338,9 @@ static int read_body_data(struct lwan_request *request) status = get_remaining_body_data_length(request, max_data_size, &total, &have); - if (status != HTTP_PARTIAL_CONTENT && status != HTTP_OK) + if (status == HTTP_OK) + return HTTP_OK; + if (status != HTTP_PARTIAL_CONTENT) return -(int)status; new_buffer = From 9d4e11deeb9cfa9e7531d9ae6dc0dc711735266a Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 27 May 2024 14:28:49 -0700 Subject: [PATCH 2304/2505] Add send-money-json-API sample --- src/samples/CMakeLists.txt | 1 + .../send-money-json-api/CMakeLists.txt | 9 ++ src/samples/send-money-json-api/main.c | 151 ++++++++++++++++++ 3 files changed, 161 insertions(+) create mode 100644 src/samples/send-money-json-api/CMakeLists.txt create mode 100644 src/samples/send-money-json-api/main.c diff --git a/src/samples/CMakeLists.txt b/src/samples/CMakeLists.txt index cb35f91d0..a79c45c48 100644 --- a/src/samples/CMakeLists.txt +++ b/src/samples/CMakeLists.txt @@ -7,6 +7,7 @@ if (NOT ${CMAKE_BUILD_TYPE} MATCHES "Coverage") add_subdirectory(asyncawait) add_subdirectory(pastebin) add_subdirectory(smolsite) + add_subdirectory(send-money-json-api) endif() add_subdirectory(techempower) diff --git a/src/samples/send-money-json-api/CMakeLists.txt b/src/samples/send-money-json-api/CMakeLists.txt new file mode 100644 index 000000000..a7c4eb9f1 --- /dev/null +++ b/src/samples/send-money-json-api/CMakeLists.txt @@ -0,0 +1,9 @@ +add_executable(send-money-json-api + main.c + ../techempower/json.c +) + +target_link_libraries(send-money-json-api + ${LWAN_COMMON_LIBS} + ${ADDITIONAL_LIBRARIES} +) diff --git a/src/samples/send-money-json-api/main.c b/src/samples/send-money-json-api/main.c new file mode 100644 index 000000000..2c9ecf348 --- /dev/null +++ b/src/samples/send-money-json-api/main.c @@ -0,0 +1,151 @@ +/* + * lwan - web server + * Copyright (c) 2024 L. A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +/* + * Hastily written to compare results with other languages and + * frameworks after this Twitter thread: + * https://twitter.com/iSeiryu/status/1793830738153889902 + */ + +#include +#include + +#define ARRAY_SIZE N_ELEMENTS + +#include "../techempower/json.h" +#include "lwan.h" + +struct address { + const char *street; + const char *city; + const char *state; + const char *zip; +}; + +struct account_holder { + const char *id; + const char *firstName; + const char *lastName; + struct address address; + const char *email; +}; + +struct send_money_request { + struct account_holder from, to; + int amount; + const char *sendOn; +}; + +struct receipt { + char *from_account; + char *to_account; + char *created_on; + char *to_address; + int amount; +}; + +static const struct json_obj_descr receipt_descr[] = { + JSON_OBJ_DESCR_PRIM(struct receipt, from_account, JSON_TOK_STRING), + JSON_OBJ_DESCR_PRIM(struct receipt, to_account, JSON_TOK_STRING), + JSON_OBJ_DESCR_PRIM(struct receipt, amount, JSON_TOK_NUMBER), + JSON_OBJ_DESCR_PRIM(struct receipt, created_on, JSON_TOK_STRING), + JSON_OBJ_DESCR_PRIM(struct receipt, to_address, JSON_TOK_STRING), +}; + +static const struct json_obj_descr address_descr[] = { + JSON_OBJ_DESCR_PRIM(struct address, street, JSON_TOK_STRING), + JSON_OBJ_DESCR_PRIM(struct address, city, JSON_TOK_STRING), + JSON_OBJ_DESCR_PRIM(struct address, state, JSON_TOK_STRING), + JSON_OBJ_DESCR_PRIM(struct address, zip, JSON_TOK_STRING), +}; + +static const struct json_obj_descr account_holder_descr[] = { + JSON_OBJ_DESCR_PRIM(struct account_holder, id, JSON_TOK_STRING), + JSON_OBJ_DESCR_PRIM(struct account_holder, firstName, JSON_TOK_STRING), + JSON_OBJ_DESCR_PRIM(struct account_holder, lastName, JSON_TOK_STRING), + JSON_OBJ_DESCR_PRIM(struct account_holder, email, JSON_TOK_STRING), + JSON_OBJ_DESCR_OBJECT(struct account_holder, address, address_descr), +}; + +static const struct json_obj_descr send_money_request_descr[] = { + JSON_OBJ_DESCR_PRIM(struct send_money_request, amount, JSON_TOK_NUMBER), + JSON_OBJ_DESCR_PRIM(struct send_money_request, sendOn, JSON_TOK_STRING), + JSON_OBJ_DESCR_OBJECT( + struct send_money_request, from, account_holder_descr), + JSON_OBJ_DESCR_OBJECT(struct send_money_request, to, account_holder_descr), +}; + +static int append_to_strbuf(const char *bytes, size_t len, void *data) +{ + struct lwan_strbuf *strbuf = data; + + return !lwan_strbuf_append_str(strbuf, bytes, len); +} + +static inline struct tm *localtime_now(void) +{ + static __thread struct tm result; + time_t now = time(NULL); + return localtime_r(&now, &result); +} + +LWAN_HANDLER_ROUTE(send_money, "/send-money") +{ + struct send_money_request smr; + const struct lwan_value *body; + + if (lwan_request_get_method(request) != REQUEST_METHOD_POST) + return HTTP_BAD_REQUEST; + + body = lwan_request_get_request_body(request); + if (!body) { + return HTTP_BAD_REQUEST; + } + + if (json_obj_parse(body->value, body->len, send_money_request_descr, + N_ELEMENTS(send_money_request_descr), + &smr) != (1 << 0 | 1 << 1 | 1 << 2 | 1 << 3)) { + return HTTP_BAD_REQUEST; + } + + char formatted_time[25]; + strftime(formatted_time, 25, "%FT%T%z", localtime_now()); + + struct receipt r = { + .from_account = coro_printf(request->conn->coro, "%s %s", + smr.from.firstName, smr.from.lastName), + .to_account = coro_printf(request->conn->coro, "%s %s", + smr.to.firstName, smr.to.lastName), + .to_address = coro_printf(request->conn->coro, "%s, %s, %s, %s", + smr.to.address.street, smr.to.address.city, + smr.to.address.state, smr.to.address.zip), + .created_on = formatted_time, + .amount = smr.amount, + }; + if (json_obj_encode_full(receipt_descr, N_ELEMENTS(receipt_descr), &r, + append_to_strbuf, response->buffer, false) != 0) { + return HTTP_INTERNAL_ERROR; + } + + response->mime_type = "application/json"; + return HTTP_OK; +} + +int main(void) { return lwan_main(); } From a75db177015c2e7b210b5f2d2dfecc8c57852bda Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 27 May 2024 14:50:30 -0700 Subject: [PATCH 2305/2505] Ensure helper->next_request won't be used if pointing to empty string --- src/lib/lwan-request.c | 50 ++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 0574410fb..7cb65a328 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -50,6 +50,8 @@ #define HEADER_TERMINATOR_LEN (sizeof("\r\n") - 1) #define MIN_REQUEST_SIZE (sizeof("GET / HTTP/1.1\r\n\r\n") - 1) +static const char *next_request_empty = ""; + enum lwan_read_finalizer { FINALIZER_DONE, FINALIZER_TRY_AGAIN, @@ -933,32 +935,38 @@ static void save_to_corpus_for_fuzzing(struct lwan_value buffer) } #endif -static enum lwan_http_status -client_read(struct lwan_request *request, - struct lwan_value *buffer, - const size_t want_to_read, - enum lwan_read_finalizer (*finalizer)(const struct lwan_value *buffer, - size_t want_to_read, - const struct lwan_request *request, - int n_packets)) +static enum lwan_http_status client_read( + struct lwan_request *request, + struct lwan_value *buffer, + const size_t want_to_read, + enum lwan_read_finalizer (*finalizer)(const struct lwan_value *buffer, + size_t want_to_read, + const struct lwan_request *request, + int n_packets)) { struct lwan_request_parser_helper *helper = request->helper; int n_packets = 0; if (helper->next_request) { - const size_t next_request_len = (size_t)(helper->next_request - buffer->value); - size_t new_len; - - if (__builtin_sub_overflow(buffer->len, next_request_len, &new_len)) { + if (UNLIKELY(helper->next_request == next_request_empty)) { helper->next_request = NULL; - } else if (new_len) { - /* FIXME: This memmove() could be eventually removed if a better - * stucture (maybe a ringbuffer, reading with readv(), and each - * pointer is coro_strdup() if they wrap around?) were used for - * the request buffer. */ - buffer->len = new_len; - memmove(buffer->value, helper->next_request, new_len); - goto try_to_finalize; + } else { + const size_t next_request_len = + (size_t)(helper->next_request - buffer->value); + size_t new_len; + + if (__builtin_sub_overflow(buffer->len, next_request_len, + &new_len)) { + helper->next_request = NULL; + } else if (new_len) { + /* FIXME: This memmove() could be eventually removed if a better + * stucture (maybe a ringbuffer, reading with readv(), and each + * pointer is coro_strdup() if they wrap around?) were used for + * the request buffer. */ + buffer->len = new_len; + memmove(buffer->value, helper->next_request, new_len); + goto try_to_finalize; + } } } @@ -1289,7 +1297,7 @@ get_remaining_body_data_length(struct lwan_request *request, if (UNLIKELY((size_t)parsed_size >= max_size)) return HTTP_TOO_LARGE; if (UNLIKELY(!parsed_size)) { - helper->next_request = ""; + helper->next_request = (char *)next_request_empty; *total = *have = 0; } else { *total = (size_t)parsed_size; From b0c653dcf8c78db0053014ccaf3ad45fa1a73ce5 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 27 May 2024 14:52:38 -0700 Subject: [PATCH 2306/2505] No need to have next_request_empty! --- src/lib/lwan-request.c | 38 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 7cb65a328..a02352171 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -50,8 +50,6 @@ #define HEADER_TERMINATOR_LEN (sizeof("\r\n") - 1) #define MIN_REQUEST_SIZE (sizeof("GET / HTTP/1.1\r\n\r\n") - 1) -static const char *next_request_empty = ""; - enum lwan_read_finalizer { FINALIZER_DONE, FINALIZER_TRY_AGAIN, @@ -948,25 +946,20 @@ static enum lwan_http_status client_read( int n_packets = 0; if (helper->next_request) { - if (UNLIKELY(helper->next_request == next_request_empty)) { + const size_t next_request_len = + (size_t)(helper->next_request - buffer->value); + size_t new_len; + + if (__builtin_sub_overflow(buffer->len, next_request_len, &new_len)) { helper->next_request = NULL; - } else { - const size_t next_request_len = - (size_t)(helper->next_request - buffer->value); - size_t new_len; - - if (__builtin_sub_overflow(buffer->len, next_request_len, - &new_len)) { - helper->next_request = NULL; - } else if (new_len) { - /* FIXME: This memmove() could be eventually removed if a better - * stucture (maybe a ringbuffer, reading with readv(), and each - * pointer is coro_strdup() if they wrap around?) were used for - * the request buffer. */ - buffer->len = new_len; - memmove(buffer->value, helper->next_request, new_len); - goto try_to_finalize; - } + } else if (new_len) { + /* FIXME: This memmove() could be eventually removed if a better + * stucture (maybe a ringbuffer, reading with readv(), and each + * pointer is coro_strdup() if they wrap around?) were used for + * the request buffer. */ + buffer->len = new_len; + memmove(buffer->value, helper->next_request, new_len); + goto try_to_finalize; } } @@ -1297,7 +1290,7 @@ get_remaining_body_data_length(struct lwan_request *request, if (UNLIKELY((size_t)parsed_size >= max_size)) return HTTP_TOO_LARGE; if (UNLIKELY(!parsed_size)) { - helper->next_request = (char *)next_request_empty; + helper->next_request = NULL; *total = *have = 0; } else { *total = (size_t)parsed_size; @@ -1313,11 +1306,12 @@ get_remaining_body_data_length(struct lwan_request *request, if (*have < *total) return HTTP_PARTIAL_CONTENT; + + helper->next_request += *total; } helper->body_data.value = helper->next_request; helper->body_data.len = *total; - helper->next_request += *total; return HTTP_OK; } From 39b37111b78a96cffa0032f33f595102aad9c102 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 28 May 2024 07:46:16 -0700 Subject: [PATCH 2307/2505] Fix reading of body data --- src/lib/lwan-request.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index a02352171..2eea11e29 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1290,7 +1290,7 @@ get_remaining_body_data_length(struct lwan_request *request, if (UNLIKELY((size_t)parsed_size >= max_size)) return HTTP_TOO_LARGE; if (UNLIKELY(!parsed_size)) { - helper->next_request = NULL; + helper->body_data.value = helper->next_request = NULL; *total = *have = 0; } else { *total = (size_t)parsed_size; @@ -1307,10 +1307,10 @@ get_remaining_body_data_length(struct lwan_request *request, if (*have < *total) return HTTP_PARTIAL_CONTENT; + helper->body_data.value = helper->next_request; helper->next_request += *total; } - helper->body_data.value = helper->next_request; helper->body_data.len = *total; return HTTP_OK; } From b7d5aea19c61db110c41ebe9e023a599119d6737 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 28 May 2024 20:55:58 -0700 Subject: [PATCH 2308/2505] Use bin2hex to bundle the HTML contents in the websockets sample --- src/samples/websocket/CMakeLists.txt | 14 ++++ src/samples/websocket/index.html | Bin 0 -> 3644 bytes src/samples/websocket/main.c | 114 ++------------------------- 3 files changed, 19 insertions(+), 109 deletions(-) create mode 100644 src/samples/websocket/index.html diff --git a/src/samples/websocket/CMakeLists.txt b/src/samples/websocket/CMakeLists.txt index 89cad2684..d85113d04 100644 --- a/src/samples/websocket/CMakeLists.txt +++ b/src/samples/websocket/CMakeLists.txt @@ -6,3 +6,17 @@ target_link_libraries(websocket ${LWAN_COMMON_LIBS} ${ADDITIONAL_LIBRARIES} ) + +add_custom_command( + OUTPUT ${CMAKE_BINARY_DIR}/websocket-sample.h + COMMAND bin2hex + ${CMAKE_SOURCE_DIR}/src/samples/websocket/index.html index_html > ${CMAKE_BINARY_DIR}/websocket-sample.h + DEPENDS ${CMAKE_SOURCE_DIR}/src/samples/websocket/index.html + bin2hex + COMMENT "Bundling websocket sample index" +) +add_custom_target(generate_websocket_sample + DEPENDS ${CMAKE_BINARY_DIR}/websocket-sample.h +) + +add_dependencies(websocket generate_websocket_sample) diff --git a/src/samples/websocket/index.html b/src/samples/websocket/index.html new file mode 100644 index 0000000000000000000000000000000000000000..743225499dccdf79c6c52def29d3d9d36bd2dd4e GIT binary patch literal 3644 zcmb_fO>^2X5be3YLe&YT15VOQh8Q2(KKt*@l&GX}iv?-R8pbh0*aV znG?mgs+M2={AYrdqAJF>J*F@TE|( z6_yV34Gh}n&`r2g{9-EP>Sb2;UoZxP}782 z_TZuY^PW6LJkNFB;d4zo^ih~t1Hahvg!+`uKcKNY$)`&ezpip!s07H;`~dI75p~AZ z6;Ig5XDJK5cR-&|4mNzBm)wp$D>Wcja+1RvVx%sgp&}!fWnryWVCqB|wv^n(`h`iu z&-M<&2vw%WFq9_4&J6eoM;{lOp640}_~1l6s<6$A;R3%Oq|vuq!7eNrzQBC}d#hp? zu9Ah-3x`xEYF(p8ht;`RHgy|EM~gKQtNplPP$tH{m;Evkr_9!0Kkv$MJNC*%0|KrY znwAc0GCVC2rk3I2v_uqlX)}-&szhI$dn)mX#PoQ1K0KM1ZX1T_8hMEv`Xqi=N_i*g zD114HC&%MOH#w4eBVcbOW>@QLv`_n-Fe%CBbDJlu6Ub?8R^6`Rutv1ZPuB=!R3}GY zjX2l9-NGS&9HB;jlQDGXquZYIO@E_#OQ zH_=M&h+*qY@)\n" - " \n" - " \n" - " \n" - " \n" - "

Lwan WebSocket demo!

\n" - "

Send-only sample: server is writing this " - "continuously:

\n" - "

Disconnected

\n" - "

Echo server sample:

\n" - "

\n" - "

Server said this:

Disconnected

\n" - "

Chat sample:

\n" - " Send message:

\n" - " \n" - " \n" - ""; - request->response.mime_type = "text/html"; - lwan_strbuf_set_static(response->buffer, message, sizeof(message) - 1); + lwan_strbuf_set_static(response->buffer, + index_html_value.value, + index_html_value.len); return HTTP_OK; } From 72116f837f0333e00f739b57f4a2afbdd1008f54 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 29 May 2024 20:53:03 -0700 Subject: [PATCH 2309/2505] Remove \0 from websockets sample's index.html --- src/samples/websocket/index.html | Bin 3644 -> 3644 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/samples/websocket/index.html b/src/samples/websocket/index.html index 743225499dccdf79c6c52def29d3d9d36bd2dd4e..4f1f91266d8095442f1a458c63248cc4909ca52b 100644 GIT binary patch delta 9 QcmdlZvqxrwH6J4v01@K?wEzGB delta 9 QcmdlZvqxrwH6J4b01?>&s{jB1 From 57270105981238c1e6b906a67273ee7c0a69ee80 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 31 May 2024 21:17:17 -0700 Subject: [PATCH 2310/2505] Cap number of file descriptors to 640k It oughta be enough for everybody. (This is a temporary thing, hopefully.) --- src/lib/lwan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index bcbc82192..e2eaba77c 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -798,7 +798,7 @@ static rlim_t setup_open_file_count_limits(void) } out: - return r.rlim_cur; + return LWAN_MIN(655360ull, r.rlim_cur); } static void allocate_connections(struct lwan *l, size_t max_open_files) From d42a8550a36e3841f3d0759b901edc8a5a8962f0 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 31 May 2024 22:26:19 -0700 Subject: [PATCH 2311/2505] Add some auxiliary Lua methods --- src/lib/lwan-lua.c | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index fe7bf787a..e42bb31cc 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -44,6 +44,44 @@ ALWAYS_INLINE struct lwan_request *lwan_lua_get_request_from_userdata(lua_State return *r; } +LWAN_LUA_METHOD(http_version) +{ + if (request->flags & REQUEST_IS_HTTP_1_0) + lua_pushstring(L, "HTTP/1.0"); + else + lua_pushstring(L, "HTTP/1.1"); + return 1; +} + +LWAN_LUA_METHOD(http_method) +{ + lua_pushstring(L, lwan_request_get_method_str(request)); + return 1; +} + +LWAN_LUA_METHOD(http_headers) +{ + const struct lwan_request_parser_helper *helper = request->helper; + + lua_newtable(L); + + for (size_t i = 0; i < helper->n_header_start; i++) { + const char *key = helper->header_start[i]; + const char *key_end = strchr(key, ':'); + + if (!key_end) + break; + + const char *value = key_end + 2; + + lua_pushlstring(L, key, (size_t)(key_end - key)); + lua_pushstring(L, value); + lua_rawset(L, -3); + } + + return 1; +} + LWAN_LUA_METHOD(say) { size_t response_str_len; From bf933d0f3b9db3d68781e9c7e1ba2447c249d9a1 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 1 Jun 2024 06:42:57 -0700 Subject: [PATCH 2312/2505] Allow generic Lua handles (independent of method and path!) --- README.md | 6 +++++- src/lib/lwan-mod-lua.c | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 97cc485cf..4393dbde6 100644 --- a/README.md +++ b/README.md @@ -609,7 +609,8 @@ single script, embedded in the configuration file or otherwise, can service many different endpoints. Scripts are supposed to implement functions with the following signature: `handle_${METHOD}_${ENDPOINT}(req)`, where `${METHOD}` can be a HTTP method (i.e. `get`, `post`, `head`, etc.), and -`${ENDPOINT}` is the desired endpoint to be handled by that function. +`${ENDPOINT}` is the desired endpoint to be handled by that function. A generic +`handle(req)` function will be called if the specific version doesn't exist. > [!TIP] > @@ -643,6 +644,9 @@ information from the request, or to set the response, as seen below: - `req:request_date()` returns the date as it'll be written in the `Date` response header. - `req:is_https()` returns `true` if this request is serviced through HTTPS, `false` otherwise. - `req:host()` returns the value of the `Host` header if present, otherwise `nil`. + - `req:http_version()` returns `HTTP/1.0` or `HTTP/1.1` depending on the request version. + - `req:http_method()` returns a string, in uppercase, with the HTTP method (e.g. `"GET"`). + - `req:http_headers()` returns a table with all headers and their values. Handler functions may return either `nil` (in which case, a `200 OK` response is generated), or a number matching an HTTP status code. Attempting to return diff --git a/src/lib/lwan-mod-lua.c b/src/lib/lwan-mod-lua.c index ed79555f4..44a830f60 100644 --- a/src/lib/lwan-mod-lua.c +++ b/src/lib/lwan-mod-lua.c @@ -158,6 +158,11 @@ static bool get_handler_function(lua_State *L, struct lwan_request *request) memcpy(method_name, url, url_len + 1); lua_getglobal(L, handler_name); + if (lua_isfunction(L, -1)) + return true; + + lua_pop(L, 1); + lua_getglobal(L, "handle"); return lua_isfunction(L, -1); } From 56e2b1d37cd1f4d44b2358c42e3f2f7a94e674dc Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 1 Jun 2024 06:56:25 -0700 Subject: [PATCH 2313/2505] Ensure strings returned in req:http_headers() are correct --- src/lib/lwan-lua.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index e42bb31cc..d630d581f 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -73,9 +73,10 @@ LWAN_LUA_METHOD(http_headers) break; const char *value = key_end + 2; + const char *value_end = helper->header_start[i + 1]; lua_pushlstring(L, key, (size_t)(key_end - key)); - lua_pushstring(L, value); + lua_pushlstring(L, value, (size_t)(value_end - value - 2)); lua_rawset(L, -3); } From 131127511ac7ea4d8e04382472b4337356ca1355 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 1 Jun 2024 09:56:05 -0700 Subject: [PATCH 2314/2505] Be more strict when iterating over parsed headers (This should be moved down a layer so this kind of validation happens at the parsing time, not where we consume this, but I'm too lazy to look at that now.) Found by fuzzing with http-garden. --- src/lib/lwan-lua.c | 20 ++++++++++++-------- src/lib/lwan-request.c | 11 +++++++---- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index d630d581f..dd688b739 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -66,17 +66,21 @@ LWAN_LUA_METHOD(http_headers) lua_newtable(L); for (size_t i = 0; i < helper->n_header_start; i++) { - const char *key = helper->header_start[i]; - const char *key_end = strchr(key, ':'); + const char *header = helper->header_start[i]; + const char *next_header = helper->header_start[i + 1]; + const char *colon = memchr(header, ':', (size_t)(next_header - header)); - if (!key_end) - break; + if (!colon) + continue; - const char *value = key_end + 2; - const char *value_end = helper->header_start[i + 1]; + const ptrdiff_t header_len = colon - header; + const ptrdiff_t value_len = next_header - colon - 4; + + if (header_len < 0 || value_len < 0) + continue; - lua_pushlstring(L, key, (size_t)(key_end - key)); - lua_pushlstring(L, value, (size_t)(value_end - value - 2)); + lua_pushlstring(L, header, (size_t)header_len); + lua_pushlstring(L, colon + 2, (size_t)value_len); lua_rawset(L, -3); } diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 2eea11e29..bd29bf3c7 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -2100,8 +2100,11 @@ void lwan_request_foreach_header_for_cgi(struct lwan_request *request, if (!colon) continue; - const size_t header_len = (size_t)(colon - header); - const size_t value_len = (size_t)(next_header - colon - 4); + const ptrdiff_t header_len = colon - header; + const ptrdiff_t value_len = next_header - colon - 4; + + if (header_len < 0 || value_len < 0) + continue; r = snprintf(header_name, sizeof(header_name), "HTTP_%.*s", (int)header_len, header); @@ -2120,7 +2123,7 @@ void lwan_request_foreach_header_for_cgi(struct lwan_request *request, continue; } - cb(header_name, header_len + sizeof("HTTP_") - 1, colon + 2, value_len, - user_data); + cb(header_name, (size_t)header_len + sizeof("HTTP_") - 1, colon + 2, + (size_t)value_len, user_data); } } From 72430e577878b31aa73b0b8396bd12585f0d2df4 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 1 Jun 2024 10:04:57 -0700 Subject: [PATCH 2315/2505] Mark some functions as cold --- src/lib/lwan-request.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index bd29bf3c7..89df548a4 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1107,6 +1107,7 @@ body_data_finalizer(const struct lwan_value *buffer, return FINALIZER_TRY_AGAIN; } +__attribute__((cold)) static const char *is_dir_good_for_tmp(const char *v) { struct statfs sb; @@ -1143,6 +1144,7 @@ static const char *is_dir_good_for_tmp(const char *v) static const char *temp_dir; static const size_t body_buffer_temp_file_thresh = 1<<20; +__attribute__((cold)) static const char * get_temp_dir(void) { From 419c07842ef06ee46813b66d65cb4256089a2546 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 1 Jun 2024 11:44:55 -0700 Subject: [PATCH 2316/2505] Allow setting up maximum number of file descriptors --- README.md | 1 + src/lib/lwan.c | 30 +++++++++++++++++++++++++++--- src/lib/lwan.h | 1 + 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4393dbde6..ec427a727 100644 --- a/README.md +++ b/README.md @@ -344,6 +344,7 @@ can be decided automatically, so some configuration options are provided. | `proxy_protocol` | `bool` | `false` | Enables the [PROXY protocol](https://www.haproxy.com/blog/haproxy/proxy-protocol/). Versions 1 and 2 are supported. Only enable this setting if using Lwan behind a proxy, and the proxy supports this protocol; otherwise, this allows anybody to spoof origin IP addresses | | `max_post_data_size` | `int` | `40960` | Sets the maximum number of data size for POST requests, in bytes | | `max_put_data_size` | `int` | `40960` | Sets the maximum number of data size for PUT requests, in bytes | +| `max_file_descriptors` | `int` | `524288` | Maximum number of file descriptors. Needs to be at least 10x `threads` | | `request_buffer_size` | `int` | `4096` | Request buffer size length. If larger than the default of `4096`, it'll be dynamically allocated. | | `allow_temp_files` | `str` | `""` | Use temporary files; set to `post` for POST requests, `put` for PUT requests, or `all` (equivalent to setting to `post put`) for both.| | `error_template` | `str` | Default error template | Template for error codes. See variables below. | diff --git a/src/lib/lwan.c b/src/lib/lwan.c index e2eaba77c..c63bac5a4 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -65,6 +65,7 @@ static const struct lwan_config default_config = { .allow_post_temp_file = false, .max_put_data_size = 10 * DEFAULT_BUFFER_SIZE, .allow_put_temp_file = false, + .max_file_descriptors = 524288, }; LWAN_HANDLER_ROUTE(brew_coffee, NULL /* do not autodetect this route */) @@ -663,6 +664,19 @@ static bool setup_from_config(struct lwan *lwan, const char *path) } lwan->config.request_buffer_size = (size_t)request_buffer_size; + } else if (streq(line->key, "max_file_descriptors")) { + long max_file_descriptors = parse_long( + line->value, (long)default_config.max_file_descriptors); + + if (max_file_descriptors < 0) { + config_error(conf, "Maximum number of file descriptors can't be negative"); + } else if (max_file_descriptors > 2000000l) { + config_error(conf, "2M file descriptors should be sufficient!"); + } else if (max_file_descriptors == 0) { + max_file_descriptors = default_config.max_file_descriptors; + } + + lwan->config.max_file_descriptors = (unsigned int)max_file_descriptors; } else if (streq(line->key, "max_post_data_size")) { long max_post_data_size = parse_long( line->value, (long)default_config.max_post_data_size); @@ -766,7 +780,7 @@ static void try_setup_from_config(struct lwan *l, (l->config.ssl.send_hsts_header ? REQUEST_WANTS_HSTS_HEADER : 0); } -static rlim_t setup_open_file_count_limits(void) +static rlim_t setup_open_file_count_limits(struct lwan *l) { struct rlimit r; @@ -789,6 +803,9 @@ static rlim_t setup_open_file_count_limits(void) goto out; } + r.rlim_cur = LWAN_MIN(l->config.max_file_descriptors, + r.rlim_cur); + if (setrlimit(RLIMIT_NOFILE, &r) < 0) { lwan_status_perror("Could not raise maximum number of file " "descriptors to %" PRIu64 ". Leaving at " @@ -798,7 +815,14 @@ static rlim_t setup_open_file_count_limits(void) } out: - return LWAN_MIN(655360ull, r.rlim_cur); + if (r.rlim_cur < 10 * l->thread.count) { + lwan_status_critical("Number of file descriptors (%ld) is smaller than 10x " + "the number of threads (%d)\n", + r.rlim_cur, + 10 * l->thread.count); + } + + return r.rlim_cur; } static void allocate_connections(struct lwan *l, size_t max_open_files) @@ -901,7 +925,7 @@ void lwan_init_with_config(struct lwan *l, const struct lwan_config *config) l->thread.count = l->config.n_threads; } - rlim_t max_open_files = setup_open_file_count_limits(); + rlim_t max_open_files = setup_open_file_count_limits(l); allocate_connections(l, (size_t)max_open_files); l->thread.max_fd = (unsigned)max_open_files / (unsigned)l->thread.count; diff --git a/src/lib/lwan.h b/src/lib/lwan.h index c7cc1eaf5..b0c94da15 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -524,6 +524,7 @@ struct lwan_config { unsigned int keep_alive_timeout; unsigned int expires; unsigned int n_threads; + unsigned int max_file_descriptors; unsigned int quiet : 1; unsigned int proxy_protocol : 1; From 44ed5ea1c799735cd4c5947efcdbad620fbf7cf8 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 7 Jun 2024 08:18:52 -0700 Subject: [PATCH 2317/2505] Remove unused headers from fastcgi module --- src/lib/lwan-mod-fastcgi.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index a01d6ba5f..fb1e766b4 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -27,7 +27,6 @@ #define _GNU_SOURCE #include -#include #include #include #include @@ -43,7 +42,6 @@ #include "lwan-private.h" #include "int-to-str.h" -#include "patterns.h" #include "realpathat.h" #include "lwan-cache.h" #include "lwan-io-wrappers.h" From 7c8900696e90e41cd45f944c988ff8dfbd481a74 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 7 Jun 2024 08:20:04 -0700 Subject: [PATCH 2318/2505] Implement SERVER_ADDR and SERVER_PORT in mod-fastcgi --- src/lib/lwan-mod-fastcgi.c | 81 +++++++++++++++++++++++++++----------- 1 file changed, 59 insertions(+), 22 deletions(-) diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index fb1e766b4..f01980734 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -245,6 +245,63 @@ static void add_header_to_strbuf(const char *header, return add_param_len(strbuf, header, header_len, value, value_len); } +static bool fill_addr_and_port(const struct lwan_request *r, + struct lwan_strbuf *strbuf) +{ + const struct lwan_thread *t = r->conn->thread; + char local_addr_buf[INET6_ADDRSTRLEN], remote_addr_buf[INET6_ADDRSTRLEN]; + struct sockaddr_storage sockaddr = {.ss_family = AF_UNSPEC}; + uint16_t local_port, remote_port; + socklen_t len = sizeof(sockaddr); + const char *local_addr, *remote_addr; + + if (r->conn->flags & CONN_TLS) { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&sockaddr; + + if (getsockname(t->tls_listen_fd, (struct sockaddr *)&sockaddr, &len) < + 0) + return false; + + assert(len == sizeof(*sin6)); + + local_addr = inet_ntop(AF_INET6, &sin6->sin6_addr, local_addr_buf, + sizeof(local_addr_buf)); + local_port = ntohs(sin6->sin6_port); + + add_param(strbuf, "HTTPS", "on"); + } else { + struct sockaddr_in *sin = (struct sockaddr_in *)&sockaddr; + + if (getsockname(t->listen_fd, (struct sockaddr *)&sockaddr, &len) < 0) + return false; + + assert(len == sizeof(*sin)); + + local_addr = inet_ntop(AF_INET, &sin->sin_addr, local_addr_buf, + sizeof(local_addr_buf)); + local_port = ntohs(sin->sin_port); + + add_param(strbuf, "HTTPS", ""); + } + + remote_addr = lwan_request_get_remote_address_and_port(r, remote_addr_buf, + &remote_port); + + if (!local_addr) + return false; + + if (!remote_addr) + return false; + + add_param(strbuf, "SERVER_ADDR", local_addr); + add_int_param(strbuf, "SERVER_PORT", local_port); + + add_param(strbuf, "REMOTE_ADDR", remote_addr); + add_int_param(strbuf, "REMOTE_PORT", remote_port); + + return true; +} + static enum lwan_http_status add_params(const struct private_data *pd, struct lwan_request *request, struct lwan_response *response) @@ -252,31 +309,11 @@ static enum lwan_http_status add_params(const struct private_data *pd, const struct lwan_request_parser_helper *request_helper = request->helper; struct lwan_strbuf *strbuf = response->buffer; - char remote_addr[INET6_ADDRSTRLEN]; - uint16_t remote_port; - - /* FIXME: let's use some hardcoded values for now so that we can - * verify that the implementation is working first */ - /* Very compliant. Much CGI. Wow. */ add_param(strbuf, "GATEWAY_INTERFACE", "CGI/1.1"); - add_param(strbuf, "REMOTE_ADDR", - lwan_request_get_remote_address_and_port(request, remote_addr, - &remote_port)); - add_int_param(strbuf, "REMOTE_PORT", remote_port); - - add_param(strbuf, "SERVER_ADDR", "127.0.0.1"); - - /* FIXME: get the actual port from thread->listen_fd or - * thread->tls_listen_fd */ - if (request->conn->flags & CONN_TLS) { - add_param(strbuf, "SERVER_PORT", "0"); - add_param(strbuf, "HTTPS", "on"); - } else { - add_param(strbuf, "SERVER_PORT", "0"); - add_param(strbuf, "HTTPS", ""); - } + if (!fill_addr_and_port(request, strbuf)) + return HTTP_INTERNAL_ERROR; add_param(strbuf, "SERVER_SOFTWARE", "Lwan"); add_param(strbuf, "SERVER_PROTOCOL", From 29a64cecc78d96b9bf164c4bc8fd4da17acf6378 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 11 Jun 2024 20:19:20 -0700 Subject: [PATCH 2319/2505] Use a branch-free binary search for post and query params, and cookies --- src/lib/lwan-request.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 89df548a4..55e3f7257 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1721,18 +1721,31 @@ void lwan_process_request(struct lwan *l, struct lwan_request *request) log_request(request, status, time_to_read_request, elapsed_time_ms(request_begin_time)); } -static inline void * -value_lookup(const struct lwan_key_value_array *array, const char *key) +static inline void *value_lookup(const struct lwan_key_value_array *array, + const char *key) { + /* Based on https://orlp.net/blog/bitwise-binary-search/ */ const struct lwan_array *la = (const struct lwan_array *)array; if (LIKELY(la->elements)) { - struct lwan_key_value k = { .key = (char *)key }; - struct lwan_key_value *entry; +#if __SIZEOF_SIZE_T__ == 8 + const size_t floor = 1ull << (64 - __builtin_clzll(la->elements)); +#else + const size_t floor = 1u << (32 - __builtin_clz(la->elements)); +#endif + struct lwan_value *base = (struct lwan_value *)la->base; + struct lwan_key_value k = {.key = (char *)key}; + int64_t b = key_value_compare(&k, &base[la->elements / 2]) > 0 + ? (int64_t)(la->elements - floor) + : -1; + + for (uint64_t bit = floor >> 1; bit != 0; bit >>= 1) { + if (key_value_compare(&k, &base[b + (int64_t)bit]) > 0) + b += (int64_t)bit; + } - entry = bsearch(&k, la->base, la->elements, sizeof(k), key_value_compare); - if (LIKELY(entry)) - return entry->value; + if (!key_value_compare(&k, &base[b + 1])) + return base[b + 1].value; } return NULL; From d9040bf33521bc10e814a98f9a50613029c0e7ee Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 11 Jun 2024 20:23:29 -0700 Subject: [PATCH 2320/2505] lwan_request_get_remote_address*() can take a const request --- src/lib/lwan-request.c | 4 ++-- src/lib/lwan.h | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 55e3f7257..8f2a9426d 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1805,7 +1805,7 @@ const char *lwan_request_get_host(struct lwan_request *request) } const char * -lwan_request_get_remote_address_and_port(struct lwan_request *request, +lwan_request_get_remote_address_and_port(const struct lwan_request *request, char buffer[static INET6_ADDRSTRLEN], uint16_t *port) { @@ -1847,7 +1847,7 @@ lwan_request_get_remote_address_and_port(struct lwan_request *request, } const char * -lwan_request_get_remote_address(struct lwan_request *request, +lwan_request_get_remote_address(const struct lwan_request *request, char buffer[static INET6_ADDRSTRLEN]) { uint16_t port; diff --git a/src/lib/lwan.h b/src/lib/lwan.h index b0c94da15..47c574bc3 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -620,14 +620,13 @@ const struct lwan_config *lwan_get_default_config(void); const char *lwan_request_get_host(struct lwan_request *request); const char * -lwan_request_get_remote_address(struct lwan_request *request, +lwan_request_get_remote_address(const struct lwan_request *request, char buffer LWAN_ARRAY_PARAM(INET6_ADDRSTRLEN)) __attribute__((warn_unused_result)); -const char * -lwan_request_get_remote_address_and_port(struct lwan_request *request, - char buffer LWAN_ARRAY_PARAM(INET6_ADDRSTRLEN), - uint16_t *port) +const char *lwan_request_get_remote_address_and_port( + const struct lwan_request *request, + char buffer LWAN_ARRAY_PARAM(INET6_ADDRSTRLEN), uint16_t *port) __attribute__((warn_unused_result)); static inline enum lwan_request_flags From 0d22917191024029b2cc4984c3850e139243c06a Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 11 Jun 2024 20:23:51 -0700 Subject: [PATCH 2321/2505] Ensure kevent-based epoll handles user data in all situations --- src/lib/missing-epoll.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/missing-epoll.c b/src/lib/missing-epoll.c index 85835c714..b7c183cd2 100644 --- a/src/lib/missing-epoll.c +++ b/src/lib/missing-epoll.c @@ -160,6 +160,7 @@ int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) } else if (kev->filter == EVFILT_WRITE && kev->udata != &epoll_no_event_marker) { ev->events |= EPOLLOUT; + ev->data.ptr = kev->udata; } last = kev->ident; From 2fbd55d5fc0fcefd2fa6ac748c9e5ce50ab9f424 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 11 Jun 2024 20:26:51 -0700 Subject: [PATCH 2322/2505] Ensure coroutine stack sizes are always aligned to page sizes Some aarch64 CPUs have 64k pages and an assert was failing because of this. --- src/lib/lwan-coro.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 45774c613..26ee34e07 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -55,11 +55,13 @@ void __asan_unpoison_memory_region(void const volatile *addr, size_t size); #endif #ifdef LWAN_HAVE_BROTLI -#define CORO_STACK_SIZE ((size_t)(8 * SIGSTKSZ)) +#define MIN_CORO_STACK_SIZE ((size_t)(8 * SIGSTKSZ)) #else -#define CORO_STACK_SIZE ((size_t)(4 * SIGSTKSZ)) +#define MIN_CORO_STACK_SIZE ((size_t)(4 * SIGSTKSZ)) #endif +#define CORO_STACK_SIZE ((MIN_CORO_STACK_SIZE + (size_t)PAGE_SIZE) & ~((size_t)PAGE_SIZE)) + #define CORO_BUMP_PTR_ALLOC_SIZE 1024 #if (!defined(NDEBUG) && defined(MAP_STACK)) || defined(__OpenBSD__) @@ -78,12 +80,6 @@ LWAN_SELF_TEST(sizes_are_same) /* Request buffer fits inside coroutine stack */ assert(DEFAULT_BUFFER_SIZE < CORO_STACK_SIZE); -#ifdef ALLOCATE_STACK_WITH_MMAP - /* Coroutine stack size is a multiple of page size */ - assert((CORO_STACK_SIZE % PAGE_SIZE) == 0); - /* Coroutine stack size is at least a page long */ - assert((CORO_STACK_SIZE >= PAGE_SIZE)); -#endif } typedef void (*defer1_func)(void *data); From 205fddc85aef69554516e438f224059789a923da Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 11 Jun 2024 23:41:47 -0700 Subject: [PATCH 2323/2505] Properly fill out SERVER_{ADDR,PORT} fields in mod-fastcgi --- src/lib/lwan-mod-fastcgi.c | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index f01980734..00e3a93a4 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -254,34 +254,33 @@ static bool fill_addr_and_port(const struct lwan_request *r, uint16_t local_port, remote_port; socklen_t len = sizeof(sockaddr); const char *local_addr, *remote_addr; + int listen_fd; if (r->conn->flags & CONN_TLS) { - struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&sockaddr; + listen_fd = t->tls_listen_fd; + add_param(strbuf, "HTTPS", "on"); + } else { + listen_fd = t->listen_fd; + add_param(strbuf, "HTTPS", ""); + } - if (getsockname(t->tls_listen_fd, (struct sockaddr *)&sockaddr, &len) < - 0) - return false; + if (getsockname(listen_fd, (struct sockaddr *)&sockaddr, &len) < 0) + return false; - assert(len == sizeof(*sin6)); + if (sockaddr.ss_family == AF_INET6) { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&sockaddr; local_addr = inet_ntop(AF_INET6, &sin6->sin6_addr, local_addr_buf, sizeof(local_addr_buf)); local_port = ntohs(sin6->sin6_port); - - add_param(strbuf, "HTTPS", "on"); - } else { + } else if (sockaddr.ss_family == AF_INET) { struct sockaddr_in *sin = (struct sockaddr_in *)&sockaddr; - if (getsockname(t->listen_fd, (struct sockaddr *)&sockaddr, &len) < 0) - return false; - - assert(len == sizeof(*sin)); - local_addr = inet_ntop(AF_INET, &sin->sin_addr, local_addr_buf, sizeof(local_addr_buf)); local_port = ntohs(sin->sin_port); - - add_param(strbuf, "HTTPS", ""); + } else { + return false; } remote_addr = lwan_request_get_remote_address_and_port(r, remote_addr_buf, From f2c6abca29baf0f7407d0c2bd522195806d57bcb Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 19 Jun 2024 07:11:55 -0700 Subject: [PATCH 2324/2505] Fix build on FreeBSD Still don't know if it works; there are lots of warnings when building with Clang, so gotta look at that later. But it's good to have a FreeBSD buildbot worker I can SSH into again! --- src/lib/lwan-io-wrappers.c | 14 +++++++------- src/lib/lwan-io-wrappers.h | 1 + src/lib/lwan-socket.c | 3 +-- src/lib/missing/sys/socket.h | 4 ++++ 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index 6084f0a3d..51ca62226 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -270,13 +270,13 @@ int lwan_sendfile_fd(struct lwan_request *request, } } #elif defined(__FreeBSD__) || defined(__APPLE__) -int lwan_sendfile(struct lwan_request *request, - int out_fd, - int in_fd, - off_t offset, - size_t count, - const char *header, - size_t header_len) +int lwan_sendfile_fd(struct lwan_request *request, + int out_fd, + int in_fd, + off_t offset, + size_t count, + const char *header, + size_t header_len) { struct sf_hdtr headers = {.headers = (struct iovec[]){{.iov_base = (void *)header, diff --git a/src/lib/lwan-io-wrappers.h b/src/lib/lwan-io-wrappers.h index 94e44df28..980c8db62 100644 --- a/src/lib/lwan-io-wrappers.h +++ b/src/lib/lwan-io-wrappers.h @@ -21,6 +21,7 @@ #pragma once #include +#include #include #include diff --git a/src/lib/lwan-socket.c b/src/lib/lwan-socket.c index 87072f183..fc6d95378 100644 --- a/src/lib/lwan-socket.c +++ b/src/lib/lwan-socket.c @@ -245,8 +245,6 @@ static int set_socket_options(const struct lwan *l, int fd) SET_SOCKET_OPTION(SOL_SOCKET, SO_LINGER, (&(struct linger){.l_onoff = 1, .l_linger = 1})); - SET_SOCKET_OPTION_MAY_FAIL(SOL_TCP, TCP_NODELAY, (int[]){1}); - #ifdef __linux__ #ifndef TCP_FASTOPEN @@ -254,6 +252,7 @@ static int set_socket_options(const struct lwan *l, int fd) #endif SET_SOCKET_OPTION_MAY_FAIL(SOL_SOCKET, SO_REUSEADDR, (int[]){1}); + SET_SOCKET_OPTION_MAY_FAIL(SOL_TCP, TCP_NODELAY, (int[]){1}); SET_SOCKET_OPTION_MAY_FAIL(SOL_TCP, TCP_FASTOPEN, (int[]){5}); SET_SOCKET_OPTION_MAY_FAIL(SOL_TCP, TCP_QUICKACK, (int[]){0}); SET_SOCKET_OPTION_MAY_FAIL(SOL_TCP, TCP_DEFER_ACCEPT, diff --git a/src/lib/missing/sys/socket.h b/src/lib/missing/sys/socket.h index 3df3ea62b..2ec51d878 100644 --- a/src/lib/missing/sys/socket.h +++ b/src/lib/missing/sys/socket.h @@ -27,6 +27,10 @@ #define MSG_MORE 0 #endif +#ifndef MSG_NOSIGNAL +#define MSG_NOSIGNAL 0 +#endif + #ifndef SOCK_CLOEXEC #define SOCK_CLOEXEC 0 #endif From 136948939b914c153a4b497ceccbf79d0530eb73 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 19 Jun 2024 08:26:16 -0700 Subject: [PATCH 2325/2505] Fix build on OpenBSD --- src/lib/lwan-io-wrappers.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-io-wrappers.c b/src/lib/lwan-io-wrappers.c index 51ca62226..8608a5e8e 100644 --- a/src/lib/lwan-io-wrappers.c +++ b/src/lib/lwan-io-wrappers.c @@ -387,8 +387,8 @@ int lwan_sendfile_fd(struct lwan_request *request, offset += bytes_read; blocks_sent++; - if (blocks_sent & 3 == 0) { - coro_yield(request->conn->coro, CONN_CORO_WRITE); + if ((blocks_sent & 3) == 0) { + coro_yield(request->conn->coro, CONN_CORO_WANT_WRITE); } } From 6514f785bc1595162aa289c726de6cd6051e8563 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 19 Jun 2024 08:27:24 -0700 Subject: [PATCH 2326/2505] Update versions of FreeBSD and OpenBSD in the build badge table --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ec427a727..1e9555d17 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,9 @@ Build status |-------------|--------|---------|-------|-----------------|------------| | Linux | x86_64 | ![release](https://shield.lwan.ws/img/gycKbr/release "Release") | ![debug](https://shield.lwan.ws/img/gycKbr/debug "Debug") | ![static-analysis](https://shield.lwan.ws/img/gycKbr/clang-analyze "Static Analysis") ![coverity](https://scan.coverity.com/projects/375/badge.svg) [Report history](https://buildbot.lwan.ws/sa/) | ![tests](https://shield.lwan.ws/img/gycKbr/unit-tests "Test") [![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/lwan.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:lwan) | | Linux | armv7 | ![release-arm](https://shield.lwan.ws/img/gycKbr/release-arm "Release") | ![debug-arm](https://shield.lwan.ws/img/gycKbr/debug-arm "Debug") | | | -| FreeBSD | x86_64 | ![freebsd-release](https://shield.lwan.ws/img/gycKbr/release-freebsd "Release FreeBSD") | ![freebsd-debug](https://shield.lwan.ws/img/gycKbr/debug-freebsd "Debug FreeBSD") | | | +| FreeBSD 14 | x86_64 | ![freebsd-release](https://shield.lwan.ws/img/gycKbr/release-freebsd "Release FreeBSD") | ![freebsd-debug](https://shield.lwan.ws/img/gycKbr/debug-freebsd "Debug FreeBSD") | | | | macOS | x86_64 | ![osx-release](https://shield.lwan.ws/img/gycKbr/release-sierra "Release macOS") | ![osx-debug](https://shield.lwan.ws/img/gycKbr/debug-sierra "Debug macOS") | | | -| OpenBSD 6.6 | x86_64 | ![openbsd-release](https://shield.lwan.ws/img/gycKbr/release-openbsd "Release OpenBSD") | ![openbsd-debug](https://shield.lwan.ws/img/gycKbr/debug-openbsd "Debug OpenBSD") | | ![openbsd-tests](https://shield.lwan.ws/img/gycKbr/openbsd-unit-tests "OpenBSD Tests") | +| OpenBSD 7.4 | x86_64 | ![openbsd-release](https://shield.lwan.ws/img/gycKbr/release-openbsd "Release OpenBSD") | ![openbsd-debug](https://shield.lwan.ws/img/gycKbr/debug-openbsd "Debug OpenBSD") | | ![openbsd-tests](https://shield.lwan.ws/img/gycKbr/openbsd-unit-tests "OpenBSD Tests") | Installing ---------- From 4eab0c7698bed71f66cd2843e9ebb513096cf867 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 19 Jun 2024 17:48:02 -0700 Subject: [PATCH 2327/2505] Remove macOS and armv7 buildbot status for now --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 1e9555d17..528561173 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,7 @@ Build status | OS | Arch | Release | Debug | Static Analysis | Tests | |-------------|--------|---------|-------|-----------------|------------| | Linux | x86_64 | ![release](https://shield.lwan.ws/img/gycKbr/release "Release") | ![debug](https://shield.lwan.ws/img/gycKbr/debug "Debug") | ![static-analysis](https://shield.lwan.ws/img/gycKbr/clang-analyze "Static Analysis") ![coverity](https://scan.coverity.com/projects/375/badge.svg) [Report history](https://buildbot.lwan.ws/sa/) | ![tests](https://shield.lwan.ws/img/gycKbr/unit-tests "Test") [![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/lwan.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:lwan) | -| Linux | armv7 | ![release-arm](https://shield.lwan.ws/img/gycKbr/release-arm "Release") | ![debug-arm](https://shield.lwan.ws/img/gycKbr/debug-arm "Debug") | | | | FreeBSD 14 | x86_64 | ![freebsd-release](https://shield.lwan.ws/img/gycKbr/release-freebsd "Release FreeBSD") | ![freebsd-debug](https://shield.lwan.ws/img/gycKbr/debug-freebsd "Debug FreeBSD") | | | -| macOS | x86_64 | ![osx-release](https://shield.lwan.ws/img/gycKbr/release-sierra "Release macOS") | ![osx-debug](https://shield.lwan.ws/img/gycKbr/debug-sierra "Debug macOS") | | | | OpenBSD 7.4 | x86_64 | ![openbsd-release](https://shield.lwan.ws/img/gycKbr/release-openbsd "Release OpenBSD") | ![openbsd-debug](https://shield.lwan.ws/img/gycKbr/debug-openbsd "Debug OpenBSD") | | ![openbsd-tests](https://shield.lwan.ws/img/gycKbr/openbsd-unit-tests "OpenBSD Tests") | Installing From 692c7a115f02e9adbba50f8852c79821f7e7f1bb Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 19 Jun 2024 18:12:16 -0700 Subject: [PATCH 2328/2505] Fix double-free in sd-daemon (Thanks, scan-build!) --- src/lib/sd-daemon.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/sd-daemon.c b/src/lib/sd-daemon.c index 4e34a6c56..3f93b864b 100644 --- a/src/lib/sd-daemon.c +++ b/src/lib/sd-daemon.c @@ -216,7 +216,7 @@ int sd_listen_fds_with_names(int unset_environment, char ***names) { } else { r = strv_extend_n(&l, "unknown", n_fds); if (r < 0) - goto fail; + return r; } *names = l; From ebe395ca3bef00864b792a1884d8b48bac77213a Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 19 Jun 2024 18:12:33 -0700 Subject: [PATCH 2329/2505] Remove dead store in lwan_request_awaitv_all() (Thanks, scan-build!) --- src/lib/lwan-thread.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index bdd94af8c..ca4a6f2fc 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -820,7 +820,7 @@ int lwan_request_awaitv_all(struct lwan_request *r, ...) if (UNLIKELY(ret < 0)) return ret; - for (ret = 0; state.num_awaiting;) { + while (state.num_awaiting) { int64_t v = coro_yield(r->conn->coro, state.request_conn_yield); struct lwan_connection *conn = (struct lwan_connection *)(uintptr_t)v; From e1973aa39d29d59f1a05398559760d1afb09bfd7 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 19 Jun 2024 18:13:02 -0700 Subject: [PATCH 2330/2505] Slight clean-up of resume_coro() --- src/lib/lwan-thread.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index ca4a6f2fc..3d62562a7 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -897,17 +897,15 @@ static ALWAYS_INLINE void resume_coro(struct timeout_queue *tq, int64_t from_coro = coro_resume_value(conn_to_resume->coro, (int64_t)(intptr_t)conn_to_yield); - if (LIKELY(from_coro != CONN_CORO_ABORT)) { - int r = update_epoll_flags( - tq->lwan, conn_to_resume, epoll_fd, - (enum lwan_connection_coro_yield)(uint32_t)from_coro); - if (LIKELY(!r)) { - timeout_queue_move_to_last(tq, conn_to_resume); - return; - } + if (UNLIKELY(from_coro == CONN_CORO_ABORT)) { + timeout_queue_expire(tq, conn_to_resume); + return; } - timeout_queue_expire(tq, conn_to_resume); + enum lwan_connection_coro_yield yield = (uint32_t)from_coro; + int r = update_epoll_flags(tq->lwan, conn_to_resume, epoll_fd, yield); + if (LIKELY(!r)) + timeout_queue_move_to_last(tq, conn_to_resume); } static void update_date_cache(struct lwan_thread *thread) From 792b8e32997587ab71aa7d14137089564876a8ef Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 19 Jun 2024 18:14:20 -0700 Subject: [PATCH 2331/2505] OpenBSD doesn't have f_type in statfs() --- src/lib/lwan-request.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 8f2a9426d..c27bc6503 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1110,7 +1110,6 @@ body_data_finalizer(const struct lwan_value *buffer, __attribute__((cold)) static const char *is_dir_good_for_tmp(const char *v) { - struct statfs sb; struct stat st; if (!v) @@ -1132,11 +1131,14 @@ static const char *is_dir_good_for_tmp(const char *v) v); } +#ifndef __OpenBSD__ /* OpenBSD doesn't have f_type */ + struct statfs sb; if (!statfs(v, &sb) && sb.f_type == TMPFS_MAGIC) { lwan_status_warning("%s is a tmpfs filesystem, " "not considering it", v); return NULL; } +#endif return v; } From df0ea56ce2a44c490f3c416023e68ed2bb0441b9 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 19 Jun 2024 18:17:44 -0700 Subject: [PATCH 2332/2505] Ensure O_NONBLOCK and O_CLOEXEC are defined in pubsub --- src/lib/lwan-pubsub.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/lwan-pubsub.c b/src/lib/lwan-pubsub.c index 6761cc0c4..ddd8de1c5 100644 --- a/src/lib/lwan-pubsub.c +++ b/src/lib/lwan-pubsub.c @@ -20,6 +20,7 @@ #define _GNU_SOURCE #include +#include #include #include #include From e37d8ca1feab79b6fc0db085f6c6950d8db8a270 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 19 Jun 2024 19:44:26 -0700 Subject: [PATCH 2333/2505] Use proper type for "last" in kqueue version of epoll_wait() --- src/lib/missing-epoll.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/missing-epoll.c b/src/lib/missing-epoll.c index b7c183cd2..1ecbd1d5d 100644 --- a/src/lib/missing-epoll.c +++ b/src/lib/missing-epoll.c @@ -137,7 +137,7 @@ int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) qsort(evs, (size_t)r, sizeof(struct kevent), kevent_ident_cmp); - int last = -1; + uintptr_t last = -1; for (i = 0; i < r; i++) { struct kevent *kev = &evs[i]; From d7be2241aa5d743f92b69ddee249edf546bc1915 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 19 Jun 2024 19:45:28 -0700 Subject: [PATCH 2334/2505] Ensure ring buffer try_put() returns true on success --- src/lib/ringbuffer.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/ringbuffer.h b/src/lib/ringbuffer.h index 9741b54e8..21086213c 100644 --- a/src/lib/ringbuffer.h +++ b/src/lib/ringbuffer.h @@ -90,6 +90,7 @@ return false; \ \ rb->array[type_name_##_mask(rb->write++)] = e; \ + return true; \ } \ \ __attribute__((unused)) static inline bool type_name_##_try_put( \ From 932b6739395e0161008c58bf2ecbdb38371606e8 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 19 Jun 2024 19:48:23 -0700 Subject: [PATCH 2335/2505] Fix return value o f getentropy(2) version of lwan_getentropy() --- src/lib/missing.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/missing.c b/src/lib/missing.c index ed0f56d88..42298ea5f 100644 --- a/src/lib/missing.c +++ b/src/lib/missing.c @@ -519,7 +519,7 @@ long int lwan_getentropy(void *buffer, size_t buffer_len, int flags) (void)flags; if (!getentropy(buffer, buffer_len)) - return buffer_len; + return 0; return lwan_getentropy_fallback(buffer, buffer_len); } From 87789d3c1c1b254e772d618a1ed5d5d64937338e Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 19 Jun 2024 19:55:37 -0700 Subject: [PATCH 2336/2505] Fix more warnings in epoll-on-kqueue implementation --- src/lib/missing-epoll.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/missing-epoll.c b/src/lib/missing-epoll.c index 1ecbd1d5d..598866d3c 100644 --- a/src/lib/missing-epoll.c +++ b/src/lib/missing-epoll.c @@ -27,7 +27,7 @@ #include #include -#include "lwan.h" +#include "lwan-private.h" #if !defined(LWAN_HAVE_EPOLL) && defined(LWAN_HAVE_KQUEUE) #include @@ -126,7 +126,7 @@ static int kevent_ident_cmp(const void *ptr0, const void *ptr1) int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) { struct epoll_event *ev = events; - struct kevent evs[maxevents]; + struct kevent *evs = alloca(sizeof(*evs) * LWAN_MIN(1024, maxevents)); struct timespec tmspec; int i, r; @@ -137,7 +137,7 @@ int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) qsort(evs, (size_t)r, sizeof(struct kevent), kevent_ident_cmp); - uintptr_t last = -1; + uintptr_t last = (uintptr_t)&epoll_no_event_marker; for (i = 0; i < r; i++) { struct kevent *kev = &evs[i]; From 8e86abc9c80adf0631ed1f6beab1f8faed817bf9 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 19 Jun 2024 19:57:35 -0700 Subject: [PATCH 2337/2505] Use doubles instead of floats in pong clock sample --- src/samples/clock/pong.c | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/samples/clock/pong.c b/src/samples/clock/pong.c index 33f8b88cf..0c3c55734 100644 --- a/src/samples/clock/pong.c +++ b/src/samples/clock/pong.c @@ -32,9 +32,9 @@ extern const uint8_t digital_clock_font[10][5]; struct tm* my_localtime(const time_t *t); -static float rand_float(float scale) +static double rand_double(double scale) { - return ((float)rand() / (float)(RAND_MAX)) * scale; + return ((double)rand() / (double)(RAND_MAX)) * scale; } static void pong_time_update(struct pong_time *pong_time) @@ -58,9 +58,9 @@ static void pong_time_update(struct pong_time *pong_time) static void pong_init_internal(struct pong *pong, ge_GIF *gif, bool reset) { - float ball_y = rand_float(16.0f) + 8.0f; + double ball_y = rand_double(16.0f) + 8.0f; struct { - float y, target_y; + double y, target_y; } left, right; if (reset) { @@ -138,12 +138,12 @@ static void pong_draw_time(const struct pong *pong) } } -static float pong_calculate_end_point(const struct pong *pong, bool hit) +static double pong_calculate_end_point(const struct pong *pong, bool hit) { - float x = pong->ball_x.pos; - float y = pong->ball_y.pos; - float vel_x = pong->ball_x.vel; - float vel_y = pong->ball_y.vel; + double x = pong->ball_x.pos; + double y = pong->ball_y.pos; + double vel_x = pong->ball_x.vel; + double vel_y = pong->ball_y.vel; for (;;) { x += vel_x; @@ -160,7 +160,7 @@ static float pong_calculate_end_point(const struct pong *pong, bool hit) } } -static void clamp(float *val, float min, float max) +static void clamp(double *val, double min, double max) { if (*val < min) { *val = min; @@ -190,9 +190,9 @@ uint64_t pong_draw(struct pong *pong) pong->ball_y.vel -= 0.2f; if (pong->ball_x.pos >= 60.0f) - pong->player_right.target_y += 1.0f + rand_float(3); + pong->player_right.target_y += 1.0f + rand_double(3); else - pong->player_left.target_y += 1.0f + rand_float(3); + pong->player_left.target_y += 1.0f + rand_double(3); } else { if (pong->ball_y.vel > 0.5f) pong->ball_y.vel -= 0.2f; @@ -200,9 +200,9 @@ uint64_t pong_draw(struct pong *pong) pong->ball_y.vel += 0.2f; if (pong->ball_x.pos >= 60.0f) - pong->player_right.target_y -= 1.0f + rand_float(3); + pong->player_right.target_y -= 1.0f + rand_double(3); else - pong->player_left.target_y -= 1.0f + rand_float(3); + pong->player_left.target_y -= 1.0f + rand_double(3); } clamp(&pong->player_left.target_y, 0.0f, 24.0f); @@ -216,10 +216,10 @@ uint64_t pong_draw(struct pong *pong) if (pong->ball_y.pos >= 30.0f || pong->ball_y.pos <= 0.0f) pong->ball_y.vel = -pong->ball_y.vel; - if (roundf(pong->ball_x.pos) == 40.0f + rand_float(13)) { + if (roundf(pong->ball_x.pos) == 40.0f + rand_double(13)) { pong->player_left.target_y = pong->ball_y.pos - 3.0f; clamp(&pong->player_left.target_y, 0.0f, 24.0f); - } else if (roundf(pong->ball_x.pos) == 8 + rand_float(13)) { + } else if (roundf(pong->ball_x.pos) == 8 + rand_double(13)) { pong->player_right.target_y = pong->ball_y.pos - 3.0f; clamp(&pong->player_right.target_y, 0.0f, 24.0f); } @@ -254,9 +254,9 @@ uint64_t pong_draw(struct pong *pong) pong_calculate_end_point(pong, pong->player_loss != -1) - 3; if (pong->player_loss == -1) { /* We need to lose */ if (pong->player_left.target_y < 16) - pong->player_left.target_y = 19 + rand_float(5); + pong->player_left.target_y = 19 + rand_double(5); else - pong->player_left.target_y = rand_float(5); + pong->player_left.target_y = rand_double(5); } clamp(&pong->player_left.target_y, 0.0f, 24.0f); @@ -265,9 +265,9 @@ uint64_t pong_draw(struct pong *pong) pong_calculate_end_point(pong, pong->player_loss != 1) - 3; if (pong->player_loss == 1) { /* We need to lose */ if (pong->player_right.target_y < 16) - pong->player_right.target_y = 19 + rand_float(5); + pong->player_right.target_y = 19 + rand_double(5); else - pong->player_right.target_y = rand_float(5); + pong->player_right.target_y = rand_double(5); } clamp(&pong->player_right.target_y, 0.0f, 24.0f); From 5c0b0ce6798c8671e36f354e3698d98a6ea1573b Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 19 Jun 2024 20:00:10 -0700 Subject: [PATCH 2338/2505] Don't use "f" suffix in double values --- src/samples/clock/pong.c | 72 ++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/src/samples/clock/pong.c b/src/samples/clock/pong.c index 0c3c55734..259640ecf 100644 --- a/src/samples/clock/pong.c +++ b/src/samples/clock/pong.c @@ -58,7 +58,7 @@ static void pong_time_update(struct pong_time *pong_time) static void pong_init_internal(struct pong *pong, ge_GIF *gif, bool reset) { - double ball_y = rand_double(16.0f) + 8.0f; + double ball_y = rand_double(16.0) + 8.0; struct { double y, target_y; } left, right; @@ -73,8 +73,8 @@ static void pong_init_internal(struct pong *pong, ge_GIF *gif, bool reset) *pong = (struct pong){ .gif = gif, - .ball_x = {.pos = 31.0f, .vel = 1.0f}, - .ball_y = {.pos = ball_y, .vel = rand() % 2 ? 0.5f : -0.5f}, + .ball_x = {.pos = 31.0, .vel = 1.0}, + .ball_y = {.pos = ball_y, .vel = rand() % 2 ? 0.5 : -0.5}, .player_left = {.y = 8, .target_y = ball_y}, .player_right = {.y = 18, .target_y = ball_y}, .player_loss = 0, @@ -149,13 +149,13 @@ static double pong_calculate_end_point(const struct pong *pong, bool hit) x += vel_x; y += vel_y; if (hit) { - if (x >= 60.0f || x <= 2.0f) + if (x >= 60.0 || x <= 2.0) return y; } else { - if (x >= 62.0f || x <= 0.0f) + if (x >= 62.0 || x <= 0.0) return y; } - if (y >= 30.0f || y <= 0.0f) + if (y >= 30.0 || y <= 0.0) vel_y = -vel_y; } } @@ -179,49 +179,49 @@ uint64_t pong_draw(struct pong *pong) pong->ball_x.pos += pong->ball_x.vel; pong->ball_y.pos += pong->ball_y.vel; - if ((pong->ball_x.pos >= 60.0f && pong->player_loss != 1) || - (pong->ball_x.pos <= 2.0f && pong->player_loss != -1)) { + if ((pong->ball_x.pos >= 60.0 && pong->player_loss != 1) || + (pong->ball_x.pos <= 2.0 && pong->player_loss != -1)) { pong->ball_x.vel = -pong->ball_x.vel; if (rand() % 4 > 0) { if (rand() % 2 == 0) { - if (pong->ball_y.vel > 0.0f && pong->ball_y.vel < 2.5f) - pong->ball_y.vel += 0.2f; - else if (pong->ball_y.vel < 0.0f && pong->ball_y.vel > -2.5f) - pong->ball_y.vel -= 0.2f; + if (pong->ball_y.vel > 0.0 && pong->ball_y.vel < 2.5) + pong->ball_y.vel += 0.2; + else if (pong->ball_y.vel < 0.0 && pong->ball_y.vel > -2.5) + pong->ball_y.vel -= 0.2; - if (pong->ball_x.pos >= 60.0f) - pong->player_right.target_y += 1.0f + rand_double(3); + if (pong->ball_x.pos >= 60.0) + pong->player_right.target_y += 1.0 + rand_double(3); else - pong->player_left.target_y += 1.0f + rand_double(3); + pong->player_left.target_y += 1.0 + rand_double(3); } else { - if (pong->ball_y.vel > 0.5f) - pong->ball_y.vel -= 0.2f; - else if (pong->ball_y.vel < -0.5f) - pong->ball_y.vel += 0.2f; + if (pong->ball_y.vel > 0.5) + pong->ball_y.vel -= 0.2; + else if (pong->ball_y.vel < -0.5) + pong->ball_y.vel += 0.2; - if (pong->ball_x.pos >= 60.0f) - pong->player_right.target_y -= 1.0f + rand_double(3); + if (pong->ball_x.pos >= 60.0) + pong->player_right.target_y -= 1.0 + rand_double(3); else - pong->player_left.target_y -= 1.0f + rand_double(3); + pong->player_left.target_y -= 1.0 + rand_double(3); } - clamp(&pong->player_left.target_y, 0.0f, 24.0f); - clamp(&pong->player_right.target_y, 0.0f, 24.0f); + clamp(&pong->player_left.target_y, 0.0, 24.0); + clamp(&pong->player_right.target_y, 0.0, 24.0); } - } else if ((pong->ball_x.pos > 62.0f && pong->player_loss == 1) || - (pong->ball_x.pos < 1.0f && pong->player_loss == -1)) { + } else if ((pong->ball_x.pos > 62.0 && pong->player_loss == 1) || + (pong->ball_x.pos < 1.0 && pong->player_loss == -1)) { pong_init_internal(pong, pong->gif, true); } - if (pong->ball_y.pos >= 30.0f || pong->ball_y.pos <= 0.0f) + if (pong->ball_y.pos >= 30.0 || pong->ball_y.pos <= 0.0) pong->ball_y.vel = -pong->ball_y.vel; - if (roundf(pong->ball_x.pos) == 40.0f + rand_double(13)) { - pong->player_left.target_y = pong->ball_y.pos - 3.0f; - clamp(&pong->player_left.target_y, 0.0f, 24.0f); + if (roundf(pong->ball_x.pos) == 40.0 + rand_double(13)) { + pong->player_left.target_y = pong->ball_y.pos - 3.0; + clamp(&pong->player_left.target_y, 0.0, 24.0); } else if (roundf(pong->ball_x.pos) == 8 + rand_double(13)) { - pong->player_right.target_y = pong->ball_y.pos - 3.0f; - clamp(&pong->player_right.target_y, 0.0f, 24.0f); + pong->player_right.target_y = pong->ball_y.pos - 3.0; + clamp(&pong->player_right.target_y, 0.0, 24.0); } if (pong->player_left.target_y > pong->player_left.y) @@ -236,7 +236,7 @@ uint64_t pong_draw(struct pong *pong) /* If the ball is in the middle, check if we need to lose and calculate * the endpoint to avoid/hit the ball */ - if (roundf(pong->ball_x.pos) == 32.0f) { + if (roundf(pong->ball_x.pos) == 32.0) { struct pong_time cur_time = {}; pong_time_update(&cur_time); @@ -259,7 +259,7 @@ uint64_t pong_draw(struct pong *pong) pong->player_left.target_y = rand_double(5); } - clamp(&pong->player_left.target_y, 0.0f, 24.0f); + clamp(&pong->player_left.target_y, 0.0, 24.0); } else if (pong->ball_x.vel > 0) { /* Moving to the right */ pong->player_right.target_y = pong_calculate_end_point(pong, pong->player_loss != 1) - 3; @@ -270,10 +270,10 @@ uint64_t pong_draw(struct pong *pong) pong->player_right.target_y = rand_double(5); } - clamp(&pong->player_right.target_y, 0.0f, 24.0f); + clamp(&pong->player_right.target_y, 0.0, 24.0); } - clamp(&pong->ball_y.pos, 0.0f, 30.0f); + clamp(&pong->ball_y.pos, 0.0, 30.0); } draw: From 9982a3fb96497b3a125544e6325b26c49ba00140 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 19 Jun 2024 20:01:37 -0700 Subject: [PATCH 2339/2505] Ensure all types in the pong sample are double and not float/int --- src/samples/clock/pong.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/samples/clock/pong.h b/src/samples/clock/pong.h index 43c367939..6da18b9d1 100644 --- a/src/samples/clock/pong.h +++ b/src/samples/clock/pong.h @@ -37,12 +37,12 @@ struct pong_time { struct pong { ge_GIF *gif; struct { - float pos; - float vel; + double pos; + double vel; } ball_x, ball_y; struct { - int y; - float target_y; + double y; + double target_y; } player_left, player_right; int player_loss; int game_stopped; From a2bc3c6370eaac9120f6fe48ee546b0ca8797707 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 19 Jun 2024 20:03:41 -0700 Subject: [PATCH 2340/2505] More type stuff in pong clock sample --- src/samples/clock/pong.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/samples/clock/pong.c b/src/samples/clock/pong.c index 259640ecf..06d16a7e6 100644 --- a/src/samples/clock/pong.c +++ b/src/samples/clock/pong.c @@ -216,10 +216,10 @@ uint64_t pong_draw(struct pong *pong) if (pong->ball_y.pos >= 30.0 || pong->ball_y.pos <= 0.0) pong->ball_y.vel = -pong->ball_y.vel; - if (roundf(pong->ball_x.pos) == 40.0 + rand_double(13)) { + if (round(pong->ball_x.pos) == 40.0 + rand_double(13)) { pong->player_left.target_y = pong->ball_y.pos - 3.0; clamp(&pong->player_left.target_y, 0.0, 24.0); - } else if (roundf(pong->ball_x.pos) == 8 + rand_double(13)) { + } else if (round(pong->ball_x.pos) == 8 + rand_double(13)) { pong->player_right.target_y = pong->ball_y.pos - 3.0; clamp(&pong->player_right.target_y, 0.0, 24.0); } @@ -236,7 +236,7 @@ uint64_t pong_draw(struct pong *pong) /* If the ball is in the middle, check if we need to lose and calculate * the endpoint to avoid/hit the ball */ - if (roundf(pong->ball_x.pos) == 32.0) { + if (round(pong->ball_x.pos) == 32.0) { struct pong_time cur_time = {}; pong_time_update(&cur_time); @@ -280,8 +280,8 @@ uint64_t pong_draw(struct pong *pong) memset(pong->gif->frame, 0, 64 * 32); pong_draw_net(pong); pong_draw_time(pong); - pong_draw_player(pong, 0, pong->player_left.y); - pong_draw_player(pong, 62, pong->player_right.y); + pong_draw_player(pong, 0, (int)pong->player_left.y); + pong_draw_player(pong, 62, (int)pong->player_right.y); pong_draw_ball(pong); return 8; From be71406cc51967f08a3495890ee215adc02e9ec5 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 19 Jun 2024 20:19:57 -0700 Subject: [PATCH 2341/2505] Export lwan_*_fd symbols --- src/lib/liblwan.sym | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/lib/liblwan.sym b/src/lib/liblwan.sym index d0f59d554..33acabd9c 100644 --- a/src/lib/liblwan.sym +++ b/src/lib/liblwan.sym @@ -46,11 +46,7 @@ global: lwan_append_*_to_strbuf; lwan_tpl_*_is_empty; - lwan_writev; - lwan_send; - lwan_sendfile; - lwan_recv; - lwan_readv; + lwan_*_fd; lwan_strbuf_append_char; lwan_strbuf_append_printf; From 18a3e13f2b434186c9762790220488ba23e0c617 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 19 Jun 2024 22:49:07 -0700 Subject: [PATCH 2342/2505] Be more consistent with async/await coro suspension/error handling --- src/lib/lwan-thread.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 3d62562a7..dd22eddd0 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -733,7 +733,7 @@ static int prepare_awaitv(struct lwan_request *r, *state = (struct awaitv_state){ .num_awaiting = 0, - .request_conn_yield = CONN_CORO_YIELD, + .request_conn_yield = CONN_CORO_SUSPEND, }; clear_awaitv_flags(l->conns, ap); @@ -817,8 +817,12 @@ int lwan_request_awaitv_all(struct lwan_request *r, ...) int ret = prepare_awaitv(r, l, ap, &state); va_end(ap); - if (UNLIKELY(ret < 0)) - return ret; + if (UNLIKELY(ret < 0)) { + errno = -ret; + lwan_status_perror("prepare_awaitv()"); + coro_yield(r->conn->coro, CONN_CORO_ABORT); + __builtin_unreachable(); + } while (state.num_awaiting) { int64_t v = coro_yield(r->conn->coro, state.request_conn_yield); From 18334710c0bbb9b66439d360a780174b18230e0a Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 20 Jun 2024 08:33:48 -0700 Subject: [PATCH 2343/2505] Update liblwan.sym Some symbols were not defined anymore after API changes, and some were defined as static inline functions in a header file, which will never become symbols in the final libraries anyway. --- src/lib/liblwan.sym | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/lib/liblwan.sym b/src/lib/liblwan.sym index 33acabd9c..aa4e03272 100644 --- a/src/lib/liblwan.sym +++ b/src/lib/liblwan.sym @@ -1,7 +1,7 @@ LIBLWAN_1 { global: - lwan_array_append; - lwan_array_init; + lwan_array_append_heap; + lwan_array_append_inline; lwan_array_reset; lwan_array_sort; @@ -41,7 +41,6 @@ global: lwan_tpl_apply_with_buffer; lwan_tpl_compile_file; lwan_tpl_compile_string; - lwan_tpl_compile_string_full; lwan_tpl_free; lwan_append_*_to_strbuf; lwan_tpl_*_is_empty; @@ -51,7 +50,6 @@ global: lwan_strbuf_append_char; lwan_strbuf_append_printf; lwan_strbuf_append_str; - lwan_strbuf_append_strz; lwan_strbuf_free; lwan_strbuf_init; lwan_strbuf_init_with_size; @@ -62,8 +60,6 @@ global: lwan_strbuf_reset; lwan_strbuf_set; lwan_strbuf_set_static; - lwan_strbuf_setz; - lwan_strbuf_set_staticz; lwan_module_info_*; lwan_handler_info_*; From 2591d8be87f5067aa282f31ddd8b0fd2a30db00e Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 20 Jun 2024 08:37:58 -0700 Subject: [PATCH 2344/2505] Make gcov optional for debug builds --- src/cmake/CodeCoverage.cmake | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cmake/CodeCoverage.cmake b/src/cmake/CodeCoverage.cmake index dd2b81702..7101191eb 100644 --- a/src/cmake/CodeCoverage.cmake +++ b/src/cmake/CodeCoverage.cmake @@ -150,9 +150,7 @@ find_program( GENHTML_PATH NAMES genhtml genhtml.perl genhtml.bat ) find_program( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/scripts/test) find_program( CPPFILT_PATH NAMES c++filt ) -if(NOT GCOV_PATH) - message(FATAL_ERROR "gcov not found! Aborting...") -endif() # NOT GCOV_PATH +if(GCOV_PATH) # Check supported compiler (Clang, GNU and Flang) get_property(LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) @@ -749,3 +747,5 @@ function(append_coverage_compiler_flags_to_target name) endfunction() endif() # CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG + +endif() # NOT GCOV_PATH From 03483837a309f6906b01d17f764cef5e0d397bba Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 20 Jun 2024 08:41:42 -0700 Subject: [PATCH 2345/2505] Also export lwan_status_*_debug symbols --- src/lib/liblwan.sym | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/liblwan.sym b/src/lib/liblwan.sym index aa4e03272..53241b454 100644 --- a/src/lib/liblwan.sym +++ b/src/lib/liblwan.sym @@ -30,12 +30,12 @@ global: lwan_set_url_map; - lwan_status_critical; - lwan_status_critical_perror; - lwan_status_error; - lwan_status_info; - lwan_status_perror; - lwan_status_warning; + lwan_status_critical*; + lwan_status_critical_perror*; + lwan_status_error*; + lwan_status_info*; + lwan_status_perror*; + lwan_status_warning*; lwan_tpl_apply; lwan_tpl_apply_with_buffer; From bcffd5c6c61434900e42b392e6dfed7cc1db2608 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 2 Jul 2024 17:33:54 -0700 Subject: [PATCH 2346/2505] Simplify topology-to-schedtbl generation Based on patch by bwitte --- src/lib/lwan-thread.c | 39 ++++++++++++++++----------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index dd22eddd0..302431511 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -1359,7 +1359,7 @@ static bool read_cpu_topology(struct lwan *l, uint32_t siblings[]) fclose(sib); } - /* Perform a sanity check here, as some systems seem to filter out the + /* Perform some validation here, as some systems seem to filter out the * result of sysconf() to obtain the number of configured and online * CPUs but don't bother changing what's available through sysfs as far * as the CPU topology information goes. It's better to fall back to a @@ -1404,8 +1404,11 @@ siblings_to_schedtbl(struct lwan *l, uint32_t siblings[], uint32_t schedtbl[]) } } - if (n_schedtbl != l->available_cpus) - memcpy(schedtbl, seen, l->available_cpus * sizeof(int)); + for (uint32_t i = 0; i < l->available_cpus && n_schedtbl < l->available_cpus; i++) { + if (seen[i] == -1) { + schedtbl[n_schedtbl++] = i; + } + } free(seen); } @@ -1414,31 +1417,21 @@ static bool topology_to_schedtbl(struct lwan *l, uint32_t schedtbl[], uint32_t n_threads) { uint32_t *siblings = calloc(l->available_cpus, sizeof(uint32_t)); + bool ret = false; - if (!siblings) - lwan_status_critical("Could not allocate siblings array"); - - if (read_cpu_topology(l, siblings)) { - uint32_t *affinity = calloc(l->available_cpus, sizeof(uint32_t)); - - if (!affinity) - lwan_status_critical("Could not allocate affinity array"); - - siblings_to_schedtbl(l, siblings, affinity); - - for (uint32_t i = 0; i < n_threads; i++) - schedtbl[i] = affinity[i % l->available_cpus]; + if (siblings) { + if (read_cpu_topology(l, siblings)) { + siblings_to_schedtbl(l, siblings, schedtbl); + ret = true; + } else { + for (uint32_t i = 0; i < n_threads; i++) + schedtbl[i] = (i / 2) % l->thread.count; + } - free(affinity); free(siblings); - return true; } - for (uint32_t i = 0; i < n_threads; i++) - schedtbl[i] = (i / 2) % l->thread.count; - - free(siblings); - return false; + return ret; } static void From dce8c22b1c241363339dceba4c1ded620794cb48 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 6 Jul 2024 10:36:54 -0700 Subject: [PATCH 2347/2505] Disable TLS by default, as it's currently broken mbedTLS changed their API, breaking the TLS support. Disable it for now. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3cc74c2d3..1f37b327b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -78,7 +78,7 @@ if (ZSTD_FOUND) set(LWAN_HAVE_ZSTD 1) endif () -option(ENABLE_TLS "Enable support for TLS (Linux-only)" "ON") +option(ENABLE_TLS "Enable support for TLS (Linux-only)" "OFF") if (ENABLE_TLS) check_include_file(linux/tls.h LWAN_HAVE_LINUX_TLS_H) if (LWAN_HAVE_LINUX_TLS_H) From 13e0c391ba6a9db179c00cab5811c861cb08af07 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 30 Jul 2024 08:36:52 -0700 Subject: [PATCH 2348/2505] Ensure request headers are terminated by CRLF and not just CR Closes #368. --- src/lib/lwan-request.c | 4 ++++ src/scripts/testsuite.py | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index c27bc6503..32f3ff909 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -637,6 +637,10 @@ static ALWAYS_INLINE ssize_t find_headers(char **header_start, next_chr = next_header + HEADER_TERMINATOR_LEN; if (UNLIKELY(next_chr >= buffer_end)) return -1; + + /* Avoid request smuggling. More info: https://github.com/lpereira/lwan/issues/368 */ + if (UNLIKELY(next_header[1] != '\n')) + return -1; } out: diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index 4d8274c5e..71d5ad2cd 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -908,6 +908,17 @@ def test_cache_mmaps_once_even_after_timeout(self): requests.get('http://127.0.0.1:8080/100.html') self.assertEqual(self.count_mmaps('/100.html'), 1) + +class TestHeaderParsing(SocketTest): + def test_check_for_crlf(self): + # https://github.com/lpereira/lwan/issues/368 + req = '''GET /hello HTTP/1.1\r\nHost: a\rXContent-Length: 31\r\n\r\nGET /evil HTTP/1.1\r\nHost: a\r\n\r\n''' + + with self.connect() as sock: + sock.send(req) + response = sock.recv(4096) + self.assertTrue(response.startswith('HTTP/1.1 400 Bad request')) + class TestProxyProtocolRequests(SocketTest): def test_proxy_version1(self): proxy = "PROXY TCP4 192.168.242.221 192.168.242.242 56324 31337\r\n" From d5cb7e10b7a6655714887402f8c019890bf0150c Mon Sep 17 00:00:00 2001 From: Brian Witte Date: Sun, 28 Jul 2024 16:34:45 -0500 Subject: [PATCH 2349/2505] Enhance value_lookup bitwise binary search Improved index tracking logic and added explicit handling of zero cases with n | 1 to avoid undefined behavior in __builtin_clz* functions. --- src/lib/lwan-request.c | 46 ++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 32f3ff909..5fe1de713 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1727,33 +1727,45 @@ void lwan_process_request(struct lwan *l, struct lwan_request *request) log_request(request, status, time_to_read_request, elapsed_time_ms(request_begin_time)); } -static inline void *value_lookup(const struct lwan_key_value_array *array, - const char *key) +static inline const char *value_lookup(const struct lwan_key_value_array *array, + const char *key) { /* Based on https://orlp.net/blog/bitwise-binary-search/ */ const struct lwan_array *la = (const struct lwan_array *)array; + struct lwan_key_value *base = (struct lwan_key_value *)la->base; + struct lwan_key_value k = { .key = (char *)key }; if (LIKELY(la->elements)) { + size_t n = la->elements; + size_t b = 0; + size_t bit; + + /* + * Determine highest power of 2 <= size of array, ensure n is non-zero. + * We use __builtin_clz* to calculate the position of highest set bit. + */ #if __SIZEOF_SIZE_T__ == 8 - const size_t floor = 1ull << (64 - __builtin_clzll(la->elements)); + bit = 1ull << (63 - __builtin_clzll(n | 1)); // 64-bit systems #else - const size_t floor = 1u << (32 - __builtin_clz(la->elements)); + bit = 1u << (31 - __builtin_clz(n | 1)); // 32-bit systems #endif - struct lwan_value *base = (struct lwan_value *)la->base; - struct lwan_key_value k = {.key = (char *)key}; - int64_t b = key_value_compare(&k, &base[la->elements / 2]) > 0 - ? (int64_t)(la->elements - floor) - : -1; - - for (uint64_t bit = floor >> 1; bit != 0; bit >>= 1) { - if (key_value_compare(&k, &base[b + (int64_t)bit]) > 0) - b += (int64_t)bit; - } - if (!key_value_compare(&k, &base[b + 1])) - return base[b + 1].value; + /* + * Bitwise search for the key using a modified lower bound approach. + * The loop uses a bitmask to converge on the index of the correct key. + */ + for (; bit != 0; bit >>= 1) { + size_t i = (b | bit) - 1; // Calculate index using bitwise OR and decrement + if (i < n && key_value_compare(&k, &base[i]) >= 0) { + b |= bit; // Set the bit in b if the condition is met + } + } + // After finding potential index, check if it exactly matches the key. + if (key_value_compare(&k, &base[b]) == 0) { + return base[b].value; + } } - + // If no match, return NULL. return NULL; } From 8d076cb48fc98842eacd0d73209538c08ef7c00b Mon Sep 17 00:00:00 2001 From: Brian Witte Date: Mon, 29 Jul 2024 19:57:53 -0500 Subject: [PATCH 2350/2505] refine bounds checking --- src/lib/lwan-request.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 5fe1de713..8f7aa1d66 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1755,15 +1755,14 @@ static inline const char *value_lookup(const struct lwan_key_value_array *array, * The loop uses a bitmask to converge on the index of the correct key. */ for (; bit != 0; bit >>= 1) { - size_t i = (b | bit) - 1; // Calculate index using bitwise OR and decrement - if (i < n && key_value_compare(&k, &base[i]) >= 0) { - b |= bit; // Set the bit in b if the condition is met - } - } - // After finding potential index, check if it exactly matches the key. - if (key_value_compare(&k, &base[b]) == 0) { - return base[b].value; + size_t i = b | bit; + // Check boundary and compare + if (i <= n && key_value_compare(&k, &base[i - 1]) >= 0) + b = i; // Update index } + // Final match check + if (b <= n && key_value_compare(&k, &base[b - 1]) == 0) + return base[b - 1].value; // Return value } // If no match, return NULL. return NULL; From ceed720ee84d0229d19a3183fb7b67bd2042cef5 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 30 Jul 2024 13:32:59 -0700 Subject: [PATCH 2351/2505] Revert "Use a branch-free binary search for post and query params, and cookies" This reverts commit 29a64cecc78d96b9bf164c4bc8fd4da17acf6378. --- src/lib/lwan-request.c | 34 ++++++---------------------------- 1 file changed, 6 insertions(+), 28 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 8f7aa1d66..d4fc6df4a 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1730,41 +1730,19 @@ void lwan_process_request(struct lwan *l, struct lwan_request *request) static inline const char *value_lookup(const struct lwan_key_value_array *array, const char *key) { - /* Based on https://orlp.net/blog/bitwise-binary-search/ */ const struct lwan_array *la = (const struct lwan_array *)array; struct lwan_key_value *base = (struct lwan_key_value *)la->base; struct lwan_key_value k = { .key = (char *)key }; if (LIKELY(la->elements)) { - size_t n = la->elements; - size_t b = 0; - size_t bit; - - /* - * Determine highest power of 2 <= size of array, ensure n is non-zero. - * We use __builtin_clz* to calculate the position of highest set bit. - */ -#if __SIZEOF_SIZE_T__ == 8 - bit = 1ull << (63 - __builtin_clzll(n | 1)); // 64-bit systems -#else - bit = 1u << (31 - __builtin_clz(n | 1)); // 32-bit systems -#endif + struct lwan_key_value k = { .key = (char *)key }; + struct lwan_key_value *entry; - /* - * Bitwise search for the key using a modified lower bound approach. - * The loop uses a bitmask to converge on the index of the correct key. - */ - for (; bit != 0; bit >>= 1) { - size_t i = b | bit; - // Check boundary and compare - if (i <= n && key_value_compare(&k, &base[i - 1]) >= 0) - b = i; // Update index - } - // Final match check - if (b <= n && key_value_compare(&k, &base[b - 1]) == 0) - return base[b - 1].value; // Return value + entry = bsearch(&k, la->base, la->elements, sizeof(k), key_value_compare); + if (LIKELY(entry)) + return entry->value; } - // If no match, return NULL. + return NULL; } From b193df981fa767537dae5c5ad990e1b71f88da57 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 2 Aug 2024 07:44:02 -0700 Subject: [PATCH 2352/2505] Also check for \r\n in the last header --- src/lib/lwan-request.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index d4fc6df4a..c13b5dd2c 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -614,12 +614,19 @@ static ALWAYS_INLINE ssize_t find_headers(char **header_start, return -1; if (next_chr == next_header) { - if (buffer_end - next_chr >= (ptrdiff_t)HEADER_TERMINATOR_LEN) { - STRING_SWITCH_SMALL (next_header) { - case STR2_INT('\r', '\n'): - *next_request = next_header + HEADER_TERMINATOR_LEN; - } - } + ptrdiff_t remaining = buffer_end - next_chr; + + if (UNLIKELY(remaining < (ptrdiff_t)HEADER_TERMINATOR_LEN)) + return -1; + + /* next_chr[0] is guaranteed to be '\r'. See comment below regarding + * request smuggling. */ + if (UNLIKELY(next_chr[1] != '\n')) + return -1; + + if (remaining > (ptrdiff_t)HEADER_TERMINATOR_LEN) + *next_request = next_header + HEADER_TERMINATOR_LEN; + goto out; } From e693e0261ce00d2ca0b70de1188d856d70e6614f Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 2 Aug 2024 07:44:46 -0700 Subject: [PATCH 2353/2505] Add query string test harness/test --- src/bin/testrunner/main.c | 15 +++++++++++++++ src/bin/testrunner/testrunner.conf | 2 ++ src/scripts/testsuite.py | 20 ++++++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/src/bin/testrunner/main.c b/src/bin/testrunner/main.c index 56a921102..7c5e7a7ab 100644 --- a/src/bin/testrunner/main.c +++ b/src/bin/testrunner/main.c @@ -80,6 +80,21 @@ LWAN_HANDLER(test_server_sent_event) return HTTP_OK; } +LWAN_HANDLER(get_query_string) +{ + const char *q = lwan_request_get_query_param(request, "q"); + if (!q) + return HTTP_BAD_REQUEST; + + const char *v = lwan_request_get_query_param(request, q); + if (!v) + return HTTP_NOT_FOUND; + + response->mime_type = "text/plain"; + lwan_strbuf_printf(response->buffer, "%s = %s", q, v); + return HTTP_OK; +} + LWAN_HANDLER(test_proxy) { struct lwan_key_value *headers = coro_malloc(request->conn->coro, sizeof(*headers) * 2); diff --git a/src/bin/testrunner/testrunner.conf b/src/bin/testrunner/testrunner.conf index 5c9ff9673..97e254479 100644 --- a/src/bin/testrunner/testrunner.conf +++ b/src/bin/testrunner/testrunner.conf @@ -56,6 +56,8 @@ site { &test_post_big /post/big + &get_query_string /get-query-string + redirect /elsewhere { to = http://lwan.ws } redirect /redirect307 { diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index 71d5ad2cd..44571f752 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -708,6 +708,26 @@ def test_valid_creds(self): self.assertEqual(r.text, 'Hello, world!') +class TestQueryString(LwanTest): + def test_query_string(self): + r = requests.get('http://localhost:8080/get-query-string') + self.assertEqual(r.status_code, 400) + + r = requests.get('http://localhost:8080/get-query-string?q=a') + self.assertResponse404(r) + + r = requests.get('http://localhost:8080/get-query-string?q=a&a=10&b=20') + self.assertHttpResponseValid(r, 200, 'text/plain') + self.assertEqual(r.text, 'a = 10') + + r = requests.get('http://localhost:8080/get-query-string?b=20&q=b&a=10') + self.assertHttpResponseValid(r, 200, 'text/plain') + self.assertEqual(r.text, 'b = 20') + + r = requests.get('http://localhost:8080/get-query-string?b=20&q=a&c=30') + self.assertResponse404(r) + + class TestHelloWorld(LwanTest): def test_request_id(self): all_request_ids = set() From 4eb7d8daafb7a8472087dba24559b7f14a040121 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 3 Aug 2024 12:49:41 -0700 Subject: [PATCH 2354/2505] Fix compiler warnings after reverting the bsearch patches --- src/lib/lwan-request.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index c13b5dd2c..463c80559 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1738,8 +1738,6 @@ static inline const char *value_lookup(const struct lwan_key_value_array *array, const char *key) { const struct lwan_array *la = (const struct lwan_array *)array; - struct lwan_key_value *base = (struct lwan_key_value *)la->base; - struct lwan_key_value k = { .key = (char *)key }; if (LIKELY(la->elements)) { struct lwan_key_value k = { .key = (char *)key }; From 60c708933447c516f706463ea283195fe460f736 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 3 Aug 2024 13:00:33 -0700 Subject: [PATCH 2355/2505] Fix unit tests --- src/lib/lwan-cache.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-cache.c b/src/lib/lwan-cache.c index 3e1ce6152..4c07f6f66 100644 --- a/src/lib/lwan-cache.c +++ b/src/lib/lwan-cache.c @@ -199,7 +199,11 @@ struct cache_entry *cache_get_and_ref_entry_with_ctx(struct cache *cache, assert(cache); assert(error); - assert(key); +#ifndef NDEBUG + if (cache->key.copy != identity_key_copy) { + assert(key); + } +#endif *error = 0; From 5844693171e161740d7175a15d04fad4757d10c2 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 4 Aug 2024 17:26:21 -0700 Subject: [PATCH 2356/2505] Add regression test case from oss-fuzz The recently-reverted value_lookup() patch fixed an issue found by oss-fuzz back in June. Add the test-case to our regression tests. https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=69572 --- ...usterfuzz-testcase-minimized-request_fuzzer-4811721351954432 | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fuzz/regression/clusterfuzz-testcase-minimized-request_fuzzer-4811721351954432 diff --git a/fuzz/regression/clusterfuzz-testcase-minimized-request_fuzzer-4811721351954432 b/fuzz/regression/clusterfuzz-testcase-minimized-request_fuzzer-4811721351954432 new file mode 100644 index 000000000..061489462 --- /dev/null +++ b/fuzz/regression/clusterfuzz-testcase-minimized-request_fuzzer-4811721351954432 @@ -0,0 +1,2 @@ +GET / HTTP/1.0 Cookie: N + From b40a19d8b9180b5faf0877cb9886022dcde9ffee Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 4 Aug 2024 17:34:56 -0700 Subject: [PATCH 2357/2505] Add search paths for MariaDB libraries --- src/samples/techempower/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/samples/techempower/CMakeLists.txt b/src/samples/techempower/CMakeLists.txt index b220c2ca9..c739f510e 100644 --- a/src/samples/techempower/CMakeLists.txt +++ b/src/samples/techempower/CMakeLists.txt @@ -4,6 +4,8 @@ pkg_check_modules(SQLITE sqlite3>=3.6.20) find_path(MYSQL_INCLUDE_DIR mysql.h /usr/local/include/mysql /usr/include/mysql + /usr/local/include/mariadb + /usr/include/mariadb ) if (MYSQL_INCLUDE_DIR AND SQLITE_FOUND) From e5a6b6ce03bd36f7eaae0f7cee078a20827e1b1f Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 4 Aug 2024 17:38:15 -0700 Subject: [PATCH 2358/2505] Use MariaDB instead of MySQL for techempower benchmarks (This will enable us to use some MariaDB-specific APIs in the future, such as its async APIs!) --- src/samples/techempower/CMakeLists.txt | 76 ++++++++++---------------- 1 file changed, 28 insertions(+), 48 deletions(-) diff --git a/src/samples/techempower/CMakeLists.txt b/src/samples/techempower/CMakeLists.txt index c739f510e..e729d9857 100644 --- a/src/samples/techempower/CMakeLists.txt +++ b/src/samples/techempower/CMakeLists.txt @@ -1,52 +1,32 @@ include(FindPkgConfig) pkg_check_modules(SQLITE sqlite3>=3.6.20) - -find_path(MYSQL_INCLUDE_DIR mysql.h - /usr/local/include/mysql - /usr/include/mysql - /usr/local/include/mariadb - /usr/include/mariadb -) - -if (MYSQL_INCLUDE_DIR AND SQLITE_FOUND) - message(STATUS "Found MySQL includes at ${MYSQL_INCLUDE_DIR}") - include_directories(AFTER ${MYSQL_INCLUDE_DIR}) - - set(MYSQL_NAMES mysqlclient mysqlclient_r) - find_library(MYSQL_LIBRARY - NAMES ${MYSQL_NAMES} - PATH_SUFFIXES mysql - ) - - if (MYSQL_LIBRARY) - message(STATUS "Found MySQL client library at ${MYSQL_LIBRARY}") - - add_executable(techempower - techempower.c - json.c - database.c - ) - - target_link_libraries(techempower - ${LWAN_COMMON_LIBS} - ${ADDITIONAL_LIBRARIES} - ${SQLITE_LIBRARIES} - ${SQLITE_LDFLAGS} - ${MYSQL_LIBRARY} - ) - include_directories(${SQLITE_INCLUDE_DIRS}) - include_directories(BEFORE ${CMAKE_BINARY_DIR}) - - if (${CMAKE_BUILD_TYPE} MATCHES "Coverage") - find_package(PythonInterp 3) - - if (PYTHONINTERP_FOUND) - add_dependencies(generate-coverage techempower) - endif() - endif () - endif () -endif () - -if (NOT MYSQL_LIBRARY) +pkg_check_modules(MARIADB mariadb>=3.3) + +if (MARIADB_FOUND AND SQLITE_FOUND) + add_executable(techempower + techempower.c + json.c + database.c + ) + + target_link_libraries(techempower + ${LWAN_COMMON_LIBS} + ${ADDITIONAL_LIBRARIES} + ${SQLITE_LIBRARIES} + ${SQLITE_LDFLAGS} + ${MARIADB_LDFLAGS} + ${MARIADB_LIBRARIES} + ) + include_directories(${SQLITE_INCLUDE_DIRS} ${MARIADB_INCLUDE_DIRS}) + include_directories(BEFORE ${CMAKE_BINARY_DIR}) + + if (${CMAKE_BUILD_TYPE} MATCHES "Coverage") + find_package(PythonInterp 3) + + if (PYTHONINTERP_FOUND) + add_dependencies(generate-coverage techempower) + endif() + endif () +else () message(STATUS "Not building benchmark suite: database libraries not found.") endif () From 22f1471cc66eee886640872e503961dbb9cbd512 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 4 Aug 2024 17:43:24 -0700 Subject: [PATCH 2359/2505] Use new CMake python-finding package --- src/bin/testrunner/CMakeLists.txt | 6 ++---- src/samples/techempower/CMakeLists.txt | 4 +--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/bin/testrunner/CMakeLists.txt b/src/bin/testrunner/CMakeLists.txt index ea8a86025..9bb738aa9 100644 --- a/src/bin/testrunner/CMakeLists.txt +++ b/src/bin/testrunner/CMakeLists.txt @@ -8,11 +8,9 @@ target_link_libraries(testrunner ) if (${CMAKE_BUILD_TYPE} MATCHES "Coverage") - find_package(PythonInterp 3) - - if (PYTHONINTERP_FOUND) + if (Python3_Interpreter_FOUND) setup_target_for_coverage(generate-coverage - "${PYTHON_EXECUTABLE} ${CMAKE_SOURCE_DIR}/src/scripts/testsuite.py -v ${CMAKE_BINARY_DIR}" + "${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/src/scripts/testsuite.py -v ${CMAKE_BINARY_DIR}" coverage ) add_dependencies(generate-coverage testrunner) diff --git a/src/samples/techempower/CMakeLists.txt b/src/samples/techempower/CMakeLists.txt index e729d9857..d461489ec 100644 --- a/src/samples/techempower/CMakeLists.txt +++ b/src/samples/techempower/CMakeLists.txt @@ -21,9 +21,7 @@ if (MARIADB_FOUND AND SQLITE_FOUND) include_directories(BEFORE ${CMAKE_BINARY_DIR}) if (${CMAKE_BUILD_TYPE} MATCHES "Coverage") - find_package(PythonInterp 3) - - if (PYTHONINTERP_FOUND) + if (Python3_Interpreter_FOUND) add_dependencies(generate-coverage techempower) endif() endif () From 8f27530373fba4a9da86e2e91f7b011f858e22bd Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 5 Aug 2024 18:18:49 -0700 Subject: [PATCH 2360/2505] Use strpbrk() instead of broken optimized finc_pct_or_plus() --- src/lib/lwan-request.c | 69 +----------------------------------------- 1 file changed, 1 insertion(+), 68 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 463c80559..920d2050f 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -266,73 +266,6 @@ static ALWAYS_INLINE char *identify_http_method(struct lwan_request *request, return NULL; } -/* has_zero() routines stolen from the Bit Twiddling Hacks page */ -static ALWAYS_INLINE uint64_t has_zero64(uint64_t v) -{ - return (v - 0x0101010101010101ull) & ~v & 0x8080808080808080ull; -} - -static ALWAYS_INLINE uint32_t has_zero32(uint64_t v) -{ - return (v - 0x01010101u) & ~v & 0x80808080u; -} - -static char *find_pct_or_plus(const char *input) -{ - const uint64_t mask_plus64 = '+' * 0x0101010101010101ull; - const uint64_t mask_pct64 = '%' * 0x0101010101010101ull; - const uint32_t mask_plus32 = '+' * 0x01010101u; - const uint32_t mask_pct32 = '%' * 0x01010101u; - char *str = (char *)input; - - while (true) { - uint64_t v = string_as_uint64(str); - uint64_t has_plus = has_zero64(v ^ mask_plus64); - uint64_t has_pct = has_zero64(v ^ mask_pct64); - uint64_t has_zero = has_zero64(v); - uint64_t m = LWAN_MAX(has_plus, has_pct); - - if (has_zero && LWAN_MAX(has_zero, m) == has_zero) { - if (__builtin_ctzll(has_zero) / 8 < 4) - goto check_small; - goto check_at_least_four; - } - - if (m) { - return str + __builtin_ctzll(m) / 8; - } - - str += 8; - } - -check_at_least_four: { - uint32_t v = string_as_uint32(str); - uint32_t has_plus = has_zero32(v ^ mask_plus32); - uint32_t has_pct = has_zero32(v ^ mask_pct32); - uint32_t has_zero = has_zero32(v); - uint32_t m = LWAN_MAX(has_plus, has_pct); - - if (has_zero && LWAN_MAX(has_zero, m) == has_zero) { - return NULL; - } - - if (m) { - return str + __builtin_ctz(m) / 8; - } - - str += 4; -} - -check_small: - while (*str) { - if (*str == '%' || *str == '+') - return str; - str++; - } - - return NULL; -} - __attribute__((nonnull(1))) static ssize_t url_decode(char *str) { static const unsigned char tbl1[256] = { @@ -353,7 +286,7 @@ __attribute__((nonnull(1))) static ssize_t url_decode(char *str) const char *inptr = str; char *outptr = str; - for (char *p = find_pct_or_plus(inptr); p; p = find_pct_or_plus(inptr)) { + for (char *p = strpbrk(inptr, "%+"); p; p = strpbrk(inptr, "%+")) { const ptrdiff_t diff = p - inptr; if (diff) outptr = mempmove(outptr, inptr, (size_t)diff); From 7bc21677cecd5dda07aa2a227e185dbac60f7e45 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 5 Aug 2024 18:19:43 -0700 Subject: [PATCH 2361/2505] Some code coverage fixes This is still broken but it's already better --- CMakeLists.txt | 1 - src/bin/testrunner/CMakeLists.txt | 11 +++++++---- src/cmake/CodeCoverage.cmake | 2 +- src/scripts/testsuite.py | 13 ++++++++++--- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f37b327b..c1e913a75 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,6 @@ include(CheckFunctionExists) include(CheckSymbolExists) include(CheckIncludeFile) include(CheckIncludeFiles) -include(CodeCoverage) include(EnableCFlag) include(FindPkgConfig) include(TrySanitizer) diff --git a/src/bin/testrunner/CMakeLists.txt b/src/bin/testrunner/CMakeLists.txt index 9bb738aa9..f815078cf 100644 --- a/src/bin/testrunner/CMakeLists.txt +++ b/src/bin/testrunner/CMakeLists.txt @@ -8,12 +8,15 @@ target_link_libraries(testrunner ) if (${CMAKE_BUILD_TYPE} MATCHES "Coverage") + include(CodeCoverage) + if (Python3_Interpreter_FOUND) - setup_target_for_coverage(generate-coverage - "${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/src/scripts/testsuite.py -v ${CMAKE_BINARY_DIR}" - coverage + setup_target_for_coverage_lcov( + NAME generate-coverage + EXECUTABLE ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/src/scripts/testsuite.py -v ${CMAKE_BINARY_DIR} + DEPENDENCIES testrunner + BASE_DIRECTORY "${CMAKE_SOURCE_DIR}" ) - add_dependencies(generate-coverage testrunner) message(STATUS "Python found; generate-coverage target enabled") else () message(STATUS "Python not found; coverage report disabled") diff --git a/src/cmake/CodeCoverage.cmake b/src/cmake/CodeCoverage.cmake index 7101191eb..1b397a06c 100644 --- a/src/cmake/CodeCoverage.cmake +++ b/src/cmake/CodeCoverage.cmake @@ -140,7 +140,7 @@ include(CMakeParseArguments) option(CODE_COVERAGE_VERBOSE "Verbose information" FALSE) get_property(GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) -if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG) +if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG OR CMAKE_BUILD_TYPE STREQUAL "Coverage") # Check prereqs find_program( GCOV_PATH gcov ) diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index 44571f752..36954769c 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -24,6 +24,13 @@ BUILD_DIR = arg sys.argv.remove(arg) +HOME_DIR = "." +with open(f"{BUILD_DIR}/CMakeCache.txt", "r") as cache: + for line in cache.readlines(): + if line.startswith("CMAKE_HOME_DIRECTORY:INTERNAL"): + _, HOME_DIR = line.strip().split("=") + break + if os.getenv('REQUESTS_DEBUG'): logging.basicConfig() logging.getLogger().setLevel(logging.DEBUG) @@ -62,7 +69,7 @@ def setUp(self, env=None, harness='testrunner'): self.files_to_remove = [] for file_to_copy in self.files_to_copy[harness]: base = os.path.basename(file_to_copy) - shutil.copyfile(file_to_copy, base) + shutil.copyfile(f'{HOME_DIR}/{file_to_copy}', base) self.files_to_remove.append(base) open('htpasswd', 'w').close() @@ -1041,14 +1048,14 @@ def run_test(self, contents): @staticmethod def wrap(name): - with open(os.path.join("fuzz", "regression", name), "rb") as f: + with open(os.path.join(HOME_DIR, "fuzz", "regression", name), "rb") as f: contents = str(f.read(), "latin-1") def run_test_wrapped(self): return self.run_test(contents) return run_test_wrapped def only_request_fuzzer_regression(): - for path in os.listdir("fuzz/regression"): + for path in os.listdir(f"{HOME_DIR}/fuzz/regression"): if not "request_fuzzer" in path: continue if path.startswith(("clusterfuzz-", "crash-")): From 11ff0ea3948e79ab2808975076f3f33014bc01bd Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 5 Aug 2024 18:25:26 -0700 Subject: [PATCH 2362/2505] Revert some testsuite.py changes (I really need to spend more time testing these things. I'm just too tired.) --- src/scripts/testsuite.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/scripts/testsuite.py b/src/scripts/testsuite.py index 36954769c..44571f752 100755 --- a/src/scripts/testsuite.py +++ b/src/scripts/testsuite.py @@ -24,13 +24,6 @@ BUILD_DIR = arg sys.argv.remove(arg) -HOME_DIR = "." -with open(f"{BUILD_DIR}/CMakeCache.txt", "r") as cache: - for line in cache.readlines(): - if line.startswith("CMAKE_HOME_DIRECTORY:INTERNAL"): - _, HOME_DIR = line.strip().split("=") - break - if os.getenv('REQUESTS_DEBUG'): logging.basicConfig() logging.getLogger().setLevel(logging.DEBUG) @@ -69,7 +62,7 @@ def setUp(self, env=None, harness='testrunner'): self.files_to_remove = [] for file_to_copy in self.files_to_copy[harness]: base = os.path.basename(file_to_copy) - shutil.copyfile(f'{HOME_DIR}/{file_to_copy}', base) + shutil.copyfile(file_to_copy, base) self.files_to_remove.append(base) open('htpasswd', 'w').close() @@ -1048,14 +1041,14 @@ def run_test(self, contents): @staticmethod def wrap(name): - with open(os.path.join(HOME_DIR, "fuzz", "regression", name), "rb") as f: + with open(os.path.join("fuzz", "regression", name), "rb") as f: contents = str(f.read(), "latin-1") def run_test_wrapped(self): return self.run_test(contents) return run_test_wrapped def only_request_fuzzer_regression(): - for path in os.listdir(f"{HOME_DIR}/fuzz/regression"): + for path in os.listdir("fuzz/regression"): if not "request_fuzzer" in path: continue if path.startswith(("clusterfuzz-", "crash-")): From bcec1ed96f2aadb32a6ad86a0e5a7af45573107e Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 7 Aug 2024 01:21:04 +0000 Subject: [PATCH 2363/2505] Bundle libucontext to allow building without internet connection --- README.md | 8 - src/3rdparty/libucontext/LICENSE | 9 + src/3rdparty/libucontext/Makefile | 292 ++++++++++++++++++ src/3rdparty/libucontext/NEWS | 133 ++++++++ src/3rdparty/libucontext/README.md | 105 +++++++ src/3rdparty/libucontext/VERSION | 1 + src/3rdparty/libucontext/arch/aarch64/defs.h | 27 ++ .../libucontext/arch/aarch64/getcontext.S | 61 ++++ .../arch/aarch64/include/libucontext/bits.h | 34 ++ .../libucontext/arch/aarch64/makecontext.c | 62 ++++ .../libucontext/arch/aarch64/setcontext.S | 53 ++++ .../libucontext/arch/aarch64/swapcontext.S | 69 +++++ .../libucontext/arch/aarch64/trampoline.c | 3 + src/3rdparty/libucontext/arch/arm/defs.h | 15 + .../libucontext/arch/arm/getcontext.S | 49 +++ .../arch/arm/include/libucontext/bits.h | 30 ++ .../libucontext/arch/arm/makecontext.c | 62 ++++ .../libucontext/arch/arm/setcontext.S | 43 +++ .../libucontext/arch/arm/swapcontext.S | 51 +++ .../libucontext/arch/arm/trampoline.c | 3 + .../libucontext/arch/common/common-defs.h | 61 ++++ .../arch/common/common-trampoline.c | 29 ++ .../arch/common/include/libucontext/bits.h | 13 + .../libucontext/arch/loongarch64/defs.h | 76 +++++ .../arch/loongarch64/freestanding/bits.h | 43 +++ .../libucontext/arch/loongarch64/getcontext.S | 46 +++ .../arch/loongarch64/makecontext.S | 108 +++++++ .../libucontext/arch/loongarch64/setcontext.S | 55 ++++ .../arch/loongarch64/startcontext.S | 33 ++ .../arch/loongarch64/swapcontext.S | 89 ++++++ src/3rdparty/libucontext/arch/m68k/defs.h | 34 ++ .../libucontext/arch/m68k/getcontext.S | 30 ++ .../arch/m68k/include/libucontext/bits.h | 28 ++ .../libucontext/arch/m68k/makecontext.c | 61 ++++ .../libucontext/arch/m68k/setcontext.S | 31 ++ .../libucontext/arch/m68k/swapcontext.S | 40 +++ .../libucontext/arch/m68k/trampoline.c | 3 + src/3rdparty/libucontext/arch/mips/defs.h | 94 ++++++ .../libucontext/arch/mips/getcontext.S | 47 +++ .../arch/mips/include/libucontext/bits.h | 27 ++ .../libucontext/arch/mips/makecontext.S | 101 ++++++ .../libucontext/arch/mips/setcontext.S | 51 +++ .../libucontext/arch/mips/startcontext.S | 35 +++ .../libucontext/arch/mips/swapcontext.S | 82 +++++ src/3rdparty/libucontext/arch/mips64/defs.h | 92 ++++++ .../libucontext/arch/mips64/getcontext.S | 47 +++ .../arch/mips64/include/libucontext/bits.h | 47 +++ .../libucontext/arch/mips64/makecontext.S | 107 +++++++ .../libucontext/arch/mips64/setcontext.S | 55 ++++ .../libucontext/arch/mips64/startcontext.S | 35 +++ .../libucontext/arch/mips64/swapcontext.S | 87 ++++++ src/3rdparty/libucontext/arch/or1k/defs.h | 22 ++ .../libucontext/arch/or1k/getcontext.S | 39 +++ .../arch/or1k/include/libucontext/bits.h | 28 ++ .../libucontext/arch/or1k/makecontext.c | 61 ++++ .../libucontext/arch/or1k/setcontext.S | 48 +++ .../libucontext/arch/or1k/swapcontext.S | 68 ++++ .../libucontext/arch/or1k/trampoline.c | 3 + src/3rdparty/libucontext/arch/ppc/defs.h | 64 ++++ .../libucontext/arch/ppc/getcontext.S | 22 ++ .../libucontext/arch/ppc/makecontext.c | 61 ++++ .../libucontext/arch/ppc/retfromsyscall.c | 25 ++ .../libucontext/arch/ppc/setcontext.S | 23 ++ .../libucontext/arch/ppc/startcontext.S | 28 ++ .../libucontext/arch/ppc/swapcontext.S | 29 ++ src/3rdparty/libucontext/arch/ppc64/defs.h | 67 ++++ .../libucontext/arch/ppc64/getcontext.S | 27 ++ .../libucontext/arch/ppc64/makecontext.c | 62 ++++ .../libucontext/arch/ppc64/retfromsyscall.c | 25 ++ .../libucontext/arch/ppc64/setcontext.S | 28 ++ .../libucontext/arch/ppc64/startcontext.S | 33 ++ .../libucontext/arch/ppc64/swapcontext.S | 34 ++ src/3rdparty/libucontext/arch/riscv32/defs.h | 55 ++++ .../libucontext/arch/riscv32/getcontext.S | 45 +++ .../arch/riscv32/include/libucontext/bits.h | 48 +++ .../libucontext/arch/riscv32/makecontext.c | 62 ++++ .../libucontext/arch/riscv32/setcontext.S | 56 ++++ .../libucontext/arch/riscv32/swapcontext.S | 81 +++++ .../libucontext/arch/riscv32/trampoline.c | 3 + src/3rdparty/libucontext/arch/riscv64/defs.h | 55 ++++ .../libucontext/arch/riscv64/getcontext.S | 45 +++ .../arch/riscv64/include/libucontext/bits.h | 48 +++ .../libucontext/arch/riscv64/makecontext.c | 62 ++++ .../libucontext/arch/riscv64/setcontext.S | 56 ++++ .../libucontext/arch/riscv64/swapcontext.S | 81 +++++ .../libucontext/arch/riscv64/trampoline.c | 3 + src/3rdparty/libucontext/arch/s390x/defs.h | 15 + .../libucontext/arch/s390x/getcontext.S | 26 ++ .../arch/s390x/include/libucontext/bits.h | 41 +++ .../libucontext/arch/s390x/makecontext.c | 68 ++++ .../libucontext/arch/s390x/setcontext.S | 25 ++ .../libucontext/arch/s390x/startcontext.S | 30 ++ .../libucontext/arch/s390x/swapcontext.S | 30 ++ src/3rdparty/libucontext/arch/sh/defs.h | 20 ++ src/3rdparty/libucontext/arch/sh/getcontext.S | 56 ++++ .../arch/sh/include/libucontext/bits.h | 29 ++ .../libucontext/arch/sh/makecontext.c | 59 ++++ src/3rdparty/libucontext/arch/sh/setcontext.S | 60 ++++ .../libucontext/arch/sh/swapcontext.S | 95 ++++++ src/3rdparty/libucontext/arch/sh/trampoline.c | 3 + src/3rdparty/libucontext/arch/x86/defs.h | 65 ++++ .../libucontext/arch/x86/getcontext.S | 50 +++ .../arch/x86/include/libucontext/bits.h | 48 +++ .../libucontext/arch/x86/makecontext.c | 59 ++++ .../libucontext/arch/x86/setcontext.S | 45 +++ .../libucontext/arch/x86/swapcontext.S | 73 +++++ .../libucontext/arch/x86/trampoline.c | 3 + src/3rdparty/libucontext/arch/x86_64/defs.h | 105 +++++++ .../libucontext/arch/x86_64/getcontext.S | 49 +++ .../arch/x86_64/include/libucontext/bits.h | 64 ++++ .../libucontext/arch/x86_64/makecontext.c | 80 +++++ .../libucontext/arch/x86_64/setcontext.S | 46 +++ .../libucontext/arch/x86_64/swapcontext.S | 75 +++++ .../libucontext/arch/x86_64/trampoline.c | 3 + src/3rdparty/libucontext/doc/libucontext.scd | 174 +++++++++++ src/3rdparty/libucontext/doc/meson.build | 21 ++ .../examples/cooperative_threading.c | 94 ++++++ .../include/libucontext/libucontext.h | 20 ++ src/3rdparty/libucontext/libucontext.pc.in | 9 + src/3rdparty/libucontext/libucontext_posix.c | 59 ++++ src/3rdparty/libucontext/meson.build | 172 +++++++++++ src/3rdparty/libucontext/meson_options.txt | 8 + src/3rdparty/libucontext/test_libucontext.c | 102 ++++++ .../libucontext/test_libucontext_posix.c | 102 ++++++ src/lib/CMakeLists.txt | 8 +- 125 files changed, 6373 insertions(+), 12 deletions(-) create mode 100644 src/3rdparty/libucontext/LICENSE create mode 100644 src/3rdparty/libucontext/Makefile create mode 100644 src/3rdparty/libucontext/NEWS create mode 100644 src/3rdparty/libucontext/README.md create mode 100644 src/3rdparty/libucontext/VERSION create mode 100644 src/3rdparty/libucontext/arch/aarch64/defs.h create mode 100644 src/3rdparty/libucontext/arch/aarch64/getcontext.S create mode 100644 src/3rdparty/libucontext/arch/aarch64/include/libucontext/bits.h create mode 100644 src/3rdparty/libucontext/arch/aarch64/makecontext.c create mode 100644 src/3rdparty/libucontext/arch/aarch64/setcontext.S create mode 100644 src/3rdparty/libucontext/arch/aarch64/swapcontext.S create mode 100644 src/3rdparty/libucontext/arch/aarch64/trampoline.c create mode 100644 src/3rdparty/libucontext/arch/arm/defs.h create mode 100644 src/3rdparty/libucontext/arch/arm/getcontext.S create mode 100644 src/3rdparty/libucontext/arch/arm/include/libucontext/bits.h create mode 100644 src/3rdparty/libucontext/arch/arm/makecontext.c create mode 100644 src/3rdparty/libucontext/arch/arm/setcontext.S create mode 100644 src/3rdparty/libucontext/arch/arm/swapcontext.S create mode 100644 src/3rdparty/libucontext/arch/arm/trampoline.c create mode 100644 src/3rdparty/libucontext/arch/common/common-defs.h create mode 100644 src/3rdparty/libucontext/arch/common/common-trampoline.c create mode 100644 src/3rdparty/libucontext/arch/common/include/libucontext/bits.h create mode 100644 src/3rdparty/libucontext/arch/loongarch64/defs.h create mode 100644 src/3rdparty/libucontext/arch/loongarch64/freestanding/bits.h create mode 100644 src/3rdparty/libucontext/arch/loongarch64/getcontext.S create mode 100644 src/3rdparty/libucontext/arch/loongarch64/makecontext.S create mode 100644 src/3rdparty/libucontext/arch/loongarch64/setcontext.S create mode 100644 src/3rdparty/libucontext/arch/loongarch64/startcontext.S create mode 100644 src/3rdparty/libucontext/arch/loongarch64/swapcontext.S create mode 100644 src/3rdparty/libucontext/arch/m68k/defs.h create mode 100644 src/3rdparty/libucontext/arch/m68k/getcontext.S create mode 100644 src/3rdparty/libucontext/arch/m68k/include/libucontext/bits.h create mode 100644 src/3rdparty/libucontext/arch/m68k/makecontext.c create mode 100644 src/3rdparty/libucontext/arch/m68k/setcontext.S create mode 100644 src/3rdparty/libucontext/arch/m68k/swapcontext.S create mode 100644 src/3rdparty/libucontext/arch/m68k/trampoline.c create mode 100644 src/3rdparty/libucontext/arch/mips/defs.h create mode 100644 src/3rdparty/libucontext/arch/mips/getcontext.S create mode 100644 src/3rdparty/libucontext/arch/mips/include/libucontext/bits.h create mode 100644 src/3rdparty/libucontext/arch/mips/makecontext.S create mode 100644 src/3rdparty/libucontext/arch/mips/setcontext.S create mode 100644 src/3rdparty/libucontext/arch/mips/startcontext.S create mode 100644 src/3rdparty/libucontext/arch/mips/swapcontext.S create mode 100644 src/3rdparty/libucontext/arch/mips64/defs.h create mode 100644 src/3rdparty/libucontext/arch/mips64/getcontext.S create mode 100644 src/3rdparty/libucontext/arch/mips64/include/libucontext/bits.h create mode 100644 src/3rdparty/libucontext/arch/mips64/makecontext.S create mode 100644 src/3rdparty/libucontext/arch/mips64/setcontext.S create mode 100644 src/3rdparty/libucontext/arch/mips64/startcontext.S create mode 100644 src/3rdparty/libucontext/arch/mips64/swapcontext.S create mode 100644 src/3rdparty/libucontext/arch/or1k/defs.h create mode 100644 src/3rdparty/libucontext/arch/or1k/getcontext.S create mode 100644 src/3rdparty/libucontext/arch/or1k/include/libucontext/bits.h create mode 100644 src/3rdparty/libucontext/arch/or1k/makecontext.c create mode 100644 src/3rdparty/libucontext/arch/or1k/setcontext.S create mode 100644 src/3rdparty/libucontext/arch/or1k/swapcontext.S create mode 100644 src/3rdparty/libucontext/arch/or1k/trampoline.c create mode 100644 src/3rdparty/libucontext/arch/ppc/defs.h create mode 100644 src/3rdparty/libucontext/arch/ppc/getcontext.S create mode 100644 src/3rdparty/libucontext/arch/ppc/makecontext.c create mode 100644 src/3rdparty/libucontext/arch/ppc/retfromsyscall.c create mode 100644 src/3rdparty/libucontext/arch/ppc/setcontext.S create mode 100644 src/3rdparty/libucontext/arch/ppc/startcontext.S create mode 100644 src/3rdparty/libucontext/arch/ppc/swapcontext.S create mode 100644 src/3rdparty/libucontext/arch/ppc64/defs.h create mode 100644 src/3rdparty/libucontext/arch/ppc64/getcontext.S create mode 100644 src/3rdparty/libucontext/arch/ppc64/makecontext.c create mode 100644 src/3rdparty/libucontext/arch/ppc64/retfromsyscall.c create mode 100644 src/3rdparty/libucontext/arch/ppc64/setcontext.S create mode 100644 src/3rdparty/libucontext/arch/ppc64/startcontext.S create mode 100644 src/3rdparty/libucontext/arch/ppc64/swapcontext.S create mode 100644 src/3rdparty/libucontext/arch/riscv32/defs.h create mode 100644 src/3rdparty/libucontext/arch/riscv32/getcontext.S create mode 100644 src/3rdparty/libucontext/arch/riscv32/include/libucontext/bits.h create mode 100644 src/3rdparty/libucontext/arch/riscv32/makecontext.c create mode 100644 src/3rdparty/libucontext/arch/riscv32/setcontext.S create mode 100644 src/3rdparty/libucontext/arch/riscv32/swapcontext.S create mode 100644 src/3rdparty/libucontext/arch/riscv32/trampoline.c create mode 100644 src/3rdparty/libucontext/arch/riscv64/defs.h create mode 100644 src/3rdparty/libucontext/arch/riscv64/getcontext.S create mode 100644 src/3rdparty/libucontext/arch/riscv64/include/libucontext/bits.h create mode 100644 src/3rdparty/libucontext/arch/riscv64/makecontext.c create mode 100644 src/3rdparty/libucontext/arch/riscv64/setcontext.S create mode 100644 src/3rdparty/libucontext/arch/riscv64/swapcontext.S create mode 100644 src/3rdparty/libucontext/arch/riscv64/trampoline.c create mode 100644 src/3rdparty/libucontext/arch/s390x/defs.h create mode 100644 src/3rdparty/libucontext/arch/s390x/getcontext.S create mode 100644 src/3rdparty/libucontext/arch/s390x/include/libucontext/bits.h create mode 100644 src/3rdparty/libucontext/arch/s390x/makecontext.c create mode 100644 src/3rdparty/libucontext/arch/s390x/setcontext.S create mode 100644 src/3rdparty/libucontext/arch/s390x/startcontext.S create mode 100644 src/3rdparty/libucontext/arch/s390x/swapcontext.S create mode 100644 src/3rdparty/libucontext/arch/sh/defs.h create mode 100644 src/3rdparty/libucontext/arch/sh/getcontext.S create mode 100644 src/3rdparty/libucontext/arch/sh/include/libucontext/bits.h create mode 100644 src/3rdparty/libucontext/arch/sh/makecontext.c create mode 100644 src/3rdparty/libucontext/arch/sh/setcontext.S create mode 100644 src/3rdparty/libucontext/arch/sh/swapcontext.S create mode 100644 src/3rdparty/libucontext/arch/sh/trampoline.c create mode 100644 src/3rdparty/libucontext/arch/x86/defs.h create mode 100644 src/3rdparty/libucontext/arch/x86/getcontext.S create mode 100644 src/3rdparty/libucontext/arch/x86/include/libucontext/bits.h create mode 100644 src/3rdparty/libucontext/arch/x86/makecontext.c create mode 100644 src/3rdparty/libucontext/arch/x86/setcontext.S create mode 100644 src/3rdparty/libucontext/arch/x86/swapcontext.S create mode 100644 src/3rdparty/libucontext/arch/x86/trampoline.c create mode 100644 src/3rdparty/libucontext/arch/x86_64/defs.h create mode 100644 src/3rdparty/libucontext/arch/x86_64/getcontext.S create mode 100644 src/3rdparty/libucontext/arch/x86_64/include/libucontext/bits.h create mode 100644 src/3rdparty/libucontext/arch/x86_64/makecontext.c create mode 100644 src/3rdparty/libucontext/arch/x86_64/setcontext.S create mode 100644 src/3rdparty/libucontext/arch/x86_64/swapcontext.S create mode 100644 src/3rdparty/libucontext/arch/x86_64/trampoline.c create mode 100644 src/3rdparty/libucontext/doc/libucontext.scd create mode 100644 src/3rdparty/libucontext/doc/meson.build create mode 100644 src/3rdparty/libucontext/examples/cooperative_threading.c create mode 100644 src/3rdparty/libucontext/include/libucontext/libucontext.h create mode 100644 src/3rdparty/libucontext/libucontext.pc.in create mode 100644 src/3rdparty/libucontext/libucontext_posix.c create mode 100644 src/3rdparty/libucontext/meson.build create mode 100644 src/3rdparty/libucontext/meson_options.txt create mode 100644 src/3rdparty/libucontext/test_libucontext.c create mode 100644 src/3rdparty/libucontext/test_libucontext_posix.c diff --git a/README.md b/README.md index 528561173..d5db95c5c 100644 --- a/README.md +++ b/README.md @@ -61,14 +61,6 @@ The build system will look for these libraries and enable/link if available. - Client libraries for either [MySQL](https://dev.mysql.com) or [MariaDB](https://mariadb.org) - [SQLite 3](http://sqlite.org) -> [!NOTE] -> -> On some systems, -> [libucontext](https://github.com/kaniini/libucontext) will be downloaded -> and built alongside Lwan. This will require a network connection, so keep -> this in mind when packaging Lwan for non-x86_64 or non-aarch64 -> architectures. - ### Common operating system package names #### Minimum to build diff --git a/src/3rdparty/libucontext/LICENSE b/src/3rdparty/libucontext/LICENSE new file mode 100644 index 000000000..9fbc5d306 --- /dev/null +++ b/src/3rdparty/libucontext/LICENSE @@ -0,0 +1,9 @@ +Copyright (c) 2018-2022 Ariadne Conill + +Permission to use, copy, modify, and/or 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. + +This software is provided 'as is' and without any warranty, express or +implied. In no event shall the authors be liable for any damages arising +from the use of this software. diff --git a/src/3rdparty/libucontext/Makefile b/src/3rdparty/libucontext/Makefile new file mode 100644 index 000000000..587729543 --- /dev/null +++ b/src/3rdparty/libucontext/Makefile @@ -0,0 +1,292 @@ +SCDOC := scdoc + +ARCH := $(shell uname -m) +ifeq ($(ARCH),$(filter $(ARCH),i386 i686)) + override ARCH = x86 +endif +ifeq ($(ARCH),$(filter $(ARCH),sh2 sh4)) + override ARCH = sh +endif +ifeq ($(ARCH),$(filter $(ARCH),ppc64le)) + override ARCH = ppc64 +endif +ifeq ($(ARCH),$(filter $(ARCH),armv7l)) + override ARCH = arm +endif +ifeq ($(ARCH),$(filter $(ARCH),arm64)) + override ARCH = aarch64 +endif + +prefix = /usr +libdir = ${prefix}/lib +shared_libdir = ${libdir} +static_libdir = ${libdir} +includedir = ${prefix}/include +pkgconfigdir = ${prefix}/lib/pkgconfig + +CFLAGS ?= -ggdb3 -O2 -Wall +CPPFLAGS := -Iinclude -Iarch/${ARCH} -Iarch/common +ifneq ($(shell uname),Darwin) + EXPORT_UNPREFIXED := yes +else + # Darwin does not support aliases + EXPORT_UNPREFIXED := no +endif +FREESTANDING := no + +ifeq ($(FREESTANDING),yes) + CPPFLAGS += -DFREESTANDING + EXPORT_UNPREFIXED = no +endif + +FORCE_SOFT_FLOAT := no + +ifeq ($(FORCE_SOFT_FLOAT),yes) + CPPFLAGS += -DFORCE_SOFT_FLOAT +endif + +FORCE_HARD_FLOAT := no + +ifeq ($(FORCE_HARD_FLOAT),yes) + CPPFLAGS += -DFORCE_HARD_FLOAT +endif + +ifeq ($(EXPORT_UNPREFIXED),yes) + CPPFLAGS += -DEXPORT_UNPREFIXED +endif + +LIBUCONTEXT_C_SRC = $(wildcard arch/${ARCH}/*.c) +LIBUCONTEXT_S_SRC = $(wildcard arch/${ARCH}/*.S) + +ifeq ($(shell test -d arch/${ARCH}/include; echo $?),0) + CPPFLAGS += -Iarch/${ARCH}/include +endif + +LIBUCONTEXT_VERSION := $(shell head -n 1 VERSION) +LIBUCONTEXT_OBJ = ${LIBUCONTEXT_C_SRC:.c=.o} ${LIBUCONTEXT_S_SRC:.S=.o} +LIBUCONTEXT_SOVERSION = 1 +ifeq ($(shell uname),Darwin) + LIBUCONTEXT_NAME = libucontext.dylib + LIBUCONTEXT_SONAME = libucontext.${LIBUCONTEXT_SOVERSION}.dylib + LIBUCONTEXT_POSIX_NAME = libucontext_posix.dylib + LIBUCONTEXT_POSIX_SONAME = libucontext_posix.${LIBUCONTEXT_SOVERSION}.dylib + LIBUCONTEXT_LINKER_FLAGS = -Wl,-dynamiclib,-install_name,${LIBUCONTEXT_SONAME},-current_version,${LIBUCONTEXT_SOVERSION},-compatibility_version,${LIBUCONTEXT_SOVERSION} + LIBUCONTEXT_POSIX_LINKER_FLAGS = -Wl,-dynamiclib,-install_name,${LIBUCONTEXT_POSIX_SONAME},-current_version,${LIBUCONTEXT_SOVERSION},-compatibility_version,${LIBUCONTEXT_SOVERSION} +else + LIBUCONTEXT_NAME = libucontext.so + LIBUCONTEXT_SONAME = libucontext.so.${LIBUCONTEXT_SOVERSION} + LIBUCONTEXT_POSIX_NAME = libucontext_posix.so + LIBUCONTEXT_POSIX_SONAME = libucontext_posix.so.${LIBUCONTEXT_SOVERSION} + LIBUCONTEXT_LINKER_FLAGS = -shared -Wl,-soname,${LIBUCONTEXT_SONAME} -Wl,-z,noexecstack + LIBUCONTEXT_POSIX_LINKER_FLAGS = -shared -Wl,-soname,${LIBUCONTEXT_POSIX_SONAME} -Wl,-z,noexecstack + ASFLAGS = -Wa,--noexecstack +endif +LIBUCONTEXT_STATIC_NAME = libucontext.a +LIBUCONTEXT_PC = libucontext.pc +LIBUCONTEXT_PATH = ${shared_libdir}/${LIBUCONTEXT_SONAME} +LIBUCONTEXT_STATIC_PATH = ${static_libdir}/${LIBUCONTEXT_STATIC_NAME} +LIBUCONTEXT_HEADERS = \ + include/libucontext/libucontext.h \ + include/libucontext/bits.h +LIBUCONTEXT_EXAMPLES = \ + examples/cooperative_threading +LIBUCONTEXT_POSIX_STATIC_NAME = libucontext_posix.a +LIBUCONTEXT_POSIX_C_SRC = libucontext_posix.c +LIBUCONTEXT_POSIX_OBJ = ${LIBUCONTEXT_POSIX_C_SRC:.c=.o} +LIBUCONTEXT_POSIX_PATH = ${shared_libdir}/${LIBUCONTEXT_POSIX_SONAME} +LIBUCONTEXT_POSIX_STATIC_PATH = ${static_libdir}/${LIBUCONTEXT_POSIX_STATIC_NAME} + +ifeq ($(FREESTANDING),yes) + LIBUCONTEXT_POSIX_NAME = + LIBUCONTEXT_POSIX_STATIC_NAME = +endif + +all: ${LIBUCONTEXT_SONAME} ${LIBUCONTEXT_STATIC_NAME} ${LIBUCONTEXT_POSIX_NAME} ${LIBUCONTEXT_POSIX_STATIC_NAME} ${LIBUCONTEXT_PC} + +${LIBUCONTEXT_POSIX_NAME}: ${LIBUCONTEXT_NAME} ${LIBUCONTEXT_POSIX_OBJ} + $(CC) -fPIC -o ${LIBUCONTEXT_POSIX_NAME} ${LIBUCONTEXT_POSIX_LINKER_FLAGS} ${LIBUCONTEXT_POSIX_OBJ} -L. -lucontext ${LDFLAGS} + +${LIBUCONTEXT_POSIX_STATIC_NAME}: ${LIBUCONTEXT_STATIC_NAME} ${LIBUCONTEXT_POSIX_OBJ} + $(AR) rcs ${LIBUCONTEXT_POSIX_STATIC_NAME} ${LIBUCONTEXT_POSIX_OBJ} + +${LIBUCONTEXT_POSIX_SONAME}: ${LIBUCONTEXT_POSIX_NAME} + ln -sf ${LIBUCONTEXT_POSIX_NAME} ${LIBUCONTEXT_POSIX_SONAME} + +${LIBUCONTEXT_STATIC_NAME}: ${LIBUCONTEXT_HEADERS} ${LIBUCONTEXT_OBJ} + $(AR) rcs ${LIBUCONTEXT_STATIC_NAME} ${LIBUCONTEXT_OBJ} + +${LIBUCONTEXT_NAME}: ${LIBUCONTEXT_HEADERS} ${LIBUCONTEXT_OBJ} + $(CC) -fPIC -o ${LIBUCONTEXT_NAME} ${LIBUCONTEXT_LINKER_FLAGS} ${LIBUCONTEXT_OBJ} ${LDFLAGS} + +${LIBUCONTEXT_SONAME}: ${LIBUCONTEXT_NAME} + ln -sf ${LIBUCONTEXT_NAME} ${LIBUCONTEXT_SONAME} + +${LIBUCONTEXT_PC}: libucontext.pc.in + sed -e s:@LIBUCONTEXT_VERSION@:${LIBUCONTEXT_VERSION}:g \ + -e s:@LIBUCONTEXT_SHARED_LIBDIR@:${shared_libdir}:g \ + -e s:@LIBUCONTEXT_STATIC_LIBDIR@:${static_libdir}:g \ + -e s:@LIBUCONTEXT_INCLUDEDIR@:${includedir}:g $< > $@ + +MANPAGES_SYMLINKS_3 = \ + libucontext_getcontext.3 \ + libucontext_makecontext.3 \ + libucontext_setcontext.3 \ + libucontext_swapcontext.3 +MANPAGES_3 = doc/libucontext.3 + +MANPAGES = ${MANPAGES_3} + +.scd.3: + ${SCDOC} < $< > $@ + +.SUFFIXES: .scd .3 + +docs: ${MANPAGES} + +.c.o: + $(CC) -std=gnu99 -D_DEFAULT_SOURCE -fPIC -DPIC ${CFLAGS} ${CPPFLAGS} -c -o $@ $< + +.S.o: + $(CC) -fPIC -DPIC ${CFLAGS} ${CPPFLAGS} ${ASFLAGS} -c -o $@ $< + +${LIBUCONTEXT_NAME}_clean: + rm -f ${LIBUCONTEXT_NAME} + +${LIBUCONTEXT_SONAME}_clean: + rm -f ${LIBUCONTEXT_SONAME} + +${LIBUCONTEXT_STATIC_NAME}_clean: + rm -f ${LIBUCONTEXT_STATIC_NAME} + +libucontext_obj_clean: + rm -f ${LIBUCONTEXT_OBJ} + +${LIBUCONTEXT_PC}_clean: + rm -f ${LIBUCONTEXT_PC} + +bits_clean: + rm -f include/libucontext/bits.h + +${LIBUCONTEXT_POSIX_NAME}_clean: + rm -f ${LIBUCONTEXT_POSIX_NAME} + +${LIBUCONTEXT_POSIX_SONAME}_clean: + rm -f ${LIBUCONTEXT_POSIX_SONAME} + +${LIBUCONTEXT_POSIX_STATIC_NAME}_clean: + rm -f ${LIBUCONTEXT_POSIX_STATIC_NAME} + +libucontext_posix_obj_clean: + rm -f ${LIBUCONTEXT_POSIX_OBJ} + +check_clean: check_bare_clean check_posix_clean check_bare_posixabi_clean + +check_bare_clean: + rm -f test_libucontext + +check_posix_clean: + rm -f test_libucontext_posix + +check_bare_posixabi_clean: + rm -f test_libucontext_bare_posixabi + +docs_clean: + rm -f ${MANPAGES} + +clean: ${LIBUCONTEXT_NAME}_clean +clean: ${LIBUCONTEXT_SONAME}_clean +clean: ${LIBUCONTEXT_STATIC_NAME}_clean +clean: ${LIBUCONTEXT_PC}_clean +clean: bits_clean +clean: ${LIBUCONTEXT_POSIX_NAME}_clean +clean: ${LIBUCONTEXT_POSIX_SONAME}_clean +clean: ${LIBUCONTEXT_POSIX_STATIC_NAME}_clean +clean: libucontext_posix_obj_clean +clean: libucontext_obj_clean +clean: check_clean +clean: docs_clean + +install-shared: ${LIBUCONTEXT_SONAME} + install -D -m755 ${LIBUCONTEXT_NAME} ${DESTDIR}${LIBUCONTEXT_PATH} + ln -sf ${LIBUCONTEXT_SONAME} ${DESTDIR}${shared_libdir}/${LIBUCONTEXT_NAME} + +install-static: ${LIBUCONTEXT_STATIC_NAME} + install -D -m664 ${LIBUCONTEXT_STATIC_NAME} ${DESTDIR}${LIBUCONTEXT_STATIC_PATH} + +install-pkgconf: + install -D -m644 ${LIBUCONTEXT_PC} ${DESTDIR}${pkgconfigdir}/${LIBUCONTEXT_PC} + if [ -n "${LIBUCONTEXT_POSIX_NAME}" ]; then \ + install -D -m755 ${LIBUCONTEXT_POSIX_NAME} ${DESTDIR}${LIBUCONTEXT_POSIX_PATH}; \ + install -D -m644 ${LIBUCONTEXT_POSIX_STATIC_NAME} ${DESTDIR}${LIBUCONTEXT_POSIX_STATIC_PATH}; \ + fi + +install-headers: + for i in ${LIBUCONTEXT_HEADERS}; do \ + destfn=$$(echo $$i | sed s:include/::g); \ + install -D -m644 $$i ${DESTDIR}${includedir}/$$destfn; \ + done + +install: all install-shared install-static install-pkgconf install-headers + +install_docs: docs + install -D -m644 doc/libucontext.3 ${DESTDIR}/usr/share/man/man3/libucontext.3 + for i in ${MANPAGES_SYMLINKS_3}; do \ + ln -s libucontext.3 ${DESTDIR}/usr/share/man/man3/$$i; \ + done + +ifneq (${FREESTANDING},yes) +check: check_libucontext_posix + +check_libucontext_posix: test_libucontext_posix ${LIBUCONTEXT_POSIX_SONAME} ${LIBUCONTEXT_SONAME} + env LD_LIBRARY_PATH=$(shell pwd) ./test_libucontext_posix + +test_libucontext_posix: test_libucontext_posix.c ${LIBUCONTEXT_POSIX_NAME} + $(CC) -std=gnu99 -D_DEFAULT_SOURCE ${CFLAGS} ${CPPFLAGS} $@.c -o $@ -L. -lucontext -lucontext_posix +endif + +ifeq ($(EXPORT_UNPREFIXED),yes) +check: check_libucontext_bare_posixabi + +check_libucontext_bare_posixabi: test_libucontext_bare_posixabi ${LIBUCONTEXT_SONAME} + env LD_LIBRARY_PATH=$(shell pwd) ./test_libucontext_bare_posixabi + +test_libucontext_bare_posixabi: test_libucontext_posix.c ${LIBUCONTEXT_NAME} + $(CC) -std=gnu99 -D_DEFAULT_SOURCE ${CFLAGS} ${CPPFLAGS} test_libucontext_posix.c -o $@ -L. -lucontext +endif + +check_libucontext: test_libucontext ${LIBUCONTEXT_SONAME} + env LD_LIBRARY_PATH=$(shell pwd) ./test_libucontext + +check: check_libucontext + +test_libucontext: test_libucontext.c ${LIBUCONTEXT_NAME} + $(CC) -std=gnu99 -D_DEFAULT_SOURCE ${CFLAGS} ${CPPFLAGS} $@.c -o $@ -L. -lucontext + +examples: ${LIBUCONTEXT_EXAMPLES} +examples/cooperative_threading: examples/cooperative_threading.c ${LIBUCONTEXT_NAME} + $(CC) -std=gnu99 -D_DEFAULT_SOURCE ${CFLAGS} ${CPPFLAGS} $@.c -o $@ -L. -lucontext + +ifeq ($(FREESTANDING),no) + +include/libucontext/bits.h: arch/common/include/libucontext/bits.h + cp $< $@ + +else + +include/libucontext/bits.h: arch/${ARCH}/include/libucontext/bits.h + cp $< $@ + +endif + +PACKAGE_NAME = libucontext +PACKAGE_VERSION = ${LIBUCONTEXT_VERSION} +DIST_NAME = ${PACKAGE_NAME}-${PACKAGE_VERSION} +DIST_TARBALL = ${DIST_NAME}.tar.xz + +distcheck: check dist +dist: ${DIST_TARBALL} +${DIST_TARBALL}: + git archive --format=tar --prefix=${DIST_NAME}/ -o ${DIST_NAME}.tar ${DIST_NAME} + xz ${DIST_NAME}.tar + +.PHONY: check dist diff --git a/src/3rdparty/libucontext/NEWS b/src/3rdparty/libucontext/NEWS new file mode 100644 index 000000000..af46a1021 --- /dev/null +++ b/src/3rdparty/libucontext/NEWS @@ -0,0 +1,133 @@ +Changes from 1.1 to 1.2 +----------------------- + +* Added Loongarch64 port. + +* Added OpenRISC (or1k) port. + +* Fixed various build system issues: + - libucontext_posix.so is no longer underlinked + - Executable stacks are now disabled when using GNU-like toolchains + - CPPFLAGS is used consistently + - Users may now build on Darwin without using Meson + +* aarch64 now implements the necessary SIMD register save/restore as + mandatory in AAPCS64. + Patches contributed by Richard Campbell. + +Changes from 1.0 to 1.1 +----------------------- + +* Added RISC-V RV32 port. + +* Cleaned up use of _GNU_SOURCE in C code. + Partially from patches contributed by osy. + +* Added automatic detection of armv7l architecture. + Patch contributed by Leandro Pereira. + +* Fixed installation path of libucontext_posix.a. + +* Work around deficiency in clang built-in assembler on AArch64 targets. + Patch contributed by osy. + +Changes from 0.13.1 to 1.0 +-------------------------- + +* Implement common libucontext_trampoline, written in C with inline + assembly. + +* Added Renesas / Hitachi SH-2/SH-4 port (sh). + +* Added Meson build system, primarily for the convenience of using + libucontext with qemu as a subproject. + +* Added support for Mach-O ABI. + +* Fixed deficiencies in libucontext ABI, SONAME has been bumped due + to the ABI regressions in 0.13. + +Changes from 0.13 to 0.13.1 +--------------------------- + +* Fix installation of libucontext.pc. + Patch contributed by Ömer Faruk IRMAK. + +Changes from 0.12 to 0.13 +------------------------- + +* Aligned RISC-V RV64 port's header usage with musl 1.2 to remove + warnings and ensure consistent register name usage. + +* Added Motorola 680X0 / NXP ColdFire port (m68k). + +* Added support for building for bare-metal targets with newlib via + make FREESTANDING=yes. Other OS are also supported (for example, + the m68k freestanding port was tested on AmigaOS), PowerPC requires + kernel assistance and cannot be built with FREESTANDING=yes. Not + all ports have support for FREESTANDING yet, patches welcome. + +Changes from 0.11 to 0.12 +------------------------- + +* Fixed compilation of RISC-V RV64 port with modern musl releases. + +Changes from 0.10 to 0.11 +------------------------- + +* Added RISC-V RV64 port. + +* Fixed compilation with clang. + Patch contributed by Khem Raj. + +* Add ${LIBDIR} variable to build system. + Patch contributed by Khem Raj. + +Changes from 0.9.0 to 0.10 +-------------------------- + +* Added MIPS O32 and MIPS N64 ports. + MIPS N32 ABI is theoretically supported by ARCH=mips64 with + a MIPS N32 toolchain, but this has not been tested. + +* Improved test program (test_libucontext) verbosity. + +* Modernized all architectures to use common assembly + macros, such as REG_OFFSET(reg), FUNC() and ALIAS(). + +* Added debugging hints to assembly functions for GDB. + +* Automatically alias i386/i686 to x86 in makefile. + Patch contributed by Gabriel Ivascu. + +Changes from 0.1.3 to 0.9.0 +--------------------------- + +* Pass ${LDFLAGS} when linking the libucontext library. + Patch contributed by Khem Raj. + +* Fix clobbering of the first stack argument on x86. + Patch contributed by A. Wilcox. + +* Add support for building a static libucontext. + Patches contributed by Gabriel Ivascu. + +* Rewrite ppc/ppc64 implementation to fully use the + swapcontext(3) syscall. + Patches contributed by Bobby Bingham. + +Changes from 0.1.1 to 0.1.3 +--------------------------- + +* Fix register clobbering on x86_64. + Patches contributed by A. Wilcox and Timo Teräs. + +Changes from 0.1.0 to 0.1.1 +--------------------------- + +* Added S390X port. + +* Cleaned up the ppc/ppc64 trampoline. + +* Fixed up GOT clobbering and removed a textrel from the x86 + trampoline. diff --git a/src/3rdparty/libucontext/README.md b/src/3rdparty/libucontext/README.md new file mode 100644 index 000000000..d9d5dbe89 --- /dev/null +++ b/src/3rdparty/libucontext/README.md @@ -0,0 +1,105 @@ +# `libucontext` + +`libucontext` is a library which provides the `ucontext.h` C API. Unlike other implementations, +it faithfully follows the kernel process ABI when doing context swaps. + +Notably, when combined with `gcompat`, it provides a fully compatible implementation of the ucontext +functions that are ABI compatible with glibc. + +Since version 0.13, for some architectures, you can deploy to bare metal using newlib via the +`FREESTANDING=yes` make option. Systems which use a syscall cannot work this way. The table +below shows which architecture ports have been adapted to build with `FREESTANDING=yes`. + +Adding support for new architectures is easy, but you need to know assembly language for the +target to do it. + + +## supported features + +| Architecture | Works on musl | Syscall | Supports FREESTANDING | Common trampoline | +|--------------|---------------|---------|-----------------------|-------------------| +| aarch64 | ✓ | | ✓ | ✓ | +| arm | ✓ | | ✓ | ✓ | +| loongarch64 | ✓ | | ✓ | | +| m68k | ✓ | | ✓ | ✓ | +| mips | ✓ | | ✓ | | +| mips64 | ✓ | | ✓ | | +| or1k | ✓ | | ✓ | ✓ | +| ppc | ✓ | ✓ | | | +| ppc64 | ✓ | ✓ | | | +| riscv32 | ✓ | | ✓ | ✓ | +| riscv64 | ✓ | | ✓ | ✓ | +| s390x | ✓ | | ✓ | | +| sh | ✓ | | ✓ | ✓ | +| x86 | ✓ | | ✓ | ✓ | +| x86_64 | ✓ | | ✓ | ✓ | + + +## building + +`libucontext` uses a simple makefile build system. You should define `ARCH=` at build time, otherwise +the build system will attempt to guess using `uname -m`. + +``` +$ make ARCH=x86_64 +$ make ARCH=x86_64 check +$ make ARCH=x86_64 DESTDIR=out install +``` + +There are a few options: + +* `ARCH`: The architecture libucontext is being built for. Must be set to one of the architectures + listed in the feature support table. If unset, the build system will attempt to guess based on what + architecture the host is running. Setting this option explicitly is highly recommended. + +* `FREESTANDING`: If this is set to `yes`, the system ucontext.h headers will not be used. Instead, + the headers in `arch/${ARCH}/freestanding` will be used for definitions where appropriate. + Default is `no`. + +* `EXPORT_UNPREFIXED`: If this is set to `yes`, the POSIX 2004 names `getcontext`, `setcontext`, + `swapcontext` and `makecontext` will be provided as weak symbols aliased against their `libucontext_` + namespaced equivalents. This is necessary for libucontext to provide these functions on musl + systems, but you may wish to disable this when using `FREESTANDING` mode to avoid conflicts with + the target's libc. Default is `yes`. + +* `DESTDIR`: If this variable is set, the installed files will be installed to the specified path instead + of the system root. + +If you have `scdoc` installed, you can build manpages and install them: + +``` +$ make docs +$ make DESTDIR=out install_docs +``` + + +## real-world use cases + +`libucontext` is used on almost all musl distributions to provide the legacy `ucontext.h` API. +Additionally, it is used by: + +* [UTM](https://getutm.app) -- friendly qemu distribution for macOS and iOS devices. UTM uses libucontext + as qemu's coroutine backend. + +* [Lwan](https://lwan.ws) -- a high-performance embeddable asynchronous web server. Lwan uses libucontext + to provide green threads when building on non-x86 architectures. + + +## caveats + +`libucontext`, while largely functionally equivalent does have some differences over traditional POSIX +ucontext functions: + +* Saving and restoring the signal mask is not implemented by default in order to avoid kernel syscall + overhead. Use `-lucontext_posix` if you actually need this functionality, which provides a POSIX + compliant implementation at the cost of performance. + +* Only basic GPR registers are saved and restored when context swapping. The glibc implementation uses + hardware capability detection to save/restore other register groups, such as the FPU registers or + vector processing (AltiVec/AVX/NEON) registers. Adding this capability detection would significantly + increase the complexity of the project and thus is not implemented. Support for compiling in code to + save/restore FPU registers or vector registers may be added in a later release as a build-time + setting -- for now, we assume a soft-float ABI with no optional processor features. In practice, this + does not really matter, code using these functions are unlikely to be impacted by this design + assumption. This is a work in progress, as newer compilers will spill even non-floating-point state + through floating point registers when allowed to do so. diff --git a/src/3rdparty/libucontext/VERSION b/src/3rdparty/libucontext/VERSION new file mode 100644 index 000000000..ea710abb9 --- /dev/null +++ b/src/3rdparty/libucontext/VERSION @@ -0,0 +1 @@ +1.2 \ No newline at end of file diff --git a/src/3rdparty/libucontext/arch/aarch64/defs.h b/src/3rdparty/libucontext/arch/aarch64/defs.h new file mode 100644 index 000000000..5398adcde --- /dev/null +++ b/src/3rdparty/libucontext/arch/aarch64/defs.h @@ -0,0 +1,27 @@ +#ifndef __ARCH_AARCH64_DEFS_H +#define __ARCH_AARCH64_DEFS_H + +#define REG_SZ (8) +#define MCONTEXT_GREGS (184) + +#define R0_OFFSET REG_OFFSET(0) + +#define SP_OFFSET 432 +#define PC_OFFSET 440 +#define PSTATE_OFFSET 448 +#define FPSIMD_CONTEXT_OFFSET 464 + +#ifndef FPSIMD_MAGIC +# define FPSIMD_MAGIC 0x46508001 +#endif + +#ifndef ESR_MAGIC +# define ESR_MAGIC 0x45535201 +#endif + +#define FETCH_LINKPTR(dest) \ + asm("mov %0, x19" : "=r" ((dest))) + +#include "common-defs.h" + +#endif diff --git a/src/3rdparty/libucontext/arch/aarch64/getcontext.S b/src/3rdparty/libucontext/arch/aarch64/getcontext.S new file mode 100644 index 000000000..37390a6bd --- /dev/null +++ b/src/3rdparty/libucontext/arch/aarch64/getcontext.S @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2018, 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "defs.h" + +ALIAS(getcontext, libucontext_getcontext) +ALIAS(__getcontext, libucontext_getcontext) + + .global PROC_NAME(libucontext_getcontext); + .align 2; + TYPE(libucontext_getcontext) + ENT(libucontext_getcontext) +PROC_NAME(libucontext_getcontext): + str xzr, [x0, #REG_OFFSET(0)] + + /* save GPRs */ + stp x0, x1, [x0, #REG_OFFSET(0)] + stp x2, x3, [x0, #REG_OFFSET(2)] + stp x4, x5, [x0, #REG_OFFSET(4)] + stp x6, x7, [x0, #REG_OFFSET(6)] + stp x8, x9, [x0, #REG_OFFSET(8)] + stp x10, x11, [x0, #REG_OFFSET(10)] + stp x12, x13, [x0, #REG_OFFSET(12)] + stp x14, x15, [x0, #REG_OFFSET(14)] + stp x16, x17, [x0, #REG_OFFSET(16)] + stp x18, x19, [x0, #REG_OFFSET(18)] + stp x20, x21, [x0, #REG_OFFSET(20)] + stp x22, x23, [x0, #REG_OFFSET(22)] + stp x24, x25, [x0, #REG_OFFSET(24)] + stp x26, x27, [x0, #REG_OFFSET(26)] + stp x28, x29, [x0, #REG_OFFSET(28)] + str x30, [x0, #REG_OFFSET(30)] + + /* save current program counter in link register */ + str x30, [x0, #PC_OFFSET] + + /* save current stack pointer */ + mov x2, sp + str x2, [x0, #SP_OFFSET] + + /* save pstate */ + str xzr, [x0, #PSTATE_OFFSET] + + add x2, x0, #FPSIMD_CONTEXT_OFFSET + stp q8, q9, [x2, #144] + stp q10, q11, [x2, #176] + stp q12, q13, [x2, #208] + stp q14, q15, [x2, #240] + + mov x0, #0 + ret +END(libucontext_getcontext) diff --git a/src/3rdparty/libucontext/arch/aarch64/include/libucontext/bits.h b/src/3rdparty/libucontext/arch/aarch64/include/libucontext/bits.h new file mode 100644 index 000000000..e249448f9 --- /dev/null +++ b/src/3rdparty/libucontext/arch/aarch64/include/libucontext/bits.h @@ -0,0 +1,34 @@ +#ifndef LIBUCONTEXT_BITS_H +#define LIBUCONTEXT_BITS_H + +typedef unsigned long libucontext_greg_t; +typedef unsigned long libucontext_gregset_t[34]; + +typedef struct { + __uint128_t vregs[32]; + unsigned int fpsr; + unsigned int fpcr; +} libucontext_fpregset_t; + +typedef struct sigcontext { + unsigned long fault_address; + unsigned long regs[31]; + unsigned long sp, pc, pstate; + long double __reserved[256]; +} libucontext_mcontext_t; + +typedef struct { + void *ss_sp; + int ss_flags; + size_t ss_size; +} libucontext_stack_t; + +typedef struct libucontext_ucontext { + unsigned long uc_flags; + struct libucontext_ucontext *uc_link; + libucontext_stack_t uc_stack; + unsigned char __pad[136]; + libucontext_mcontext_t uc_mcontext; +} libucontext_ucontext_t; + +#endif diff --git a/src/3rdparty/libucontext/arch/aarch64/makecontext.c b/src/3rdparty/libucontext/arch/aarch64/makecontext.c new file mode 100644 index 000000000..f6823cf6c --- /dev/null +++ b/src/3rdparty/libucontext/arch/aarch64/makecontext.c @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2018 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include +#include +#include +#include +#include "defs.h" +#include + + +extern void libucontext_trampoline(void); + +_Static_assert(offsetof(libucontext_ucontext_t, uc_mcontext.regs[0]) == R0_OFFSET, "R0_OFFSET is invalid"); +_Static_assert(offsetof(libucontext_ucontext_t, uc_mcontext.sp) == SP_OFFSET, "SP_OFFSET is invalid"); +_Static_assert(offsetof(libucontext_ucontext_t, uc_mcontext.pc) == PC_OFFSET, "PC_OFFSET is invalid"); +_Static_assert(offsetof(libucontext_ucontext_t, uc_mcontext.pstate) == PSTATE_OFFSET, "PSTATE_OFFSET is invalid"); + +void +libucontext_makecontext(libucontext_ucontext_t *ucp, void (*func)(void), int argc, ...) +{ + unsigned long *sp; + unsigned long *regp; + va_list va; + int i; + + sp = (unsigned long *) ((uintptr_t) ucp->uc_stack.ss_sp + ucp->uc_stack.ss_size); + sp -= argc < 8 ? 0 : argc - 8; + sp = (unsigned long *) (((uintptr_t) sp & -16L)); + + ucp->uc_mcontext.sp = (uintptr_t) sp; + ucp->uc_mcontext.pc = (uintptr_t) func; + ucp->uc_mcontext.regs[19] = (uintptr_t) ucp->uc_link; + ucp->uc_mcontext.regs[30] = (uintptr_t) &libucontext_trampoline; + + va_start(va, argc); + + regp = &(ucp->uc_mcontext.regs[0]); + + for (i = 0; (i < argc && i < 8); i++) + *regp++ = va_arg (va, unsigned long); + + for (; i < argc; i++) + *sp++ = va_arg (va, unsigned long); + + va_end(va); +} + +#ifdef EXPORT_UNPREFIXED +extern __typeof(libucontext_makecontext) makecontext __attribute__((weak, __alias__("libucontext_makecontext"))); +extern __typeof(libucontext_makecontext) __makecontext __attribute__((weak, __alias__("libucontext_makecontext"))); +#endif diff --git a/src/3rdparty/libucontext/arch/aarch64/setcontext.S b/src/3rdparty/libucontext/arch/aarch64/setcontext.S new file mode 100644 index 000000000..f34836026 --- /dev/null +++ b/src/3rdparty/libucontext/arch/aarch64/setcontext.S @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018, 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "defs.h" + +ALIAS(setcontext, libucontext_setcontext) +ALIAS(__setcontext, libucontext_setcontext) + + .global PROC_NAME(libucontext_setcontext); + .align 2; + TYPE(libucontext_setcontext) + ENT(libucontext_setcontext) +PROC_NAME(libucontext_setcontext): + /* restore GPRs */ + ldp x18, x19, [x0, #REG_OFFSET(18)] + ldp x20, x21, [x0, #REG_OFFSET(20)] + ldp x22, x23, [x0, #REG_OFFSET(22)] + ldp x24, x25, [x0, #REG_OFFSET(24)] + ldp x26, x27, [x0, #REG_OFFSET(26)] + ldp x28, x29, [x0, #REG_OFFSET(28)] + ldr x30, [x0, #REG_OFFSET(30)] + + /* save current stack pointer */ + ldr x2, [x0, #SP_OFFSET] + mov sp, x2 + + add x2, x0, #FPSIMD_CONTEXT_OFFSET + ldp q8, q9, [x2, #144] + ldp q10, q11, [x2, #176] + ldp q12, q13, [x2, #208] + ldp q14, q15, [x2, #240] + + /* save current program counter in link register */ + ldr x16, [x0, #PC_OFFSET] + + /* restore args */ + ldp x2, x3, [x0, #REG_OFFSET(2)] + ldp x4, x5, [x0, #REG_OFFSET(4)] + ldp x6, x7, [x0, #REG_OFFSET(6)] + ldp x0, x1, [x0, #REG_OFFSET(0)] + + /* jump to new PC */ + br x16 +END(libucontext_setcontext) diff --git a/src/3rdparty/libucontext/arch/aarch64/swapcontext.S b/src/3rdparty/libucontext/arch/aarch64/swapcontext.S new file mode 100644 index 000000000..fc587e389 --- /dev/null +++ b/src/3rdparty/libucontext/arch/aarch64/swapcontext.S @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2018 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "defs.h" + +ALIAS(swapcontext, libucontext_swapcontext) +ALIAS(__swapcontext, libucontext_swapcontext) + + .global PROC_NAME(libucontext_swapcontext); + .align 2; + TYPE(libucontext_swapcontext) + ENT(libucontext_swapcontext) +PROC_NAME(libucontext_swapcontext): + str xzr, [x0, #REG_OFFSET(0)] + + /* save GPRs */ + stp x2, x3, [x0, #REG_OFFSET(2)] + stp x4, x5, [x0, #REG_OFFSET(4)] + stp x6, x7, [x0, #REG_OFFSET(6)] + stp x8, x9, [x0, #REG_OFFSET(8)] + stp x10, x11, [x0, #REG_OFFSET(10)] + stp x12, x13, [x0, #REG_OFFSET(12)] + stp x14, x15, [x0, #REG_OFFSET(14)] + stp x16, x17, [x0, #REG_OFFSET(16)] + stp x18, x19, [x0, #REG_OFFSET(18)] + stp x20, x21, [x0, #REG_OFFSET(20)] + stp x22, x23, [x0, #REG_OFFSET(22)] + stp x24, x25, [x0, #REG_OFFSET(24)] + stp x26, x27, [x0, #REG_OFFSET(26)] + stp x28, x29, [x0, #REG_OFFSET(28)] + str x30, [x0, #REG_OFFSET(30)] + + /* save current program counter in link register */ + str x30, [x0, #PC_OFFSET] + + /* save current stack pointer */ + mov x2, sp + str x2, [x0, #SP_OFFSET] + + /* save pstate */ + str xzr, [x0, #PSTATE_OFFSET] + + add x2, x0, #FPSIMD_CONTEXT_OFFSET + stp q8, q9, [x2, #144] + stp q10, q11, [x2, #176] + stp q12, q13, [x2, #208] + stp q14, q15, [x2, #240] + + /* context to swap to is in x1 so... we move to x0 and call setcontext */ + /* store our link register in x28 */ + mov x28, x30 + + /* move x1 to x0 and call setcontext */ + mov x0, x1 + bl PROC_NAME(libucontext_setcontext) + + /* hmm, we came back here try to return */ + mov x30, x28 + ret +END(libucontext_swapcontext) diff --git a/src/3rdparty/libucontext/arch/aarch64/trampoline.c b/src/3rdparty/libucontext/arch/aarch64/trampoline.c new file mode 100644 index 000000000..699a0505d --- /dev/null +++ b/src/3rdparty/libucontext/arch/aarch64/trampoline.c @@ -0,0 +1,3 @@ +#include "defs.h" +#include +#include "common-trampoline.c" diff --git a/src/3rdparty/libucontext/arch/arm/defs.h b/src/3rdparty/libucontext/arch/arm/defs.h new file mode 100644 index 000000000..1c3b7ddea --- /dev/null +++ b/src/3rdparty/libucontext/arch/arm/defs.h @@ -0,0 +1,15 @@ +#ifndef __ARCH_ARM_DEFS_H + +#define REG_SZ (4) +#define MCONTEXT_GREGS (32) +#define VFP_MAGIC_OFFSET (232) +#define VFP_D8_OFFSET (304) + +#define TYPE(__proc) .type __proc, %function; + +#define FETCH_LINKPTR(dest) \ + asm("movs %0, r4" : "=r" ((dest))) + +#include "common-defs.h" + +#endif diff --git a/src/3rdparty/libucontext/arch/arm/getcontext.S b/src/3rdparty/libucontext/arch/arm/getcontext.S new file mode 100644 index 000000000..0afa1506b --- /dev/null +++ b/src/3rdparty/libucontext/arch/arm/getcontext.S @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2018, 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "defs.h" + +ALIAS(getcontext, libucontext_getcontext) +ALIAS(__getcontext, libucontext_getcontext) + +FUNC(libucontext_getcontext) + /* copy all of the current registers into the ucontext structure */ + add r1, r0, #REG_OFFSET(4) + stmia r1, {r4-r12} + str r13, [r0, #REG_OFFSET(13)] + str r14, [r0, #REG_OFFSET(15)] + +#ifndef FORCE_SOFT_FLOAT +#ifndef FORCE_HARD_FLOAT + /* test for vfp, set kernel-defined magic number in uc_regspace */ + push {r0-r1,fp,lr} + mov r0, #16 + bl getauxval + tst r0, #64 + pop {r0-r1,fp,lr} + moveq r2, #0 + ldrne r2, =#0x56465001 + str r2, [r0, #VFP_MAGIC_OFFSET] + beq 1f +#endif + /* if vfp detected, save d8-d15 */ + .fpu vfp + add r1, r0, #VFP_D8_OFFSET + vstmia r1, {d8-d15} + .fpu softvfp +1: +#endif + + /* return 0 */ + mov r0, #0 + mov pc, lr +END(libucontext_getcontext) diff --git a/src/3rdparty/libucontext/arch/arm/include/libucontext/bits.h b/src/3rdparty/libucontext/arch/arm/include/libucontext/bits.h new file mode 100644 index 000000000..caad8d4bd --- /dev/null +++ b/src/3rdparty/libucontext/arch/arm/include/libucontext/bits.h @@ -0,0 +1,30 @@ +#ifndef LIBUCONTEXT_BITS_H +#define LIBUCONTEXT_BITS_H + +typedef int libucontext_greg_t, libucontext_gregset_t[18]; + +typedef struct { + unsigned long trap_no, error_code, oldmask; + unsigned long arm_r0, arm_r1, arm_r2, arm_r3; + unsigned long arm_r4, arm_r5, arm_r6, arm_r7; + unsigned long arm_r8, arm_r9, arm_r10, arm_fp; + unsigned long arm_ip, arm_sp, arm_lr, arm_pc; + unsigned long arm_cpsr, fault_address; +} libucontext_mcontext_t; + +typedef struct { + void *ss_sp; + int ss_flags; + size_t ss_size; +} libucontext_stack_t; + +typedef struct libucontext_ucontext { + unsigned long uc_flags; + struct libucontext_ucontext *uc_link; + libucontext_stack_t uc_stack; + libucontext_mcontext_t uc_mcontext; + unsigned long uc_sigmask[128 / sizeof(long)]; + unsigned long long uc_regspace[64]; +} libucontext_ucontext_t; + +#endif diff --git a/src/3rdparty/libucontext/arch/arm/makecontext.c b/src/3rdparty/libucontext/arch/arm/makecontext.c new file mode 100644 index 000000000..b137a16f8 --- /dev/null +++ b/src/3rdparty/libucontext/arch/arm/makecontext.c @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2018, 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include +#include +#include +#include + + +#include "defs.h" +#include + + +extern void libucontext_trampoline(void); + + +void +libucontext_makecontext(libucontext_ucontext_t *ucp, void (*func)(void), int argc, ...) +{ + unsigned long *sp; + unsigned long *regp; + va_list va; + int i; + + sp = (unsigned long *) ((uintptr_t) ucp->uc_stack.ss_sp + ucp->uc_stack.ss_size); + sp = (unsigned long *) (((uintptr_t) sp & -16L) - 8); + + if (argc > 4) + sp -= (argc - 4); + + ucp->uc_mcontext.arm_sp = (uintptr_t) sp; + ucp->uc_mcontext.arm_pc = (uintptr_t) func; + ucp->uc_mcontext.arm_r4 = (uintptr_t) ucp->uc_link; + ucp->uc_mcontext.arm_lr = (uintptr_t) &libucontext_trampoline; + + va_start(va, argc); + + regp = &(ucp->uc_mcontext.arm_r0); + + for (i = 0; (i < argc && i < 4); i++) + *regp++ = va_arg (va, unsigned long); + + for (; i < argc; i++) + *sp++ = va_arg (va, unsigned long); + + va_end(va); +} + +#ifdef EXPORT_UNPREFIXED +extern __typeof(libucontext_makecontext) makecontext __attribute__((weak, __alias__("libucontext_makecontext"))); +extern __typeof(libucontext_makecontext) __makecontext __attribute__((weak, __alias__("libucontext_makecontext"))); +#endif diff --git a/src/3rdparty/libucontext/arch/arm/setcontext.S b/src/3rdparty/libucontext/arch/arm/setcontext.S new file mode 100644 index 000000000..792d8f272 --- /dev/null +++ b/src/3rdparty/libucontext/arch/arm/setcontext.S @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018, 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "defs.h" + +ALIAS(setcontext, libucontext_setcontext) +ALIAS(__setcontext, libucontext_setcontext) + +FUNC(libucontext_setcontext) +#ifndef FORCE_SOFT_FLOAT +#ifndef FORCE_HARD_FLOAT + /* test for vfp magic number set by getcontext */ + ldr r2, [r0, #VFP_MAGIC_OFFSET] + ldr r3, =#0x56465001 + cmp r2, r3 + bne 1f +#endif + /* if vfp in use, restore d8-d15 from uc_regspace */ + .fpu vfp + add r14, r0, #VFP_D8_OFFSET + vldmia r14, {d8-d15} + .fpu softvfp +1: +#endif + + /* copy all of the current registers into the ucontext structure */ + add r14, r0, #REG_OFFSET(0) + ldmia r14, {r0-r12} + ldr r13, [r14, #52] + add r14, r14, #56 + + /* load link register and jump to new context */ + ldmia r14, {r14, pc} +END(libucontext_setcontext) diff --git a/src/3rdparty/libucontext/arch/arm/swapcontext.S b/src/3rdparty/libucontext/arch/arm/swapcontext.S new file mode 100644 index 000000000..f8411828f --- /dev/null +++ b/src/3rdparty/libucontext/arch/arm/swapcontext.S @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2018, 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "defs.h" + +ALIAS(swapcontext, libucontext_swapcontext) +ALIAS(__swapcontext, libucontext_swapcontext) + +FUNC(libucontext_swapcontext) + /* copy all of the current registers into the ucontext structure */ + add r2, r0, #REG_OFFSET(0) + stmia r2, {r0-r12} + str r13, [r0,#REG_OFFSET(13)] + str r14, [r0,#REG_OFFSET(15)] + +#ifndef FORCE_SOFT_FLOAT +#ifndef FORCE_HARD_FLOAT + /* test for vfp magic number, copy to other ucontext */ + ldr r3, [r1, #VFP_MAGIC_OFFSET] + ldr r4, =#0x56465001 + str r3, [r0, #VFP_MAGIC_OFFSET] + cmp r3, r4 + bne 1f +#endif + /* if vfp in use, save and restore d8-d15 */ + .fpu vfp + add r2, r0, #VFP_D8_OFFSET + vstmia r2, {d8-d15} + + add r14, r1, #VFP_D8_OFFSET + vldmia r14, {d8-d15} + .fpu softvfp +1: +#endif + + /* load new registers from the second ucontext structure */ + add r14, r1, #REG_OFFSET(0) + ldmia r14, {r0-r12} + ldr r13, [r14, #52] + add r14, r14, #56 + ldmia r14, {r14, pc} +END(libucontext_swapcontext) diff --git a/src/3rdparty/libucontext/arch/arm/trampoline.c b/src/3rdparty/libucontext/arch/arm/trampoline.c new file mode 100644 index 000000000..699a0505d --- /dev/null +++ b/src/3rdparty/libucontext/arch/arm/trampoline.c @@ -0,0 +1,3 @@ +#include "defs.h" +#include +#include "common-trampoline.c" diff --git a/src/3rdparty/libucontext/arch/common/common-defs.h b/src/3rdparty/libucontext/arch/common/common-defs.h new file mode 100644 index 000000000..8ba97bfd4 --- /dev/null +++ b/src/3rdparty/libucontext/arch/common/common-defs.h @@ -0,0 +1,61 @@ +#ifndef __ARCH_COMMON_COMMON_DEFS_H +#define __ARCH_COMMON_COMMON_DEFS_H + +#ifndef SETUP_FRAME +# define SETUP_FRAME(__proc) +#endif + +#ifndef PUSH_FRAME +# define PUSH_FRAME(__proc) +#endif + +#ifndef POP_FRAME +# define POP_FRAME(__proc) +#endif + +#ifndef ENT +# define ENT(__proc) +#endif + +#ifndef TYPE +# ifdef __clang__ +# define TYPE(__proc) // .type not supported +# else +# define TYPE(__proc) .type __proc, @function; +#endif +#endif + +#ifndef PROC_NAME +# ifdef __MACH__ +# define PROC_NAME(__proc) _ ## __proc +# else +# define PROC_NAME(__proc) __proc +# endif +#endif + +#define FUNC(__proc) \ + .global PROC_NAME(__proc); \ + .align 2; \ + TYPE(__proc) \ + ENT(__proc) \ +PROC_NAME(__proc): \ + SETUP_FRAME(__proc) +#ifdef __clang__ +#define END(__proc) +#else +#define END(__proc) \ + .end __proc; \ + .size __proc,.-__proc; +#endif + +#ifdef EXPORT_UNPREFIXED +#define ALIAS(__alias, __real) \ + .weak __alias; \ + __alias = __real; +#else +#define ALIAS(...) +#endif + +#define REG_OFFSET(__reg) (MCONTEXT_GREGS + ((__reg) * REG_SZ)) + +#endif diff --git a/src/3rdparty/libucontext/arch/common/common-trampoline.c b/src/3rdparty/libucontext/arch/common/common-trampoline.c new file mode 100644 index 000000000..5b5232760 --- /dev/null +++ b/src/3rdparty/libucontext/arch/common/common-trampoline.c @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include +#include + +__attribute__ ((visibility ("hidden"))) +void +libucontext_trampoline(void) +{ + register libucontext_ucontext_t *uc_link = NULL; + + FETCH_LINKPTR(uc_link); + + if (uc_link == NULL) + exit(0); + + libucontext_setcontext(uc_link); +} diff --git a/src/3rdparty/libucontext/arch/common/include/libucontext/bits.h b/src/3rdparty/libucontext/arch/common/include/libucontext/bits.h new file mode 100644 index 000000000..65a039f59 --- /dev/null +++ b/src/3rdparty/libucontext/arch/common/include/libucontext/bits.h @@ -0,0 +1,13 @@ +#ifndef LIBUCONTEXT_BITS_H +#define LIBUCONTEXT_BITS_H + +#ifndef FREESTANDING + +#include + +typedef greg_t libucontext_greg_t; +typedef ucontext_t libucontext_ucontext_t; + +#endif + +#endif diff --git a/src/3rdparty/libucontext/arch/loongarch64/defs.h b/src/3rdparty/libucontext/arch/loongarch64/defs.h new file mode 100644 index 000000000..94d014a8e --- /dev/null +++ b/src/3rdparty/libucontext/arch/loongarch64/defs.h @@ -0,0 +1,76 @@ +#ifndef __ARCH_LOONGARCH64_DEFS_H +#define __ARCH_LOONGARCH64_DEFS_H + +#define REG_SZ (8) + +#define REG_R0 (0) +#define REG_R1 (1) +#define REG_R2 (2) +#define REG_R3 (3) +#define REG_R4 (4) +#define REG_R5 (5) +#define REG_R6 (6) +#define REG_R7 (7) +#define REG_R8 (8) +#define REG_R9 (9) +#define REG_R10 (10) +#define REG_R11 (11) +#define REG_R12 (12) +#define REG_R13 (13) +#define REG_R14 (14) +#define REG_R15 (15) +#define REG_R16 (16) +#define REG_R17 (17) +#define REG_R18 (18) +#define REG_R19 (19) +#define REG_R20 (20) +#define REG_R21 (21) +#define REG_R22 (22) +#define REG_R23 (23) +#define REG_R24 (24) +#define REG_R25 (25) +#define REG_R26 (26) +#define REG_R27 (27) +#define REG_R28 (28) +#define REG_R29 (29) +#define REG_R30 (30) +#define REG_R31 (31) + +/* $a0 is $4 , also $v0, same as $5, $a1 and $v1*/ +#define REG_A0 (4) + +/* stack pointer is actually $3 */ +#define REG_SP (3) + +/* frame pointer is actually $22 */ +#define REG_FP (22) + +/* offset to mc_gregs in ucontext_t */ +#define MCONTEXT_GREGS (48) + +/* offset to PC in ucontext_t */ +#define MCONTEXT_PC (40) + +/* offset to uc_link in ucontext_t */ +#define UCONTEXT_UC_LINK (8) + +/* offset to uc_stack.ss_sp in ucontext_t */ +#define UCONTEXT_STACK_PTR (16) + +/* offset to uc_stack.ss_size in ucontext_t */ +#define UCONTEXT_STACK_SIZE (32) + +/* Stack alignment, from Kernel source */ +#define ALSZ 15 +#define ALMASK ~15 +#define FRAMESZ (((LOCALSZ * REG_SZ) + ALSZ) & ALMASK) + +#define PUSH_FRAME(__proc) \ + addi.d $sp, $sp, -FRAMESZ; + +#define POP_FRAME(__proc) \ + addi.d $sp, $sp, FRAMESZ; + +#include + +#endif diff --git a/src/3rdparty/libucontext/arch/loongarch64/freestanding/bits.h b/src/3rdparty/libucontext/arch/loongarch64/freestanding/bits.h new file mode 100644 index 000000000..b567678c5 --- /dev/null +++ b/src/3rdparty/libucontext/arch/loongarch64/freestanding/bits.h @@ -0,0 +1,43 @@ +#ifndef LIBUCONTEXT_BITS_H +#define LIBUCONTEXT_BITS_H + +typedef unsigned long long libucontext_greg_t, libucontext_gregset_t[32]; + +/* Container for all general registers. */ +typedef __loongarch_mc_gp_state gregset_t; + +/* Container for floating-point state. */ +typedef union __loongarch_mc_fp_state fpregset_t; + +union __loongarch_mc_fp_state { + unsigned int __val32[256 / 32]; + unsigned long long __val64[256 / 64]; +}; + +typedef struct mcontext_t { + unsigned long long __pc; + unsigned long long __gregs[32]; + unsigned int __flags; + + unsigned int __fcsr; + unsigned int __vcsr; + unsigned long long __fcc; + union __loongarch_mc_fp_state __fpregs[32] __attribute__((__aligned__ (32))); + + unsigned int __reserved; +} mcontext_t; + +typedef struct { + void *ss_sp; + int ss_flags; + size_t ss_size; +} libucontext_stack_t; + +typedef struct libucontext_ucontext { + unsigned long uc_flags; + struct libucontext_ucontext *uc_link; + libucontext_stack_t uc_stack; + libucontext_mcontext_t uc_mcontext; +} libucontext_ucontext_t; + +#endif diff --git a/src/3rdparty/libucontext/arch/loongarch64/getcontext.S b/src/3rdparty/libucontext/arch/loongarch64/getcontext.S new file mode 100644 index 000000000..49c90a9e8 --- /dev/null +++ b/src/3rdparty/libucontext/arch/loongarch64/getcontext.S @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021 Peng Fan + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#define LOCALSZ (1) + +#include "defs.h" + +ALIAS(getcontext, libucontext_getcontext) + +FUNC(libucontext_getcontext) + /* copy $sp, $fp to temporary registers so we don't clobber them */ + move $a2, $sp + move $a3, $fp + + PUSH_FRAME(libucontext_getcontext) + + /* set registers */ + st.d $s0, $a0, REG_OFFSET(23) + st.d $s1, $a0, REG_OFFSET(24) + st.d $s2, $a0, REG_OFFSET(25) + st.d $s3, $a0, REG_OFFSET(26) + st.d $s4, $a0, REG_OFFSET(27) + st.d $s5, $a0, REG_OFFSET(28) + st.d $s6, $a0, REG_OFFSET(29) + st.d $s7, $a0, REG_OFFSET(30) + st.d $s8, $a0, REG_OFFSET(31) + + st.d $a2, $a0, REG_OFFSET(3) + st.d $a3, $a0, REG_OFFSET(22) + st.d $ra, $a0, REG_OFFSET(1) + + st.d $ra, $a0, (MCONTEXT_PC) + + POP_FRAME(libucontext_getcontext) + + jr $ra +END(libucontext_getcontext) diff --git a/src/3rdparty/libucontext/arch/loongarch64/makecontext.S b/src/3rdparty/libucontext/arch/loongarch64/makecontext.S new file mode 100644 index 000000000..44ec9a593 --- /dev/null +++ b/src/3rdparty/libucontext/arch/loongarch64/makecontext.S @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2021 Peng Fan + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "defs.h" + +#define LOCALSZ (6) + +#define A3_OFF (FRAMESZ - (5 * REG_SZ)) +#define A4_OFF (FRAMESZ - (4 * REG_SZ)) +#define A5_OFF (FRAMESZ - (3 * REG_SZ)) +#define A6_OFF (FRAMESZ - (2 * REG_SZ)) +#define A7_OFF (FRAMESZ - (1 * REG_SZ)) + +ALIAS(makecontext, libucontext_makecontext) + +FUNC(libucontext_makecontext) + PUSH_FRAME(libucontext_makecontext) + + move $t5, $a0 + move $t4, $a1 + + /* store $a3 through $a7 to the stack frame. */ + st.d $a3, $sp, A3_OFF + st.d $a4, $sp, A4_OFF + st.d $a5, $sp, A5_OFF + st.d $a6, $sp, A6_OFF + st.d $a7, $sp, A7_OFF + + /* set $zero in the mcontext to 1. */ + addi.d $v0, $zero, 1 + st.d $v0, $t5, REG_OFFSET(0) + + /* ensure the stack is aligned on a quad-word boundary. */ + ld.d $t0, $t5, UCONTEXT_STACK_PTR + ld.d $t2, $t5, UCONTEXT_STACK_SIZE + /* the third argument(from zero), that's the first argument of func() */ + addi.d $t1, $sp, A3_OFF + add.d $t0, $t0, $t2 + + addi.d $t7, $zero, ALMASK + and $t0, $t0, $t7 + + /* number of args */ + beq $a2, $zero, no_more_arguments + bltu $a2, $zero, no_more_arguments + + /* store register arguments. */ + addi.d $t2, $t5, MCONTEXT_GREGS + (4 * REG_SZ) + move $t3, $zero + +store_register_arg: + addi.d $t3, $t3, 1 + ld.d $v1, $t1, 0 + addi.d $t1, $t1, REG_SZ + st.d $v1, $t2, 0 + addi.d $t2, $t2, REG_SZ + addi.d $t6, $zero, 8 + bltu $t3, $t6, store_register_arg + bgeu $t3, $a2, no_more_arguments + + /* make room for stack arguments. */ + sub.d $t2, $a2, $t3 + + addi.d $t6, $zero, 3 + sll.d $t2, $t2, $t6 + + sub.d $t0, $t0, $t2 + + addi.d $t6, $zero, ALMASK + and $t0, $t0, $t6 + + /* store stack arguments. */ + move $t2, $t0 + +store_stack_arg: + addi.d $t3, $t3, 1 + ld.d $v1, $t1, 0 + addi.d $t1, $t1, REG_SZ + st.d $v1, $t2, 0 + addi.d $t2, $t2, REG_SZ + bltu $t3, $a2, store_stack_arg + +no_more_arguments: + /* trampoline setup. */ + la.got $t8, libucontext_trampoline + + ld.d $v1, $t5, UCONTEXT_UC_LINK + st.d $v1, $t5, REG_OFFSET(23) + + st.d $t0, $t5, REG_OFFSET(3) + + st.d $t8, $t5, REG_OFFSET(1) + + st.d $t4, $t5, MCONTEXT_PC + + POP_FRAME(libucontext_makecontext) + + jr $ra +END(libucontext_makecontext) diff --git a/src/3rdparty/libucontext/arch/loongarch64/setcontext.S b/src/3rdparty/libucontext/arch/loongarch64/setcontext.S new file mode 100644 index 000000000..9a0e0b7ea --- /dev/null +++ b/src/3rdparty/libucontext/arch/loongarch64/setcontext.S @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021 Peng Fan + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#define LOCALSZ (1) + +#include "defs.h" + +ALIAS(setcontext, libucontext_setcontext) + +FUNC(libucontext_setcontext) + PUSH_FRAME(libucontext_setcontext) + + /* move the context to $v0, in LA, $v0 = $a0 = $4 */ + move $t5, $a0 + + /* load the registers */ + ld.d $a0, $t5, REG_OFFSET(4) + ld.d $a1, $t5, REG_OFFSET(5) + ld.d $a2, $t5, REG_OFFSET(6) + ld.d $a3, $t5, REG_OFFSET(7) + ld.d $a4, $t5, REG_OFFSET(8) + ld.d $a5, $t5, REG_OFFSET(9) + ld.d $a6, $t5, REG_OFFSET(10) + ld.d $a7, $t5, REG_OFFSET(11) + + ld.d $s0, $t5, REG_OFFSET(23) + ld.d $s1, $t5, REG_OFFSET(24) + ld.d $s2, $t5, REG_OFFSET(25) + ld.d $s3, $t5, REG_OFFSET(26) + ld.d $s4, $t5, REG_OFFSET(27) + ld.d $s5, $t5, REG_OFFSET(28) + ld.d $s6, $t5, REG_OFFSET(29) + ld.d $s7, $t5, REG_OFFSET(30) + ld.d $s8, $t5, REG_OFFSET(31) + + ld.d $sp, $t5, REG_OFFSET(3) + ld.d $fp, $t5, REG_OFFSET(22) + ld.d $ra, $t5, REG_OFFSET(1) + + ld.d $t8, $t5, (MCONTEXT_PC) + + jr $t8 + move $v0, $zero + + POP_FRAME(libucontext_setcontext) +END(libucontext_setcontext) diff --git a/src/3rdparty/libucontext/arch/loongarch64/startcontext.S b/src/3rdparty/libucontext/arch/loongarch64/startcontext.S new file mode 100644 index 000000000..b26450c0c --- /dev/null +++ b/src/3rdparty/libucontext/arch/loongarch64/startcontext.S @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2021 Peng Fan + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#define LOCALSZ (4) + +#include "defs.h" + +FUNC(libucontext_trampoline) + + /* call setcontext */ + move $a0, $s0 + /* we receive our initial ucontext in $s0, so if $s0 is nil, bail */ + beqz $s0, no_linked_context + + la.got $t8, libucontext_setcontext + + jr $t8 + +no_linked_context: + move $a0, $zero + la.global $t8, exit + jr $t8 + +END(libucontext_trampoline) diff --git a/src/3rdparty/libucontext/arch/loongarch64/swapcontext.S b/src/3rdparty/libucontext/arch/loongarch64/swapcontext.S new file mode 100644 index 000000000..b5063f68b --- /dev/null +++ b/src/3rdparty/libucontext/arch/loongarch64/swapcontext.S @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2021 Peng Fan + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#define LOCALSZ (4) + +#include "defs.h" + +#define A1_OFFSET (FRAMESZ - (1 * REG_SZ)) + +ALIAS(swapcontext, libucontext_swapcontext) + +FUNC(libucontext_swapcontext) + /* copy $sp, $fp to temporary registers so we don't clobber them */ + move $a3, $sp + move $a4, $fp + + move $t5, $a0 + + PUSH_FRAME(libucontext_swapcontext) + + /* set registers */ + st.d $s0, $t5, REG_OFFSET(23) + st.d $s1, $t5, REG_OFFSET(24) + st.d $s2, $t5, REG_OFFSET(25) + st.d $s3, $t5, REG_OFFSET(26) + st.d $s4, $t5, REG_OFFSET(27) + st.d $s5, $t5, REG_OFFSET(28) + st.d $s6, $t5, REG_OFFSET(29) + st.d $s7, $t5, REG_OFFSET(30) + st.d $s8, $t5, REG_OFFSET(31) + + st.d $a3, $t5, REG_OFFSET(3) + st.d $a4, $t5, REG_OFFSET(22) + st.d $ra, $t5, REG_OFFSET(1) + + st.d $ra, $t5, (MCONTEXT_PC) + + /* copy new context address in $a1 to stack */ + st.d $a1, $sp, A1_OFFSET + + /* load new context address into $v0 */ + ld.d $t4, $sp, A1_OFFSET + + /* load the registers */ + ld.d $a0, $t4, REG_OFFSET(4) + ld.d $a1, $t4, REG_OFFSET(5) + ld.d $a2, $t4, REG_OFFSET(6) + ld.d $a3, $t4, REG_OFFSET(7) + ld.d $a4, $t4, REG_OFFSET(8) + ld.d $a5, $t4, REG_OFFSET(9) + ld.d $a6, $t4, REG_OFFSET(10) + ld.d $a7, $t4, REG_OFFSET(11) + + ld.d $s0, $t4, REG_OFFSET(23) + ld.d $s1, $t4, REG_OFFSET(24) + ld.d $s2, $t4, REG_OFFSET(25) + ld.d $s3, $t4, REG_OFFSET(26) + ld.d $s4, $t4, REG_OFFSET(27) + ld.d $s5, $t4, REG_OFFSET(28) + ld.d $s6, $t4, REG_OFFSET(29) + ld.d $s7, $t4, REG_OFFSET(30) + ld.d $s8, $t4, REG_OFFSET(31) + + ld.d $sp, $t4, REG_OFFSET(3) + ld.d $fp, $t4, REG_OFFSET(22) + ld.d $ra, $t4, REG_OFFSET(1) + + ld.d $t8, $t4, (MCONTEXT_PC) + + jr $t8 + move $v0, $zero + +fail: + la.global $t8, exit + + POP_FRAME(libucontext_swapcontext) + + jirl $ra, $t8, 0 + move $v0, $zero +END(libucontext_swapcontext) diff --git a/src/3rdparty/libucontext/arch/m68k/defs.h b/src/3rdparty/libucontext/arch/m68k/defs.h new file mode 100644 index 000000000..fc1925280 --- /dev/null +++ b/src/3rdparty/libucontext/arch/m68k/defs.h @@ -0,0 +1,34 @@ +#ifndef __ARCH_M68K_DEFS_H +#define __ARCH_M68K_DEFS_H + +#define REG_SZ (4) +#define MCONTEXT_GREGS (24) + +#define REG_D0 (0) +#define REG_D1 (1) +#define REG_D2 (2) +#define REG_D3 (3) +#define REG_D4 (4) +#define REG_D5 (5) +#define REG_D6 (6) +#define REG_D7 (7) +#define REG_A0 (8) +#define REG_A1 (9) +#define REG_A2 (10) +#define REG_A3 (11) +#define REG_A4 (12) +#define REG_A5 (13) +#define REG_A6 (14) +#define REG_A7 (15) +#define REG_SP (15) +#define REG_PC (16) +#define REG_PS (17) + +#define PC_OFFSET REG_OFFSET(REG_PC) + +#define FETCH_LINKPTR(dest) \ + asm("mov.l (%%sp, %%d7.l * 4), %0" :: "r" ((dest))) + +#include "common-defs.h" + +#endif diff --git a/src/3rdparty/libucontext/arch/m68k/getcontext.S b/src/3rdparty/libucontext/arch/m68k/getcontext.S new file mode 100644 index 000000000..40f2c1490 --- /dev/null +++ b/src/3rdparty/libucontext/arch/m68k/getcontext.S @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "defs.h" + +ALIAS(getcontext, libucontext_getcontext) +ALIAS(__getcontext, libucontext_getcontext) + +FUNC(libucontext_getcontext) + move.l 4(%sp), %a0 /* load ucontext_t pointer from stack */ + + movem.l %d2-%d7, REG_OFFSET(REG_D2)(%a0) /* preserve $d2 through $d7 */ + movem.l %a2-%a6, REG_OFFSET(REG_A2)(%a0) /* preserve $a2 through $a6 */ + + lea 4(%sp), %a1 /* load stack pointer into $a1 */ + move.l %a1, REG_OFFSET(REG_SP)(%a0) /* store $a1 in ucontext */ + move.l (%sp), REG_OFFSET(REG_PC)(%a0) /* store return address in ucontext's PC register */ + + clr.l %d0 /* return 0 */ + rts +END(libucontext_getcontext) diff --git a/src/3rdparty/libucontext/arch/m68k/include/libucontext/bits.h b/src/3rdparty/libucontext/arch/m68k/include/libucontext/bits.h new file mode 100644 index 000000000..17d3bf80b --- /dev/null +++ b/src/3rdparty/libucontext/arch/m68k/include/libucontext/bits.h @@ -0,0 +1,28 @@ +#ifndef LIBUCONTEXT_BITS_H +#define LIBUCONTEXT_BITS_H + +typedef struct sigaltstack { + void *ss_sp; + int ss_flags; + size_t ss_size; +} libucontext_stack_t; + +typedef int libucontext_greg_t, libucontext_gregset_t[18]; +typedef struct { + int f_pcr, f_psr, f_fpiaddr, f_fpregs[8][3]; +} libucontext_fpregset_t; + +typedef struct { + int version; + libucontext_gregset_t gregs; + libucontext_fpregset_t fpregs; +} libucontext_mcontext_t; + +typedef struct libucontext_ucontext { + unsigned long uc_flags; + struct libucontext_ucontext *uc_link; + libucontext_stack_t uc_stack; + libucontext_mcontext_t uc_mcontext; +} libucontext_ucontext_t; + +#endif diff --git a/src/3rdparty/libucontext/arch/m68k/makecontext.c b/src/3rdparty/libucontext/arch/m68k/makecontext.c new file mode 100644 index 000000000..c1a38e27c --- /dev/null +++ b/src/3rdparty/libucontext/arch/m68k/makecontext.c @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include +#include +#include +#include +#include "defs.h" +#include + + +extern void libucontext_trampoline(void); + + +void +libucontext_makecontext(libucontext_ucontext_t *ucp, void (*func)(void), int argc, ...) +{ + libucontext_greg_t *sp; + va_list va; + int i; + + /* set up and align the stack. */ + sp = (libucontext_greg_t *) ((uintptr_t) ucp->uc_stack.ss_sp + ucp->uc_stack.ss_size); + sp -= (argc + 2); + sp = (libucontext_greg_t *) (((uintptr_t) sp & ~0x3)); + + /* set up the ucontext structure */ + ucp->uc_mcontext.gregs[REG_SP] = (libucontext_greg_t) sp; + ucp->uc_mcontext.gregs[REG_A6] = 0; + ucp->uc_mcontext.gregs[REG_D7] = argc; + ucp->uc_mcontext.gregs[REG_PC] = (libucontext_greg_t) func; + + /* return address */ + *sp++ = (libucontext_greg_t) libucontext_trampoline; + + va_start(va, argc); + + /* all arguments overflow into stack */ + for (i = 0; i < argc; i++) + *sp++ = va_arg (va, libucontext_greg_t); + + va_end(va); + + /* link pointer */ + *sp++ = (libucontext_greg_t) ucp->uc_link; +} + +#ifdef EXPORT_UNPREFIXED +extern __typeof(libucontext_makecontext) makecontext __attribute__((weak, __alias__("libucontext_makecontext"))); +extern __typeof(libucontext_makecontext) __makecontext __attribute__((weak, __alias__("libucontext_makecontext"))); +#endif diff --git a/src/3rdparty/libucontext/arch/m68k/setcontext.S b/src/3rdparty/libucontext/arch/m68k/setcontext.S new file mode 100644 index 000000000..994c37eae --- /dev/null +++ b/src/3rdparty/libucontext/arch/m68k/setcontext.S @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "defs.h" + +ALIAS(setcontext, libucontext_setcontext) +ALIAS(__setcontext, libucontext_setcontext) + +FUNC(libucontext_setcontext) + move.l 4(%sp), %a0 /* load ucontext_t pointer from stack */ + + move.l REG_OFFSET(REG_SP)(%a0), %sp /* load new stack pointer */ + + movem.l REG_OFFSET(REG_D2)(%a0), %d2-%d7 /* load $d2 through $d7 */ + movem.l REG_OFFSET(REG_A2)(%a0), %a2-%a6 /* load $a2 through $a6 */ + + clr.l %d0 /* clear $d0 */ + + move.l REG_OFFSET(REG_PC)(%a0), %a1 /* load jump target */ + + jmp (%a1) /* jump to *$a1 */ +END(libucontext_setcontext) diff --git a/src/3rdparty/libucontext/arch/m68k/swapcontext.S b/src/3rdparty/libucontext/arch/m68k/swapcontext.S new file mode 100644 index 000000000..e74eca5a7 --- /dev/null +++ b/src/3rdparty/libucontext/arch/m68k/swapcontext.S @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "defs.h" + +ALIAS(swapcontext, libucontext_swapcontext) +ALIAS(__swapcontext, libucontext_swapcontext) + +FUNC(libucontext_swapcontext) + move.l 4(%sp), %a0 /* load save ucontext_t pointer from stack */ + + movem.l %d2-%d7, REG_OFFSET(REG_D2)(%a0) /* preserve $d2 through $d7 */ + movem.l %a2-%a6, REG_OFFSET(REG_A2)(%a0) /* preserve $a2 through $a6 */ + + lea 4(%sp), %a1 /* load stack pointer into $a1 */ + move.l %a1, REG_OFFSET(REG_SP)(%a0) /* store $a1 in ucontext */ + move.l (%sp), REG_OFFSET(REG_PC)(%a0) /* store return address in ucontext's PC register */ + + move.l 8(%sp), %a0 /* load new ucontext_t pointer from stack */ + + move.l REG_OFFSET(REG_SP)(%a0), %sp /* load new stack pointer */ + + movem.l REG_OFFSET(REG_D2)(%a0), %d2-%d7 /* load $d2 through $d7 */ + movem.l REG_OFFSET(REG_A2)(%a0), %a2-%a6 /* load $a2 through $a6 */ + + clr.l %d0 /* clear $d0 */ + + move.l REG_OFFSET(REG_PC)(%a0), %a1 /* load jump target */ + + jmp (%a1) /* jump to *$a1 */ +END(libucontext_swapcontext) diff --git a/src/3rdparty/libucontext/arch/m68k/trampoline.c b/src/3rdparty/libucontext/arch/m68k/trampoline.c new file mode 100644 index 000000000..699a0505d --- /dev/null +++ b/src/3rdparty/libucontext/arch/m68k/trampoline.c @@ -0,0 +1,3 @@ +#include "defs.h" +#include +#include "common-trampoline.c" diff --git a/src/3rdparty/libucontext/arch/mips/defs.h b/src/3rdparty/libucontext/arch/mips/defs.h new file mode 100644 index 000000000..0d605dba1 --- /dev/null +++ b/src/3rdparty/libucontext/arch/mips/defs.h @@ -0,0 +1,94 @@ +#ifndef __ARCH_MIPS64_DEFS_H +#define __ARCH_MIPS64_DEFS_H + +#define REG_SZ (4) + +#define REG_R0 (0) +#define REG_R1 (1) +#define REG_R2 (2) +#define REG_R3 (3) +#define REG_R4 (4) +#define REG_R5 (5) +#define REG_R6 (6) +#define REG_R7 (7) +#define REG_R8 (8) +#define REG_R9 (9) +#define REG_R10 (10) +#define REG_R11 (11) +#define REG_R12 (12) +#define REG_R13 (13) +#define REG_R14 (14) +#define REG_R15 (15) +#define REG_R16 (16) +#define REG_R17 (17) +#define REG_R18 (18) +#define REG_R19 (19) +#define REG_R20 (20) +#define REG_R21 (21) +#define REG_R22 (22) +#define REG_R23 (23) +#define REG_R24 (24) +#define REG_R25 (25) +#define REG_R26 (26) +#define REG_R27 (27) +#define REG_R28 (28) +#define REG_R29 (29) +#define REG_R30 (30) +#define REG_R31 (31) + +/* $a0 is $4 */ +#define REG_A0 (4) + +/* stack pointer is actually $29 */ +#define REG_SP (29) + +/* frame pointer is actually $30 */ +#define REG_FP (30) + +/* $s0 ($16) is used as link register */ +#define REG_LNK (16) + +/* $t9 ($25) is used as entry */ +#define REG_ENTRY (25) + +/* offset to mc_gregs in ucontext_t */ +#define MCONTEXT_GREGS (40) + +/* offset to PC in ucontext_t */ +#define MCONTEXT_PC (32) + +/* offset to uc_link in ucontext_t */ +#define UCONTEXT_UC_LINK (4) + +/* offset to uc_stack.ss_sp in ucontext_t */ +#define UCONTEXT_STACK_PTR (8) + +/* offset to uc_stack.ss_size in ucontext_t */ +#define UCONTEXT_STACK_SIZE (12) + +/* setup frame, from MIPS N32/N64 calling convention manual */ +#define ALSZ 15 +#define ALMASK ~15 +#define FRAMESZ (((LOCALSZ * REG_SZ) + ALSZ) & ALMASK) // 16 +#define GPOFF (FRAMESZ - (LOCALSZ * REG_SZ)) // [16 - 16] + +#define SETUP_FRAME(__proc) \ + .frame $sp, FRAMESZ, $ra; \ + .mask 0x10000000, 0; \ + .fmask 0x00000000, 0; \ + .set noreorder; \ + .cpload $25; \ + .set reorder; + +#define PUSH_FRAME(__proc) \ + addiu $sp, -FRAMESZ; \ + .cprestore GPOFF; + +#define POP_FRAME(__proc) \ + addiu $sp, FRAMESZ + +#define ENT(__proc) .ent __proc, 0; + +#include + +#endif diff --git a/src/3rdparty/libucontext/arch/mips/getcontext.S b/src/3rdparty/libucontext/arch/mips/getcontext.S new file mode 100644 index 000000000..1655612be --- /dev/null +++ b/src/3rdparty/libucontext/arch/mips/getcontext.S @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +LOCALSZ = 1 + +#include "defs.h" + +ALIAS(getcontext, libucontext_getcontext) +ALIAS(__getcontext, libucontext_getcontext) + +FUNC(libucontext_getcontext) + /* copy $gp, $sp, $fp to temporary registers so we don't clobber them */ + move $a2, $gp + move $a3, $sp + move $a1, $fp + + PUSH_FRAME(libucontext_getcontext) + + /* set registers */ + sw $s0, REG_OFFSET(16)($a0) + sw $s1, REG_OFFSET(17)($a0) + sw $s2, REG_OFFSET(18)($a0) + sw $s3, REG_OFFSET(19)($a0) + sw $s4, REG_OFFSET(20)($a0) + sw $s5, REG_OFFSET(21)($a0) + sw $s6, REG_OFFSET(22)($a0) + sw $s7, REG_OFFSET(23)($a0) + + sw $a2, REG_OFFSET(28)($a0) + sw $a3, REG_OFFSET(29)($a0) + sw $a1, REG_OFFSET(30)($a0) + sw $ra, REG_OFFSET(31)($a0) + sw $ra, (MCONTEXT_PC)($a0) + + POP_FRAME(libucontext_getcontext) + + jr $ra +END(libucontext_getcontext) diff --git a/src/3rdparty/libucontext/arch/mips/include/libucontext/bits.h b/src/3rdparty/libucontext/arch/mips/include/libucontext/bits.h new file mode 100644 index 000000000..41a271a82 --- /dev/null +++ b/src/3rdparty/libucontext/arch/mips/include/libucontext/bits.h @@ -0,0 +1,27 @@ +#ifndef LIBUCONTEXT_BITS_H +#define LIBUCONTEXT_BITS_H + +typedef unsigned long long libucontext_greg_t, libucontext_gregset_t[32]; + +typedef struct { + unsigned regmask, status; + unsigned long long pc, gregs[32], fpregs[32]; + unsigned ownedfp, fpc_csr, fpc_eir, used_math, dsp; + unsigned long long mdhi, mdlo; + unsigned long hi1, lo1, hi2, lo2, hi3, lo3; +} libucontext_mcontext_t; + +typedef struct { + void *ss_sp; + size_t ss_size; + int ss_flags; +} libucontext_stack_t; + +typedef struct libucontext_ucontext { + unsigned long uc_flags; + struct libucontext_ucontext *uc_link; + libucontext_stack_t uc_stack; + libucontext_mcontext_t uc_mcontext; +} libucontext_ucontext_t; + +#endif diff --git a/src/3rdparty/libucontext/arch/mips/makecontext.S b/src/3rdparty/libucontext/arch/mips/makecontext.S new file mode 100644 index 000000000..bb841aed7 --- /dev/null +++ b/src/3rdparty/libucontext/arch/mips/makecontext.S @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +LOCALSZ = 2 + +#include "defs.h" + +A3_OFF = FRAMESZ + (3 * REG_SZ) + +/* + * Because we have to fiddle with $gp, we have to implement this in + * assembly rather than C. Annoying, that... + */ + +ALIAS(makecontext, libucontext_makecontext) +ALIAS(__makecontext, libucontext_makecontext) + +FUNC(libucontext_makecontext) + PUSH_FRAME(libucontext_makecontext) + + /* store $a3 through $a7 to the stack frame. */ + sw $a3, A3_OFF($sp) + + /* set $zero in the mcontext to 1. */ + li $v0, 1 + sw $v0, REG_OFFSET(0)($a0) + + /* ensure the stack is aligned on a quad-word boundary. */ + lw $t0, UCONTEXT_STACK_PTR($a0) + lw $t2, UCONTEXT_STACK_SIZE($a0) + addiu $t1, $sp, A3_OFF + addu $t0, $t2 + and $t0, ALMASK + blez $a2, no_more_arguments + + /* store register arguments. */ + addiu $t2, $a0, MCONTEXT_GREGS + (4 * REG_SZ) + move $t3, $zero + +store_register_arg: + addiu $t3, 1 + lw $v1, ($t1) + addiu $t1, REG_SZ + sw $v1, ($t2) + addiu $t2, REG_SZ + bgeu $t3, $a2, no_more_arguments + bltu $t3, 4, store_register_arg + + /* make room for stack arguments. */ + subu $t2, $a2, $t3 + sll $t2, 3 + subu $t0, $t2 + and $t0, ALMASK + + /* store stack arguments. */ + move $t2, $t0 + +store_stack_arg: + addiu $t3, 1 + lw $v1, ($t1) + addiu $t1, REG_SZ + sw $v1, ($t2) + addiu $t2, REG_SZ + bltu $t3, $a2, store_stack_arg + +no_more_arguments: + /* make room for $a0-$a3 storage */ + addiu $t0, -(4 * REG_SZ) + + /* trampoline setup. */ + la $t9, libucontext_trampoline + + /* copy link pointer as $s0... */ + lw $v1, UCONTEXT_UC_LINK($a0) + sw $v1, REG_OFFSET(16)($a0) + + /* set our $sp */ + sw $t0, REG_OFFSET(29)($a0) + + /* $gp is copied as $s1 */ + sw $gp, REG_OFFSET(17)($a0) + + /* set our $ra */ + sw $t9, REG_OFFSET(31)($a0) + + /* set our $pc */ + sw $a1, MCONTEXT_PC($a0) + + POP_FRAME(libucontext_makecontext) + + jr $ra +END(libucontext_makecontext) diff --git a/src/3rdparty/libucontext/arch/mips/setcontext.S b/src/3rdparty/libucontext/arch/mips/setcontext.S new file mode 100644 index 000000000..6017048f4 --- /dev/null +++ b/src/3rdparty/libucontext/arch/mips/setcontext.S @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +LOCALSZ = 1 + +#include "defs.h" + +ALIAS(setcontext, libucontext_setcontext) +ALIAS(__setcontext, libucontext_setcontext) + +FUNC(libucontext_setcontext) + PUSH_FRAME(libucontext_setcontext) + + /* move the context to $v0 */ + move $v0, $a0 + + /* load the registers */ + lw $a0, REG_OFFSET(4)($v0) + lw $a1, REG_OFFSET(5)($v0) + lw $a2, REG_OFFSET(6)($v0) + lw $a3, REG_OFFSET(7)($v0) + + lw $s0, REG_OFFSET(16)($v0) + lw $s1, REG_OFFSET(17)($v0) + lw $s2, REG_OFFSET(18)($v0) + lw $s3, REG_OFFSET(19)($v0) + lw $s4, REG_OFFSET(20)($v0) + lw $s5, REG_OFFSET(21)($v0) + lw $s6, REG_OFFSET(22)($v0) + lw $s7, REG_OFFSET(23)($v0) + + lw $gp, REG_OFFSET(28)($v0) + lw $sp, REG_OFFSET(29)($v0) + lw $fp, REG_OFFSET(30)($v0) + lw $ra, REG_OFFSET(31)($v0) + lw $t9, (MCONTEXT_PC)($v0) + + move $v0, $zero + jr $t9 + + POP_FRAME(libucontext_setcontext) +END(libucontext_setcontext) diff --git a/src/3rdparty/libucontext/arch/mips/startcontext.S b/src/3rdparty/libucontext/arch/mips/startcontext.S new file mode 100644 index 000000000..2ac79ec76 --- /dev/null +++ b/src/3rdparty/libucontext/arch/mips/startcontext.S @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +LOCALSZ = 1 + +#include "defs.h" + +.hidden libucontext_trampoline +FUNC(libucontext_trampoline) + move $gp, $s1 + + /* we receive our initial ucontext in $s0, so if $s0 is nil, bail */ + beqz $s0, no_linked_context + + /* call setcontext */ + move $a0, $s0 + la $t9, PROC_NAME(libucontext_setcontext) + + jr $t9 + +no_linked_context: + move $a0, $zero + la $t9, exit + jalr $t9 + nop +END(libucontext_trampoline) diff --git a/src/3rdparty/libucontext/arch/mips/swapcontext.S b/src/3rdparty/libucontext/arch/mips/swapcontext.S new file mode 100644 index 000000000..5e5647083 --- /dev/null +++ b/src/3rdparty/libucontext/arch/mips/swapcontext.S @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +LOCALSZ = 2 + +#include "defs.h" + +A1_OFFSET = FRAMESZ - (1 * REG_SZ) + +ALIAS(swapcontext, libucontext_swapcontext) +ALIAS(__swapcontext, libucontext_swapcontext) + +FUNC(libucontext_swapcontext) + /* copy $gp, $sp, $fp to temporary registers so we don't clobber them */ + move $a2, $gp + move $a3, $sp + + PUSH_FRAME(libucontext_swapcontext) + + /* set registers */ + sd $s0, REG_OFFSET(16)($a0) + sd $s1, REG_OFFSET(17)($a0) + sd $s2, REG_OFFSET(18)($a0) + sd $s3, REG_OFFSET(19)($a0) + sd $s4, REG_OFFSET(20)($a0) + sd $s5, REG_OFFSET(21)($a0) + sd $s6, REG_OFFSET(22)($a0) + sd $s7, REG_OFFSET(23)($a0) + + sd $a2, REG_OFFSET(28)($a0) + sd $a3, REG_OFFSET(29)($a0) + sd $fp, REG_OFFSET(30)($a0) + sd $ra, REG_OFFSET(31)($a0) + sd $ra, (MCONTEXT_PC)($a0) + + /* copy new context address in $a1 to stack */ + sd $a1, A1_OFFSET($sp) + + /* load new context address into $v0 */ + ld $v0, A1_OFFSET($sp) + + /* load the registers */ + ld $a0, REG_OFFSET(4)($v0) + ld $a1, REG_OFFSET(5)($v0) + ld $a2, REG_OFFSET(6)($v0) + ld $a3, REG_OFFSET(7)($v0) + + ld $s0, REG_OFFSET(16)($v0) + ld $s1, REG_OFFSET(17)($v0) + ld $s2, REG_OFFSET(18)($v0) + ld $s3, REG_OFFSET(19)($v0) + ld $s4, REG_OFFSET(20)($v0) + ld $s5, REG_OFFSET(21)($v0) + ld $s6, REG_OFFSET(22)($v0) + ld $s7, REG_OFFSET(23)($v0) + + ld $gp, REG_OFFSET(28)($v0) + ld $sp, REG_OFFSET(29)($v0) + ld $fp, REG_OFFSET(30)($v0) + ld $ra, REG_OFFSET(31)($v0) + ld $t9, (MCONTEXT_PC)($v0) + + move $v0, $zero + jr $t9 + +fail: + la $t9, exit + + POP_FRAME(libucontext_swapcontext) + + move $v0, $zero + jalr $t9 +END(libucontext_swapcontext) diff --git a/src/3rdparty/libucontext/arch/mips64/defs.h b/src/3rdparty/libucontext/arch/mips64/defs.h new file mode 100644 index 000000000..ade528842 --- /dev/null +++ b/src/3rdparty/libucontext/arch/mips64/defs.h @@ -0,0 +1,92 @@ +#ifndef __ARCH_MIPS64_DEFS_H +#define __ARCH_MIPS64_DEFS_H + +#define REG_SZ (8) + +#define REG_R0 (0) +#define REG_R1 (1) +#define REG_R2 (2) +#define REG_R3 (3) +#define REG_R4 (4) +#define REG_R5 (5) +#define REG_R6 (6) +#define REG_R7 (7) +#define REG_R8 (8) +#define REG_R9 (9) +#define REG_R10 (10) +#define REG_R11 (11) +#define REG_R12 (12) +#define REG_R13 (13) +#define REG_R14 (14) +#define REG_R15 (15) +#define REG_R16 (16) +#define REG_R17 (17) +#define REG_R18 (18) +#define REG_R19 (19) +#define REG_R20 (20) +#define REG_R21 (21) +#define REG_R22 (22) +#define REG_R23 (23) +#define REG_R24 (24) +#define REG_R25 (25) +#define REG_R26 (26) +#define REG_R27 (27) +#define REG_R28 (28) +#define REG_R29 (29) +#define REG_R30 (30) +#define REG_R31 (31) + +/* $a0 is $4 */ +#define REG_A0 (4) + +/* stack pointer is actually $29 */ +#define REG_SP (29) + +/* frame pointer is actually $30 */ +#define REG_FP (30) + +/* $s0 ($16) is used as link register */ +#define REG_LNK (16) + +/* $t9 ($25) is used as entry */ +#define REG_ENTRY (25) + +/* offset to mc_gregs in ucontext_t */ +#define MCONTEXT_GREGS (40) + +/* offset to PC in ucontext_t */ +#define MCONTEXT_PC (MCONTEXT_GREGS + 576) + +/* offset to uc_link in ucontext_t */ +#define UCONTEXT_UC_LINK (8) + +/* offset to uc_stack.ss_sp in ucontext_t */ +#define UCONTEXT_STACK_PTR (16) + +/* offset to uc_stack.ss_size in ucontext_t */ +#define UCONTEXT_STACK_SIZE (24) + +/* setup frame, from MIPS N32/N64 calling convention manual */ +#define ALSZ 15 +#define ALMASK ~15 +#define FRAMESZ (((LOCALSZ * REG_SZ) + ALSZ) & ALMASK) // 16 +#define GPOFF (FRAMESZ - (LOCALSZ * REG_SZ)) // [16 - 16] + +#define SETUP_FRAME(__proc) \ + .frame $sp, FRAMESZ, $ra; \ + .mask 0x10000000, 0; \ + .fmask 0x00000000, 0; + +#define PUSH_FRAME(__proc) \ + daddiu $sp, -FRAMESZ; \ + .cpsetup $25, GPOFF, __proc; + +#define POP_FRAME(__proc) \ + .cpreturn; \ + daddiu $sp, FRAMESZ + +#define ENT(__proc) .ent __proc, 0; + +#include + +#endif diff --git a/src/3rdparty/libucontext/arch/mips64/getcontext.S b/src/3rdparty/libucontext/arch/mips64/getcontext.S new file mode 100644 index 000000000..9912573cc --- /dev/null +++ b/src/3rdparty/libucontext/arch/mips64/getcontext.S @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +LOCALSZ = 1 + +#include "defs.h" + +ALIAS(getcontext, libucontext_getcontext) +ALIAS(__getcontext, libucontext_getcontext) + +FUNC(libucontext_getcontext) + /* copy $gp, $sp, $fp to temporary registers so we don't clobber them */ + move $a2, $gp + move $a3, $sp + move $a4, $fp + + PUSH_FRAME(libucontext_getcontext) + + /* set registers */ + sd $s0, REG_OFFSET(16)($a0) + sd $s1, REG_OFFSET(17)($a0) + sd $s2, REG_OFFSET(18)($a0) + sd $s3, REG_OFFSET(19)($a0) + sd $s4, REG_OFFSET(20)($a0) + sd $s5, REG_OFFSET(21)($a0) + sd $s6, REG_OFFSET(22)($a0) + sd $s7, REG_OFFSET(23)($a0) + + sd $a2, REG_OFFSET(28)($a0) + sd $a3, REG_OFFSET(29)($a0) + sd $a4, REG_OFFSET(30)($a0) + sd $ra, REG_OFFSET(31)($a0) + sd $ra, (MCONTEXT_PC)($a0) + + POP_FRAME(libucontext_getcontext) + + jr $ra +END(libucontext_getcontext) diff --git a/src/3rdparty/libucontext/arch/mips64/include/libucontext/bits.h b/src/3rdparty/libucontext/arch/mips64/include/libucontext/bits.h new file mode 100644 index 000000000..538dcd580 --- /dev/null +++ b/src/3rdparty/libucontext/arch/mips64/include/libucontext/bits.h @@ -0,0 +1,47 @@ +#ifndef LIBUCONTEXT_BITS_H +#define LIBUCONTEXT_BITS_H + +typedef unsigned long long libucontext_greg_t, libucontext_gregset_t[32]; + +typedef struct { + union { + double fp_dregs[32]; + struct { + float _fp_fregs; + unsigned _fp_pad; + } fp_fregs[32]; + } fp_r; +} libucontext_fpregset_t; + +typedef struct { + libucontext_gregset_t gregs; + libucontext_fpregset_t fpregs; + libucontext_greg_t mdhi; + libucontext_greg_t hi1; + libucontext_greg_t hi2; + libucontext_greg_t hi3; + libucontext_greg_t mdlo; + libucontext_greg_t lo1; + libucontext_greg_t lo2; + libucontext_greg_t lo3; + libucontext_greg_t pc; + unsigned int fpc_csr; + unsigned int used_math; + unsigned int dsp; + unsigned int reserved; +} libucontext_mcontext_t; + +typedef struct { + void *ss_sp; + size_t ss_size; + int ss_flags; +} libucontext_stack_t; + +typedef struct libucontext_ucontext { + unsigned long uc_flags; + struct libucontext_ucontext *uc_link; + libucontext_stack_t uc_stack; + libucontext_mcontext_t uc_mcontext; +} libucontext_ucontext_t; + +#endif diff --git a/src/3rdparty/libucontext/arch/mips64/makecontext.S b/src/3rdparty/libucontext/arch/mips64/makecontext.S new file mode 100644 index 000000000..98b4b514b --- /dev/null +++ b/src/3rdparty/libucontext/arch/mips64/makecontext.S @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +/* $gp + 5 args */ +LOCALSZ = 6 + +#include "defs.h" + +A3_OFF = FRAMESZ - (5 * REG_SZ) +A4_OFF = FRAMESZ - (4 * REG_SZ) +A5_OFF = FRAMESZ - (3 * REG_SZ) +A6_OFF = FRAMESZ - (2 * REG_SZ) +A7_OFF = FRAMESZ - (1 * REG_SZ) + +/* + * Because we have to fiddle with $gp, we have to implement this in + * assembly rather than C. Annoying, that... + */ + +ALIAS(makecontext, libucontext_makecontext) +ALIAS(__makecontext, libucontext_makecontext) + +FUNC(libucontext_makecontext) + PUSH_FRAME(libucontext_makecontext) + + /* store $a3 through $a7 to the stack frame. */ + sd $a3, A3_OFF($sp) + sd $a4, A4_OFF($sp) + sd $a5, A5_OFF($sp) + sd $a6, A6_OFF($sp) + sd $a7, A7_OFF($sp) + + /* set $zero in the mcontext to 1. */ + li $v0, 1 + sd $v0, REG_OFFSET(0)($a0) + + /* ensure the stack is aligned on a quad-word boundary. */ + ld $t0, UCONTEXT_STACK_PTR($a0) + ld $t2, UCONTEXT_STACK_SIZE($a0) + daddiu $t1, $sp, A3_OFF + daddu $t0, $t2 + and $t0, ALMASK + blez $a2, no_more_arguments + + /* store register arguments. */ + daddiu $t2, $a0, MCONTEXT_GREGS + (4 * REG_SZ) + move $t3, $zero + +store_register_arg: + daddiu $t3, 1 + ld $v1, ($t1) + daddiu $t1, REG_SZ + sd $v1, ($t2) + daddiu $t2, REG_SZ + bgeu $t3, $a2, no_more_arguments + bltu $t3, 8, store_register_arg + + /* make room for stack arguments. */ + dsubu $t2, $a2, $t3 + dsll $t2, 3 + dsubu $t0, $t2 + and $t0, ALMASK + + /* store stack arguments. */ + move $t2, $t0 + +store_stack_arg: + daddiu $t3, 1 + ld $v1, ($t1) + daddiu $t1, REG_SZ + sd $v1, ($t2) + daddiu $t2, REG_SZ + bltu $t3, $a2, store_stack_arg + +no_more_arguments: + /* trampoline setup. */ + dla $t9, libucontext_trampoline + + /* copy link pointer as $s0... */ + ld $v1, UCONTEXT_UC_LINK($a0) + sd $v1, REG_OFFSET(16)($a0) + + /* set our $sp */ + sd $t0, REG_OFFSET(29)($a0) + + /* $gp is copied as $s1 */ + sd $gp, REG_OFFSET(17)($a0) + + /* set our $ra */ + sd $t9, REG_OFFSET(31)($a0) + + /* set our $pc */ + sd $a1, MCONTEXT_PC($a0) + + POP_FRAME(libucontext_makecontext) + + jr $ra +END(libucontext_makecontext) diff --git a/src/3rdparty/libucontext/arch/mips64/setcontext.S b/src/3rdparty/libucontext/arch/mips64/setcontext.S new file mode 100644 index 000000000..17b9969fc --- /dev/null +++ b/src/3rdparty/libucontext/arch/mips64/setcontext.S @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +LOCALSZ = 1 + +#include "defs.h" + +ALIAS(setcontext, libucontext_setcontext) +ALIAS(__setcontext, libucontext_setcontext) + +FUNC(libucontext_setcontext) + PUSH_FRAME(libucontext_setcontext) + + /* move the context to $v0 */ + move $v0, $a0 + + /* load the registers */ + ld $a0, REG_OFFSET(4)($v0) + ld $a1, REG_OFFSET(5)($v0) + ld $a2, REG_OFFSET(6)($v0) + ld $a3, REG_OFFSET(7)($v0) + ld $a4, REG_OFFSET(8)($v0) + ld $a5, REG_OFFSET(9)($v0) + ld $a6, REG_OFFSET(10)($v0) + ld $a7, REG_OFFSET(11)($v0) + + ld $s0, REG_OFFSET(16)($v0) + ld $s1, REG_OFFSET(17)($v0) + ld $s2, REG_OFFSET(18)($v0) + ld $s3, REG_OFFSET(19)($v0) + ld $s4, REG_OFFSET(20)($v0) + ld $s5, REG_OFFSET(21)($v0) + ld $s6, REG_OFFSET(22)($v0) + ld $s7, REG_OFFSET(23)($v0) + + ld $gp, REG_OFFSET(28)($v0) + ld $sp, REG_OFFSET(29)($v0) + ld $fp, REG_OFFSET(30)($v0) + ld $ra, REG_OFFSET(31)($v0) + ld $t9, (MCONTEXT_PC)($v0) + + move $v0, $zero + jr $t9 + + POP_FRAME(libucontext_setcontext) +END(libucontext_setcontext) diff --git a/src/3rdparty/libucontext/arch/mips64/startcontext.S b/src/3rdparty/libucontext/arch/mips64/startcontext.S new file mode 100644 index 000000000..a96027957 --- /dev/null +++ b/src/3rdparty/libucontext/arch/mips64/startcontext.S @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +LOCALSZ = 1 + +#include "defs.h" + +.hidden libucontext_trampoline +FUNC(libucontext_trampoline) + move $gp, $s1 + + /* we receive our initial ucontext in $s0, so if $s0 is nil, bail */ + beqz $s0, no_linked_context + + /* call setcontext */ + move $a0, $s0 + dla $t9, PROC_NAME(libucontext_setcontext) + + jr $t9 + +no_linked_context: + move $a0, $zero + dla $t9, exit + jalr $t9 + nop +END(libucontext_trampoline) diff --git a/src/3rdparty/libucontext/arch/mips64/swapcontext.S b/src/3rdparty/libucontext/arch/mips64/swapcontext.S new file mode 100644 index 000000000..22846611f --- /dev/null +++ b/src/3rdparty/libucontext/arch/mips64/swapcontext.S @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +LOCALSZ = 2 + +#include "defs.h" + +A1_OFFSET = FRAMESZ - (1 * REG_SZ) + +ALIAS(swapcontext, libucontext_swapcontext) +ALIAS(__swapcontext, libucontext_swapcontext) + +FUNC(libucontext_swapcontext) + /* copy $gp, $sp, $fp to temporary registers so we don't clobber them */ + move $a2, $gp + move $a3, $sp + move $a4, $fp + + PUSH_FRAME(libucontext_swapcontext) + + /* set registers */ + sd $s0, REG_OFFSET(16)($a0) + sd $s1, REG_OFFSET(17)($a0) + sd $s2, REG_OFFSET(18)($a0) + sd $s3, REG_OFFSET(19)($a0) + sd $s4, REG_OFFSET(20)($a0) + sd $s5, REG_OFFSET(21)($a0) + sd $s6, REG_OFFSET(22)($a0) + sd $s7, REG_OFFSET(23)($a0) + + sd $a2, REG_OFFSET(28)($a0) + sd $a3, REG_OFFSET(29)($a0) + sd $a4, REG_OFFSET(30)($a0) + sd $ra, REG_OFFSET(31)($a0) + sd $ra, (MCONTEXT_PC)($a0) + + /* copy new context address in $a1 to stack */ + sd $a1, A1_OFFSET($sp) + + /* load new context address into $v0 */ + ld $v0, A1_OFFSET($sp) + + /* load the registers */ + ld $a0, REG_OFFSET(4)($v0) + ld $a1, REG_OFFSET(5)($v0) + ld $a2, REG_OFFSET(6)($v0) + ld $a3, REG_OFFSET(7)($v0) + ld $a4, REG_OFFSET(8)($v0) + ld $a5, REG_OFFSET(9)($v0) + ld $a6, REG_OFFSET(10)($v0) + ld $a7, REG_OFFSET(11)($v0) + + ld $s0, REG_OFFSET(16)($v0) + ld $s1, REG_OFFSET(17)($v0) + ld $s2, REG_OFFSET(18)($v0) + ld $s3, REG_OFFSET(19)($v0) + ld $s4, REG_OFFSET(20)($v0) + ld $s5, REG_OFFSET(21)($v0) + ld $s6, REG_OFFSET(22)($v0) + ld $s7, REG_OFFSET(23)($v0) + + ld $gp, REG_OFFSET(28)($v0) + ld $sp, REG_OFFSET(29)($v0) + ld $fp, REG_OFFSET(30)($v0) + ld $ra, REG_OFFSET(31)($v0) + ld $t9, (MCONTEXT_PC)($v0) + + move $v0, $zero + jr $t9 + +fail: + dla $t9, exit + + POP_FRAME(libucontext_swapcontext) + + move $v0, $zero + jalr $t9 +END(libucontext_swapcontext) diff --git a/src/3rdparty/libucontext/arch/or1k/defs.h b/src/3rdparty/libucontext/arch/or1k/defs.h new file mode 100644 index 000000000..fe2b31f91 --- /dev/null +++ b/src/3rdparty/libucontext/arch/or1k/defs.h @@ -0,0 +1,22 @@ +#ifndef __ARCH_OR1K_DEFS_H +#define __ARCH_OR1K_DEFS_H + +#define REG_SZ (4) +#define MCONTEXT_GREGS (20) + +#define REG_SP (1) +#define REG_FP (2) +#define REG_RA (9) +#define REG_SA (11) +#define REG_LR (14) +#define REG_PC (33) +#define REG_SR (34) + +#define PC_OFFSET REG_OFFSET(REG_PC) + +#define FETCH_LINKPTR(dest) \ + asm("l.ori %0, r14, 0" :: "r" ((dest))) + +#include "common-defs.h" + +#endif diff --git a/src/3rdparty/libucontext/arch/or1k/getcontext.S b/src/3rdparty/libucontext/arch/or1k/getcontext.S new file mode 100644 index 000000000..0d7e55c7a --- /dev/null +++ b/src/3rdparty/libucontext/arch/or1k/getcontext.S @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2022 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "defs.h" + +ALIAS(getcontext, libucontext_getcontext) +ALIAS(__getcontext, libucontext_getcontext) + +FUNC(libucontext_getcontext) + l.sw REG_OFFSET(1)(r3), r1 /* store r1 and r2 */ + l.sw REG_OFFSET(2)(r3), r2 + + l.sw REG_OFFSET(9)(r3), r9 /* store r9 to both r9 and r11 in the context */ + l.sw REG_OFFSET(11)(r3), r9 + + l.sw REG_OFFSET(10)(r3), r10 /* store r10 for TLS */ + + l.sw REG_OFFSET(14)(r3), r14 /* store r14 through r30 even */ + l.sw REG_OFFSET(16)(r3), r16 + l.sw REG_OFFSET(18)(r3), r18 + l.sw REG_OFFSET(20)(r3), r20 + l.sw REG_OFFSET(22)(r3), r22 + l.sw REG_OFFSET(24)(r3), r24 + l.sw REG_OFFSET(26)(r3), r26 + l.sw REG_OFFSET(28)(r3), r28 + l.sw REG_OFFSET(30)(r3), r30 + + l.jr r9 + l.ori r11, r0, 0 +END(libucontext_getcontext) diff --git a/src/3rdparty/libucontext/arch/or1k/include/libucontext/bits.h b/src/3rdparty/libucontext/arch/or1k/include/libucontext/bits.h new file mode 100644 index 000000000..650351ec3 --- /dev/null +++ b/src/3rdparty/libucontext/arch/or1k/include/libucontext/bits.h @@ -0,0 +1,28 @@ +#ifndef LIBUCONTEXT_BITS_H +#define LIBUCONTEXT_BITS_H + +typedef struct sigaltstack { + void *ss_sp; + int ss_flags; + size_t ss_size; +} libucontext_stack_t; + +typedef int libucontext_greg_t, libucontext_gregset_t[32]; + +typedef struct { + struct { + libucontext_gregset_t gpr; + libucontext_greg_t pc; + libucontext_greg_t sr; + } regs; + unsigned long oldmask; +} libucontext_mcontext_t; + +typedef struct libucontext_ucontext { + unsigned long uc_flags; + struct libucontext_ucontext *uc_link; + libucontext_stack_t uc_stack; + libucontext_mcontext_t uc_mcontext; +} libucontext_ucontext_t; + +#endif diff --git a/src/3rdparty/libucontext/arch/or1k/makecontext.c b/src/3rdparty/libucontext/arch/or1k/makecontext.c new file mode 100644 index 000000000..863ba777c --- /dev/null +++ b/src/3rdparty/libucontext/arch/or1k/makecontext.c @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2022 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include +#include +#include +#include +#include "defs.h" +#include + + +extern void libucontext_trampoline(void); + + +void +libucontext_makecontext(libucontext_ucontext_t *ucp, void (*func)(void), int argc, ...) +{ + libucontext_greg_t *sp; + va_list va; + int i; + + /* set up and align the stack. */ + sp = (libucontext_greg_t *) ((uintptr_t) ucp->uc_stack.ss_sp + ucp->uc_stack.ss_size); + sp -= argc < 6 ? 0 : (argc - 6); + sp = (libucontext_greg_t *) (((uintptr_t) sp & ~0x3)); + + /* set up the ucontext structure */ + ucp->uc_mcontext.regs.gpr[REG_SP] = (libucontext_greg_t) sp; + ucp->uc_mcontext.regs.gpr[REG_RA] = (libucontext_greg_t) &libucontext_trampoline; + ucp->uc_mcontext.regs.gpr[REG_FP] = 0; + ucp->uc_mcontext.regs.gpr[REG_SA] = (libucontext_greg_t) func; + ucp->uc_mcontext.regs.gpr[REG_LR] = (libucontext_greg_t) ucp->uc_link; + + va_start(va, argc); + + /* args less than argv[6] have dedicated registers, else they overflow onto stack */ + for (i = 0; i < argc; i++) + { + if (i < 6) + ucp->uc_mcontext.regs.gpr[i + 3] = va_arg (va, libucontext_greg_t); + else + sp[i - 6] = va_arg (va, libucontext_greg_t); + } + + va_end(va); +} + +#ifdef EXPORT_UNPREFIXED +extern __typeof(libucontext_makecontext) makecontext __attribute__((weak, __alias__("libucontext_makecontext"))); +extern __typeof(libucontext_makecontext) __makecontext __attribute__((weak, __alias__("libucontext_makecontext"))); +#endif diff --git a/src/3rdparty/libucontext/arch/or1k/setcontext.S b/src/3rdparty/libucontext/arch/or1k/setcontext.S new file mode 100644 index 000000000..d25c8009e --- /dev/null +++ b/src/3rdparty/libucontext/arch/or1k/setcontext.S @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2022 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "defs.h" + +ALIAS(setcontext, libucontext_setcontext) +ALIAS(__setcontext, libucontext_setcontext) + +FUNC(libucontext_setcontext) + l.ori r30, r3, 0 /* avoid clobbering r3 by copying to r30 */ + + l.lwz r3, REG_OFFSET(3)(r30) /* restore r3-r8 (argument registers) */ + l.lwz r4, REG_OFFSET(4)(r30) + l.lwz r5, REG_OFFSET(5)(r30) + l.lwz r6, REG_OFFSET(6)(r30) + l.lwz r7, REG_OFFSET(7)(r30) + l.lwz r8, REG_OFFSET(8)(r30) + + l.lwz r1, REG_OFFSET(1)(r30) /* restore stack/frame pointers */ + l.lwz r2, REG_OFFSET(2)(r30) + + l.lwz r9, REG_OFFSET(9)(r30) /* restore link register and starting address register */ + l.lwz r11, REG_OFFSET(11)(r30) + + l.lwz r10, REG_OFFSET(10)(r30) /* restore TLS register */ + + l.lwz r14, REG_OFFSET(14)(r30) /* restore r14-r30, even only */ + l.lwz r16, REG_OFFSET(16)(r30) + l.lwz r18, REG_OFFSET(18)(r30) + l.lwz r20, REG_OFFSET(20)(r30) + l.lwz r22, REG_OFFSET(22)(r30) + l.lwz r24, REG_OFFSET(24)(r30) + l.lwz r26, REG_OFFSET(26)(r30) + l.lwz r28, REG_OFFSET(28)(r30) + l.lwz r30, REG_OFFSET(30)(r30) + + l.jr r11 /* jump to new starting address */ + l.ori r11, r0, 0 +END(libucontext_setcontext) diff --git a/src/3rdparty/libucontext/arch/or1k/swapcontext.S b/src/3rdparty/libucontext/arch/or1k/swapcontext.S new file mode 100644 index 000000000..07198e524 --- /dev/null +++ b/src/3rdparty/libucontext/arch/or1k/swapcontext.S @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2022 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "defs.h" + +ALIAS(swapcontext, libucontext_swapcontext) +ALIAS(__swapcontext, libucontext_swapcontext) + +FUNC(libucontext_swapcontext) + /* copy context into r3 like getcontext */ + l.sw REG_OFFSET(1)(r3), r1 /* store r1 and r2 */ + l.sw REG_OFFSET(2)(r3), r2 + + l.sw REG_OFFSET(9)(r3), r9 /* store r9 to both r9 and r11 in the context */ + l.sw REG_OFFSET(11)(r3), r9 + + l.sw REG_OFFSET(10)(r3), r10 /* store r10 for TLS */ + + l.sw REG_OFFSET(14)(r3), r14 /* store r14 through r30 even */ + l.sw REG_OFFSET(16)(r3), r16 + l.sw REG_OFFSET(18)(r3), r18 + l.sw REG_OFFSET(20)(r3), r20 + l.sw REG_OFFSET(22)(r3), r22 + l.sw REG_OFFSET(24)(r3), r24 + l.sw REG_OFFSET(26)(r3), r26 + l.sw REG_OFFSET(28)(r3), r28 + l.sw REG_OFFSET(30)(r3), r30 + + /* set the new context from r4 */ + l.ori r30, r4, 0 /* copy r4 to r30 to avoid clobbering */ + + l.lwz r3, REG_OFFSET(3)(r30) /* restore r3-r8 (argument registers) */ + l.lwz r4, REG_OFFSET(4)(r30) + l.lwz r5, REG_OFFSET(5)(r30) + l.lwz r6, REG_OFFSET(6)(r30) + l.lwz r7, REG_OFFSET(7)(r30) + l.lwz r8, REG_OFFSET(8)(r30) + + l.lwz r1, REG_OFFSET(1)(r30) /* restore stack/frame pointers */ + l.lwz r2, REG_OFFSET(2)(r30) + + l.lwz r9, REG_OFFSET(9)(r30) /* restore link register and starting address register */ + l.lwz r11, REG_OFFSET(11)(r30) + + l.lwz r10, REG_OFFSET(10)(r30) /* restore TLS register */ + + l.lwz r14, REG_OFFSET(14)(r30) /* restore r14-r30, even only */ + l.lwz r16, REG_OFFSET(16)(r30) + l.lwz r18, REG_OFFSET(18)(r30) + l.lwz r20, REG_OFFSET(20)(r30) + l.lwz r22, REG_OFFSET(22)(r30) + l.lwz r24, REG_OFFSET(24)(r30) + l.lwz r26, REG_OFFSET(26)(r30) + l.lwz r28, REG_OFFSET(28)(r30) + l.lwz r30, REG_OFFSET(30)(r30) + + l.jr r11 /* jump to new starting address */ + l.ori r11, r0, 0 +END(libucontext_swapcontext) diff --git a/src/3rdparty/libucontext/arch/or1k/trampoline.c b/src/3rdparty/libucontext/arch/or1k/trampoline.c new file mode 100644 index 000000000..699a0505d --- /dev/null +++ b/src/3rdparty/libucontext/arch/or1k/trampoline.c @@ -0,0 +1,3 @@ +#include "defs.h" +#include +#include "common-trampoline.c" diff --git a/src/3rdparty/libucontext/arch/ppc/defs.h b/src/3rdparty/libucontext/arch/ppc/defs.h new file mode 100644 index 000000000..531e55120 --- /dev/null +++ b/src/3rdparty/libucontext/arch/ppc/defs.h @@ -0,0 +1,64 @@ +#ifndef __ARCH_PPC_DEFS_H +#define __ARCH_PPC_DEFS_H + +#define REG_R0 (0) +#define REG_R1 (1) +#define REG_R2 (2) +#define REG_R3 (3) +#define REG_R4 (4) +#define REG_R5 (5) +#define REG_R6 (6) +#define REG_R7 (7) +#define REG_R8 (8) +#define REG_R9 (9) +#define REG_R10 (10) +#define REG_R11 (11) +#define REG_R12 (12) +#define REG_R13 (13) +#define REG_R14 (14) +#define REG_R15 (15) +#define REG_R16 (16) +#define REG_R17 (17) +#define REG_R18 (18) +#define REG_R19 (19) +#define REG_R20 (20) +#define REG_R21 (21) +#define REG_R22 (22) +#define REG_R23 (23) +#define REG_R24 (24) +#define REG_R25 (25) +#define REG_R26 (26) +#define REG_R27 (27) +#define REG_R28 (28) +#define REG_R29 (29) +#define REG_R30 (30) +#define REG_R31 (31) +#define REG_R32 (32) +#define REG_R33 (33) +#define REG_R34 (34) +#define REG_R35 (35) +#define REG_R36 (36) +#define REG_R37 (37) +#define REG_R38 (38) +#define REG_R39 (39) +#define REG_R40 (40) +#define REG_R41 (41) +#define REG_R42 (42) +#define REG_R43 (43) +#define REG_R44 (44) +#define REG_R45 (45) +#define REG_R46 (46) +#define REG_R47 (47) + +/* sp register is actually %r1 */ +#define REG_SP REG_R1 + +/* nip register is actually %srr0 (r32) */ +#define REG_NIP REG_R32 + +/* lnk register is actually r32 */ +#define REG_LNK REG_R36 + +#include "common-defs.h" + +#endif diff --git a/src/3rdparty/libucontext/arch/ppc/getcontext.S b/src/3rdparty/libucontext/arch/ppc/getcontext.S new file mode 100644 index 000000000..4920fd3c9 --- /dev/null +++ b/src/3rdparty/libucontext/arch/ppc/getcontext.S @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2019 Bobby Bingham + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "common-defs.h" + +ALIAS(getcontext, libucontext_getcontext) +ALIAS(__getcontext, libucontext_getcontext) + +.hidden __libucontext_swapcontext +FUNC(libucontext_getcontext) + li 4, 0 + b __libucontext_swapcontext@local +END(libucontext_getcontext) diff --git a/src/3rdparty/libucontext/arch/ppc/makecontext.c b/src/3rdparty/libucontext/arch/ppc/makecontext.c new file mode 100644 index 000000000..1b820384b --- /dev/null +++ b/src/3rdparty/libucontext/arch/ppc/makecontext.c @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2018 Ariadne Conill + * Copyright (c) 2019 Bobby Bingham + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include + + +#include "defs.h" +#include + + +extern void libucontext_trampoline(void); + + +void +libucontext_makecontext(libucontext_ucontext_t *ucp, void (*func)(), int argc, ...) +{ + libucontext_greg_t *sp; + va_list va; + int i; + unsigned int stack_args; + + stack_args = argc > 8 ? argc - 8 : 0; + + sp = (libucontext_greg_t *) ((uintptr_t) ucp->uc_stack.ss_sp + ucp->uc_stack.ss_size); + sp -= stack_args + 2; + sp = (libucontext_greg_t *) ((uintptr_t) sp & -16L); + + ucp->uc_mcontext.gregs[REG_NIP] = (uintptr_t) func; + ucp->uc_mcontext.gregs[REG_LNK] = (uintptr_t) &libucontext_trampoline; + ucp->uc_mcontext.gregs[REG_R31] = (uintptr_t) ucp->uc_link; + ucp->uc_mcontext.gregs[REG_SP] = (uintptr_t) sp; + + sp[0] = 0; + + va_start(va, argc); + + for (i = 0; i < argc; i++) { + if (i < 8) + ucp->uc_mcontext.gregs[i + 3] = va_arg (va, libucontext_greg_t); + else + sp[i-8 + 2] = va_arg (va, libucontext_greg_t); + } + + va_end(va); +} + +#ifdef EXPORT_UNPREFIXED +extern __typeof(libucontext_makecontext) makecontext __attribute__((weak, __alias__("libucontext_makecontext"))); +extern __typeof(libucontext_makecontext) __makecontext __attribute__((weak, __alias__("libucontext_makecontext"))); +#endif diff --git a/src/3rdparty/libucontext/arch/ppc/retfromsyscall.c b/src/3rdparty/libucontext/arch/ppc/retfromsyscall.c new file mode 100644 index 000000000..150c13dcd --- /dev/null +++ b/src/3rdparty/libucontext/arch/ppc/retfromsyscall.c @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018 Ariadne Conill + * Copyright (c) 2019 Bobby Bingham + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include + +__attribute__ ((visibility ("hidden"))) +int __retfromsyscall(long retval) +{ + if (retval < 0) { + errno = -retval; + return -1; + } + return 0; +} + diff --git a/src/3rdparty/libucontext/arch/ppc/setcontext.S b/src/3rdparty/libucontext/arch/ppc/setcontext.S new file mode 100644 index 000000000..d634e66f2 --- /dev/null +++ b/src/3rdparty/libucontext/arch/ppc/setcontext.S @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2019 Bobby Bingham + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "common-defs.h" + +ALIAS(setcontext, libucontext_setcontext) +ALIAS(__setcontext, libucontext_setcontext) + +.hidden __libucontext_swapcontext +FUNC(libucontext_setcontext) + mr 4, 3 + li 3, 0 + b __libucontext_swapcontext@local +END(libucontext_setcontext) diff --git a/src/3rdparty/libucontext/arch/ppc/startcontext.S b/src/3rdparty/libucontext/arch/ppc/startcontext.S new file mode 100644 index 000000000..783380871 --- /dev/null +++ b/src/3rdparty/libucontext/arch/ppc/startcontext.S @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "common-defs.h" + +.hidden libucontext_trampoline +FUNC(libucontext_trampoline) + /* get the proper context into position and test for NULL */ + mr. 3,31 + + /* if we have no linked context, lets get out of here */ + beq no_linked_context + + /* jump to setcontext */ + bl libucontext_setcontext@local + +no_linked_context: + b exit@GOT +END(libucontext_trampoline) diff --git a/src/3rdparty/libucontext/arch/ppc/swapcontext.S b/src/3rdparty/libucontext/arch/ppc/swapcontext.S new file mode 100644 index 000000000..3d1efe016 --- /dev/null +++ b/src/3rdparty/libucontext/arch/ppc/swapcontext.S @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2019 Bobby Bingham + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "common-defs.h" + +ALIAS(swapcontext, __libucontext_swapcontext) +ALIAS(__swapcontext, __libucontext_swapcontext) + +/* make sure this is visible regardless of EXPORT_UNPREFIXED */ +.weak libucontext_swapcontext +libucontext_swapcontext = __libucontext_swapcontext + +FUNC(__libucontext_swapcontext) + li 0, 249 # SYS_swapcontext + li 5, 1184 # sizeof(ucontext_t) + sc + +.hidden __retfromsyscall + b __retfromsyscall@local +END(__libucontext_swapcontext) diff --git a/src/3rdparty/libucontext/arch/ppc64/defs.h b/src/3rdparty/libucontext/arch/ppc64/defs.h new file mode 100644 index 000000000..936591110 --- /dev/null +++ b/src/3rdparty/libucontext/arch/ppc64/defs.h @@ -0,0 +1,67 @@ +#ifndef __ARCH_PPC_DEFS_H +#define __ARCH_PPC_DEFS_H + +#define REG_R0 (0) +#define REG_R1 (1) +#define REG_R2 (2) +#define REG_R3 (3) +#define REG_R4 (4) +#define REG_R5 (5) +#define REG_R6 (6) +#define REG_R7 (7) +#define REG_R8 (8) +#define REG_R9 (9) +#define REG_R10 (10) +#define REG_R11 (11) +#define REG_R12 (12) +#define REG_R13 (13) +#define REG_R14 (14) +#define REG_R15 (15) +#define REG_R16 (16) +#define REG_R17 (17) +#define REG_R18 (18) +#define REG_R19 (19) +#define REG_R20 (20) +#define REG_R21 (21) +#define REG_R22 (22) +#define REG_R23 (23) +#define REG_R24 (24) +#define REG_R25 (25) +#define REG_R26 (26) +#define REG_R27 (27) +#define REG_R28 (28) +#define REG_R29 (29) +#define REG_R30 (30) +#define REG_R31 (31) +#define REG_R32 (32) +#define REG_R33 (33) +#define REG_R34 (34) +#define REG_R35 (35) +#define REG_R36 (36) +#define REG_R37 (37) +#define REG_R38 (38) +#define REG_R39 (39) +#define REG_R40 (40) +#define REG_R41 (41) +#define REG_R42 (42) +#define REG_R43 (43) +#define REG_R44 (44) +#define REG_R45 (45) +#define REG_R46 (46) +#define REG_R47 (47) + +/* sp register is actually %r1 */ +#define REG_SP REG_R1 + +/* nip register is actually %srr0 (r32) */ +#define REG_NIP REG_R32 + +/* entry register is actually %r12 */ +#define REG_ENTRY REG_R12 + +/* lnk register is actually %r36 */ +#define REG_LNK REG_R36 + +#include "common-defs.h" + +#endif diff --git a/src/3rdparty/libucontext/arch/ppc64/getcontext.S b/src/3rdparty/libucontext/arch/ppc64/getcontext.S new file mode 100644 index 000000000..1a67424e7 --- /dev/null +++ b/src/3rdparty/libucontext/arch/ppc64/getcontext.S @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2019 Bobby Bingham + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "common-defs.h" + +ALIAS(getcontext, libucontext_getcontext) +ALIAS(__getcontext, libucontext_getcontext) + +.hidden __libucontext_swapcontext +FUNC(libucontext_getcontext) + addis 2, 12, .TOC.-libucontext_getcontext@ha + addi 2, 12, .TOC.-libucontext_getcontext@l + + .localentry libucontext_getcontext,.-libucontext_getcontext + + li 4, 0 + b __libucontext_swapcontext +END(libucontext_getcontext) diff --git a/src/3rdparty/libucontext/arch/ppc64/makecontext.c b/src/3rdparty/libucontext/arch/ppc64/makecontext.c new file mode 100644 index 000000000..15ddbf9f8 --- /dev/null +++ b/src/3rdparty/libucontext/arch/ppc64/makecontext.c @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2018 Ariadne Conill + * Copyright (c) 2019 Bobby Bingham + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include + + +#include "defs.h" +#include + + +extern void libucontext_trampoline(void); + + +void +libucontext_makecontext(libucontext_ucontext_t *ucp, void (*func)(), int argc, ...) +{ + libucontext_greg_t *sp; + va_list va; + int i; + unsigned int stack_args; + + stack_args = argc > 8 ? argc : 0; + + sp = (libucontext_greg_t *) ((uintptr_t) ucp->uc_stack.ss_sp + ucp->uc_stack.ss_size); + sp -= stack_args + 4; + sp = (libucontext_greg_t *) ((uintptr_t) sp & -16L); + + ucp->uc_mcontext.gp_regs[REG_NIP] = (uintptr_t) func; + ucp->uc_mcontext.gp_regs[REG_LNK] = (uintptr_t) &libucontext_trampoline; + ucp->uc_mcontext.gp_regs[REG_SP] = (uintptr_t) sp; + ucp->uc_mcontext.gp_regs[REG_ENTRY] = (uintptr_t) func; + ucp->uc_mcontext.gp_regs[REG_R31] = (uintptr_t) ucp->uc_link; + + sp[0] = 0; + + va_start(va, argc); + + for (i = 0; i < argc; i++) { + if (i < 8) + ucp->uc_mcontext.gp_regs[i + 3] = va_arg (va, libucontext_greg_t); + else + sp[i + 4] = va_arg (va, libucontext_greg_t); + } + + va_end(va); +} + +#ifdef EXPORT_UNPREFIXED +extern __typeof(libucontext_makecontext) makecontext __attribute__((weak, __alias__("libucontext_makecontext"))); +extern __typeof(libucontext_makecontext) __makecontext __attribute__((weak, __alias__("libucontext_makecontext"))); +#endif diff --git a/src/3rdparty/libucontext/arch/ppc64/retfromsyscall.c b/src/3rdparty/libucontext/arch/ppc64/retfromsyscall.c new file mode 100644 index 000000000..150c13dcd --- /dev/null +++ b/src/3rdparty/libucontext/arch/ppc64/retfromsyscall.c @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018 Ariadne Conill + * Copyright (c) 2019 Bobby Bingham + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include + +__attribute__ ((visibility ("hidden"))) +int __retfromsyscall(long retval) +{ + if (retval < 0) { + errno = -retval; + return -1; + } + return 0; +} + diff --git a/src/3rdparty/libucontext/arch/ppc64/setcontext.S b/src/3rdparty/libucontext/arch/ppc64/setcontext.S new file mode 100644 index 000000000..0cd918673 --- /dev/null +++ b/src/3rdparty/libucontext/arch/ppc64/setcontext.S @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2019 Bobby Bingham + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "common-defs.h" + +ALIAS(setcontext, libucontext_setcontext) +ALIAS(__setcontext, libucontext_setcontext) + +.hidden __libucontext_swapcontext +FUNC(libucontext_setcontext) + addis 2, 12, .TOC.-libucontext_setcontext@ha + addi 2, 12, .TOC.-libucontext_setcontext@l + + .localentry libucontext_setcontext,.-libucontext_setcontext + + mr 4, 3 + li 3, 0 + b __libucontext_swapcontext +END(libucontext_setcontext) diff --git a/src/3rdparty/libucontext/arch/ppc64/startcontext.S b/src/3rdparty/libucontext/arch/ppc64/startcontext.S new file mode 100644 index 000000000..367f99d2f --- /dev/null +++ b/src/3rdparty/libucontext/arch/ppc64/startcontext.S @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "common-defs.h" + +.hidden libucontext_trampoline +FUNC(libucontext_trampoline) + cmpdi 31,0 /* test if ucontext link pointer is null */ + beq no_linked_context /* if it is, exit */ + + /* now, call SYS_swapcontext */ + mr 4,31 /* ucp is in r31 */ + li 3,0 /* don't care about restoring, set oucp to NULL */ + li 5,1696 /* sizeof(ucontext_t) */ + li 0,249 /* SYS_swapcontext */ + sc + + /* we should not wind back up here, if we do, exit with -1 */ + li 3,-1 + +no_linked_context: + b exit@GOT + nop +END(libucontext_trampoline) diff --git a/src/3rdparty/libucontext/arch/ppc64/swapcontext.S b/src/3rdparty/libucontext/arch/ppc64/swapcontext.S new file mode 100644 index 000000000..facd491d1 --- /dev/null +++ b/src/3rdparty/libucontext/arch/ppc64/swapcontext.S @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019 Bobby Bingham + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "common-defs.h" + +ALIAS(swapcontext, __libucontext_swapcontext) +ALIAS(__swapcontext, __libucontext_swapcontext) + +/* make sure this is visible regardless of EXPORT_UNPREFIXED */ +.weak libucontext_swapcontext +libucontext_swapcontext = __libucontext_swapcontext + +FUNC(__libucontext_swapcontext) + addis 2, 12, .TOC.-__libucontext_swapcontext@ha + addi 2, 12, .TOC.-__libucontext_swapcontext@l + + .localentry __libucontext_swapcontext,.-__libucontext_swapcontext + + li 0, 249 # SYS_swapcontext + li 5, 1696 # sizeof(ucontext_t) + sc + +.hidden __retfromsyscall + b __retfromsyscall +END(__libucontext_swapcontext) diff --git a/src/3rdparty/libucontext/arch/riscv32/defs.h b/src/3rdparty/libucontext/arch/riscv32/defs.h new file mode 100644 index 000000000..3f737116b --- /dev/null +++ b/src/3rdparty/libucontext/arch/riscv32/defs.h @@ -0,0 +1,55 @@ +#ifndef __ARCH_RISCV64_DEFS_H +#define __ARCH_RISCV64_DEFS_H + +#define REG_SZ (4) +#define MCONTEXT_GREGS (160) + +/* program counter is saved in x0 as well as x1, similar to mips */ +#ifndef REG_PC +#define REG_PC (0) +#endif + +#ifndef REG_RA +#define REG_RA (1) +#endif + +#ifndef REG_SP +#define REG_SP (2) +#endif + +#ifndef REG_S0 +#define REG_S0 (8) +#endif + +#define REG_S1 (9) + +#ifndef REG_A0 +#define REG_A0 (10) +#endif + +#define REG_A1 (11) +#define REG_A2 (12) +#define REG_A3 (13) +#define REG_A4 (14) +#define REG_A5 (15) +#define REG_A6 (16) +#define REG_A7 (17) +#define REG_S2 (18) +#define REG_S3 (19) +#define REG_S4 (20) +#define REG_S5 (21) +#define REG_S6 (22) +#define REG_S7 (23) +#define REG_S8 (24) +#define REG_S9 (25) +#define REG_S10 (26) +#define REG_S11 (27) + +#define PC_OFFSET REG_OFFSET(REG_PC) + +#define FETCH_LINKPTR(dest) \ + asm("mv %0, s1" : "=r" ((dest))) + +#include "common-defs.h" + +#endif diff --git a/src/3rdparty/libucontext/arch/riscv32/getcontext.S b/src/3rdparty/libucontext/arch/riscv32/getcontext.S new file mode 100644 index 000000000..888b6ab1e --- /dev/null +++ b/src/3rdparty/libucontext/arch/riscv32/getcontext.S @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "defs.h" + +ALIAS(getcontext, libucontext_getcontext) +ALIAS(__getcontext, libucontext_getcontext) + +FUNC(libucontext_getcontext) + sw ra, REG_OFFSET(REG_PC)(a0) + sw ra, REG_OFFSET(REG_RA)(a0) + sw sp, REG_OFFSET(REG_SP)(a0) + + /* first saved register block */ + sw s0, REG_OFFSET(REG_S0)(a0) + sw s1, REG_OFFSET(REG_S1)(a0) + + /* return register block */ + sw a0, REG_OFFSET(REG_A0)(a0) + sw a1, REG_OFFSET(REG_A1)(a0) + + /* second saved register block */ + sw s2, REG_OFFSET(REG_S2)(a0) + sw s3, REG_OFFSET(REG_S3)(a0) + sw s4, REG_OFFSET(REG_S4)(a0) + sw s5, REG_OFFSET(REG_S5)(a0) + sw s6, REG_OFFSET(REG_S6)(a0) + sw s7, REG_OFFSET(REG_S7)(a0) + sw s8, REG_OFFSET(REG_S8)(a0) + sw s9, REG_OFFSET(REG_S9)(a0) + sw s10, REG_OFFSET(REG_S10)(a0) + sw s11, REG_OFFSET(REG_S11)(a0) + + /* done saving, return */ + ret +END(libucontext_getcontext) diff --git a/src/3rdparty/libucontext/arch/riscv32/include/libucontext/bits.h b/src/3rdparty/libucontext/arch/riscv32/include/libucontext/bits.h new file mode 100644 index 000000000..8d7867608 --- /dev/null +++ b/src/3rdparty/libucontext/arch/riscv32/include/libucontext/bits.h @@ -0,0 +1,48 @@ +#ifndef LIBUCONTEXT_BITS_H +#define LIBUCONTEXT_BITS_H + +typedef unsigned long libucontext_greg_t; +typedef unsigned long libucontext__riscv_mc_gp_state[32]; + +struct libucontext__riscv_mc_f_ext_state { + unsigned int __f[32]; + unsigned int __fcsr; +}; + +struct libucontext__riscv_mc_d_ext_state { + unsigned long long __f[32]; + unsigned int __fcsr; +}; + +struct libucontext__riscv_mc_q_ext_state { + unsigned long long __f[64] __attribute__((aligned(16))); + unsigned int __fcsr; + unsigned int __reserved[3]; +}; + +union libucontext__riscv_mc_fp_state { + struct libucontext__riscv_mc_f_ext_state __f; + struct libucontext__riscv_mc_d_ext_state __d; + struct libucontext__riscv_mc_q_ext_state __q; +}; + +typedef struct libucontext_mcontext { + libucontext__riscv_mc_gp_state __gregs; + union libucontext__riscv_mc_fp_state __fpregs; +} libucontext_mcontext_t; + +typedef struct { + void *ss_sp; + int ss_flags; + size_t ss_size; +} libucontext_stack_t; + +typedef struct libucontext_ucontext { + unsigned long uc_flags; + struct libucontext_ucontext *uc_link; + libucontext_stack_t uc_stack; + unsigned char __pad[128]; + libucontext_mcontext_t uc_mcontext; +} libucontext_ucontext_t; + +#endif diff --git a/src/3rdparty/libucontext/arch/riscv32/makecontext.c b/src/3rdparty/libucontext/arch/riscv32/makecontext.c new file mode 100644 index 000000000..9a43ef4e9 --- /dev/null +++ b/src/3rdparty/libucontext/arch/riscv32/makecontext.c @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2018 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include +#include +#include +#include +#include "defs.h" +#include + + +extern void libucontext_trampoline(void); + + +void +libucontext_makecontext(libucontext_ucontext_t *ucp, void (*func)(void), int argc, ...) +{ + libucontext_greg_t *sp, *regp; + va_list va; + int i; + + /* set up and align the stack. */ + sp = (libucontext_greg_t *) ((uintptr_t) ucp->uc_stack.ss_sp + ucp->uc_stack.ss_size); + sp -= argc < 8 ? 0 : argc - 8; + sp = (libucontext_greg_t *) (((uintptr_t) sp & -16L)); + + /* set up the ucontext structure */ + ucp->uc_mcontext.__gregs[REG_RA] = (libucontext_greg_t) libucontext_trampoline; + ucp->uc_mcontext.__gregs[REG_S0] = 0; + ucp->uc_mcontext.__gregs[REG_S1] = (libucontext_greg_t) ucp->uc_link; + ucp->uc_mcontext.__gregs[REG_SP] = (libucontext_greg_t) sp; + ucp->uc_mcontext.__gregs[REG_PC] = (libucontext_greg_t) func; + + va_start(va, argc); + + /* first 8 args go in $a0 through $a7. */ + regp = &(ucp->uc_mcontext.__gregs[REG_A0]); + + for (i = 0; (i < argc && i < 8); i++) + *regp++ = va_arg (va, libucontext_greg_t); + + /* remainder overflows into stack */ + for (; i < argc; i++) + *sp++ = va_arg (va, libucontext_greg_t); + + va_end(va); +} + +#ifdef EXPORT_UNPREFIXED +extern __typeof(libucontext_makecontext) makecontext __attribute__((weak, __alias__("libucontext_makecontext"))); +extern __typeof(libucontext_makecontext) __makecontext __attribute__((weak, __alias__("libucontext_makecontext"))); +#endif diff --git a/src/3rdparty/libucontext/arch/riscv32/setcontext.S b/src/3rdparty/libucontext/arch/riscv32/setcontext.S new file mode 100644 index 000000000..06727851e --- /dev/null +++ b/src/3rdparty/libucontext/arch/riscv32/setcontext.S @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "defs.h" + +ALIAS(setcontext, libucontext_setcontext) +ALIAS(__setcontext, libucontext_setcontext) + +FUNC(libucontext_setcontext) + /* move $a0 to $t0 to avoid clobbering. */ + mv t0, a0 + + lw t1, PC_OFFSET(t0) + lw ra, REG_OFFSET(REG_RA)(t0) + lw sp, REG_OFFSET(REG_SP)(t0) + + /* first saved register block */ + lw s0, REG_OFFSET(REG_S0)(t0) + lw s1, REG_OFFSET(REG_S1)(t0) + + /* return register block */ + lw a0, REG_OFFSET(REG_A0)(t0) + lw a1, REG_OFFSET(REG_A1)(t0) + + /* argument register block */ + lw a2, REG_OFFSET(REG_A2)(t0) + lw a3, REG_OFFSET(REG_A3)(t0) + lw a4, REG_OFFSET(REG_A4)(t0) + lw a5, REG_OFFSET(REG_A5)(t0) + lw a6, REG_OFFSET(REG_A6)(t0) + lw a7, REG_OFFSET(REG_A7)(t0) + + /* second saved register block */ + lw s2, REG_OFFSET(REG_S2)(t0) + lw s3, REG_OFFSET(REG_S3)(t0) + lw s4, REG_OFFSET(REG_S4)(t0) + lw s5, REG_OFFSET(REG_S5)(t0) + lw s6, REG_OFFSET(REG_S6)(t0) + lw s7, REG_OFFSET(REG_S7)(t0) + lw s8, REG_OFFSET(REG_S8)(t0) + lw s9, REG_OFFSET(REG_S9)(t0) + lw s10, REG_OFFSET(REG_S10)(t0) + lw s11, REG_OFFSET(REG_S11)(t0) + + /* done restoring, jump to new pc in S1 */ + jr t1 +END(libucontext_setcontext) diff --git a/src/3rdparty/libucontext/arch/riscv32/swapcontext.S b/src/3rdparty/libucontext/arch/riscv32/swapcontext.S new file mode 100644 index 000000000..a4c7138a9 --- /dev/null +++ b/src/3rdparty/libucontext/arch/riscv32/swapcontext.S @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "defs.h" + +ALIAS(swapcontext, libucontext_swapcontext) +ALIAS(__swapcontext, libucontext_swapcontext) + +FUNC(libucontext_swapcontext) + /* move $a1 to $t0 to avoid clobbering. */ + mv t0, a1 + + sw ra, REG_OFFSET(REG_PC)(a0) + sw ra, REG_OFFSET(REG_RA)(a0) + sw sp, REG_OFFSET(REG_SP)(a0) + + /* first saved register block */ + sw s0, REG_OFFSET(REG_S0)(a0) + sw s1, REG_OFFSET(REG_S1)(a0) + + /* return register block */ + sw a0, REG_OFFSET(REG_A0)(a0) + sw a1, REG_OFFSET(REG_A1)(a0) + + /* second saved register block */ + sw s2, REG_OFFSET(REG_S2)(a0) + sw s3, REG_OFFSET(REG_S3)(a0) + sw s4, REG_OFFSET(REG_S4)(a0) + sw s5, REG_OFFSET(REG_S5)(a0) + sw s6, REG_OFFSET(REG_S6)(a0) + sw s7, REG_OFFSET(REG_S7)(a0) + sw s8, REG_OFFSET(REG_S8)(a0) + sw s9, REG_OFFSET(REG_S9)(a0) + sw s10, REG_OFFSET(REG_S10)(a0) + sw s11, REG_OFFSET(REG_S11)(a0) + + /* restore the other context from $t0. */ + lw t1, REG_OFFSET(REG_PC)(t0) + lw ra, REG_OFFSET(REG_RA)(t0) + lw sp, REG_OFFSET(REG_SP)(t0) + + /* first saved register block */ + lw s0, REG_OFFSET(REG_S0)(t0) + lw s1, REG_OFFSET(REG_S1)(t0) + + /* return register block */ + lw a0, REG_OFFSET(REG_A0)(t0) + lw a1, REG_OFFSET(REG_A1)(t0) + + /* argument register block */ + lw a2, REG_OFFSET(REG_A2)(t0) + lw a3, REG_OFFSET(REG_A3)(t0) + lw a4, REG_OFFSET(REG_A4)(t0) + lw a5, REG_OFFSET(REG_A5)(t0) + lw a6, REG_OFFSET(REG_A6)(t0) + lw a7, REG_OFFSET(REG_A7)(t0) + + /* second saved register block */ + lw s2, REG_OFFSET(REG_S2)(t0) + lw s3, REG_OFFSET(REG_S3)(t0) + lw s4, REG_OFFSET(REG_S4)(t0) + lw s5, REG_OFFSET(REG_S5)(t0) + lw s6, REG_OFFSET(REG_S6)(t0) + lw s7, REG_OFFSET(REG_S7)(t0) + lw s8, REG_OFFSET(REG_S8)(t0) + lw s9, REG_OFFSET(REG_S9)(t0) + lw s10, REG_OFFSET(REG_S10)(t0) + lw s11, REG_OFFSET(REG_S11)(t0) + + /* done swapping, jump to new PC in S1 */ + jr t1 +END(libucontext_swapcontext) diff --git a/src/3rdparty/libucontext/arch/riscv32/trampoline.c b/src/3rdparty/libucontext/arch/riscv32/trampoline.c new file mode 100644 index 000000000..699a0505d --- /dev/null +++ b/src/3rdparty/libucontext/arch/riscv32/trampoline.c @@ -0,0 +1,3 @@ +#include "defs.h" +#include +#include "common-trampoline.c" diff --git a/src/3rdparty/libucontext/arch/riscv64/defs.h b/src/3rdparty/libucontext/arch/riscv64/defs.h new file mode 100644 index 000000000..d9b764713 --- /dev/null +++ b/src/3rdparty/libucontext/arch/riscv64/defs.h @@ -0,0 +1,55 @@ +#ifndef __ARCH_RISCV64_DEFS_H +#define __ARCH_RISCV64_DEFS_H + +#define REG_SZ (8) +#define MCONTEXT_GREGS (176) + +/* program counter is saved in x0 as well as x1, similar to mips */ +#ifndef REG_PC +#define REG_PC (0) +#endif + +#ifndef REG_RA +#define REG_RA (1) +#endif + +#ifndef REG_SP +#define REG_SP (2) +#endif + +#ifndef REG_S0 +#define REG_S0 (8) +#endif + +#define REG_S1 (9) + +#ifndef REG_A0 +#define REG_A0 (10) +#endif + +#define REG_A1 (11) +#define REG_A2 (12) +#define REG_A3 (13) +#define REG_A4 (14) +#define REG_A5 (15) +#define REG_A6 (16) +#define REG_A7 (17) +#define REG_S2 (18) +#define REG_S3 (19) +#define REG_S4 (20) +#define REG_S5 (21) +#define REG_S6 (22) +#define REG_S7 (23) +#define REG_S8 (24) +#define REG_S9 (25) +#define REG_S10 (26) +#define REG_S11 (27) + +#define PC_OFFSET REG_OFFSET(REG_PC) + +#define FETCH_LINKPTR(dest) \ + asm("mv %0, s1" : "=r" ((dest))) + +#include "common-defs.h" + +#endif diff --git a/src/3rdparty/libucontext/arch/riscv64/getcontext.S b/src/3rdparty/libucontext/arch/riscv64/getcontext.S new file mode 100644 index 000000000..99a9c9ad9 --- /dev/null +++ b/src/3rdparty/libucontext/arch/riscv64/getcontext.S @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "defs.h" + +ALIAS(getcontext, libucontext_getcontext) +ALIAS(__getcontext, libucontext_getcontext) + +FUNC(libucontext_getcontext) + sd ra, REG_OFFSET(REG_PC)(a0) + sd ra, REG_OFFSET(REG_RA)(a0) + sd sp, REG_OFFSET(REG_SP)(a0) + + /* first saved register block */ + sd s0, REG_OFFSET(REG_S0)(a0) + sd s1, REG_OFFSET(REG_S1)(a0) + + /* return register block */ + sd a0, REG_OFFSET(REG_A0)(a0) + sd a1, REG_OFFSET(REG_A1)(a0) + + /* second saved register block */ + sd s2, REG_OFFSET(REG_S2)(a0) + sd s3, REG_OFFSET(REG_S3)(a0) + sd s4, REG_OFFSET(REG_S4)(a0) + sd s5, REG_OFFSET(REG_S5)(a0) + sd s6, REG_OFFSET(REG_S6)(a0) + sd s7, REG_OFFSET(REG_S7)(a0) + sd s8, REG_OFFSET(REG_S8)(a0) + sd s9, REG_OFFSET(REG_S9)(a0) + sd s10, REG_OFFSET(REG_S10)(a0) + sd s11, REG_OFFSET(REG_S11)(a0) + + /* done saving, return */ + ret +END(libucontext_getcontext) diff --git a/src/3rdparty/libucontext/arch/riscv64/include/libucontext/bits.h b/src/3rdparty/libucontext/arch/riscv64/include/libucontext/bits.h new file mode 100644 index 000000000..8d7867608 --- /dev/null +++ b/src/3rdparty/libucontext/arch/riscv64/include/libucontext/bits.h @@ -0,0 +1,48 @@ +#ifndef LIBUCONTEXT_BITS_H +#define LIBUCONTEXT_BITS_H + +typedef unsigned long libucontext_greg_t; +typedef unsigned long libucontext__riscv_mc_gp_state[32]; + +struct libucontext__riscv_mc_f_ext_state { + unsigned int __f[32]; + unsigned int __fcsr; +}; + +struct libucontext__riscv_mc_d_ext_state { + unsigned long long __f[32]; + unsigned int __fcsr; +}; + +struct libucontext__riscv_mc_q_ext_state { + unsigned long long __f[64] __attribute__((aligned(16))); + unsigned int __fcsr; + unsigned int __reserved[3]; +}; + +union libucontext__riscv_mc_fp_state { + struct libucontext__riscv_mc_f_ext_state __f; + struct libucontext__riscv_mc_d_ext_state __d; + struct libucontext__riscv_mc_q_ext_state __q; +}; + +typedef struct libucontext_mcontext { + libucontext__riscv_mc_gp_state __gregs; + union libucontext__riscv_mc_fp_state __fpregs; +} libucontext_mcontext_t; + +typedef struct { + void *ss_sp; + int ss_flags; + size_t ss_size; +} libucontext_stack_t; + +typedef struct libucontext_ucontext { + unsigned long uc_flags; + struct libucontext_ucontext *uc_link; + libucontext_stack_t uc_stack; + unsigned char __pad[128]; + libucontext_mcontext_t uc_mcontext; +} libucontext_ucontext_t; + +#endif diff --git a/src/3rdparty/libucontext/arch/riscv64/makecontext.c b/src/3rdparty/libucontext/arch/riscv64/makecontext.c new file mode 100644 index 000000000..9a43ef4e9 --- /dev/null +++ b/src/3rdparty/libucontext/arch/riscv64/makecontext.c @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2018 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include +#include +#include +#include +#include "defs.h" +#include + + +extern void libucontext_trampoline(void); + + +void +libucontext_makecontext(libucontext_ucontext_t *ucp, void (*func)(void), int argc, ...) +{ + libucontext_greg_t *sp, *regp; + va_list va; + int i; + + /* set up and align the stack. */ + sp = (libucontext_greg_t *) ((uintptr_t) ucp->uc_stack.ss_sp + ucp->uc_stack.ss_size); + sp -= argc < 8 ? 0 : argc - 8; + sp = (libucontext_greg_t *) (((uintptr_t) sp & -16L)); + + /* set up the ucontext structure */ + ucp->uc_mcontext.__gregs[REG_RA] = (libucontext_greg_t) libucontext_trampoline; + ucp->uc_mcontext.__gregs[REG_S0] = 0; + ucp->uc_mcontext.__gregs[REG_S1] = (libucontext_greg_t) ucp->uc_link; + ucp->uc_mcontext.__gregs[REG_SP] = (libucontext_greg_t) sp; + ucp->uc_mcontext.__gregs[REG_PC] = (libucontext_greg_t) func; + + va_start(va, argc); + + /* first 8 args go in $a0 through $a7. */ + regp = &(ucp->uc_mcontext.__gregs[REG_A0]); + + for (i = 0; (i < argc && i < 8); i++) + *regp++ = va_arg (va, libucontext_greg_t); + + /* remainder overflows into stack */ + for (; i < argc; i++) + *sp++ = va_arg (va, libucontext_greg_t); + + va_end(va); +} + +#ifdef EXPORT_UNPREFIXED +extern __typeof(libucontext_makecontext) makecontext __attribute__((weak, __alias__("libucontext_makecontext"))); +extern __typeof(libucontext_makecontext) __makecontext __attribute__((weak, __alias__("libucontext_makecontext"))); +#endif diff --git a/src/3rdparty/libucontext/arch/riscv64/setcontext.S b/src/3rdparty/libucontext/arch/riscv64/setcontext.S new file mode 100644 index 000000000..36704c91a --- /dev/null +++ b/src/3rdparty/libucontext/arch/riscv64/setcontext.S @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "defs.h" + +ALIAS(setcontext, libucontext_setcontext) +ALIAS(__setcontext, libucontext_setcontext) + +FUNC(libucontext_setcontext) + /* move $a0 to $t0 to avoid clobbering. */ + mv t0, a0 + + ld t1, PC_OFFSET(t0) + ld ra, REG_OFFSET(REG_RA)(t0) + ld sp, REG_OFFSET(REG_SP)(t0) + + /* first saved register block */ + ld s0, REG_OFFSET(REG_S0)(t0) + ld s1, REG_OFFSET(REG_S1)(t0) + + /* return register block */ + ld a0, REG_OFFSET(REG_A0)(t0) + ld a1, REG_OFFSET(REG_A1)(t0) + + /* argument register block */ + ld a2, REG_OFFSET(REG_A2)(t0) + ld a3, REG_OFFSET(REG_A3)(t0) + ld a4, REG_OFFSET(REG_A4)(t0) + ld a5, REG_OFFSET(REG_A5)(t0) + ld a6, REG_OFFSET(REG_A6)(t0) + ld a7, REG_OFFSET(REG_A7)(t0) + + /* second saved register block */ + ld s2, REG_OFFSET(REG_S2)(t0) + ld s3, REG_OFFSET(REG_S3)(t0) + ld s4, REG_OFFSET(REG_S4)(t0) + ld s5, REG_OFFSET(REG_S5)(t0) + ld s6, REG_OFFSET(REG_S6)(t0) + ld s7, REG_OFFSET(REG_S7)(t0) + ld s8, REG_OFFSET(REG_S8)(t0) + ld s9, REG_OFFSET(REG_S9)(t0) + ld s10, REG_OFFSET(REG_S10)(t0) + ld s11, REG_OFFSET(REG_S11)(t0) + + /* done restoring, jump to new pc in S1 */ + jr t1 +END(libucontext_setcontext) diff --git a/src/3rdparty/libucontext/arch/riscv64/swapcontext.S b/src/3rdparty/libucontext/arch/riscv64/swapcontext.S new file mode 100644 index 000000000..a2b013e1c --- /dev/null +++ b/src/3rdparty/libucontext/arch/riscv64/swapcontext.S @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "defs.h" + +ALIAS(swapcontext, libucontext_swapcontext) +ALIAS(__swapcontext, libucontext_swapcontext) + +FUNC(libucontext_swapcontext) + /* move $a1 to $t0 to avoid clobbering. */ + mv t0, a1 + + sd ra, REG_OFFSET(REG_PC)(a0) + sd ra, REG_OFFSET(REG_RA)(a0) + sd sp, REG_OFFSET(REG_SP)(a0) + + /* first saved register block */ + sd s0, REG_OFFSET(REG_S0)(a0) + sd s1, REG_OFFSET(REG_S1)(a0) + + /* return register block */ + sd a0, REG_OFFSET(REG_A0)(a0) + sd a1, REG_OFFSET(REG_A1)(a0) + + /* second saved register block */ + sd s2, REG_OFFSET(REG_S2)(a0) + sd s3, REG_OFFSET(REG_S3)(a0) + sd s4, REG_OFFSET(REG_S4)(a0) + sd s5, REG_OFFSET(REG_S5)(a0) + sd s6, REG_OFFSET(REG_S6)(a0) + sd s7, REG_OFFSET(REG_S7)(a0) + sd s8, REG_OFFSET(REG_S8)(a0) + sd s9, REG_OFFSET(REG_S9)(a0) + sd s10, REG_OFFSET(REG_S10)(a0) + sd s11, REG_OFFSET(REG_S11)(a0) + + /* restore the other context from $t0. */ + ld t1, REG_OFFSET(REG_PC)(t0) + ld ra, REG_OFFSET(REG_RA)(t0) + ld sp, REG_OFFSET(REG_SP)(t0) + + /* first saved register block */ + ld s0, REG_OFFSET(REG_S0)(t0) + ld s1, REG_OFFSET(REG_S1)(t0) + + /* return register block */ + ld a0, REG_OFFSET(REG_A0)(t0) + ld a1, REG_OFFSET(REG_A1)(t0) + + /* argument register block */ + ld a2, REG_OFFSET(REG_A2)(t0) + ld a3, REG_OFFSET(REG_A3)(t0) + ld a4, REG_OFFSET(REG_A4)(t0) + ld a5, REG_OFFSET(REG_A5)(t0) + ld a6, REG_OFFSET(REG_A6)(t0) + ld a7, REG_OFFSET(REG_A7)(t0) + + /* second saved register block */ + ld s2, REG_OFFSET(REG_S2)(t0) + ld s3, REG_OFFSET(REG_S3)(t0) + ld s4, REG_OFFSET(REG_S4)(t0) + ld s5, REG_OFFSET(REG_S5)(t0) + ld s6, REG_OFFSET(REG_S6)(t0) + ld s7, REG_OFFSET(REG_S7)(t0) + ld s8, REG_OFFSET(REG_S8)(t0) + ld s9, REG_OFFSET(REG_S9)(t0) + ld s10, REG_OFFSET(REG_S10)(t0) + ld s11, REG_OFFSET(REG_S11)(t0) + + /* done swapping, jump to new PC in S1 */ + jr t1 +END(libucontext_swapcontext) diff --git a/src/3rdparty/libucontext/arch/riscv64/trampoline.c b/src/3rdparty/libucontext/arch/riscv64/trampoline.c new file mode 100644 index 000000000..699a0505d --- /dev/null +++ b/src/3rdparty/libucontext/arch/riscv64/trampoline.c @@ -0,0 +1,3 @@ +#include "defs.h" +#include +#include "common-trampoline.c" diff --git a/src/3rdparty/libucontext/arch/s390x/defs.h b/src/3rdparty/libucontext/arch/s390x/defs.h new file mode 100644 index 000000000..cad21845f --- /dev/null +++ b/src/3rdparty/libucontext/arch/s390x/defs.h @@ -0,0 +1,15 @@ +#ifndef __ARCH_S390X_DEFS_H +#define __ARCH_S390X_DEFS_H + +#define REG_SZ (8) +#define AREG_SZ (4) + +#define MCONTEXT_GREGS (56) +#define MCONTEXT_AREGS (184) +#define MCONTEXT_FPREGS (248) + +#define AREG_OFFSET(__reg) (MCONTEXT_AREGS + ((__reg) * AREG_SZ)) + +#include "common-defs.h" + +#endif diff --git a/src/3rdparty/libucontext/arch/s390x/getcontext.S b/src/3rdparty/libucontext/arch/s390x/getcontext.S new file mode 100644 index 000000000..fd19d9b80 --- /dev/null +++ b/src/3rdparty/libucontext/arch/s390x/getcontext.S @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018, 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "defs.h" + +ALIAS(getcontext, libucontext_getcontext) +ALIAS(__getcontext, libucontext_getcontext) + +FUNC(libucontext_getcontext) + lgr %r1, %r2 /* use %r1 as our working register */ + la %r2, 0 /* we will return 0 */ + + stam %a0, %a15, AREG_OFFSET(0)(%r1) /* store access registers */ + stmg %r0, %r15, REG_OFFSET(0)(%r1) /* store general-purpose registers */ + + br %r14 /* return to where we came from */ +END(libucontext_getcontext) diff --git a/src/3rdparty/libucontext/arch/s390x/include/libucontext/bits.h b/src/3rdparty/libucontext/arch/s390x/include/libucontext/bits.h new file mode 100644 index 000000000..bce841cbc --- /dev/null +++ b/src/3rdparty/libucontext/arch/s390x/include/libucontext/bits.h @@ -0,0 +1,41 @@ +#ifndef LIBUCONTEXT_BITS_H +#define LIBUCONTEXT_BITS_H + +typedef unsigned long libucontext_greg_t, libucontext_gregset_t[27]; + +typedef struct { + unsigned long mask; + unsigned long addr; +} libucontext_psw_t; + +typedef union { + double d; + float f; +} libucontext_fpreg_t; + +typedef struct { + unsigned fpc; + libucontext_fpreg_t fprs[16]; +} libucontext_fpregset_t; + +typedef struct { + libucontext_psw_t psw; + unsigned long gregs[16]; + unsigned aregs[16]; + libucontext_fpregset_t fpregs; +} libucontext_mcontext_t; + +typedef struct { + void *ss_sp; + int ss_flags; + size_t ss_size; +} libucontext_stack_t; + +typedef struct libucontext_ucontext { + unsigned long uc_flags; + struct libucontext_ucontext *uc_link; + libucontext_stack_t uc_stack; + libucontext_mcontext_t uc_mcontext; +} libucontext_ucontext_t; + +#endif diff --git a/src/3rdparty/libucontext/arch/s390x/makecontext.c b/src/3rdparty/libucontext/arch/s390x/makecontext.c new file mode 100644 index 000000000..22ce2527b --- /dev/null +++ b/src/3rdparty/libucontext/arch/s390x/makecontext.c @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018, 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include +#include +#include + + +#include "defs.h" +#include + + +extern void libucontext_trampoline(void); +extern int libucontext_setcontext(const libucontext_ucontext_t *ucp); + + +void +libucontext_makecontext(libucontext_ucontext_t *ucp, void (*func)(void), int argc, ...) +{ + libucontext_greg_t *sp; + va_list va; + int i; + + sp = (libucontext_greg_t *) ((uintptr_t) ucp->uc_stack.ss_sp + ucp->uc_stack.ss_size); + sp = (libucontext_greg_t *) (((uintptr_t) sp & -8L)); + + ucp->uc_mcontext.gregs[7] = (uintptr_t) func; + ucp->uc_mcontext.gregs[8] = (uintptr_t) ucp->uc_link; + ucp->uc_mcontext.gregs[9] = (uintptr_t) &libucontext_setcontext; + ucp->uc_mcontext.gregs[14] = (uintptr_t) &libucontext_trampoline; + + va_start(va, argc); + + for (i = 0; i < argc && i < 5; i++) + ucp->uc_mcontext.gregs[i + 2] = va_arg (va, libucontext_greg_t); + + if (argc > 5) + { + sp -= argc - 5; + + for (i = 5; i < argc; i++) + sp[i - 5] = va_arg (va, libucontext_greg_t); + } + + va_end(va); + + /* make room for backchain / register save area */ + sp -= 20; + *sp = 0; + + /* set up %r15 as sp */ + ucp->uc_mcontext.gregs[15] = (uintptr_t) sp; +} + +#ifdef EXPORT_UNPREFIXED +extern __typeof(libucontext_makecontext) makecontext __attribute__((weak, __alias__("libucontext_makecontext"))); +extern __typeof(libucontext_makecontext) __makecontext __attribute__((weak, __alias__("libucontext_makecontext"))); +#endif diff --git a/src/3rdparty/libucontext/arch/s390x/setcontext.S b/src/3rdparty/libucontext/arch/s390x/setcontext.S new file mode 100644 index 000000000..03fd4a175 --- /dev/null +++ b/src/3rdparty/libucontext/arch/s390x/setcontext.S @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018, 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "defs.h" + +ALIAS(setcontext, libucontext_setcontext) +ALIAS(__setcontext, libucontext_setcontext) + +FUNC(libucontext_setcontext) + lgr %r1, %r2 /* use %r1 as our working register */ + + lam %a2, %a15, AREG_OFFSET(2)(%r1) /* load access registers, but skip %a0 and %a1 which are for TLS */ + lmg %r0, %r15, REG_OFFSET(0)(%r1) /* store general-purpose registers */ + + br %r14 /* return to new link register address */ +END(libucontext_setcontext) diff --git a/src/3rdparty/libucontext/arch/s390x/startcontext.S b/src/3rdparty/libucontext/arch/s390x/startcontext.S new file mode 100644 index 000000000..16569437d --- /dev/null +++ b/src/3rdparty/libucontext/arch/s390x/startcontext.S @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018, 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "defs.h" + +.hidden libucontext_trampoline +FUNC(libucontext_trampoline) + basr %r14, %r7 /* run function pointer (%r7) and return here */ + ltgr %r8, %r8 /* check to see if uc_link (%r8) is null */ + + jz no_linked_context /* if we have no linked context, prepare to exit */ + + lgr %r2, %r8 /* copy the uc_link structure address to %r2 */ + br %r9 /* call setcontext */ + +no_linked_context: + la %r2, 0 /* return 0 */ + brasl %r14, exit@plt /* call exit */ + + j .+2 /* crash if exit returns */ +END(libucontext_trampoline) diff --git a/src/3rdparty/libucontext/arch/s390x/swapcontext.S b/src/3rdparty/libucontext/arch/s390x/swapcontext.S new file mode 100644 index 000000000..21a9b5ad6 --- /dev/null +++ b/src/3rdparty/libucontext/arch/s390x/swapcontext.S @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018, 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "defs.h" + +ALIAS(swapcontext, libucontext_swapcontext) +ALIAS(__swapcontext, libucontext_swapcontext) + +FUNC(libucontext_swapcontext) + lgr %r1, %r2 /* use %r1 to save current context to */ + lgr %r0, %r3 /* use %r0 for source context */ + + stam %a0, %a15, AREG_OFFSET(0)(%r1) /* store access registers */ + stmg %r0, %r15, REG_OFFSET(0)(%r1) /* store general-purpose registers */ + + lgr %r2, %r0 /* swap %r0 to %r2 (XXX: figure out why it hates loading from %r0) */ + lam %a2, %a15, AREG_OFFSET(2)(%r2) /* load access registers, but skip %a0 and %a1 which are for TLS */ + lmg %r0, %r15, REG_OFFSET(0)(%r2) /* load general-purpose registers */ + + br %r14 /* return to new link register address */ +END(libucontext_swapcontext) diff --git a/src/3rdparty/libucontext/arch/sh/defs.h b/src/3rdparty/libucontext/arch/sh/defs.h new file mode 100644 index 000000000..7cddaeffd --- /dev/null +++ b/src/3rdparty/libucontext/arch/sh/defs.h @@ -0,0 +1,20 @@ +#ifndef __ARCH_SH4_DEFS_H +#define __ARCH_SH4_DEFS_H + +#define REG_SZ (4) +#define MCONTEXT_GREGS (24) + +#define REG_SP (15) +#define REG_PC (16) +#define REG_PR (17) +#define REG_SR (18) +#define REG_GBR (19) +#define REG_MACH (20) +#define REG_MACL (21) + +#define FETCH_LINKPTR(dest) \ + asm("mov r8, %0" : "=r" (dest)); + +#include "common-defs.h" + +#endif diff --git a/src/3rdparty/libucontext/arch/sh/getcontext.S b/src/3rdparty/libucontext/arch/sh/getcontext.S new file mode 100644 index 000000000..0978af610 --- /dev/null +++ b/src/3rdparty/libucontext/arch/sh/getcontext.S @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "defs.h" + +ALIAS(getcontext, libucontext_getcontext) +ALIAS(__getcontext, libucontext_getcontext) + +FUNC(libucontext_getcontext) + mov r4, r0 /* move r4 to r0, and increment by REG_OFFSET(REG_MACL) + REG_SZ. */ + add #(REG_OFFSET(REG_MACL + 1)), r0 + + sts.l macl, @-r0 /* save macl/mach registers */ + sts.l mach, @-r0 + + stc.l gbr, @-r0 /* save gbr register */ + + movt r1 /* load T-flag into r1 */ + mov.l r1, @-r0 /* save T-flag as SR register */ + + sts.l pr, @-r0 /* save current PR */ + sts.l pr, @-r0 /* save current PR as PC as well */ + + mov.l r15, @-r0 /* preserve registers backwards, from r15 to r1 */ + mov.l r14, @-r0 + mov.l r13, @-r0 + mov.l r12, @-r0 + mov.l r11, @-r0 + mov.l r10, @-r0 + mov.l r9, @-r0 + mov.l r8, @-r0 + mov.l r7, @-r0 + mov.l r6, @-r0 + mov.l r5, @-r0 + mov.l r4, @-r0 + mov.l r3, @-r0 + mov.l r2, @-r0 + mov.l r1, @-r0 + + mov r0, r1 + mov #0, r0 + + mov.l r0, @-r1 /* preserve r0 as explicit zero */ + + mov #0, r0 /* set return value as zero */ + rts +END(libucontext_getcontext) diff --git a/src/3rdparty/libucontext/arch/sh/include/libucontext/bits.h b/src/3rdparty/libucontext/arch/sh/include/libucontext/bits.h new file mode 100644 index 000000000..3db92add4 --- /dev/null +++ b/src/3rdparty/libucontext/arch/sh/include/libucontext/bits.h @@ -0,0 +1,29 @@ +#ifndef LIBUCONTEXT_BITS_H +#define LIBUCONTEXT_BITS_H + +typedef unsigned long libucontext_greg_t, libucontext_gregset_t[16]; +typedef unsigned long libucontext_freg_t, libucontext_fpregset_t[16]; +typedef struct sigcontext { + unsigned long oldmask; + unsigned long gregs[16]; + unsigned long pc, pr, sr; + unsigned long gbr, mach, macl; + unsigned long fpregs[16]; + unsigned long xfpregs[16]; + unsigned int fpscr, fpul, ownedfp; +} libucontext_mcontext_t; + +typedef struct { + void *ss_sp; + int ss_flags; + size_t ss_size; +} libucontext_stack_t; + +typedef struct libucontext_ucontext { + unsigned long uc_flags; + struct libucontext_ucontext *uc_link; + libucontext_stack_t uc_stack; + libucontext_mcontext_t uc_mcontext; +} libucontext_ucontext_t; + +#endif diff --git a/src/3rdparty/libucontext/arch/sh/makecontext.c b/src/3rdparty/libucontext/arch/sh/makecontext.c new file mode 100644 index 000000000..7e28e2aff --- /dev/null +++ b/src/3rdparty/libucontext/arch/sh/makecontext.c @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include +#include +#include +#include +#include "defs.h" +#include + + +extern void libucontext_trampoline(void); + + +void +libucontext_makecontext(libucontext_ucontext_t *ucp, void (*func)(void), int argc, ...) +{ + libucontext_greg_t *sp, *regp; + va_list va; + int i; + + /* set up and align the stack */ + sp = (libucontext_greg_t *) (((uintptr_t) ucp->uc_stack.ss_sp + ucp->uc_stack.ss_size) & -4L); + sp -= argc > 4 ? argc - 4 : 0; + + /* set up the context */ + ucp->uc_mcontext.gregs[REG_SP] = (libucontext_greg_t) sp; + ucp->uc_mcontext.pr = (libucontext_greg_t) libucontext_trampoline; + ucp->uc_mcontext.pc = (libucontext_greg_t) func; + ucp->uc_mcontext.gregs[8] = (libucontext_greg_t) ucp->uc_link; + + /* pass up to four args in r4-r7, rest on stack */ + va_start(va, argc); + + regp = &ucp->uc_mcontext.gregs[4]; + + for (i = 0; i < argc && i < 4; i++) + *regp++ = va_arg(va, libucontext_greg_t); + + for (; i < argc; i++) + *sp++ = va_arg(va, libucontext_greg_t); + + va_end(va); +} + +#ifdef EXPORT_UNPREFIXED +extern __typeof(libucontext_makecontext) makecontext __attribute__((weak, __alias__("libucontext_makecontext"))); +extern __typeof(libucontext_makecontext) __makecontext __attribute__((weak, __alias__("libucontext_makecontext"))); +#endif diff --git a/src/3rdparty/libucontext/arch/sh/setcontext.S b/src/3rdparty/libucontext/arch/sh/setcontext.S new file mode 100644 index 000000000..cee065c0f --- /dev/null +++ b/src/3rdparty/libucontext/arch/sh/setcontext.S @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "defs.h" + +ALIAS(setcontext, libucontext_setcontext) +ALIAS(__setcontext, libucontext_setcontext) + +FUNC(libucontext_setcontext) + mov r4, r0 + + add #(REG_OFFSET(5)), r0 /* restore GPRs r5-15 */ + mov.l @r0+, r5 + mov.l @r0+, r6 + mov.l @r0+, r7 + mov.l @r0+, r8 + mov.l @r0+, r9 + mov.l @r0+, r10 + mov.l @r0+, r11 + mov.l @r0+, r12 + mov.l @r0+, r13 + mov.l @r0+, r14 + mov.l @r0+, r15 + + mov.l @r0+, r2 /* restore PR */ + lds.l @r0+, pr + + mov.l @r0+, r1 /* restore T-flag */ + shlr r1 + + add #REG_SZ, r0 /* skip GBR (used for TLS) */ + + lds.l @r0+, mach /* load mach/macl registers */ + lds.l @r0+, macl + + mov r4, r0 /* bring r0 back to the top of the context */ + add #(REG_OFFSET(0)), r1 /* restore r0 into r1 (temporarily) */ + mov.l r1, @-r15 /* push to stack from r1 */ + mov.l r2, @-r15 /* push PC to stack */ + + mov.l @(REG_OFFSET(1), r0), r1 /* restore real r1 */ + mov.l @(REG_OFFSET(2), r0), r2 /* restore real r2 */ + mov.l @(REG_OFFSET(3), r0), r3 /* restore real r2 */ + mov.l @(REG_OFFSET(4), r0), r4 /* restore real r2 */ + + mov.l @r15+, r0 /* pop PC from stack */ + + jmp @r0 /* jump to new PC */ + + mov.l @r15+, r0 /* pop original r0 from stack */ +END(libucontext_setcontext) diff --git a/src/3rdparty/libucontext/arch/sh/swapcontext.S b/src/3rdparty/libucontext/arch/sh/swapcontext.S new file mode 100644 index 000000000..289061373 --- /dev/null +++ b/src/3rdparty/libucontext/arch/sh/swapcontext.S @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "defs.h" + +ALIAS(swapcontext, libucontext_swapcontext) +ALIAS(__swapcontext, libucontext_swapcontext) + +FUNC(libucontext_swapcontext) + mov r4, r0 /* move r4 to r0, and increment by REG_OFFSET(REG_MACL) + REG_SZ. */ + add #(REG_OFFSET(REG_MACL + 1)), r0 + + sts.l macl, @-r0 /* save macl/mach registers */ + sts.l mach, @-r0 + + stc.l gbr, @-r0 /* save gbr register */ + + movt r1 /* load T-flag into r1 */ + mov.l r1, @-r0 /* save T-flag as SR register */ + + sts.l pr, @-r0 /* save current PR */ + sts.l pr, @-r0 /* save current PR as PC as well */ + + mov.l r15, @-r0 /* preserve registers backwards, from r15 to r1 */ + mov.l r14, @-r0 + mov.l r13, @-r0 + mov.l r12, @-r0 + mov.l r11, @-r0 + mov.l r10, @-r0 + mov.l r9, @-r0 + mov.l r8, @-r0 + mov.l r7, @-r0 + mov.l r6, @-r0 + mov.l r5, @-r0 + mov.l r4, @-r0 + mov.l r3, @-r0 + mov.l r2, @-r0 + mov.l r1, @-r0 + + mov r0, r1 + mov #0, r0 + + mov.l r0, @-r1 /* preserve r0 as explicit zero */ + + mov r5, r0 /* now restore the new context */ + + add #(REG_OFFSET(6)), r0 /* restore GPRs r6-15 */ + mov.l @r0+, r6 + mov.l @r0+, r7 + mov.l @r0+, r8 + mov.l @r0+, r9 + mov.l @r0+, r10 + mov.l @r0+, r11 + mov.l @r0+, r12 + mov.l @r0+, r13 + mov.l @r0+, r14 + mov.l @r0+, r15 + + mov.l @r0+, r2 /* restore PR */ + lds.l @r0+, pr + + mov.l @r0+, r1 /* restore T-flag */ + shlr r1 + + add #REG_SZ, r0 /* skip GBR (used for TLS) */ + + lds.l @r0+, mach /* load mach/macl registers */ + lds.l @r0+, macl + + mov r5, r0 /* bring r0 back to the top of the context */ + add #(REG_OFFSET(0)), r1 /* restore r0 into r1 (temporarily) */ + mov.l r1, @-r15 /* push to stack from r1 */ + mov.l r2, @-r15 /* push PC to stack */ + + mov.l @(REG_OFFSET(1), r0), r1 /* restore real r1 */ + mov.l @(REG_OFFSET(2), r0), r2 /* restore real r2 */ + mov.l @(REG_OFFSET(3), r0), r3 /* restore real r3 */ + mov.l @(REG_OFFSET(4), r0), r4 /* restore real r4 */ + mov.l @(REG_OFFSET(5), r0), r5 /* restore real r5 */ + + mov.l @r15+, r0 /* pop PC from stack */ + + jmp @r0 /* jump to new PC */ + + mov.l @r15+, r0 /* pop original r0 from stack */ +END(libucontext_swapcontext) diff --git a/src/3rdparty/libucontext/arch/sh/trampoline.c b/src/3rdparty/libucontext/arch/sh/trampoline.c new file mode 100644 index 000000000..699a0505d --- /dev/null +++ b/src/3rdparty/libucontext/arch/sh/trampoline.c @@ -0,0 +1,3 @@ +#include "defs.h" +#include +#include "common-trampoline.c" diff --git a/src/3rdparty/libucontext/arch/x86/defs.h b/src/3rdparty/libucontext/arch/x86/defs.h new file mode 100644 index 000000000..9370869a3 --- /dev/null +++ b/src/3rdparty/libucontext/arch/x86/defs.h @@ -0,0 +1,65 @@ +#ifndef __ARCH_X86_DEFS_H +#define __ARCH_X86_DEFS_H + +#ifndef REG_GS +# define REG_GS (0) +#endif + +#ifndef REG_FS +# define REG_FS (1) +#endif + +#ifndef REG_ES +# define REG_ES (2) +#endif + +#ifndef REG_DS +# define REG_DS (3) +#endif + +#ifndef REG_EDI +# define REG_EDI (4) +#endif + +#ifndef REG_ESI +# define REG_ESI (5) +#endif + +#ifndef REG_EBP +# define REG_EBP (6) +#endif + +#ifndef REG_ESP +# define REG_ESP (7) +#endif + +#ifndef REG_EBX +# define REG_EBX (8) +#endif + +#ifndef REG_EDX +# define REG_EDX (9) +#endif + +#ifndef REG_ECX +# define REG_ECX (10) +#endif + +#ifndef REG_EAX +# define REG_EAX (11) +#endif + +#ifndef REG_EIP +# define REG_EIP (14) +#endif + +#define REG_SZ (4) + +#define MCONTEXT_GREGS (20) + +#define FETCH_LINKPTR(dest) \ + asm("movl (%%esp, %%ebx, 4), %0" : "=r" ((dest))); + +#include "common-defs.h" + +#endif diff --git a/src/3rdparty/libucontext/arch/x86/getcontext.S b/src/3rdparty/libucontext/arch/x86/getcontext.S new file mode 100644 index 000000000..4b9519786 --- /dev/null +++ b/src/3rdparty/libucontext/arch/x86/getcontext.S @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2018, 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "defs.h" + +ALIAS(getcontext, libucontext_getcontext) +ALIAS(__getcontext, libucontext_getcontext) + +FUNC(libucontext_getcontext) + /* load address of the ucontext structure */ + movl 4(%esp), %eax + + /* EAX is not a preserved register */ + movl $0, REG_OFFSET(REG_EAX)(%eax) + + /* copy all of the current registers into the ucontext structure */ + movl %ecx, REG_OFFSET(REG_ECX)(%eax) + movl %ebx, REG_OFFSET(REG_EBX)(%eax) + movl %edx, REG_OFFSET(REG_EDX)(%eax) + movl %edi, REG_OFFSET(REG_EDI)(%eax) + movl %esi, REG_OFFSET(REG_ESI)(%eax) + movl %ebp, REG_OFFSET(REG_EBP)(%eax) + + /* the first argument on the stack is the jump target (%eip), so we store it in the EIP + register in the ucontext structure. */ + movl (%esp), %ecx + movl %ecx, REG_OFFSET(REG_EIP)(%eax) + + /* take the stack pointer address (%esp) offsetting by 4 to skip over the jump target. */ + leal 4(%esp), %ecx + movl %ecx, REG_OFFSET(REG_ESP)(%eax) + + /* finally, save the FS segment register */ + xorl %ecx, %ecx + movw %fs, %cx + movl %ecx, REG_OFFSET(REG_FS)(%eax) + + /* we're all done here, return 0 */ + xorl %eax, %eax + ret +END(libucontext_getcontext) diff --git a/src/3rdparty/libucontext/arch/x86/include/libucontext/bits.h b/src/3rdparty/libucontext/arch/x86/include/libucontext/bits.h new file mode 100644 index 000000000..c4b28aa29 --- /dev/null +++ b/src/3rdparty/libucontext/arch/x86/include/libucontext/bits.h @@ -0,0 +1,48 @@ +#ifndef LIBUCONTEXT_BITS_H +#define LIBUCONTEXT_BITS_H + +#define REG_GS (0) +#define REG_FS (1) +#define REG_ES (2) +#define REG_DS (3) +#define REG_EDI (4) +#define REG_ESI (5) +#define REG_EBP (6) +#define REG_ESP (7) +#define REG_EBX (8) +#define REG_EDX (9) +#define REG_ECX (10) +#define REG_EAX (11) +#define REG_EIP (14) + +typedef int libucontext_greg_t, libucontext_gregset_t[19]; + +typedef struct libucontext_fpstate { + unsigned long cw, sw, tag, ipoff, cssel, dataoff, datasel; + struct { + unsigned short significand[4], exponent; + } _st[8]; + unsigned long status; +} *libucontext_fpregset_t; + +typedef struct { + libucontext_gregset_t gregs; + libucontext_fpregset_t fpregs; + unsigned long oldmask, cr2; +} libucontext_mcontext_t; + +typedef struct { + void *ss_sp; + int ss_flags; + size_t ss_size; +} libucontext_stack_t; + +typedef struct libucontext_ucontext { + unsigned long uc_flags; + struct libucontext_ucontext *uc_link; + libucontext_stack_t uc_stack; + libucontext_mcontext_t uc_mcontext; +} libucontext_ucontext_t; + +#endif + diff --git a/src/3rdparty/libucontext/arch/x86/makecontext.c b/src/3rdparty/libucontext/arch/x86/makecontext.c new file mode 100644 index 000000000..2f8138918 --- /dev/null +++ b/src/3rdparty/libucontext/arch/x86/makecontext.c @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2018 Ariadne Conill + * Copyright (c) 2019 A. Wilcox + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include +#include +#include +#include "defs.h" +#include + + +extern void libucontext_trampoline(void); + + +void +libucontext_makecontext(libucontext_ucontext_t *ucp, void (*func)(void), int argc, ...) +{ + libucontext_greg_t *sp, *argp; + va_list va; + int i; + unsigned int uc_link; + + uc_link = (argc > 6 ? argc - 6 : 0) + 1; + + sp = (libucontext_greg_t *) ((uintptr_t) ucp->uc_stack.ss_sp + ucp->uc_stack.ss_size); + sp -= uc_link; + sp = (libucontext_greg_t *) (((uintptr_t) sp & -16L) - 8); + + ucp->uc_mcontext.gregs[REG_EIP] = (uintptr_t) func; + ucp->uc_mcontext.gregs[REG_EBX] = (uintptr_t) argc; + ucp->uc_mcontext.gregs[REG_ESP] = (uintptr_t) sp; + + argp = sp; + *argp++ = (uintptr_t) &libucontext_trampoline; + + va_start(va, argc); + + for (i = 0; i < argc; i++) + *argp++ = va_arg (va, libucontext_greg_t); + + va_end(va); + + *argp++ = (uintptr_t) ucp->uc_link; +} + +#ifdef EXPORT_UNPREFIXED +extern __typeof(libucontext_makecontext) makecontext __attribute__((weak, __alias__("libucontext_makecontext"))); +extern __typeof(libucontext_makecontext) __makecontext __attribute__((weak, __alias__("libucontext_makecontext"))); +#endif diff --git a/src/3rdparty/libucontext/arch/x86/setcontext.S b/src/3rdparty/libucontext/arch/x86/setcontext.S new file mode 100644 index 000000000..75d2108eb --- /dev/null +++ b/src/3rdparty/libucontext/arch/x86/setcontext.S @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018, 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "defs.h" + +ALIAS(setcontext, libucontext_setcontext) +ALIAS(__setcontext, libucontext_setcontext) + +FUNC(libucontext_setcontext) + /* load address of the ucontext structure */ + movl 4(%esp), %eax + + /* set up the FS segment register */ + movl REG_OFFSET(REG_FS)(%eax), %ecx + movw %cx, %fs + + /* fetch the new EIP */ + movl REG_OFFSET(REG_EIP)(%eax), %ecx + + /* set up the new stack pointer */ + movl REG_OFFSET(REG_ESP)(%eax), %esp + + /* push the return address onto the stack */ + pushl %ecx + + /* set all of the registers */ + movl REG_OFFSET(REG_EBX)(%eax), %ebx + movl REG_OFFSET(REG_ECX)(%eax), %ecx + movl REG_OFFSET(REG_EDX)(%eax), %edx + movl REG_OFFSET(REG_EBP)(%eax), %ebp + movl REG_OFFSET(REG_EDI)(%eax), %edi + movl REG_OFFSET(REG_ESI)(%eax), %esi + movl REG_OFFSET(REG_EAX)(%eax), %eax + + ret +END(libucontext_setcontext) diff --git a/src/3rdparty/libucontext/arch/x86/swapcontext.S b/src/3rdparty/libucontext/arch/x86/swapcontext.S new file mode 100644 index 000000000..3594fc0e5 --- /dev/null +++ b/src/3rdparty/libucontext/arch/x86/swapcontext.S @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2018, 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "defs.h" + +ALIAS(swapcontext, libucontext_swapcontext) +ALIAS(__swapcontext, libucontext_swapcontext) + +FUNC(libucontext_swapcontext) + /* load address of the ucontext structure */ + movl 4(%esp), %eax + + /* EAX is not a preserved register */ + movl $0, REG_OFFSET(REG_EAX)(%eax) + + /* copy all of the current registers into the ucontext structure */ + movl %ecx, REG_OFFSET(REG_ECX)(%eax) + movl %ebx, REG_OFFSET(REG_EBX)(%eax) + movl %edx, REG_OFFSET(REG_EDX)(%eax) + movl %edi, REG_OFFSET(REG_EDI)(%eax) + movl %esi, REG_OFFSET(REG_ESI)(%eax) + movl %ebp, REG_OFFSET(REG_EBP)(%eax) + + /* the first argument on the stack is the jump target (%eip), so we store it in the EIP + register in the ucontext structure. */ + movl (%esp), %ecx + movl %ecx, REG_OFFSET(REG_EIP)(%eax) + + /* take the stack pointer address (%esp) offsetting by 4 to skip over the jump target. */ + leal 4(%esp), %ecx + movl %ecx, REG_OFFSET(REG_ESP)(%eax) + + /* finally, save the FS segment register */ + xorl %ecx, %ecx + movw %fs, %cx + movl %ecx, REG_OFFSET(REG_FS)(%eax) + + /* load address of the ucontext structure */ + movl 8(%esp), %eax + + /* set up the FS segment register */ + movl REG_OFFSET(REG_FS)(%eax), %ecx + movw %cx, %fs + + /* fetch the new EIP */ + movl REG_OFFSET(REG_EIP)(%eax), %ecx + + /* set up the new stack pointer */ + movl REG_OFFSET(REG_ESP)(%eax), %esp + + /* push the return address onto the stack */ + pushl %ecx + + /* set all of the registers */ + movl REG_OFFSET(REG_EBX)(%eax), %ebx + movl REG_OFFSET(REG_ECX)(%eax), %ecx + movl REG_OFFSET(REG_EDX)(%eax), %edx + movl REG_OFFSET(REG_EBP)(%eax), %ebp + movl REG_OFFSET(REG_EDI)(%eax), %edi + movl REG_OFFSET(REG_ESI)(%eax), %esi + movl REG_OFFSET(REG_EAX)(%eax), %eax + + ret +END(libucontext_swapcontext) diff --git a/src/3rdparty/libucontext/arch/x86/trampoline.c b/src/3rdparty/libucontext/arch/x86/trampoline.c new file mode 100644 index 000000000..699a0505d --- /dev/null +++ b/src/3rdparty/libucontext/arch/x86/trampoline.c @@ -0,0 +1,3 @@ +#include "defs.h" +#include +#include "common-trampoline.c" diff --git a/src/3rdparty/libucontext/arch/x86_64/defs.h b/src/3rdparty/libucontext/arch/x86_64/defs.h new file mode 100644 index 000000000..1ebf3a93f --- /dev/null +++ b/src/3rdparty/libucontext/arch/x86_64/defs.h @@ -0,0 +1,105 @@ +#ifndef __ARCH_X86_64_DEFS_H +#define __ARCH_X86_64_DEFS_H + +#ifndef REG_R8 +# define REG_R8 (0) +#endif + +#ifndef REG_R9 +# define REG_R9 (1) +#endif + +#ifndef REG_R10 +# define REG_R10 (2) +#endif + +#ifndef REG_R11 +# define REG_R11 (3) +#endif + +#ifndef REG_R12 +# define REG_R12 (4) +#endif + +#ifndef REG_R13 +# define REG_R13 (5) +#endif + +#ifndef REG_R14 +# define REG_R14 (6) +#endif + +#ifndef REG_R15 +# define REG_R15 (7) +#endif + +#ifndef REG_RDI +# define REG_RDI (8) +#endif + +#ifndef REG_RSI +# define REG_RSI (9) +#endif + +#ifndef REG_RBP +# define REG_RBP (10) +#endif + +#ifndef REG_RBX +# define REG_RBX (11) +#endif + +#ifndef REG_RDX +# define REG_RDX (12) +#endif + +#ifndef REG_RAX +# define REG_RAX (13) +#endif + +#ifndef REG_RCX +# define REG_RCX (14) +#endif + +#ifndef REG_RSP +# define REG_RSP (15) +#endif + +#ifndef REG_RIP +# define REG_RIP (16) +#endif + +#ifndef REG_EFL +# define REG_EFL (17) +#endif + +#ifndef REG_CSGSFS +# define REG_CSGSFS (18) +#endif + +#ifndef REG_ERR +# define REG_ERR (19) +#endif + +#ifndef REG_TRAPNO +# define REG_TRAPNO (20) +#endif + +#ifndef REG_OLDMASK +# define REG_OLDMASK (21) +#endif + +#ifndef REG_CR2 +# define REG_CR2 (22) +#endif + +#define MCONTEXT_GREGS (40) + +#define REG_SZ (8) + +#define FETCH_LINKPTR(dest) \ + asm("movq (%%rbx), %0" : "=r" ((dest))); + +#include "common-defs.h" + +#endif diff --git a/src/3rdparty/libucontext/arch/x86_64/getcontext.S b/src/3rdparty/libucontext/arch/x86_64/getcontext.S new file mode 100644 index 000000000..168fa473d --- /dev/null +++ b/src/3rdparty/libucontext/arch/x86_64/getcontext.S @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2018, 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "defs.h" + +ALIAS(getcontext, libucontext_getcontext) +ALIAS(__getcontext, libucontext_getcontext) + +FUNC(libucontext_getcontext) + /* copy all of the current registers into the ucontext structure */ + movq %r8, REG_OFFSET(REG_R8)(%rdi) + movq %r9, REG_OFFSET(REG_R9)(%rdi) + movq %r10, REG_OFFSET(REG_R10)(%rdi) + movq %r11, REG_OFFSET(REG_R11)(%rdi) + movq %r12, REG_OFFSET(REG_R12)(%rdi) + movq %r13, REG_OFFSET(REG_R13)(%rdi) + movq %r14, REG_OFFSET(REG_R14)(%rdi) + movq %r15, REG_OFFSET(REG_R15)(%rdi) + movq %rdi, REG_OFFSET(REG_RDI)(%rdi) + movq %rsi, REG_OFFSET(REG_RSI)(%rdi) + movq %rbp, REG_OFFSET(REG_RBP)(%rdi) + movq %rbx, REG_OFFSET(REG_RBX)(%rdi) + movq %rdx, REG_OFFSET(REG_RDX)(%rdi) + movq %rax, REG_OFFSET(REG_RAX)(%rdi) + movq %rcx, REG_OFFSET(REG_RCX)(%rdi) + + /* the first argument on the stack is the jump target (%rip), so we store it in the RIP + register in the ucontext structure. */ + movq (%rsp), %rcx + movq %rcx, REG_OFFSET(REG_RIP)(%rdi) + + /* finally take the stack pointer address (%rsp) offsetting by 8 to skip over the jump + target. */ + leaq 8(%rsp), %rcx + movq %rcx, REG_OFFSET(REG_RSP)(%rdi) + + /* we're all done here, return 0 */ + xorl %eax, %eax + ret +END(libucontext_getcontext) diff --git a/src/3rdparty/libucontext/arch/x86_64/include/libucontext/bits.h b/src/3rdparty/libucontext/arch/x86_64/include/libucontext/bits.h new file mode 100644 index 000000000..79567663b --- /dev/null +++ b/src/3rdparty/libucontext/arch/x86_64/include/libucontext/bits.h @@ -0,0 +1,64 @@ +#include + +#ifndef LIBUCONTEXT_BITS_H +#define LIBUCONTEXT_BITS_H + +#define REG_R8 (0) +#define REG_R9 (1) +#define REG_R10 (2) +#define REG_R11 (3) +#define REG_R12 (4) +#define REG_R13 (5) +#define REG_R14 (6) +#define REG_R15 (7) +#define REG_RDI (8) +#define REG_RSI (9) +#define REG_RBP (10) +#define REG_RBX (11) +#define REG_RDX (12) +#define REG_RAX (13) +#define REG_RCX (14) +#define REG_RSP (15) +#define REG_RIP (16) +#define REG_EFL (17) +#define REG_CSGSFS (18) +#define REG_ERR (19) +#define REG_TRAPNO (20) +#define REG_OLDMASK (21) +#define REG_CR2 (22) + +typedef long long libucontext_greg_t, libucontext_gregset_t[23]; + +typedef struct libucontext_fpstate { + unsigned short cwd, swd, ftw, fop; + unsigned long long rip, rdp; + unsigned mxcsr, mxcr_mask; + struct { + unsigned short significand[4], exponent, padding[3]; + } _st[8]; + struct { + unsigned element[4]; + } _xmm[16]; + unsigned padding[24]; +} *libucontext_fpregset_t; + +typedef struct { + libucontext_gregset_t gregs; + libucontext_fpregset_t fpregs; + unsigned long long __reserved1[8]; +} libucontext_mcontext_t; + +typedef struct { + void *ss_sp; + int ss_flags; + size_t ss_size; +} libucontext_stack_t; + +typedef struct libucontext_ucontext { + unsigned long uc_flags; + struct libucontext_ucontext *uc_link; + libucontext_stack_t uc_stack; + libucontext_mcontext_t uc_mcontext; +} libucontext_ucontext_t; + +#endif diff --git a/src/3rdparty/libucontext/arch/x86_64/makecontext.c b/src/3rdparty/libucontext/arch/x86_64/makecontext.c new file mode 100644 index 000000000..cba9cb1b4 --- /dev/null +++ b/src/3rdparty/libucontext/arch/x86_64/makecontext.c @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2018 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include +#include +#include + +#include "defs.h" +#include + +extern void libucontext_trampoline(void); + +_Static_assert(offsetof(libucontext_ucontext_t, uc_mcontext.gregs) == MCONTEXT_GREGS, "MCONTEXT_GREGS is invalid"); + +void +libucontext_makecontext(libucontext_ucontext_t *ucp, void (*func)(void), int argc, ...) +{ + libucontext_greg_t *sp; + va_list va; + int i; + unsigned int uc_link; + + uc_link = (argc > 6 ? argc - 6 : 0) + 1; + + sp = (libucontext_greg_t *) ((uintptr_t) ucp->uc_stack.ss_sp + ucp->uc_stack.ss_size); + sp -= uc_link; + sp = (libucontext_greg_t *) (((uintptr_t) sp & -16L) - 8); + + ucp->uc_mcontext.gregs[REG_RIP] = (uintptr_t) func; + ucp->uc_mcontext.gregs[REG_RBX] = (uintptr_t) &sp[uc_link]; + ucp->uc_mcontext.gregs[REG_RSP] = (uintptr_t) sp; + + sp[0] = (uintptr_t) &libucontext_trampoline; + sp[uc_link] = (uintptr_t) ucp->uc_link; + + va_start(va, argc); + + for (i = 0; i < argc; i++) + switch (i) + { + case 0: + ucp->uc_mcontext.gregs[REG_RDI] = va_arg (va, libucontext_greg_t); + break; + case 1: + ucp->uc_mcontext.gregs[REG_RSI] = va_arg (va, libucontext_greg_t); + break; + case 2: + ucp->uc_mcontext.gregs[REG_RDX] = va_arg (va, libucontext_greg_t); + break; + case 3: + ucp->uc_mcontext.gregs[REG_RCX] = va_arg (va, libucontext_greg_t); + break; + case 4: + ucp->uc_mcontext.gregs[REG_R8] = va_arg (va, libucontext_greg_t); + break; + case 5: + ucp->uc_mcontext.gregs[REG_R9] = va_arg (va, libucontext_greg_t); + break; + default: + sp[i - 5] = va_arg (va, libucontext_greg_t); + break; + } + + va_end(va); +} + +#ifdef EXPORT_UNPREFIXED +extern __typeof(libucontext_makecontext) makecontext __attribute__((weak, __alias__("libucontext_makecontext"))); +extern __typeof(libucontext_makecontext) __makecontext __attribute__((weak, __alias__("libucontext_makecontext"))); +#endif diff --git a/src/3rdparty/libucontext/arch/x86_64/setcontext.S b/src/3rdparty/libucontext/arch/x86_64/setcontext.S new file mode 100644 index 000000000..4dc270242 --- /dev/null +++ b/src/3rdparty/libucontext/arch/x86_64/setcontext.S @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2018, 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "defs.h" + +ALIAS(setcontext, libucontext_setcontext) +ALIAS(__setcontext, libucontext_setcontext) + +FUNC(libucontext_setcontext) + /* set all of the registers */ + movq REG_OFFSET(REG_R8)(%rdi), %r8 + movq REG_OFFSET(REG_R9)(%rdi), %r9 + movq REG_OFFSET(REG_R10)(%rdi), %r10 + movq REG_OFFSET(REG_R11)(%rdi), %r11 + movq REG_OFFSET(REG_R12)(%rdi), %r12 + movq REG_OFFSET(REG_R13)(%rdi), %r13 + movq REG_OFFSET(REG_R14)(%rdi), %r14 + movq REG_OFFSET(REG_R15)(%rdi), %r15 + movq REG_OFFSET(REG_RSI)(%rdi), %rsi + movq REG_OFFSET(REG_RBP)(%rdi), %rbp + movq REG_OFFSET(REG_RBX)(%rdi), %rbx + movq REG_OFFSET(REG_RDX)(%rdi), %rdx + movq REG_OFFSET(REG_RAX)(%rdi), %rax + movq REG_OFFSET(REG_RCX)(%rdi), %rcx + movq REG_OFFSET(REG_RSP)(%rdi), %rsp + + /* set the jump target by pushing it to the stack. + ret will pop the new %rip from the stack, causing us to jump there. */ + pushq REG_OFFSET(REG_RIP)(%rdi) + + /* finally, set %rdi correctly. */ + movq REG_OFFSET(REG_RDI)(%rdi), %rdi + + /* we're all done here, return 0 */ + xorl %eax, %eax + ret +END(libucontext_setcontext) diff --git a/src/3rdparty/libucontext/arch/x86_64/swapcontext.S b/src/3rdparty/libucontext/arch/x86_64/swapcontext.S new file mode 100644 index 000000000..43dafb594 --- /dev/null +++ b/src/3rdparty/libucontext/arch/x86_64/swapcontext.S @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2018, 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "defs.h" + +ALIAS(swapcontext, libucontext_swapcontext) +ALIAS(__swapcontext, libucontext_swapcontext) + +FUNC(libucontext_swapcontext) + /* copy all of the current registers into the ucontext structure pointed by + the first argument */ + movq %r8, REG_OFFSET(REG_R8)(%rdi) + movq %r9, REG_OFFSET(REG_R9)(%rdi) + movq %r10, REG_OFFSET(REG_R10)(%rdi) + movq %r11, REG_OFFSET(REG_R11)(%rdi) + movq %r12, REG_OFFSET(REG_R12)(%rdi) + movq %r13, REG_OFFSET(REG_R13)(%rdi) + movq %r14, REG_OFFSET(REG_R14)(%rdi) + movq %r15, REG_OFFSET(REG_R15)(%rdi) + movq %rdi, REG_OFFSET(REG_RDI)(%rdi) + movq %rsi, REG_OFFSET(REG_RSI)(%rdi) + movq %rbp, REG_OFFSET(REG_RBP)(%rdi) + movq %rbx, REG_OFFSET(REG_RBX)(%rdi) + movq %rdx, REG_OFFSET(REG_RDX)(%rdi) + movq %rax, REG_OFFSET(REG_RAX)(%rdi) + movq %rcx, REG_OFFSET(REG_RCX)(%rdi) + + /* the first argument on the stack is the jump target (%rip), so we store it in the RIP + register in the ucontext structure. */ + movq (%rsp), %rcx + movq %rcx, REG_OFFSET(REG_RIP)(%rdi) + + /* finally take the stack pointer address (%rsp) offsetting by 8 to skip over the jump + target. */ + leaq 8(%rsp), %rcx + movq %rcx, REG_OFFSET(REG_RSP)(%rdi) + + /* set all of the registers to their new states, stored in the second + ucontext structure */ + movq REG_OFFSET(REG_R8)(%rsi), %r8 + movq REG_OFFSET(REG_R9)(%rsi), %r9 + movq REG_OFFSET(REG_R10)(%rsi), %r10 + movq REG_OFFSET(REG_R11)(%rsi), %r11 + movq REG_OFFSET(REG_R12)(%rsi), %r12 + movq REG_OFFSET(REG_R13)(%rsi), %r13 + movq REG_OFFSET(REG_R14)(%rsi), %r14 + movq REG_OFFSET(REG_R15)(%rsi), %r15 + movq REG_OFFSET(REG_RDI)(%rsi), %rdi + movq REG_OFFSET(REG_RBP)(%rsi), %rbp + movq REG_OFFSET(REG_RBX)(%rsi), %rbx + movq REG_OFFSET(REG_RDX)(%rsi), %rdx + movq REG_OFFSET(REG_RAX)(%rsi), %rax + movq REG_OFFSET(REG_RCX)(%rsi), %rcx + movq REG_OFFSET(REG_RSP)(%rsi), %rsp + + /* set the jump target by pushing it to the stack. + ret will pop the new %rip from the stack, causing us to jump there. */ + pushq REG_OFFSET(REG_RIP)(%rsi) + + /* finally, set %rsi correctly since we do not need it anymore. */ + movq REG_OFFSET(REG_RSI)(%rsi), %rsi + + /* we're all done here, return 0 */ + xorl %eax, %eax + ret +END(libucontext_swapcontext) diff --git a/src/3rdparty/libucontext/arch/x86_64/trampoline.c b/src/3rdparty/libucontext/arch/x86_64/trampoline.c new file mode 100644 index 000000000..699a0505d --- /dev/null +++ b/src/3rdparty/libucontext/arch/x86_64/trampoline.c @@ -0,0 +1,3 @@ +#include "defs.h" +#include +#include "common-trampoline.c" diff --git a/src/3rdparty/libucontext/doc/libucontext.scd b/src/3rdparty/libucontext/doc/libucontext.scd new file mode 100644 index 000000000..1f6c07acb --- /dev/null +++ b/src/3rdparty/libucontext/doc/libucontext.scd @@ -0,0 +1,174 @@ +libucontext(3) + +# NAME + +libucontext - a library for userspace context swapping + +# SYNOPSIS + +*#include * + +``` +typedef struct { + /* depends on target architecture */ +} libucontext_mcontext_t; + +typedef struct { + void *ss_sp; + int ss_flags; + size_t ss_size; +} libucontext_stack_t; + +typedef struct libucontext_ucontext { + unsigned int uc_flags; + struct libucontext_ucontext *uc_link; + libucontext_stack_t uc_stack; + libucontext_mcontext_t uc_mcontext; +} libucontext_ucontext_t; +``` + +*int libucontext_getcontext(libucontext_ucontext_t* \*_ucp_*);* + +*int libucontext_setcontext(const libucontext_ucontext_t* \*_ucp_*);* + +*void libucontext_makecontext(libucontext_ucontext_t* \*_ucp_*, void* _(\*func)()_*, int* _argc_*,* _..._*);* + +*int libucontext_swapcontext(libucontext_ucontext_t* \*_oucp_*, const libucontext_ucontext_t* \*_ucp_*);* + +# DESCRIPTION + +The *libucontext* library provides an implementation of the SysV ucontext functions. These +are traditionally used to implement user-space context swapping. This is achieved by using +the *libucontext_getcontext*, *libucontext_setcontext*, *libucontext_makecontext* and +*libucontext_swapcontext* functions as appropriate. + +The *libucontext_getcontext* function initializes a structure pointed to by _ucp_ with the +current user context. + +The *libucontext_setcontext* function sets the current user context to the structure pointed +to by _ucp_. It discards the current user context. + +The *libucontext_swapcontext* function saves the current user context in a structure pointed +to by _oucp_ and then sets the current user context to the new context in a structure pointed +to by _ucp_. + +The *libucontext_makecontext* function modifies a user context in a structure pointed to by +_ucp_ to run a function pointed to by _func_ and sets up an argument list of _argc_ values. + +# CAVEATS + +In SysV, the ucontext functions save and restore signal masks. The *libucontext* library, +however, does not. In practice, this does not usually matter, as users of these functions +rarely change the signal mask between contexts. + +Other implementations may or may not save and restore additional processor registers that +this implementation does not. The *libucontext* library only saves and restores the general +purpose registers. In practice, this has proven sufficient. + +# EXAMPLE + +A practical example showing cooperative multithreading. This program is intended for +illustrative purpose only and has been written in a way favoring simplicity over performance +and robustness: + +``` +#include +#include +#include +#include +#include +#include +#include + +libucontext_ucontext_t mainctx = {}; +libucontext_ucontext_t *curthr = &mainctx; +libucontext_ucontext_t *threads = NULL; +size_t thrcount = 0; + +void +yieldto(libucontext_ucontext_t *target) +{ + libucontext_ucontext_t *oldthr = curthr; + curthr = target; + + libucontext_swapcontext(oldthr, curthr); +} + +void +yield(void) +{ + libucontext_ucontext_t *newthr; + + /* we set uc_flags to non-zero to signal thread completion. */ + do + newthr = &threads[random() % thrcount]; + while (newthr == curthr || newthr->uc_flags); + + srandom(time(NULL)); + + yieldto(newthr); +} + +void +worker(size_t multiple) +{ + size_t accum = 1; + + for (size_t i = 0; i < 10; i++) + { + accum += (multiple * i); + + printf("[%p] accumulated %zu\n", curthr, accum); + yield(); + } + + /* mark thread as completed, so we don't return here */ + curthr->uc_flags = 1; +} + +void +create(size_t multiple) +{ + libucontext_ucontext_t *cursor; + + thrcount += 1; + threads = realloc(threads, sizeof(*threads) * thrcount); + + cursor = &threads[thrcount - 1]; + memset(cursor, '\0', sizeof *cursor); + + /* initialize the new thread's values to our current context */ + libucontext_getcontext(cursor); + + /* set up uc_link */ + cursor->uc_link = thrcount > 1 ? &threads[thrcount - 2] : &mainctx; + + /* set up a stack */ + cursor->uc_stack.ss_size = 8192; + cursor->uc_stack.ss_sp = calloc(1, cursor->uc_stack.ss_size); + + /* set up the function call */ + libucontext_makecontext(cursor, worker, 1, multiple); +} + +int +main(int argc, const char *argv[]) +{ + srandom(time(NULL)); + + libucontext_getcontext(&mainctx); + + for (size_t i = 1; i < 4; i++) + create(i); + + /* start the threads off by yielding to the last one */ + yieldto(&threads[thrcount - 1]); + + return EXIT_SUCCESS; +} +``` + +# AUTHORS + +Ariadne Conill + diff --git a/src/3rdparty/libucontext/doc/meson.build b/src/3rdparty/libucontext/doc/meson.build new file mode 100644 index 000000000..8f5ac7964 --- /dev/null +++ b/src/3rdparty/libucontext/doc/meson.build @@ -0,0 +1,21 @@ +scdoc = find_program('scdoc', required: true) + +custom_target( + 'libucontext.3', + output: 'libucontext.3', + input: 'libucontext.scd', + command: [ scdoc ], + feed: true, + capture: true, + install: true, + install_dir: get_option('mandir') / 'man3' +) + +if meson.version().version_compare('>=0.61.0') + foreach link : [ 'get', 'make', 'set', 'swap' ] + install_symlink('libucontext_' + link + 'context.3', + pointing_to: 'libucontext.3', + install_dir: get_option('mandir') / 'man3' + ) + endforeach +endif diff --git a/src/3rdparty/libucontext/examples/cooperative_threading.c b/src/3rdparty/libucontext/examples/cooperative_threading.c new file mode 100644 index 000000000..123e9eaa6 --- /dev/null +++ b/src/3rdparty/libucontext/examples/cooperative_threading.c @@ -0,0 +1,94 @@ +#include +#include +#include +#include +#include +#include +#include + +libucontext_ucontext_t mainctx = {}; +libucontext_ucontext_t *curthr = &mainctx; +libucontext_ucontext_t *threads = NULL; +size_t thrcount = 0; + +void +yieldto(libucontext_ucontext_t *target) +{ + libucontext_ucontext_t *oldthr = curthr; + curthr = target; + + libucontext_swapcontext(oldthr, curthr); +} + +void +yield(void) +{ + libucontext_ucontext_t *newthr; + + /* we set uc_flags to non-zero to signal thread completion. */ + do + newthr = &threads[random() % thrcount]; + while (newthr == curthr || newthr->uc_flags); + + srandom(time(NULL)); + + yieldto(newthr); +} + +void +worker(size_t multiple) +{ + size_t accum = 1; + + for (size_t i = 0; i < 10; i++) + { + accum += (multiple * i); + + printf("[%p] accumulated %zu\n", curthr, accum); + yield(); + } + + /* mark thread as completed, so we don't return here */ + curthr->uc_flags = 1; +} + +void +create(size_t multiple) +{ + libucontext_ucontext_t *cursor; + + thrcount += 1; + threads = realloc(threads, sizeof(*threads) * thrcount); + + cursor = &threads[thrcount - 1]; + memset(cursor, '\0', sizeof *cursor); + + /* initialize the new thread's values to our current context */ + libucontext_getcontext(cursor); + + /* set up uc_link */ + cursor->uc_link = thrcount > 1 ? &threads[thrcount - 2] : &mainctx; + + /* set up a stack */ + cursor->uc_stack.ss_size = 8192; + cursor->uc_stack.ss_sp = calloc(1, cursor->uc_stack.ss_size); + + /* set up the function call */ + libucontext_makecontext(cursor, worker, 1, multiple); +} + +int +main(int argc, const char *argv[]) +{ + srandom(time(NULL)); + + libucontext_getcontext(&mainctx); + + for (size_t i = 1; i < 4; i++) + create(i); + + /* start the threads off by yielding to the last one */ + yieldto(&threads[thrcount - 1]); + + return EXIT_SUCCESS; +} diff --git a/src/3rdparty/libucontext/include/libucontext/libucontext.h b/src/3rdparty/libucontext/include/libucontext/libucontext.h new file mode 100644 index 000000000..aac33f6ac --- /dev/null +++ b/src/3rdparty/libucontext/include/libucontext/libucontext.h @@ -0,0 +1,20 @@ +#ifndef LIBUCONTEXT_LIBUCONTEXT_H +#define LIBUCONTEXT_LIBUCONTEXT_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int libucontext_getcontext(libucontext_ucontext_t *); +void libucontext_makecontext(libucontext_ucontext_t *, void (*)(), int, ...); +int libucontext_setcontext(const libucontext_ucontext_t *); +int libucontext_swapcontext(libucontext_ucontext_t *, const libucontext_ucontext_t *); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/3rdparty/libucontext/libucontext.pc.in b/src/3rdparty/libucontext/libucontext.pc.in new file mode 100644 index 000000000..10962d859 --- /dev/null +++ b/src/3rdparty/libucontext/libucontext.pc.in @@ -0,0 +1,9 @@ +libdir=@LIBUCONTEXT_SHARED_LIBDIR@ +static_libdir=@LIBUCONTEXT_STATIC_LIBDIR@ +includedir=@LIBUCONTEXT_INCLUDEDIR@ + +Name: libucontext +Version: @LIBUCONTEXT_VERSION@ +Description: ucontext library implementation (standalone) +Libs: -L${libdir} -L${static_libdir} -lucontext +Cflags: -I${includedir} diff --git a/src/3rdparty/libucontext/libucontext_posix.c b/src/3rdparty/libucontext/libucontext_posix.c new file mode 100644 index 000000000..859b40767 --- /dev/null +++ b/src/3rdparty/libucontext/libucontext_posix.c @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or 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. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include +#include +#include + +#ifdef FREESTANDING +# error libucontext_posix cannot be built in FREESTANDING mode. +#endif + +#ifdef DEBUG +# define TRACE(...) fprintf(stderr, "TRACE: " __VA_ARGS__) +#else +# define TRACE(...) +#endif + +int +getcontext(libucontext_ucontext_t *ucp) +{ + TRACE("getcontext(%p)\n", ucp); + + if (sigprocmask(SIG_SETMASK, NULL, &ucp->uc_sigmask)) + return -1; + + return libucontext_getcontext(ucp); +} + +int +setcontext(const libucontext_ucontext_t *ucp) +{ + TRACE("setcontext(%p)\n", ucp); + + if (sigprocmask(SIG_SETMASK, &ucp->uc_sigmask, NULL)) + return -1; + + return libucontext_setcontext(ucp); +} + +int +swapcontext(libucontext_ucontext_t *oucp, const libucontext_ucontext_t *ucp) +{ + TRACE("swapcontext(%p, %p)\n", oucp, ucp); + + if (sigprocmask(SIG_SETMASK, &ucp->uc_sigmask, &oucp->uc_sigmask)) + return -1; + + return libucontext_swapcontext(oucp, ucp); +} diff --git a/src/3rdparty/libucontext/meson.build b/src/3rdparty/libucontext/meson.build new file mode 100644 index 000000000..7dc9f8be2 --- /dev/null +++ b/src/3rdparty/libucontext/meson.build @@ -0,0 +1,172 @@ +project( + 'libucontext', + 'c', + meson_version : '>=0.59.0', + default_options: ['c_std=gnu11', 'default_library=both'], + version : run_command('head', files('VERSION')).stdout() +) + +cpu = get_option('cpu') +if cpu == '' + cpu = host_machine.cpu_family() +endif + +if cpu == 'sh4' + cpu = 'sh' +endif + +project_description = 'Portable implementation of ucontext' + +project_headers = [ + 'include/libucontext/libucontext.h' +] + +project_source_files = [ + 'arch' / cpu / 'getcontext.S', + 'arch' / cpu / 'setcontext.S', + 'arch' / cpu / 'swapcontext.S', +] +if cpu in ['mips', 'mips64'] + project_source_files += [ + 'arch' / cpu / 'makecontext.S' + ] +else + project_source_files += [ + 'arch' / cpu / 'makecontext.c' + ] +endif +if cpu in ['ppc', 'ppc64'] + project_source_files += [ + 'arch' / cpu / 'retfromsyscall.c' + ] +endif +if cpu not in ['mips', 'mips64', 'ppc', 'ppc64', 's390x'] + project_source_files += [ + 'arch' / cpu / 'trampoline.c' + ] +else + project_source_files += [ + 'arch' / cpu / 'startcontext.S' + ] +endif + +project_includes = [ + 'include', + 'arch/common' +] + +build_args = [ + '-D_BSD_SOURCE' +] + + +# =================================================================== + +# ====== +# Options +# ====== + +freestanding = get_option('freestanding') +export_unprefixed = get_option('export_unprefixed') +build_posix = true + +if freestanding + build_args += '-DFREESTANDING' + build_posix = false + export_unprefixed = false + project_headers += ['arch' / cpu / 'include/libucontext/bits.h'] + project_includes += ['arch' / cpu / 'include'] +else + project_headers += ['arch/common/include/libucontext/bits.h'] + project_includes += ['arch/common/include'] +endif + +if export_unprefixed + build_args += '-DEXPORT_UNPREFIXED' +endif + +# ====== +# Target +# ====== + +headers = include_directories(project_includes) + +libucontext_target = library( + 'ucontext', + project_source_files, + version: '1', + install : not meson.is_subproject(), + c_args : build_args, + pic: true, + include_directories : headers, +) +libucontext_dep = declare_dependency( + include_directories: headers, + link_with : libucontext_target +) + +if build_posix + libucontext_posix_target = library( + 'ucontext_posix', + project_source_files + ['libucontext_posix.c'], + version: '1', + install : not meson.is_subproject(), + c_args : build_args, + pic: true, + include_directories : headers, + ) + libucontext_posix_dep = declare_dependency( + include_directories: headers, + link_with : libucontext_posix_target + ) +endif + +# ======= +# Project +# ======= + +if not meson.is_subproject() + # Make this library usable from the system's + # package manager. + install_headers(project_headers, subdir : meson.project_name()) + + pkg_mod = import('pkgconfig') + pkg_mod.generate( + name : meson.project_name(), + filebase : meson.project_name(), + description : project_description, + subdirs : meson.project_name(), + libraries : libucontext_target, + ) +endif + +# ==== +# Docs +# ==== + +if not meson.is_subproject() and get_option('docs') + subdir('doc') +endif + +# ========== +# Unit Tests +# ========== + +test('test_libucontext', + executable( + 'test_libucontext', + files('test_libucontext.c'), + dependencies : libucontext_dep, + install : false + ) +) +if build_posix + test('test_libucontext_posix', + executable( + 'test_libucontext_posix', + files('test_libucontext_posix.c'), + dependencies : [libucontext_dep, libucontext_posix_dep], + install : false + ) + ) +endif diff --git a/src/3rdparty/libucontext/meson_options.txt b/src/3rdparty/libucontext/meson_options.txt new file mode 100644 index 000000000..e37217df4 --- /dev/null +++ b/src/3rdparty/libucontext/meson_options.txt @@ -0,0 +1,8 @@ +option('freestanding', type : 'boolean', value : false, + description: 'Do not use system headers') +option('export_unprefixed', type : 'boolean', value : true, + description: 'Export POSIX 2004 ucontext names as alises') +option('cpu', type : 'string', value : '', + description: 'Target CPU architecture for cross compile') +option('docs', type : 'boolean', value : false, + description: 'Build and install man pages') diff --git a/src/3rdparty/libucontext/test_libucontext.c b/src/3rdparty/libucontext/test_libucontext.c new file mode 100644 index 000000000..72b000e6f --- /dev/null +++ b/src/3rdparty/libucontext/test_libucontext.c @@ -0,0 +1,102 @@ +/* + * libucontext test program based on POSIX example program. + * Public domain. + */ + +#include +#include +#include +#include +#include + +static libucontext_ucontext_t ctx[3]; + + +static void check_arg(int actual, int expected) { + if (actual == expected) return; + fprintf(stderr, "argument has wrong value. got %d, expected %d.\n", actual, expected); + abort(); +} + + +static void f1 (int a, int b, int c, int d, int e, int f, int g, int h, int i, int j) { + printf("start f1\n"); + + printf("checking provided arguments to function f1\n"); + check_arg(a, 1); + check_arg(b, 2); + check_arg(c, 3); + check_arg(d, 4); + check_arg(e, 5); + check_arg(f, 6); + check_arg(g, 7); + check_arg(h, 8); + check_arg(i, 9); + check_arg(j, 10); + printf("looks like all arguments are passed correctly\n"); + + printf("swap back to f2\n"); + libucontext_swapcontext(&ctx[1], &ctx[2]); + printf("finish f1\n"); +} + + +static void f2 (void) { + printf("start f2\n"); + printf("swap to f1\n"); + libucontext_swapcontext(&ctx[2], &ctx[1]); + printf("finish f2, should swap to f1\n"); +} + + +int main (int argc, const char *argv[]) { + char st1[8192]; + char st2[8192]; + volatile int done = 0; + + + /* poison each coroutine's stack memory for debugging purposes */ + memset(st1, 'A', sizeof st1); + memset(st2, 'B', sizeof st2); + + + printf("setting up context 1\n"); + + + libucontext_getcontext(&ctx[1]); + ctx[1].uc_stack.ss_sp = st1; + ctx[1].uc_stack.ss_size = sizeof st1; + ctx[1].uc_link = &ctx[0]; + libucontext_makecontext(&ctx[1], f1, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + + + printf("setting up context 2\n"); + + + libucontext_getcontext(&ctx[2]); + ctx[2].uc_stack.ss_sp = st2; + ctx[2].uc_stack.ss_size = sizeof st2; + ctx[2].uc_link = &ctx[1]; + libucontext_makecontext(&ctx[2], f2, 0); + + + printf("doing initial swapcontext\n"); + + + libucontext_swapcontext(&ctx[0], &ctx[2]); + + + printf("returned from initial swapcontext\n"); + + + /* test ability to use getcontext/setcontext without makecontext */ + libucontext_getcontext(&ctx[1]); + printf("done = %d\n", done); + if (done++ == 0) libucontext_setcontext(&ctx[1]); + if (done != 2) { + fprintf(stderr, "wrong value for done. got %d, expected 2\n", done); + abort(); + } + + return 0; +} diff --git a/src/3rdparty/libucontext/test_libucontext_posix.c b/src/3rdparty/libucontext/test_libucontext_posix.c new file mode 100644 index 000000000..9b8c01d52 --- /dev/null +++ b/src/3rdparty/libucontext/test_libucontext_posix.c @@ -0,0 +1,102 @@ +/* + * libucontext test program based on POSIX example program. + * Public domain. + */ + +#include +#include +#include +#include +#include + +static ucontext_t ctx[3]; + + +static void check_arg(int actual, int expected) { + if (actual == expected) return; + fprintf(stderr, "argument has wrong value. got %d, expected %d.\n", actual, expected); + abort(); +} + + +static void f1 (int a, int b, int c, int d, int e, int f, int g, int h, int i, int j) { + printf("start f1\n"); + + printf("checking provided arguments to function f1\n"); + check_arg(a, 1); + check_arg(b, 2); + check_arg(c, 3); + check_arg(d, 4); + check_arg(e, 5); + check_arg(f, 6); + check_arg(g, 7); + check_arg(h, 8); + check_arg(i, 9); + check_arg(j, 10); + printf("looks like all arguments are passed correctly\n"); + + printf("swap back to f2\n"); + swapcontext(&ctx[1], &ctx[2]); + printf("finish f1\n"); +} + + +static void f2 (void) { + printf("start f2\n"); + printf("swap to f1\n"); + swapcontext(&ctx[2], &ctx[1]); + printf("finish f2, should swap to f1\n"); +} + + +int main (int argc, const char *argv[]) { + char st1[8192]; + char st2[8192]; + volatile int done = 0; + + + /* poison each coroutine's stack memory for debugging purposes */ + memset(st1, 'A', sizeof st1); + memset(st2, 'B', sizeof st2); + + + printf("setting up context 1\n"); + + + getcontext(&ctx[1]); + ctx[1].uc_stack.ss_sp = st1; + ctx[1].uc_stack.ss_size = sizeof st1; + ctx[1].uc_link = &ctx[0]; + makecontext(&ctx[1], f1, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + + + printf("setting up context 2\n"); + + + getcontext(&ctx[2]); + ctx[2].uc_stack.ss_sp = st2; + ctx[2].uc_stack.ss_size = sizeof st2; + ctx[2].uc_link = &ctx[1]; + makecontext(&ctx[2], f2, 0); + + + printf("doing initial swapcontext\n"); + + + swapcontext(&ctx[0], &ctx[2]); + + + printf("returned from initial swapcontext\n"); + + + /* test ability to use getcontext/setcontext without makecontext */ + getcontext(&ctx[1]); + printf("done = %d\n", done); + if (done++ == 0) setcontext(&ctx[1]); + if (done != 2) { + fprintf(stderr, "wrong value for done. got %d, expected 2\n", done); + abort(); + } + + return 0; +} diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index e9fccf9e0..07ebcf432 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -147,13 +147,13 @@ if (LWAN_HAVE_LIBUCONTEXT) include(ExternalProject) ExternalProject_Add(libucontext - GIT_REPOSITORY https://github.com/kaniini/libucontext + SOURCE_DIR ${CMAKE_SOURCE_DIR}/src/3rdparty/libucontext BUILD_IN_SOURCE ON CONFIGURE_COMMAND "" - BUILD_COMMAND make - INSTALL_COMMAND make install DESTDIR=${CMAKE_BINARY_DIR} + BUILD_COMMAND make libucontext.a FREESTANDING=yes + INSTALL_COMMAND make install-static install-headers DESTDIR=${CMAKE_BINARY_DIR} FREESTANDING=yes BUILD_BYPRODUCTS ${CMAKE_BINARY_DIR}/lib/libucontext.a @@ -162,7 +162,7 @@ if (LWAN_HAVE_LIBUCONTEXT) ) add_dependencies(lwan-static libucontext) - set(ADDITIONAL_LIBRARIES ${CMAKE_BINARY_DIR}/lib/libucontext.a ${ADDITIONAL_LIBRARIES} PARENT_SCOPE) + set(ADDITIONAL_LIBRARIES ${CMAKE_BINARY_DIR}/usr/lib/libucontext.a ${ADDITIONAL_LIBRARIES} PARENT_SCOPE) include_directories(${CMAKE_BINARY_DIR}/usr/include) else () message(STATUS "Using built-in context switching routines for ${CMAKE_SYSTEM_PROCESSOR} processors") From cff84ba2efe9c3c2a9c076474cb73ac893fa76bd Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 11 Aug 2024 15:57:42 -0700 Subject: [PATCH 2364/2505] Update dependencies in README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d5db95c5c..6791710c4 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ The build system will look for these libraries and enable/link if available. - [Weighttp](https://github.com/lpereira/weighttp) -- bundled and built alongside Lwan for convenience - [Matplotlib](https://github.com/matplotlib/matplotlib) - To build TechEmpower benchmark suite: - - Client libraries for either [MySQL](https://dev.mysql.com) or [MariaDB](https://mariadb.org) + - Client libraries for [MariaDB](https://mariadb.org) - [SQLite 3](http://sqlite.org) ### Common operating system package names @@ -70,10 +70,10 @@ The build system will look for these libraries and enable/link if available. - macOS: `brew install cmake` #### Build with all optional features - - ArchLinux: `pacman -S cmake zlib sqlite luajit libmariadbclient gperftools valgrind mbedtls` + - ArchLinux: `pacman -S cmake zlib sqlite luajit mariadb-libs gperftools valgrind mbedtls` - FreeBSD: `pkg install cmake pkgconf sqlite3 lua51` - - Ubuntu 14+: `apt-get update && apt-get install git cmake zlib1g-dev pkg-config lua5.1-dev libsqlite3-dev libmysqlclient-dev libmbedtls-dev` - - macOS: `brew install cmake mysql-connector-c sqlite lua@5.1 pkg-config` + - Ubuntu 14+: `apt-get update && apt-get install git cmake zlib1g-dev pkg-config lua5.1-dev libsqlite3-dev libmariadb-dev libmbedtls-dev` + - macOS: `brew install cmake mariadb-connector-c sqlite lua@5.1 pkg-config` ### Build commands @@ -111,7 +111,7 @@ This will generate a few binaries: - `src/bin/lwan/lwan`: The main Lwan executable. May be executed with `--help` for guidance. - `src/bin/testrunner/testrunner`: Contains code to execute the test suite (`src/scripts/testsuite.py`). - `src/samples/freegeoip/freegeoip`: [FreeGeoIP sample implementation](https://freegeoip.lwan.ws). Requires SQLite. - - `src/samples/techempower/techempower`: Code for the TechEmpower Web Framework benchmark. Requires SQLite and MySQL libraries. + - `src/samples/techempower/techempower`: Code for the TechEmpower Web Framework benchmark. Requires SQLite and MariaDB libraries. - `src/samples/clock/clock`: [Clock sample](https://time.lwan.ws). Generates a GIF file that always shows the local time. - `src/bin/tools/mimegen`: Builds the extension-MIME type table. Used during the build process. - `src/bin/tools/bin2hex`: Generates a C file from a binary file, suitable for use with #include. Used during the build process. From eb72e2b5f65d385874aaa9f03b970fccb277fc98 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 17 Aug 2024 08:28:47 -0700 Subject: [PATCH 2365/2505] Increase coroutine bump pointer allocator arena to PAGE_SIZE --- src/lib/lwan-coro.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 26ee34e07..b42ba755c 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -62,7 +62,7 @@ void __asan_unpoison_memory_region(void const volatile *addr, size_t size); #define CORO_STACK_SIZE ((MIN_CORO_STACK_SIZE + (size_t)PAGE_SIZE) & ~((size_t)PAGE_SIZE)) -#define CORO_BUMP_PTR_ALLOC_SIZE 1024 +#define CORO_BUMP_PTR_ALLOC_SIZE PAGE_SIZE #if (!defined(NDEBUG) && defined(MAP_STACK)) || defined(__OpenBSD__) /* As an exploit mitigation, OpenBSD requires any stacks to be allocated via From f671c9eb11aa673bdffb4fceed57b2e0f3d57861 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 17 Aug 2024 08:29:03 -0700 Subject: [PATCH 2366/2505] Load/restore registers in x86-64 coroutine switching in order --- src/lib/lwan-coro.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index b42ba755c..3315c49e8 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -164,7 +164,6 @@ asm(".text\n\t" "movq %rcx,64(%rdi)\n\t" "leaq 0x8(%rsp),%rcx\n\t" "movq %rcx,72(%rdi)\n\t" - "movq 72(%rsi),%rsp\n\t" "movq 0(%rsi),%rbx\n\t" "movq 8(%rsi),%rbp\n\t" "movq 16(%rsi),%r12\n\t" @@ -172,8 +171,9 @@ asm(".text\n\t" "movq 32(%rsi),%r14\n\t" "movq 40(%rsi),%r15\n\t" "movq 48(%rsi),%rdi\n\t" - "movq 64(%rsi),%rcx\n\t" "movq 56(%rsi),%rsi\n\t" + "movq 64(%rsi),%rcx\n\t" + "movq 72(%rsi),%rsp\n\t" "jmpq *%rcx\n\t"); #elif defined(__aarch64__) void __attribute__((noinline, visibility("internal"))) From 5a21056f4af60eb02f0f188ad5ac29e856209806 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 17 Aug 2024 09:15:07 -0700 Subject: [PATCH 2367/2505] Add arena allocator Although coro_malloc() works pretty much the same as this arena, if one needs to control when allocations are freed (e.g. inside a loop), it's not a good way to allocate memory. --- src/lib/CMakeLists.txt | 28 +++++++------ src/lib/lwan-arena.c | 95 ++++++++++++++++++++++++++++++++++++++++++ src/lib/lwan-arena.h | 41 ++++++++++++++++++ src/lib/lwan-array.h | 1 + src/lib/lwan-coro.h | 1 + 5 files changed, 154 insertions(+), 12 deletions(-) create mode 100644 src/lib/lwan-arena.c create mode 100644 src/lib/lwan-arena.h diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index 07ebcf432..3d311b18f 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -4,23 +4,17 @@ if (CMAKE_CROSSCOMPILING) endif () set(SOURCES - base64.c - hash.c - int-to-str.c - list.c + lwan-arena.c lwan-array.c lwan.c lwan-cache.c lwan-config.c lwan-coro.c + lwan-h2-huffman.c lwan-http-authorize.c lwan-io-wrappers.c lwan-job.c - lwan-mod-redirect.c - lwan-mod-response.c - lwan-mod-rewrite.c - lwan-mod-serve-files.c - lwan-mod-fastcgi.c + lwan-pubsub.c lwan-readahead.c lwan-request.c lwan-response.c @@ -35,16 +29,26 @@ set(SOURCES lwan-tq.c lwan-trie.c lwan-websocket.c - lwan-pubsub.c + + lwan-mod-fastcgi.c + lwan-mod-redirect.c + lwan-mod-response.c + lwan-mod-rewrite.c + lwan-mod-serve-files.c + missing.c - missing-pthread.c missing-epoll.c + missing-pthread.c + + base64.c + hash.c + int-to-str.c + list.c patterns.c realpathat.c sd-daemon.c sha1.c timeout.c - lwan-h2-huffman.c ) if (LWAN_HAVE_LUA) diff --git a/src/lib/lwan-arena.c b/src/lib/lwan-arena.c new file mode 100644 index 000000000..18bddbed3 --- /dev/null +++ b/src/lib/lwan-arena.c @@ -0,0 +1,95 @@ +/* + * lwan - web server + * Copyright (c) 2024 L. A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include "lwan-private.h" +#include "lwan-arena.h" + +void arena_init(struct arena *a) +{ + ptr_array_init(&a->ptrs); + a->bump_ptr_alloc.ptr = NULL; + a->bump_ptr_alloc.remaining = 0; +} + +void arena_destroy(struct arena *a) +{ + void **iter; + + LWAN_ARRAY_FOREACH(&a->ptrs, iter) { + free(*iter); + } + + arena_init(a); +} + +static void *arena_bump_ptr(struct arena *a, size_t sz) +{ + void *ptr = a->bump_ptr_alloc.ptr; + + assert(a->bump_ptr_alloc.remaining >= sz); + + a->bump_ptr_alloc.remaining -= sz; + a->bump_ptr_alloc.ptr = (char *)a->bump_ptr_alloc.ptr + sz; + + return ptr; +} + +void *arena_alloc(struct arena *a, size_t sz) +{ + sz = (sz + sizeof(void *) - 1ul) & ~(sizeof(void *) - 1ul); + + if (a->bump_ptr_alloc.remaining < sz) { + void *ptr = malloc(LWAN_MAX((size_t)PAGE_SIZE, sz)); + + if (UNLIKELY(!ptr)) + return NULL; + + void **saved_ptr = ptr_array_append(&a->ptrs); + if (UNLIKELY(!saved_ptr)) { + free(ptr); + return NULL; + } + + *saved_ptr = ptr; + + a->bump_ptr_alloc.ptr = ptr; + a->bump_ptr_alloc.remaining = PAGE_SIZE; + } + + return arena_bump_ptr(a, sz); +} + +static void destroy_arena(void *data) +{ + struct arena *arena = data; + arena_destroy(arena); +} + +struct arena *coro_arena_new(struct coro *coro) +{ + struct arena *arena = coro_malloc(coro, sizeof(*arena)); + + if (LIKELY(arena)) { + arena_init(arena); + coro_defer(coro, destroy_arena, arena); + } + + return arena; +} diff --git a/src/lib/lwan-arena.h b/src/lib/lwan-arena.h new file mode 100644 index 000000000..833c42c45 --- /dev/null +++ b/src/lib/lwan-arena.h @@ -0,0 +1,41 @@ +/* + * lwan - web server + * Copyright (c) 2024 L. A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#pragma once + +#include "lwan-array.h" +#include "lwan-coro.h" + +DEFINE_ARRAY_TYPE(ptr_array, void *) + +struct arena { + struct ptr_array ptrs; + + struct { + void *ptr; + size_t remaining; + } bump_ptr_alloc; +}; + +void arena_init(struct arena *a); +struct arena *coro_arena_new(struct coro *coro); +void arena_destroy(struct arena *a); + +void *arena_alloc(struct arena *a, size_t sz); diff --git a/src/lib/lwan-array.h b/src/lib/lwan-array.h index 3da94dd41..73344b52a 100644 --- a/src/lib/lwan-array.h +++ b/src/lib/lwan-array.h @@ -21,6 +21,7 @@ #pragma once #include +#include #include #include "lwan-coro.h" diff --git a/src/lib/lwan-coro.h b/src/lib/lwan-coro.h index c4b0faa08..82f60ad34 100644 --- a/src/lib/lwan-coro.h +++ b/src/lib/lwan-coro.h @@ -22,6 +22,7 @@ #include #include +#include #if defined(__x86_64__) typedef uintptr_t coro_context[10]; From b3c4dc95febd5cc82cdcd5660ff16dbcb08e7d22 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 17 Aug 2024 09:18:21 -0700 Subject: [PATCH 2368/2505] Add convenience function to get an iov out of a strbuf --- src/lib/lwan-strbuf.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/lib/lwan-strbuf.h b/src/lib/lwan-strbuf.h index 69d4c929f..7e691d138 100644 --- a/src/lib/lwan-strbuf.h +++ b/src/lib/lwan-strbuf.h @@ -24,6 +24,7 @@ #include #include #include +#include struct lwan_strbuf { char *buffer; @@ -102,3 +103,10 @@ static inline char *lwan_strbuf_get_buffer(const struct lwan_strbuf *s) bool lwan_strbuf_init_from_file(struct lwan_strbuf *s, const char *path); struct lwan_strbuf *lwan_strbuf_new_from_file(const char *path); + +static inline struct iovec +lwan_strbuf_to_iovec(const struct lwan_strbuf *s) +{ + return (struct iovec){.iov_base = lwan_strbuf_get_buffer(s), + .iov_len = lwan_strbuf_get_length(s)}; +} From 33f31e49b18862b9a853ed7c689defe0481b9981 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 17 Aug 2024 09:18:46 -0700 Subject: [PATCH 2369/2505] Use an arena to allocate stdin records in the FastCGI module Using a lwan_array() as an arena like the way it was being used is dangerous as it may realloc() and the pointer may move! --- src/lib/lwan-mod-fastcgi.c | 136 +++++++++++++++++++------------------ 1 file changed, 69 insertions(+), 67 deletions(-) diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index 00e3a93a4..79ca6736d 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -43,6 +43,7 @@ #include "int-to-str.h" #include "realpathat.h" +#include "lwan-arena.h" #include "lwan-cache.h" #include "lwan-io-wrappers.h" #include "lwan-mod-fastcgi.h" @@ -573,59 +574,69 @@ try_initiating_chunked_response(struct lwan_request *request) } DEFINE_ARRAY_TYPE_INLINEFIRST(iovec_array, struct iovec) -DEFINE_ARRAY_TYPE_INLINEFIRST(record_array, struct record) static bool build_stdin_records(struct lwan_request *request, struct iovec_array *iovec_array, - struct record_array *record_array, int fcgi_fd, const struct lwan_value *body_data) { - if (!body_data) - return true; - - size_t to_send = body_data->len; - char *buffer = body_data->value; + struct iovec *iovec; - while (to_send) { - struct record *record; - struct iovec *iovec; - size_t block_size = LWAN_MIN(0xffffull, to_send); + if (body_data) { + struct arena *arena = coro_arena_new(request->conn->coro); + size_t to_send = body_data->len; + char *buffer = body_data->value; - record = record_array_append(record_array); - if (UNLIKELY(!record)) - return false; - *record = (struct record){ - .version = 1, - .type = FASTCGI_TYPE_STDIN, - .id = htons(1), - .len_content = htons((uint16_t)block_size), - }; - - iovec = iovec_array_append(iovec_array); - if (UNLIKELY(!iovec)) - return false; - *iovec = (struct iovec){.iov_base = record, .iov_len = sizeof(*record)}; + while (to_send) { + struct record *record; + size_t block_size = LWAN_MIN(0xffffull, to_send); - iovec = iovec_array_append(iovec_array); - if (UNLIKELY(!iovec)) - return false; - *iovec = (struct iovec){.iov_base = buffer, .iov_len = block_size}; + record = arena_alloc(arena, sizeof(*record)); + if (UNLIKELY(!record)) + return false; + *record = (struct record){ + .version = 1, + .type = FASTCGI_TYPE_STDIN, + .id = htons(1), + .len_content = htons((uint16_t)block_size), + }; + + iovec = iovec_array_append(iovec_array); + if (UNLIKELY(!iovec)) + return false; + *iovec = + (struct iovec){.iov_base = record, .iov_len = sizeof(*record)}; - if (iovec_array_len(iovec_array) == LWAN_ARRAY_INCREMENT) { - if (lwan_writev_fd(request, fcgi_fd, - iovec_array_get_array(iovec_array), - (int)iovec_array_len(iovec_array)) < 0) { + iovec = iovec_array_append(iovec_array); + if (UNLIKELY(!iovec)) return false; + *iovec = (struct iovec){.iov_base = buffer, .iov_len = block_size}; + + if (iovec_array_len(iovec_array) == LWAN_ARRAY_INCREMENT) { + if (lwan_writev_fd(request, fcgi_fd, + iovec_array_get_array(iovec_array), + (int)iovec_array_len(iovec_array)) < 0) { + return false; + } + iovec_array_reset(iovec_array); + arena_destroy(arena); } - iovec_array_reset(iovec_array); - record_array_reset(record_array); - } - to_send -= block_size; - buffer += block_size; + to_send -= block_size; + buffer += block_size; + } } + iovec = iovec_array_append(iovec_array); + if (UNLIKELY(!iovec)) + return HTTP_INTERNAL_ERROR; + *iovec = (struct iovec){ + .iov_base = &(struct record){.version = 1, + .type = FASTCGI_TYPE_STDIN, + .id = htons(1)}, + .iov_len = sizeof(struct record), + }; + return true; } @@ -655,12 +666,10 @@ static enum lwan_http_status send_request(struct private_data *pd, } struct iovec_array iovec_array; - struct record_array record_array; struct iovec *iovec; - /* These arrays should never go beyond the inlinefirst threshold, so they + /* This arrays should never go beyond the inlinefirst threshold, so it * shouldn't leak -- thus requiring no defer to reset them. */ - record_array_init(&record_array); iovec_array_init(&iovec_array); iovec = iovec_array_append(&iovec_array); @@ -669,27 +678,31 @@ static enum lwan_http_status send_request(struct private_data *pd, *iovec = (struct iovec){ .iov_base = &(struct request_header){ - .begin_request = {.version = 1, - .type = FASTCGI_TYPE_BEGIN_REQUEST, - .id = htons(1), - .len_content = htons((uint16_t)sizeof( - struct begin_request_body))}, + .begin_request = + { + .version = 1, + .type = FASTCGI_TYPE_BEGIN_REQUEST, + .id = htons(1), + .len_content = + htons((uint16_t)sizeof(struct begin_request_body)), + }, .begin_request_body = {.role = htons(FASTCGI_ROLE_RESPONDER)}, - .begin_params = {.version = 1, - .type = FASTCGI_TYPE_PARAMS, - .id = htons(1), - .len_content = - htons((uint16_t)lwan_strbuf_get_length( - response->buffer))}}, + .begin_params = + { + .version = 1, + .type = FASTCGI_TYPE_PARAMS, + .id = htons(1), + .len_content = htons( + (uint16_t)lwan_strbuf_get_length(response->buffer)), + }, + }, .iov_len = sizeof(struct request_header), }; iovec = iovec_array_append(&iovec_array); if (UNLIKELY(!iovec)) return HTTP_INTERNAL_ERROR; - *iovec = - (struct iovec){.iov_base = lwan_strbuf_get_buffer(response->buffer), - .iov_len = lwan_strbuf_get_length(response->buffer)}; + *iovec = lwan_strbuf_to_iovec(response->buffer); iovec = iovec_array_append(&iovec_array); if (UNLIKELY(!iovec)) @@ -701,27 +714,16 @@ static enum lwan_http_status send_request(struct private_data *pd, .iov_len = sizeof(struct record), }; - if (!build_stdin_records(request, &iovec_array, &record_array, fcgi_fd, + if (!build_stdin_records(request, &iovec_array, fcgi_fd, lwan_request_get_request_body(request))) { return HTTP_INTERNAL_ERROR; } - iovec = iovec_array_append(&iovec_array); - if (UNLIKELY(!iovec)) - return HTTP_INTERNAL_ERROR; - *iovec = (struct iovec){ - .iov_base = &(struct record){.version = 1, - .type = FASTCGI_TYPE_STDIN, - .id = htons(1)}, - .iov_len = sizeof(struct record), - }; - if (lwan_writev_fd(request, fcgi_fd, iovec_array_get_array(&iovec_array), (int)iovec_array_len(&iovec_array)) < 0) { return HTTP_INTERNAL_ERROR; } iovec_array_reset(&iovec_array); - record_array_reset(&record_array); lwan_strbuf_reset(response->buffer); From 4b477758651f1c79fc4030db1ebe0b5e72cf98b5 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 17 Aug 2024 09:22:50 -0700 Subject: [PATCH 2370/2505] Add convenience function to convert strbuf to lwan_value --- src/lib/lwan-mod-fastcgi.c | 5 +---- src/lib/lwan-mod-serve-files.c | 5 +---- src/lib/lwan-strbuf.c | 13 +++++++++++++ src/lib/lwan-strbuf.h | 8 ++------ src/lib/lwan.c | 3 +-- 5 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index 79ca6736d..f409cc291 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -475,10 +475,7 @@ try_initiating_chunked_response(struct lwan_request *request) char *header_start[N_HEADER_START]; char *next_request; enum lwan_http_status status_code = HTTP_OK; - struct lwan_value buffer = { - .value = lwan_strbuf_get_buffer(response->buffer), - .len = lwan_strbuf_get_length(response->buffer), - }; + struct lwan_value buffer = lwan_strbuf_to_value(response->buffer); assert(!(request->flags & (RESPONSE_CHUNKED_ENCODING | RESPONSE_SENT_HEADERS))); diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 8baafcb37..2e88829d5 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -663,10 +663,7 @@ static bool dirlist_init(struct file_cache_entry *ce, ce->mime_type = "text/html"; - struct lwan_value rendered = { - .value = lwan_strbuf_get_buffer(&dd->rendered), - .len = lwan_strbuf_get_length(&dd->rendered), - }; + struct lwan_value rendered = lwan_strbuf_to_value(&dd->rendered); deflate_value(&rendered, &dd->deflated); #if defined(LWAN_HAVE_BROTLI) brotli_value(&rendered, &dd->brotli, &dd->deflated); diff --git a/src/lib/lwan-strbuf.c b/src/lib/lwan-strbuf.c index 5d4f8a111..43d726a96 100644 --- a/src/lib/lwan-strbuf.c +++ b/src/lib/lwan-strbuf.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include "lwan-private.h" @@ -438,3 +439,15 @@ struct lwan_strbuf *lwan_strbuf_new_from_file(const char *path) free(strbuf); return NULL; } + +struct lwan_value lwan_strbuf_to_value(const struct lwan_strbuf *s) +{ + return (struct lwan_value){.value = lwan_strbuf_get_buffer(s), + .len = lwan_strbuf_get_length(s)}; +} + +struct iovec lwan_strbuf_to_iovec(const struct lwan_strbuf *s) +{ + return (struct iovec){.iov_base = lwan_strbuf_get_buffer(s), + .iov_len = lwan_strbuf_get_length(s)}; +} diff --git a/src/lib/lwan-strbuf.h b/src/lib/lwan-strbuf.h index 7e691d138..991c32d05 100644 --- a/src/lib/lwan-strbuf.h +++ b/src/lib/lwan-strbuf.h @@ -104,9 +104,5 @@ static inline char *lwan_strbuf_get_buffer(const struct lwan_strbuf *s) bool lwan_strbuf_init_from_file(struct lwan_strbuf *s, const char *path); struct lwan_strbuf *lwan_strbuf_new_from_file(const char *path); -static inline struct iovec -lwan_strbuf_to_iovec(const struct lwan_strbuf *s) -{ - return (struct iovec){.iov_base = lwan_strbuf_get_buffer(s), - .iov_len = lwan_strbuf_get_length(s)}; -} +struct iovec lwan_strbuf_to_iovec(const struct lwan_strbuf *s); +struct lwan_value lwan_strbuf_to_value(const struct lwan_strbuf *s); diff --git a/src/lib/lwan.c b/src/lib/lwan.c index c63bac5a4..2cba18e44 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -192,8 +192,7 @@ static void build_response_headers(struct lwan *l, lwan_strbuf_append_strz(&strbuf, "\r\n\r\n"); - l->headers = (struct lwan_value){.value = lwan_strbuf_get_buffer(&strbuf), - .len = lwan_strbuf_get_length(&strbuf)}; + l->headers = lwan_strbuf_to_value(&strbuf); } static void parse_global_headers(struct config *c, From 99e23b7f8098186f16f031390a1bd2aa1f0a2ba8 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 18 Aug 2024 18:13:18 -0700 Subject: [PATCH 2371/2505] Ensure remaining size is stored correctly in the arena allocator --- src/lib/lwan-arena.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-arena.c b/src/lib/lwan-arena.c index 18bddbed3..ce249e95e 100644 --- a/src/lib/lwan-arena.c +++ b/src/lib/lwan-arena.c @@ -39,7 +39,7 @@ void arena_destroy(struct arena *a) arena_init(a); } -static void *arena_bump_ptr(struct arena *a, size_t sz) +static inline void *arena_bump_ptr(struct arena *a, size_t sz) { void *ptr = a->bump_ptr_alloc.ptr; @@ -56,7 +56,8 @@ void *arena_alloc(struct arena *a, size_t sz) sz = (sz + sizeof(void *) - 1ul) & ~(sizeof(void *) - 1ul); if (a->bump_ptr_alloc.remaining < sz) { - void *ptr = malloc(LWAN_MAX((size_t)PAGE_SIZE, sz)); + size_t alloc_sz = LWAN_MAX((size_t)PAGE_SIZE, sz); + void *ptr = malloc(alloc_sz); if (UNLIKELY(!ptr)) return NULL; @@ -70,7 +71,7 @@ void *arena_alloc(struct arena *a, size_t sz) *saved_ptr = ptr; a->bump_ptr_alloc.ptr = ptr; - a->bump_ptr_alloc.remaining = PAGE_SIZE; + a->bump_ptr_alloc.remaining = alloc_sz; } return arena_bump_ptr(a, sz); From dc8e0f0e105d1182372f3d282bf2b0da5b54c127 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 18 Aug 2024 18:17:57 -0700 Subject: [PATCH 2372/2505] Declare the arena array as inlinefirst --- src/lib/lwan-arena.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-arena.h b/src/lib/lwan-arena.h index 833c42c45..1764b4264 100644 --- a/src/lib/lwan-arena.h +++ b/src/lib/lwan-arena.h @@ -23,7 +23,7 @@ #include "lwan-array.h" #include "lwan-coro.h" -DEFINE_ARRAY_TYPE(ptr_array, void *) +DEFINE_ARRAY_TYPE_INLINEFIRST(ptr_array, void *) struct arena { struct ptr_array ptrs; From 9509197c7bfbc813e0c2f148c2231b8905d083cd Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 18 Aug 2024 18:18:24 -0700 Subject: [PATCH 2373/2505] Rename `arena_destroy()` to `arena_reset()` This function might free memory and whatnot, but it leaves the arena object in a usable state, so the "reset" terminology is more appropriate. --- src/lib/lwan-arena.c | 8 ++++---- src/lib/lwan-arena.h | 2 +- src/lib/lwan-mod-fastcgi.c | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/lwan-arena.c b/src/lib/lwan-arena.c index ce249e95e..bc105c857 100644 --- a/src/lib/lwan-arena.c +++ b/src/lib/lwan-arena.c @@ -28,7 +28,7 @@ void arena_init(struct arena *a) a->bump_ptr_alloc.remaining = 0; } -void arena_destroy(struct arena *a) +void arena_reset(struct arena *a) { void **iter; @@ -77,10 +77,10 @@ void *arena_alloc(struct arena *a, size_t sz) return arena_bump_ptr(a, sz); } -static void destroy_arena(void *data) +static void reset_arena(void *data) { struct arena *arena = data; - arena_destroy(arena); + arena_reset(arena); } struct arena *coro_arena_new(struct coro *coro) @@ -89,7 +89,7 @@ struct arena *coro_arena_new(struct coro *coro) if (LIKELY(arena)) { arena_init(arena); - coro_defer(coro, destroy_arena, arena); + coro_defer(coro, reset_arena, arena); } return arena; diff --git a/src/lib/lwan-arena.h b/src/lib/lwan-arena.h index 1764b4264..9db87b069 100644 --- a/src/lib/lwan-arena.h +++ b/src/lib/lwan-arena.h @@ -36,6 +36,6 @@ struct arena { void arena_init(struct arena *a); struct arena *coro_arena_new(struct coro *coro); -void arena_destroy(struct arena *a); +void arena_reset(struct arena *a); void *arena_alloc(struct arena *a, size_t sz); diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index f409cc291..4d93f61c7 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -616,7 +616,7 @@ static bool build_stdin_records(struct lwan_request *request, return false; } iovec_array_reset(iovec_array); - arena_destroy(arena); + arena_reset(arena); } to_send -= block_size; From cc94f78ff24168a15bfbf174b89407a672adeb06 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 18 Aug 2024 18:28:11 -0700 Subject: [PATCH 2374/2505] Implement lwan_strbuf_append_value(), that takes a lwan_value --- src/lib/lwan-config.c | 22 +++++++--------------- src/lib/lwan-strbuf.c | 6 ++++++ src/lib/lwan-strbuf.h | 3 +++ 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/lib/lwan-config.c b/src/lib/lwan-config.c index 7f0648f52..14c8ffa63 100644 --- a/src/lib/lwan-config.c +++ b/src/lib/lwan-config.c @@ -75,10 +75,7 @@ enum lexeme_type { struct lexeme { enum lexeme_type type; - struct { - const char *value; - size_t len; - } value; + struct lwan_value value; }; DEFINE_RING_BUFFER_TYPE(lexeme_ring_buffer, struct lexeme, 4) @@ -266,7 +263,7 @@ static void emit(struct lexer *lexer, enum lexeme_type type) { struct lexeme lexeme = { .type = type, - .value = {.value = lexer->start, .len = current_len(lexer)}, + .value = {.value = (char *)lexer->start, .len = current_len(lexer)}, }; emit_lexeme(lexer, &lexeme); } @@ -569,8 +566,7 @@ static void *parse_key_value(struct parser *parser) if (lexeme->type != LEXEME_STRING) return PARSER_ERROR(parser, "Expecting string"); - lwan_strbuf_append_str(&parser->strbuf, lexeme->value.value, - lexeme->value.len); + lwan_strbuf_append_value(&parser->strbuf, &lexeme->value); if (!lexeme_ring_buffer_empty(&parser->buffer)) lwan_strbuf_append_char(&parser->strbuf, '_'); @@ -621,8 +617,7 @@ static void *parse_key_value(struct parser *parser) "Using default value of '%.*s' for variable '${%.*s}'", (int)lexeme->value.len, lexeme->value.value, (int)var_name->value.len, var_name->value.value); - lwan_strbuf_append_str(&parser->strbuf, lexeme->value.value, - lexeme->value.len); + lwan_strbuf_append_value(&parser->strbuf, &lexeme->value); } else { lwan_strbuf_append_strz(&parser->strbuf, value); } @@ -638,8 +633,7 @@ static void *parse_key_value(struct parser *parser) if (last_lexeme == LEXEME_STRING) lwan_strbuf_append_char(&parser->strbuf, ' '); - lwan_strbuf_append_str(&parser->strbuf, lexeme->value.value, - lexeme->value.len); + lwan_strbuf_append_value(&parser->strbuf, &lexeme->value); break; @@ -683,8 +677,7 @@ static void *parse_section(struct parser *parser) if (!lexeme || lexeme->type != LEXEME_STRING) return PARSER_ERROR(parser, "Expecting a string"); - lwan_strbuf_append_str(&parser->strbuf, lexeme->value.value, - lexeme->value.len); + lwan_strbuf_append_value(&parser->strbuf, &lexeme->value); name_len = lexeme->value.len; lwan_strbuf_append_char(&parser->strbuf, '\0'); @@ -692,8 +685,7 @@ static void *parse_section(struct parser *parser) if (lexeme->type != LEXEME_STRING) return PARSER_ERROR(parser, "Expecting a string"); - lwan_strbuf_append_str(&parser->strbuf, lexeme->value.value, - lexeme->value.len); + lwan_strbuf_append_value(&parser->strbuf, &lexeme->value); if (!lexeme_ring_buffer_empty(&parser->buffer)) lwan_strbuf_append_char(&parser->strbuf, ' '); diff --git a/src/lib/lwan-strbuf.c b/src/lib/lwan-strbuf.c index 43d726a96..9b8fc71ab 100644 --- a/src/lib/lwan-strbuf.c +++ b/src/lib/lwan-strbuf.c @@ -451,3 +451,9 @@ struct iovec lwan_strbuf_to_iovec(const struct lwan_strbuf *s) return (struct iovec){.iov_base = lwan_strbuf_get_buffer(s), .iov_len = lwan_strbuf_get_length(s)}; } + +bool lwan_strbuf_append_value(struct lwan_strbuf *s1, + const struct lwan_value *s2) +{ + return lwan_strbuf_append_str(s1, s2->value, s2->len); +} diff --git a/src/lib/lwan-strbuf.h b/src/lib/lwan-strbuf.h index 991c32d05..84dd99007 100644 --- a/src/lib/lwan-strbuf.h +++ b/src/lib/lwan-strbuf.h @@ -65,6 +65,9 @@ static inline bool lwan_strbuf_append_strz(struct lwan_strbuf *s1, return lwan_strbuf_append_str(s1, s2, strlen(s2)); } +struct lwan_value; +bool lwan_strbuf_append_value(struct lwan_strbuf *s1, const struct lwan_value *s2); + bool lwan_strbuf_set_static(struct lwan_strbuf *s1, const char *s2, size_t sz); static inline bool lwan_strbuf_set_staticz(struct lwan_strbuf *s1, const char *s2) From 23227c882e5c2987b688361e12e653851a4973cb Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 18 Aug 2024 19:32:38 -0700 Subject: [PATCH 2375/2505] No need to ignore leading whitespace in requests The HTTP RFC doesn't mention that leading whitespaces might be a thing. --- src/lib/lwan-request.c | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 920d2050f..46d576eb7 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -77,8 +77,6 @@ struct proxy_header_v2 { } addr; }; -static char *ignore_leading_whitespace(char *buffer) __attribute__((pure)); - static bool parse_ascii_port(char *port, unsigned short *out) @@ -783,14 +781,6 @@ parse_accept_encoding(struct lwan_request *request) } } -static ALWAYS_INLINE char * -ignore_leading_whitespace(char *buffer) -{ - while (lwan_char_isspace(*buffer)) - buffer++; - return buffer; -} - static ALWAYS_INLINE void parse_connection_header(struct lwan_request *request) { struct lwan_request_parser_helper *helper = request->helper; @@ -1350,8 +1340,6 @@ static enum lwan_http_status parse_http_request(struct lwan_request *request) return HTTP_BAD_REQUEST; } - buffer = ignore_leading_whitespace(buffer); - if (UNLIKELY(buffer > helper->buffer->value + helper->buffer->len - MIN_REQUEST_SIZE)) return HTTP_BAD_REQUEST; From 83b872535c65d64d9802764c3a74f1b62e5d569a Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 18 Aug 2024 19:33:37 -0700 Subject: [PATCH 2376/2505] Add some assertions to the template compiler --- src/lib/lwan-template.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index 26ab8d1dd..3b02fc3aa 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -1117,6 +1117,8 @@ static bool post_process_template(struct parser *parser) break; } + assert(prev_chunk->flags & FLAGS_NO_FREE); + struct chunk_descriptor *cd = malloc(sizeof(*cd)); if (!cd) lwan_status_critical_perror("malloc"); @@ -1152,6 +1154,8 @@ static bool post_process_template(struct parser *parser) } } + assert(prev_chunk->flags & FLAGS_NO_FREE); + struct chunk_descriptor *cd = malloc(sizeof(*cd)); if (!cd) lwan_status_critical_perror("malloc"); From a3b8a1b65fc4f106f9f764f56f36cdfd2b6b4c9f Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 23 Aug 2024 06:46:03 -0700 Subject: [PATCH 2377/2505] Revert "Load/restore registers in x86-64 coroutine switching in order" This reverts commit f671c9eb11aa673bdffb4fceed57b2e0f3d57861. --- src/lib/lwan-coro.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 3315c49e8..b42ba755c 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -164,6 +164,7 @@ asm(".text\n\t" "movq %rcx,64(%rdi)\n\t" "leaq 0x8(%rsp),%rcx\n\t" "movq %rcx,72(%rdi)\n\t" + "movq 72(%rsi),%rsp\n\t" "movq 0(%rsi),%rbx\n\t" "movq 8(%rsi),%rbp\n\t" "movq 16(%rsi),%r12\n\t" @@ -171,9 +172,8 @@ asm(".text\n\t" "movq 32(%rsi),%r14\n\t" "movq 40(%rsi),%r15\n\t" "movq 48(%rsi),%rdi\n\t" - "movq 56(%rsi),%rsi\n\t" "movq 64(%rsi),%rcx\n\t" - "movq 72(%rsi),%rsp\n\t" + "movq 56(%rsi),%rsi\n\t" "jmpq *%rcx\n\t"); #elif defined(__aarch64__) void __attribute__((noinline, visibility("internal"))) From f9ebc6635078ad1e88b8517648a4ed4690efb71d Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 24 Aug 2024 07:04:12 -0700 Subject: [PATCH 2378/2505] Also allow "straightjacket" spelling in configuration files --- src/lib/lwan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 2cba18e44..361760371 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -723,7 +723,7 @@ static bool setup_from_config(struct lwan *lwan, const char *path) } else { config_error(conf, "Only one site may be configured"); } - } else if (streq(line->key, "straitjacket")) { + } else if (streq(line->key, "straitjacket") || streq(line->key, "straightjacket")) { lwan_straitjacket_enforce_from_config(conf); } else if (streq(line->key, "headers")) { parse_global_headers(conf, lwan); From 557a6e78256144beb95a175e2d8a7106b01d16c9 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 24 Aug 2024 16:27:29 -0700 Subject: [PATCH 2379/2505] Use mincore(2) to queue an madvise(2) call if necessary --- CMakeLists.txt | 1 + src/cmake/lwan-build-config.h.cmake | 1 + src/lib/lwan-mod-serve-files.c | 31 ++++++++++++++++++++++++++++- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c1e913a75..461b18742 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -175,6 +175,7 @@ set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) check_function_exists(get_current_dir_name LWAN_HAVE_GET_CURRENT_DIR_NAME) check_symbol_exists(reallocarray stdlib.h LWAN_HAVE_REALLOCARRAY) check_symbol_exists(eventfd sys/eventfd.h LWAN_HAVE_EVENTFD) +check_symbol_exists(mincore sys/mman.h LWAN_HAVE_MINCORE) check_function_exists(mempcpy LWAN_HAVE_MEMPCPY) check_function_exists(memrchr LWAN_HAVE_MEMRCHR) check_function_exists(pipe2 LWAN_HAVE_PIPE2) diff --git a/src/cmake/lwan-build-config.h.cmake b/src/cmake/lwan-build-config.h.cmake index 6b50d454f..7cf31ab5f 100644 --- a/src/cmake/lwan-build-config.h.cmake +++ b/src/cmake/lwan-build-config.h.cmake @@ -49,6 +49,7 @@ #cmakedefine LWAN_HAVE_SYSLOG #cmakedefine LWAN_HAVE_STPCPY #cmakedefine LWAN_HAVE_EVENTFD +#cmakedefine LWAN_HAVE_MINCORE /* Compiler builtins for specific CPU instruction support */ #cmakedefine LWAN_HAVE_BUILTIN_CLZLL diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 2e88829d5..8a5bcc149 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -50,6 +50,10 @@ #include #endif +#define MMAP_SIZE_THRESHOLD 16384 +#define MINCORE_CALL_THRESHOLD 10 +#define MINCORE_VEC_LEN(len) (((len) + PAGE_SIZE - 1) / PAGE_SIZE) + static const struct lwan_key_value deflate_compression_hdr[] = { {"Content-Encoding", "deflate"}, {} }; @@ -113,6 +117,7 @@ struct mmap_cache_data { #if defined(LWAN_HAVE_ZSTD) struct lwan_value zstd; #endif + int mincore_call_threshold; }; struct sendfile_cache_data { @@ -544,6 +549,8 @@ static bool mmap_init(struct file_cache_entry *ce, ce->mime_type = lwan_determine_mime_type_for_file_name(full_path + priv->root_path_len); + md->mincore_call_threshold = MINCORE_CALL_THRESHOLD; + return true; } @@ -755,7 +762,7 @@ static const struct cache_funcs *get_funcs(struct serve_files_priv *priv, /* It's not a directory: choose the fastest way to serve the file * judging by its size. */ - if (st->st_size < 16384) + if (st->st_size < MMAP_SIZE_THRESHOLD) return &mmap_funcs; return &sendfile_funcs; @@ -1252,6 +1259,28 @@ static enum lwan_http_status mmap_serve(struct lwan_request *request, return serve_value_ok(request, fce->mime_type, to_serve, compression_hdr); +#ifdef LWAN_HAVE_MINCORE + if (md->mincore_call_threshold-- == 0) { + unsigned char mincore_vec[MINCORE_VEC_LEN(MMAP_SIZE_THRESHOLD)]; + + if (!mincore(to_serve->value, to_serve->len, mincore_vec)) { + const size_t pgs = MINCORE_VEC_LEN(to_serve->len); + + for (size_t pg = 0; pg < pgs; pg++) { + if (mincore_vec[pg] & 0x01) + continue; + + /* FIXME: madvise only the page that's not in core */ + lwan_madvise_queue(to_serve->value, to_serve->len); + coro_yield(request->conn->coro, CONN_CORO_WANT_WRITE); + break; + } + } + + md->mincore_call_threshold = MINCORE_CALL_THRESHOLD; + } +#endif + off_t from, to; enum lwan_http_status status = compute_range(request, &from, &to, (off_t)to_serve->len); From b8a873c84a35e3550d7a4b9a6e1c461cd610c23f Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 24 Aug 2024 20:03:40 -0700 Subject: [PATCH 2380/2505] Ensure mincore_call_threshold is decremented atomically --- src/lib/lwan-mod-serve-files.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 8a5bcc149..2c0b1e673 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -117,7 +117,7 @@ struct mmap_cache_data { #if defined(LWAN_HAVE_ZSTD) struct lwan_value zstd; #endif - int mincore_call_threshold; + unsigned int mincore_call_threshold; }; struct sendfile_cache_data { @@ -1260,9 +1260,11 @@ static enum lwan_http_status mmap_serve(struct lwan_request *request, compression_hdr); #ifdef LWAN_HAVE_MINCORE - if (md->mincore_call_threshold-- == 0) { + if (ATOMIC_DEC(md->mincore_call_threshold) == 0) { unsigned char mincore_vec[MINCORE_VEC_LEN(MMAP_SIZE_THRESHOLD)]; + md->mincore_call_threshold = MINCORE_CALL_THRESHOLD; + if (!mincore(to_serve->value, to_serve->len, mincore_vec)) { const size_t pgs = MINCORE_VEC_LEN(to_serve->len); @@ -1276,8 +1278,6 @@ static enum lwan_http_status mmap_serve(struct lwan_request *request, break; } } - - md->mincore_call_threshold = MINCORE_CALL_THRESHOLD; } #endif From 953d765b646f2871af5d682770b34b535d98ea1d Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 26 Aug 2024 09:32:44 -0700 Subject: [PATCH 2381/2505] Use arena allocator to implement coro_malloc() --- src/lib/lwan-arena.c | 58 ++++++++++++------ src/lib/lwan-arena.h | 2 +- src/lib/lwan-coro.c | 139 ++---------------------------------------- src/lib/lwan-thread.c | 2 +- 4 files changed, 49 insertions(+), 152 deletions(-) diff --git a/src/lib/lwan-arena.c b/src/lib/lwan-arena.c index bc105c857..30695d4d3 100644 --- a/src/lib/lwan-arena.c +++ b/src/lib/lwan-arena.c @@ -21,6 +21,23 @@ #include "lwan-private.h" #include "lwan-arena.h" +#if !defined(NDEBUG) && defined(LWAN_HAVE_VALGRIND) +#define INSTRUMENT_FOR_VALGRIND +#include +#include +#endif + +#if defined(__clang__) +# if defined(__has_feature) && __has_feature(address_sanitizer) +# define __SANITIZE_ADDRESS__ +# endif +#endif +#if defined(__SANITIZE_ADDRESS__) +#define INSTRUMENT_FOR_ASAN +void __asan_poison_memory_region(void const volatile *addr, size_t size); +void __asan_unpoison_memory_region(void const volatile *addr, size_t size); +#endif + void arena_init(struct arena *a) { ptr_array_init(&a->ptrs); @@ -39,24 +56,12 @@ void arena_reset(struct arena *a) arena_init(a); } -static inline void *arena_bump_ptr(struct arena *a, size_t sz) +void *arena_alloc(struct arena *a, const size_t sz) { - void *ptr = a->bump_ptr_alloc.ptr; - - assert(a->bump_ptr_alloc.remaining >= sz); - - a->bump_ptr_alloc.remaining -= sz; - a->bump_ptr_alloc.ptr = (char *)a->bump_ptr_alloc.ptr + sz; + const size_t aligned_sz = (sz + sizeof(void *) - 1ul) & ~(sizeof(void *) - 1ul); - return ptr; -} - -void *arena_alloc(struct arena *a, size_t sz) -{ - sz = (sz + sizeof(void *) - 1ul) & ~(sizeof(void *) - 1ul); - - if (a->bump_ptr_alloc.remaining < sz) { - size_t alloc_sz = LWAN_MAX((size_t)PAGE_SIZE, sz); + if (a->bump_ptr_alloc.remaining < aligned_sz) { + const size_t alloc_sz = LWAN_MAX((size_t)PAGE_SIZE, aligned_sz); void *ptr = malloc(alloc_sz); if (UNLIKELY(!ptr)) @@ -72,9 +77,28 @@ void *arena_alloc(struct arena *a, size_t sz) a->bump_ptr_alloc.ptr = ptr; a->bump_ptr_alloc.remaining = alloc_sz; + +#if defined(INSTRUMENT_FOR_ASAN) + __asan_poison_memory_region(ptr, alloc_sz); +#endif +#if defined(INSTRUMENT_FOR_VALGRIND) + VALGRIND_MAKE_MEM_NOACCESS(ptr, alloc_sz); +#endif } - return arena_bump_ptr(a, sz); + void *ptr = a->bump_ptr_alloc.ptr; + +#if defined(INSTRUMENT_FOR_VALGRIND) + VALGRIND_MAKE_MEM_UNDEFINED(ptr, sz); +#endif +#if defined(INSTRUMENT_FOR_ASAN) + __asan_unpoison_memory_region(ptr, sz); +#endif + + a->bump_ptr_alloc.remaining -= aligned_sz; + a->bump_ptr_alloc.ptr = (char *)ptr + aligned_sz; + + return ptr; } static void reset_arena(void *data) diff --git a/src/lib/lwan-arena.h b/src/lib/lwan-arena.h index 9db87b069..fb9973d10 100644 --- a/src/lib/lwan-arena.h +++ b/src/lib/lwan-arena.h @@ -38,4 +38,4 @@ void arena_init(struct arena *a); struct arena *coro_arena_new(struct coro *coro); void arena_reset(struct arena *a); -void *arena_alloc(struct arena *a, size_t sz); +void *arena_alloc(struct arena *a, const size_t sz); diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index b42ba755c..12e96f5f9 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -30,6 +30,7 @@ #include "lwan-private.h" +#include "lwan-arena.h" #include "lwan-array.h" #include "lwan-coro.h" @@ -39,17 +40,6 @@ #include #endif -#if defined(__clang__) -# if defined(__has_feature) && __has_feature(address_sanitizer) -# define __SANITIZE_ADDRESS__ -# endif -#endif -#if defined(__SANITIZE_ADDRESS__) -#define INSTRUMENT_FOR_ASAN -void __asan_poison_memory_region(void const volatile *addr, size_t size); -void __asan_unpoison_memory_region(void const volatile *addr, size_t size); -#endif - #if !defined(SIGSTKSZ) #define SIGSTKSZ 16384 #endif @@ -62,8 +52,6 @@ void __asan_unpoison_memory_region(void const volatile *addr, size_t size); #define CORO_STACK_SIZE ((MIN_CORO_STACK_SIZE + (size_t)PAGE_SIZE) & ~((size_t)PAGE_SIZE)) -#define CORO_BUMP_PTR_ALLOC_SIZE PAGE_SIZE - #if (!defined(NDEBUG) && defined(MAP_STACK)) || defined(__OpenBSD__) /* As an exploit mitigation, OpenBSD requires any stacks to be allocated via * mmap(... MAP_STACK ...). @@ -109,12 +97,7 @@ struct coro { int64_t yield_value; - struct { - /* This allocator is instrumented on debug builds using asan and/or valgrind, if - * enabled during configuration time. See coro_malloc_bump_ptr() for details. */ - void *ptr; - size_t remaining; - } bump_ptr_alloc; + struct arena arena; #if defined(INSTRUMENT_FOR_VALGRIND) unsigned int vg_stack_id; @@ -271,8 +254,8 @@ void coro_reset(struct coro *coro, coro_function_t func, void *data) unsigned char *stack = coro->stack; coro_deferred_run(coro, 0); + arena_reset(&coro->arena); coro_defer_array_reset(&coro->defer); - coro->bump_ptr_alloc.remaining = 0; #if defined(__x86_64__) /* coro_entry_point() for x86-64 has 3 arguments, but RDX isn't @@ -340,6 +323,7 @@ coro_new(struct coro_switcher *switcher, coro_function_t function, void *data) #endif coro_defer_array_init(&coro->defer); + arena_init(&coro->arena); coro->switcher = switcher; coro_reset(coro, function, data); @@ -390,6 +374,7 @@ void coro_free(struct coro *coro) assert(coro); coro_deferred_run(coro, 0); + arena_reset(&coro->arena); coro_defer_array_reset(&coro->defer); #if defined(INSTRUMENT_FOR_VALGRIND) @@ -498,121 +483,9 @@ void *coro_malloc_full(struct coro *coro, return ptr; } -#if defined(INSTRUMENT_FOR_VALGRIND) || defined(INSTRUMENT_FOR_ASAN) -static void instrument_bpa_free(void *ptr, void *size) -{ -#if defined(INSTRUMENT_FOR_VALGRIND) - VALGRIND_MAKE_MEM_NOACCESS(ptr, (size_t)(uintptr_t)size); -#endif - -#if defined(INSTRUMENT_FOR_ASAN) - __asan_poison_memory_region(ptr, (size_t)(uintptr_t)size); -#endif -} -#endif - -#if defined(INSTRUMENT_FOR_ASAN) || defined(INSTRUMENT_FOR_VALGRIND) -static inline void *coro_malloc_bump_ptr(struct coro *coro, - size_t aligned_size, - size_t requested_size) -#else -static inline void *coro_malloc_bump_ptr(struct coro *coro, size_t aligned_size) -#endif -{ - void *ptr = coro->bump_ptr_alloc.ptr; - - coro->bump_ptr_alloc.remaining -= aligned_size; - coro->bump_ptr_alloc.ptr = (char *)ptr + aligned_size; - - /* This instrumentation is desirable to find buffer overflows, but it's not - * cheap. Enable it only in debug builds (for Valgrind) or when using - * address sanitizer (always the case when fuzz-testing on OSS-Fuzz). See: - * https://blog.fuzzing-project.org/65-When-your-Memory-Allocator-hides-Security-Bugs.html - */ - -#if defined(INSTRUMENT_FOR_VALGRIND) - VALGRIND_MAKE_MEM_UNDEFINED(ptr, requested_size); -#endif -#if defined(INSTRUMENT_FOR_ASAN) - __asan_unpoison_memory_region(ptr, requested_size); -#endif -#if defined(INSTRUMENT_FOR_VALGRIND) || defined(INSTRUMENT_FOR_ASAN) - coro_defer2(coro, instrument_bpa_free, ptr, - (void *)(uintptr_t)requested_size); -#endif - - return ptr; -} - -#if defined(INSTRUMENT_FOR_ASAN) || defined(INSTRUMENT_FOR_VALGRIND) -#define CORO_MALLOC_BUMP_PTR(coro_, aligned_size_, requested_size_) \ - coro_malloc_bump_ptr(coro_, aligned_size_, requested_size_) -#else -#define CORO_MALLOC_BUMP_PTR(coro_, aligned_size_, requested_size_) \ - coro_malloc_bump_ptr(coro_, aligned_size_) -#endif - -static void free_bump_ptr(void *arg1, void *arg2) -{ - struct coro *coro = arg1; - -#if defined(INSTRUMENT_FOR_VALGRIND) - VALGRIND_MAKE_MEM_UNDEFINED(arg2, CORO_BUMP_PTR_ALLOC_SIZE); -#endif -#if defined(INSTRUMENT_FOR_ASAN) - __asan_unpoison_memory_region(arg2, CORO_BUMP_PTR_ALLOC_SIZE); -#endif - - /* Instead of checking if bump_ptr_alloc.ptr is part of the allocation - * with base in arg2, just zero out the arena for this coroutine to - * prevent coro_malloc() from carving up this and any other - * (potentially) freed arenas. */ - coro->bump_ptr_alloc.remaining = 0; - - return free(arg2); -} - void *coro_malloc(struct coro *coro, size_t size) { - /* The bump pointer allocator can't be in the generic coro_malloc_full() - * since destroy_funcs are supposed to free the memory. In this function, we - * guarantee that the destroy_func is free(), so that if an allocation goes - * through the bump pointer allocator, there's nothing that needs to be done - * to free the memory (other than freeing the whole bump pointer arena with - * the defer call below). */ - - const size_t aligned_size = - (size + sizeof(void *) - 1ul) & ~(sizeof(void *) - 1ul); - - if (LIKELY(coro->bump_ptr_alloc.remaining >= aligned_size)) - return CORO_MALLOC_BUMP_PTR(coro, aligned_size, size); - - /* This will allocate as many "bump pointer arenas" as necessary; the - * old ones will be freed automatically as each allocations coro_defers - * the free() call. Just don't bother allocating an arena larger than - * CORO_BUMP_PTR_ALLOC. */ - if (LIKELY(aligned_size <= CORO_BUMP_PTR_ALLOC_SIZE)) { - coro->bump_ptr_alloc.ptr = malloc(CORO_BUMP_PTR_ALLOC_SIZE); - if (UNLIKELY(!coro->bump_ptr_alloc.ptr)) - return NULL; - - coro->bump_ptr_alloc.remaining = CORO_BUMP_PTR_ALLOC_SIZE; - -#if defined(INSTRUMENT_FOR_ASAN) - __asan_poison_memory_region(coro->bump_ptr_alloc.ptr, - CORO_BUMP_PTR_ALLOC_SIZE); -#endif -#if defined(INSTRUMENT_FOR_VALGRIND) - VALGRIND_MAKE_MEM_NOACCESS(coro->bump_ptr_alloc.ptr, - CORO_BUMP_PTR_ALLOC_SIZE); -#endif - - coro_defer2(coro, free_bump_ptr, coro, coro->bump_ptr_alloc.ptr); - - return CORO_MALLOC_BUMP_PTR(coro, aligned_size, size); - } - - return coro_malloc_full(coro, size, free); + return arena_alloc(&coro->arena, size); } char *coro_strndup(struct coro *coro, const char *str, size_t max_len) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 302431511..8319f0a34 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -479,7 +479,7 @@ __attribute__((noreturn)) static int process_request_coro(struct coro *coro, if (request_buffer_size > DEFAULT_BUFFER_SIZE) { buffer = (struct lwan_value){ - .value = coro_malloc(conn->coro, request_buffer_size), + .value = coro_malloc_full(conn->coro, request_buffer_size, free), .len = request_buffer_size, }; From af47365cab2f638f6beabd0c70cd4beabb4e4dbf Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 27 Aug 2024 14:49:07 -0700 Subject: [PATCH 2382/2505] Abstract __attribute__((constructor)) Some platforms (e.g. macOS) do not support constructor priorities, so at least make them compile. Ideally, we'd register a function to be called at initialization the same way Lua modules are registered, and manage the order inside lwan_init(); but this is good for now. --- src/bin/tools/bin2hex.c | 4 ++-- src/lib/hash.c | 3 ++- src/lib/lwan-lua.c | 2 +- src/lib/lwan-private.h | 6 ++++++ src/lib/lwan-readahead.c | 3 ++- src/lib/lwan-request.c | 3 ++- src/lib/lwan-status.c | 3 ++- src/lib/lwan.c | 3 ++- src/samples/clock/blocks.c | 3 ++- src/samples/clock/main.c | 4 ++-- src/samples/clock/xdaliclock.c | 2 +- 11 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/bin/tools/bin2hex.c b/src/bin/tools/bin2hex.c index 92da6396b..2f780f627 100644 --- a/src/bin/tools/bin2hex.c +++ b/src/bin/tools/bin2hex.c @@ -26,11 +26,11 @@ #include #include -#include "lwan.h" +#include "lwan-private.h" static int constructor_attr_supported = 0; -__attribute__((constructor)) +LWAN_CONSTRUCTOR() static void initialize_constructor_attr_supported(void) { constructor_attr_supported = 1; diff --git a/src/lib/hash.c b/src/lib/hash.c index 6b765741e..f882eec98 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -208,7 +208,8 @@ static inline unsigned int hash_int64_crc32(const void *keyptr) #endif -__attribute__((constructor(65535))) static void initialize_fnv1a_seed(void) +LWAN_CONSTRUCTOR(65535) +static void initialize_fnv1a_seed(void) { uint8_t entropy[128]; diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index dd688b739..f2a018bdc 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -425,7 +425,7 @@ static int luaopen_log(lua_State *L) DEFINE_ARRAY_TYPE(lwan_lua_method_array, luaL_reg) static struct lwan_lua_method_array lua_methods; -__attribute__((constructor)) +LWAN_CONSTRUCTOR() __attribute__((no_sanitize_address)) static void register_lua_methods(void) { diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 2f1ce966c..891af4cb1 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -28,6 +28,12 @@ #define DEFAULT_BUFFER_SIZE 4096 #define DEFAULT_HEADERS_SIZE 2048 +#if defined(__APPLE__) +# define LWAN_CONSTRUCTOR(prio_) __attribute__((constructor)) +#else +# define LWAN_CONSTRUCTOR(prio_) __attribute__((constructor(prio_))) +#endif + struct lwan_request_parser_helper { struct lwan_value *buffer; /* The whole request buffer */ char *next_request; /* For pipelined requests */ diff --git a/src/lib/lwan-readahead.c b/src/lib/lwan-readahead.c index 4d0f68ec8..3379ee0a4 100644 --- a/src/lib/lwan-readahead.c +++ b/src/lib/lwan-readahead.c @@ -60,7 +60,8 @@ static pthread_t readahead_self; static long page_size = PAGE_SIZE; #ifdef _SC_PAGESIZE -__attribute__((constructor)) static void get_page_size(void) +LWAN_CONSTRUCTOR() +static void get_page_size(void) { long ps = sysconf(_SC_PAGESIZE); diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 46d576eb7..8aa6e2bcc 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1112,7 +1112,8 @@ get_temp_dir(void) return NULL; } -__attribute__((constructor)) static void initialize_temp_dir(void) +LWAN_CONSTRUCTOR() +static void initialize_temp_dir(void) { temp_dir = get_temp_dir(); } diff --git a/src/lib/lwan-status.c b/src/lib/lwan-status.c index b62aadf5d..5da2ac78f 100644 --- a/src/lib/lwan-status.c +++ b/src/lib/lwan-status.c @@ -243,7 +243,8 @@ void lwan_syslog_status_out( lwan_strbuf_free(&buf); } -__attribute__((constructor)) static void register_lwan_to_syslog(void) +LWAN_CONSTRUCTOR() +static void register_lwan_to_syslog(void) { openlog("lwan", LOG_NDELAY | LOG_PID | LOG_CONS, LOG_USER); } diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 361760371..a4e15291d 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -975,7 +975,8 @@ void lwan_main_loop(struct lwan *l) } #ifdef CLOCK_MONOTONIC_COARSE -__attribute__((constructor)) static void detect_fastest_monotonic_clock(void) +LWAN_CONSTRUCTOR() +static void detect_fastest_monotonic_clock(void) { struct timespec ts; diff --git a/src/samples/clock/blocks.c b/src/samples/clock/blocks.c index 156dfd2b1..ababb9b5a 100644 --- a/src/samples/clock/blocks.c +++ b/src/samples/clock/blocks.c @@ -12,6 +12,7 @@ #include #include +#include "lwan-private.h" #include "blocks.h" enum shape { @@ -175,7 +176,7 @@ static const struct fall *fall[] = { static int block_sizes[10]; -__attribute__((constructor)) void calculate_block_sizes(void) +LWAN_CONSTRUCTOR() void calculate_block_sizes(void) { for (int i = 0; i < 10; i++) { const struct fall *instr = fall[i]; diff --git a/src/samples/clock/main.c b/src/samples/clock/main.c index 5bf4795c1..63f93145c 100644 --- a/src/samples/clock/main.c +++ b/src/samples/clock/main.c @@ -21,7 +21,7 @@ #include #include -#include "lwan.h" +#include "lwan-private.h" #include "lwan-template.h" #include "lwan-mod-redirect.h" #include "gifenc.h" @@ -245,7 +245,7 @@ static const struct lwan_var_descriptor index_desc[] = { static struct lwan_tpl *index_tpl; -__attribute__((constructor)) static void initialize_template(void) +LWAN_CONSTRUCTOR() static void initialize_template(void) { static const char index[] = "\n" diff --git a/src/samples/clock/xdaliclock.c b/src/samples/clock/xdaliclock.c index 7f44b8999..47bf08a88 100644 --- a/src/samples/clock/xdaliclock.c +++ b/src/samples/clock/xdaliclock.c @@ -155,7 +155,7 @@ frame_from_pixmap(const unsigned char *bits, int width, int height) return frame; } -__attribute__((constructor)) static void initialize_numbers(void) +LWAN_CONSTRUCTOR() static void initialize_numbers(void) { const struct raw_number *raw = get_raw_numbers(); From 9f3736635ed85f10063123e2021f2ce33aabdd0b Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 28 Aug 2024 06:24:43 -0700 Subject: [PATCH 2383/2505] Fix build dependency of libucontext when building with ninja --- src/lib/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index 3d311b18f..8cdf4e281 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -159,7 +159,7 @@ if (LWAN_HAVE_LIBUCONTEXT) BUILD_COMMAND make libucontext.a FREESTANDING=yes INSTALL_COMMAND make install-static install-headers DESTDIR=${CMAKE_BINARY_DIR} FREESTANDING=yes - BUILD_BYPRODUCTS ${CMAKE_BINARY_DIR}/lib/libucontext.a + BUILD_BYPRODUCTS ${CMAKE_BINARY_DIR}/usr/lib/libucontext.a BUILD_ALWAYS OFF UPDATE_DISCONNECTED ON From cac0dd8b9a821fd201c3f7cb80a707c6bf67d119 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 29 Aug 2024 07:01:57 -0700 Subject: [PATCH 2384/2505] Support for static constructors with priorities Instead of relying on __attribute__((constructor)), use our own system that should work everywhere -- regardless of the C standard library, or the system supporting them. As a bonus, each constructor now gets a pointer to a Lwan struct, making it easier to install initialization hooks. --- src/bin/tools/bin2hex.c | 50 ++++++++++++---------------------- src/lib/hash.c | 3 +- src/lib/lwan-lua.c | 4 +-- src/lib/lwan-private.h | 20 ++++++++++---- src/lib/lwan-readahead.c | 3 +- src/lib/lwan-request.c | 3 +- src/lib/lwan-status.c | 3 +- src/lib/lwan.c | 38 ++++++++++++++++++++++++-- src/samples/clock/blocks.c | 2 +- src/samples/clock/main.c | 2 +- src/samples/clock/xdaliclock.c | 2 +- 11 files changed, 77 insertions(+), 53 deletions(-) diff --git a/src/bin/tools/bin2hex.c b/src/bin/tools/bin2hex.c index 2f780f627..3c84d23b0 100644 --- a/src/bin/tools/bin2hex.c +++ b/src/bin/tools/bin2hex.c @@ -28,14 +28,6 @@ #include "lwan-private.h" -static int constructor_attr_supported = 0; - -LWAN_CONSTRUCTOR() -static void initialize_constructor_attr_supported(void) -{ - constructor_attr_supported = 1; -} - static int bin2hex_mmap(const char *path, const char *identifier) { int fd = open(path, O_RDONLY | O_CLOEXEC); @@ -104,15 +96,12 @@ static int bin2hex(const char *path, const char *identifier) printf("\n/* Contents of %s available through %s_value */\n", path, identifier); - if (constructor_attr_supported) { - printf("#if defined(__GNUC__) || defined(__clang__)\n"); - r |= bin2hex_incbin(path, identifier); - printf("#else\n"); - r |= bin2hex_mmap(path, identifier); - printf("#endif\n\n"); - } else { - r |= bin2hex_mmap(path, identifier); - } + printf("#if defined(__GNUC__) || defined(__clang__)\n"); + r |= bin2hex_incbin(path, identifier); + printf("#else\n"); + r |= bin2hex_mmap(path, identifier); + printf("#endif\n\n"); + return r; } @@ -133,7 +122,7 @@ int main(int argc, char *argv[]) printf("/* Auto generated by %s, do not edit. */\n", argv[0]); printf("#pragma once\n\n"); - printf("#include \"lwan.h\"\n"); + printf("#include \"lwan-private.h\"\n"); for (arg = 1; arg < argc; arg += 2) { const char *path = argv[arg]; @@ -146,21 +135,18 @@ int main(int argc, char *argv[]) } } - if (constructor_attr_supported) { - printf("#if defined(__GNUC__) || defined(__clang__)\n"); - printf("__attribute__((constructor (101))) static void\n"); - printf("initialize_bin2hex_%016lx(void)\n", (uintptr_t)argv); - printf("{\n"); - for (arg = 1; arg < argc; arg += 2) { - const char *identifier = argv[arg + 1]; - - printf(" %s_value = (struct lwan_value) {.value = (char *)%s_start, " - ".len = (size_t)(%s_end - %s_start)};\n", - identifier, identifier, identifier, identifier); - } - printf("}\n"); - printf("#endif\n"); + printf("#if defined(__GNUC__) || defined(__clang__)\n"); + printf("LWAN_CONSTRUCTOR(bin2hex_%016lx, 0)\n", (uintptr_t)argv); + printf("{\n"); + for (arg = 1; arg < argc; arg += 2) { + const char *identifier = argv[arg + 1]; + + printf(" %s_value = (struct lwan_value) {.value = (char *)%s_start, " + ".len = (size_t)(%s_end - %s_start)};\n", + identifier, identifier, identifier, identifier); } + printf("}\n"); + printf("#endif\n"); return 0; } diff --git a/src/lib/hash.c b/src/lib/hash.c index f882eec98..0bf022a7d 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -208,8 +208,7 @@ static inline unsigned int hash_int64_crc32(const void *keyptr) #endif -LWAN_CONSTRUCTOR(65535) -static void initialize_fnv1a_seed(void) +LWAN_CONSTRUCTOR(fnv1a_seed, 65535) { uint8_t entropy[128]; diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index f2a018bdc..400a46544 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -425,9 +425,7 @@ static int luaopen_log(lua_State *L) DEFINE_ARRAY_TYPE(lwan_lua_method_array, luaL_reg) static struct lwan_lua_method_array lua_methods; -LWAN_CONSTRUCTOR() -__attribute__((no_sanitize_address)) -static void register_lua_methods(void) +LWAN_CONSTRUCTOR(register_lua_methods, 0) { const struct lwan_lua_method_info *info; luaL_reg *r; diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 891af4cb1..fa822010b 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -28,11 +28,21 @@ #define DEFAULT_BUFFER_SIZE 4096 #define DEFAULT_HEADERS_SIZE 2048 -#if defined(__APPLE__) -# define LWAN_CONSTRUCTOR(prio_) __attribute__((constructor)) -#else -# define LWAN_CONSTRUCTOR(prio_) __attribute__((constructor(prio_))) -#endif +struct lwan_constructor_callback_info { + void (*func)(struct lwan *); + int prio; +}; + +#define LWAN_CONSTRUCTOR(name_, prio_) \ + __attribute__((no_sanitize_address)) static void lwan_constructor_##name_( \ + struct lwan *l __attribute__((unused))); \ + static const struct lwan_constructor_callback_info __attribute__(( \ + used, section(LWAN_SECTION_NAME( \ + lwan_constructor)))) lwan_constructor_info_##name_ = { \ + .func = lwan_constructor_##name_, \ + .prio = (prio_), \ + }; \ + static ALWAYS_INLINE void lwan_constructor_##name_(struct lwan *l) struct lwan_request_parser_helper { struct lwan_value *buffer; /* The whole request buffer */ diff --git a/src/lib/lwan-readahead.c b/src/lib/lwan-readahead.c index 3379ee0a4..48e1e0ca0 100644 --- a/src/lib/lwan-readahead.c +++ b/src/lib/lwan-readahead.c @@ -60,8 +60,7 @@ static pthread_t readahead_self; static long page_size = PAGE_SIZE; #ifdef _SC_PAGESIZE -LWAN_CONSTRUCTOR() -static void get_page_size(void) +LWAN_CONSTRUCTOR(get_page_size, 0) { long ps = sysconf(_SC_PAGESIZE); diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 8aa6e2bcc..fc1fa3ba8 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1112,8 +1112,7 @@ get_temp_dir(void) return NULL; } -LWAN_CONSTRUCTOR() -static void initialize_temp_dir(void) +LWAN_CONSTRUCTOR(initialize_temp_dir, 0) { temp_dir = get_temp_dir(); } diff --git a/src/lib/lwan-status.c b/src/lib/lwan-status.c index 5da2ac78f..8115e9424 100644 --- a/src/lib/lwan-status.c +++ b/src/lib/lwan-status.c @@ -243,8 +243,7 @@ void lwan_syslog_status_out( lwan_strbuf_free(&buf); } -LWAN_CONSTRUCTOR() -static void register_lwan_to_syslog(void) +LWAN_CONSTRUCTOR(register_lwan_to_syslog, 0) { openlog("lwan", LOG_NDELAY | LOG_PID | LOG_CONS, LOG_USER); } diff --git a/src/lib/lwan.c b/src/lib/lwan.c index a4e15291d..8936553df 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -869,6 +869,39 @@ static char *dup_or_null(const char *s) return s ? strdup(s) : NULL; } +DEFINE_ARRAY_TYPE(constructor_array, struct lwan_constructor_callback_info) + +static int constructor_sort(const void *a, const void *b) +{ + const struct lwan_constructor_callback_info *ca = a; + const struct lwan_constructor_callback_info *cb = b; + return (ca->prio < cb->prio) - (ca->prio > cb->prio); +} + +__attribute__((no_sanitize_address)) static void +call_constructors(struct lwan *l) +{ + struct constructor_array constructors; + const struct lwan_constructor_callback_info *iter; + + constructor_array_init(&constructors); + LWAN_SECTION_FOREACH(lwan_constructor, iter) + { + struct lwan_constructor_callback_info *info = + constructor_array_append(&constructors); + if (!info) + lwan_status_critical("Could not append to constructor array"); + *info = *iter; + } + constructor_array_sort(&constructors, constructor_sort); + + LWAN_ARRAY_FOREACH (&constructors, iter) { + iter->func(l); + } + + constructor_array_reset(&constructors); +} + void lwan_init_with_config(struct lwan *l, const struct lwan_config *config) { /* Load defaults */ @@ -883,6 +916,8 @@ void lwan_init_with_config(struct lwan *l, const struct lwan_config *config) * their initialization. */ lwan_status_init(l); + call_constructors(l); + /* These will only print debugging messages. Debug messages are always * printed if we're on a debug build, so the quiet setting will be * respected. */ @@ -975,8 +1010,7 @@ void lwan_main_loop(struct lwan *l) } #ifdef CLOCK_MONOTONIC_COARSE -LWAN_CONSTRUCTOR() -static void detect_fastest_monotonic_clock(void) +LWAN_CONSTRUCTOR(detect_fastest_monotonic_clock, 0) { struct timespec ts; diff --git a/src/samples/clock/blocks.c b/src/samples/clock/blocks.c index ababb9b5a..1de1e5105 100644 --- a/src/samples/clock/blocks.c +++ b/src/samples/clock/blocks.c @@ -176,7 +176,7 @@ static const struct fall *fall[] = { static int block_sizes[10]; -LWAN_CONSTRUCTOR() void calculate_block_sizes(void) +LWAN_CONSTRUCTOR(calculate_block_sizes, 0) { for (int i = 0; i < 10; i++) { const struct fall *instr = fall[i]; diff --git a/src/samples/clock/main.c b/src/samples/clock/main.c index 63f93145c..3ef4df4b2 100644 --- a/src/samples/clock/main.c +++ b/src/samples/clock/main.c @@ -245,7 +245,7 @@ static const struct lwan_var_descriptor index_desc[] = { static struct lwan_tpl *index_tpl; -LWAN_CONSTRUCTOR() static void initialize_template(void) +LWAN_CONSTRUCTOR(initialize_template, 0) { static const char index[] = "\n" diff --git a/src/samples/clock/xdaliclock.c b/src/samples/clock/xdaliclock.c index 47bf08a88..31d2a25ca 100644 --- a/src/samples/clock/xdaliclock.c +++ b/src/samples/clock/xdaliclock.c @@ -155,7 +155,7 @@ frame_from_pixmap(const unsigned char *bits, int width, int height) return frame; } -LWAN_CONSTRUCTOR() static void initialize_numbers(void) +LWAN_CONSTRUCTOR(initialize_numbers, 0) { const struct raw_number *raw = get_raw_numbers(); From c0d3224a1e2d7e98b6a8001c8897a3081c98f682 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 5 Sep 2024 05:57:46 -0700 Subject: [PATCH 2385/2505] Document that both straitjacket/straightjacket are accepted --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6791710c4..d4dfc2c8e 100644 --- a/README.md +++ b/README.md @@ -352,8 +352,8 @@ Lwan can drop its privileges to a user in the system, and limit its filesystem view with a chroot. While not bulletproof, this provides a first layer of security in the case there's a bug in Lwan. -In order to use this feature, declare a `straitjacket` section, and set -some options. This requires Lwan to be executed as `root`. +In order to use this feature, declare a `straitjacket` (or `straightjacket`) +section, and set some options. This requires Lwan to be executed as `root`. Although this section can be written anywhere in the file (as long as it is a top level declaration), if any directories are open, due to From 5528ec7f90d0973277e460de870094b58e0a2927 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 21 Sep 2024 10:17:33 -0700 Subject: [PATCH 2386/2505] lwan_response_websocket_read() should return errno on recv() failure --- src/lib/lwan-websocket.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-websocket.c b/src/lib/lwan-websocket.c index e6ea64b98..abeb64a4b 100644 --- a/src/lib/lwan-websocket.c +++ b/src/lib/lwan-websocket.c @@ -335,7 +335,7 @@ int lwan_response_websocket_read_hint(struct lwan_request *request, uint16_t header; bool continuation = false; - if (!(request->conn->flags & CONN_IS_WEBSOCKET)) + if (UNLIKELY(!(request->conn->flags & CONN_IS_WEBSOCKET))) return ENOTCONN; lwan_strbuf_reset_trim(request->response.buffer, size_hint); @@ -345,9 +345,8 @@ int lwan_response_websocket_read_hint(struct lwan_request *request, ssize_t r = lwan_recv(request, &header, sizeof(header), MSG_DONTWAIT | MSG_NOSIGNAL); - if (r < 0) { - return (int)-r; - } + if (UNLIKELY(r < 0)) + return errno; header = htons(header); continuation = false; From 8bed4757ad5b0896bdbf13b754542f7c7556dc9c Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 21 Sep 2024 10:27:08 -0700 Subject: [PATCH 2387/2505] Fix formatting in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d4dfc2c8e..dea5d2c1f 100644 --- a/README.md +++ b/README.md @@ -275,7 +275,7 @@ constants { ``` The same syntax for default values specified above is valid here (e.g. -specifying `user_name` to be `${USER:nobody}` will set `${user_name} to +specifying `user_name` to be `${USER:nobody}` will set `${user_name}` to `nobody` if `${USER}` isn't set in the environment variable or isn't another constant.) From 5093a78bd794c60e162ccbe28d884c634dd36e5e Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 23 Sep 2024 09:35:00 -0700 Subject: [PATCH 2388/2505] Also assert CONN_ASYNC_AWAITV flag isn't set when spawning coro --- src/lib/lwan-thread.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 8319f0a34..44fa6d9cf 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -933,7 +933,7 @@ static ALWAYS_INLINE bool spawn_coro(struct lwan_connection *conn, #endif assert(!conn->coro); - assert(!(conn->flags & (CONN_ASYNC_AWAIT | CONN_HUNG_UP))); + assert(!(conn->flags & (CONN_ASYNC_AWAITV | CONN_ASYNC_AWAIT | CONN_HUNG_UP))); assert(!(conn->flags & CONN_LISTENER)); assert(t); assert((uintptr_t)t >= (uintptr_t)tq->lwan->thread.threads); From 56104408b4a0a45051f426fc79aa173390e222aa Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 12 Oct 2024 15:38:45 -0700 Subject: [PATCH 2389/2505] Add API to know if a cache entry was an existing item or a brand new one --- src/lib/lwan-cache.c | 12 ++++++++++-- src/lib/lwan-cache.h | 2 ++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-cache.c b/src/lib/lwan-cache.c index 4c07f6f66..cb7ec180a 100644 --- a/src/lib/lwan-cache.c +++ b/src/lib/lwan-cache.c @@ -37,6 +37,7 @@ enum { FLOATING = 1 << 0, TEMPORARY = 1 << 1, FREE_KEY_ON_DESTROY = 1 << 2, + EXISTING_ITEM = 1 << 3, /* Cache flags */ SHUTTING_DOWN = 1 << 0, @@ -190,6 +191,11 @@ void cache_destroy(struct cache *cache) free(cache); } +bool cache_entry_is_new(const struct cache_entry *entry) +{ + return !(entry->flags & EXISTING_ITEM); +} + struct cache_entry *cache_get_and_ref_entry_with_ctx(struct cache *cache, const void *key, void *create_ctx, int *error) @@ -209,12 +215,14 @@ struct cache_entry *cache_get_and_ref_entry_with_ctx(struct cache *cache, if (cache->flags & READ_ONLY) { entry = hash_find(cache->hash.table, key); + if (LIKELY(entry)) { + entry->flags |= EXISTING_ITEM; #ifndef NDEBUG - if (LIKELY(entry)) ATOMIC_INC(cache->stats.hits); - else + } else { ATOMIC_INC(cache->stats.misses); #endif + } return entry; } diff --git a/src/lib/lwan-cache.h b/src/lib/lwan-cache.h index 4956c73d2..169a469e1 100644 --- a/src/lib/lwan-cache.h +++ b/src/lib/lwan-cache.h @@ -63,3 +63,5 @@ struct cache_entry *cache_coro_get_and_ref_entry_with_ctx(struct cache *cache, void cache_entry_unref(struct cache *cache, struct cache_entry *entry); void cache_make_read_only(struct cache *cache); + +bool cache_entry_is_new(const struct cache_entry *entry); From 3b32adea27e92ece5cea72d6590d8f25a19e0dff Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 12 Oct 2024 15:39:19 -0700 Subject: [PATCH 2390/2505] Only create new paste entry if none existed with that key --- src/samples/pastebin/main.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/samples/pastebin/main.c b/src/samples/pastebin/main.c index 5074e3c85..3b677c4e4 100644 --- a/src/samples/pastebin/main.c +++ b/src/samples/pastebin/main.c @@ -84,6 +84,9 @@ static enum lwan_http_status post_paste(struct lwan_request *request, pastes, request->conn->coro, key, (void *)body); if (paste) { + if (!cache_entry_is_new(paste)) + continue; + const char *host_hdr = lwan_request_get_host(request); if (!host_hdr) From d2b882ea4295961b79f1b8e768c5cea4578aff53 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 24 Jan 2025 10:02:49 -0800 Subject: [PATCH 2391/2505] Increase buffer size for building HTTP response headers Fixes loading of smolsite.zip's main page --- src/lib/lwan-private.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index fa822010b..9b2a0523e 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -26,7 +26,7 @@ #define N_HEADER_START 64 #define DEFAULT_BUFFER_SIZE 4096 -#define DEFAULT_HEADERS_SIZE 2048 +#define DEFAULT_HEADERS_SIZE 4096 struct lwan_constructor_callback_info { void (*func)(struct lwan *); From e7697acb00b871688f1d90c5b4f494cbcb73d137 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 27 Jan 2025 16:50:59 -0800 Subject: [PATCH 2392/2505] Add FORTH implementation for a Forth Salon-compatible sample This FORTH implementation has been written from scratch to be compatible with the Forth Salon (https://forthsalon.appspot.com) website, in order to use it in conjunction with LED Matrix panels to create interactive art projects. It should be 100% compatible, although it hasn't been fully tested with a substantial corpus from FS. Minimal fuzzing has been performed and it's stable enough for a first version. No integration with the Lwan build system or with the rgb-led-matrix (https://github.com/hzeller/rpi-rgb-led-matrix) project has been written yet; those will come later. The idea is to have something similar to the Forth Salon website, but editing a haiku would make it show up in a LED matrix panel. --- src/samples/forthsalon/CMakeLists.txt | 8 + src/samples/forthsalon/forth.c | 1104 +++++++++++++++++++++++++ src/samples/forthsalon/main.c | 32 + 3 files changed, 1144 insertions(+) create mode 100644 src/samples/forthsalon/CMakeLists.txt create mode 100644 src/samples/forthsalon/forth.c create mode 100644 src/samples/forthsalon/main.c diff --git a/src/samples/forthsalon/CMakeLists.txt b/src/samples/forthsalon/CMakeLists.txt new file mode 100644 index 000000000..97acea732 --- /dev/null +++ b/src/samples/forthsalon/CMakeLists.txt @@ -0,0 +1,8 @@ +add_executable(forthsalon + main.c +) + +target_link_libraries(forthsalon + ${LWAN_COMMON_LIBS} + ${ADDITIONAL_LIBRARIES} +) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c new file mode 100644 index 000000000..40c2f3670 --- /dev/null +++ b/src/samples/forthsalon/forth.c @@ -0,0 +1,1104 @@ +/* + * lwan - web server + * Copyright (c) 2025 L. A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/* + * This is a FORTH dialect compatible with the Forth Salon[1] dialect, + * to be used as a pixel shader in art projects. + * [1] https://forthsalon.appspot.com + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hash.h" +#include "lwan-array.h" +#include "lwan-private.h" + +enum flags { + IS_INSIDE_COMMENT = 1 << 0, + IS_INSIDE_WORD_DEF = 1 << 1, +}; + +enum forth_opcode { + OP_CALL_BUILTIN, + OP_EVAL_CODE, + OP_NUMBER, + OP_JUMP_IF, + OP_JUMP, + OP_NOP, +}; + +struct forth_ctx; +struct forth_vars; +struct forth_code; + +struct forth_inst { + union { + double number; + struct forth_code *code; + bool (*callback)(struct forth_ctx *ctx, struct forth_vars *vars); + size_t pc; + }; + enum forth_opcode opcode; +}; + +DEFINE_ARRAY_TYPE(forth_code, struct forth_inst) + +struct forth_word { + union { + bool (*callback)(struct forth_ctx *ctx, struct forth_vars *vars); + struct forth_code code; + }; + bool is_builtin; + char name[]; +}; + +struct forth_ctx { + struct forth_word *defining_word; + struct forth_word *main; + + struct hash *words; + + struct { + double values[256]; + size_t pos; + } r_stack, d_stack; + + double memory[64]; + + enum flags flags; +}; + +struct forth_vars { + double x, y; + int t, dt; +}; + +#define PUSH_D(value_) \ + ({ \ + if (UNLIKELY(ctx->d_stack.pos >= N_ELEMENTS(ctx->d_stack.values))) \ + return false; \ + ctx->d_stack.values[ctx->d_stack.pos++] = (value_); \ + }) +#define POP_D(value_) \ + ({ \ + double v; \ + if (LIKELY(ctx->d_stack.pos > 0)) { \ + v = ctx->d_stack.values[--ctx->d_stack.pos]; \ + } else { \ + v = NAN; \ + } \ + v; \ + }) +#define PUSH_R(value_) \ + ({ \ + if (UNLIKELY(ctx->r_stack.pos >= N_ELEMENTS(ctx->r_stack.values))) \ + return false; \ + ctx->r_stack.values[ctx->r_stack.pos++] = (value_); \ + }) +#define POP_R(value_) \ + ({ \ + double v; \ + if (LIKELY(ctx->r_stack.pos > 0)) { \ + v = ctx->r_stack.values[--ctx->r_stack.pos]; \ + } else { \ + v = NAN; \ + } \ + v; \ + }) +#define LOAD(addr_) \ + ({ \ + size_t v = (size_t)(int32_t)(addr_); \ + if (v > N_ELEMENTS(ctx->memory)) \ + return false; \ + ctx->memory[v]; \ + }) +#define STORE(addr_, value_) \ + ({ \ + size_t v = (size_t)(int32_t)(addr_); \ + if (v > N_ELEMENTS(ctx->memory)) \ + return false; \ + ctx->memory[v] = (value_); \ + }) + +#if DUMP_CODE +static void dump_code(const struct forth_code *code) +{ + const struct forth_inst *inst; + size_t i = 0; + + printf("dumping code @ %p\n", code); + + LWAN_ARRAY_FOREACH (code, inst) { + printf("%08zu ", i); + i++; + + switch (inst->opcode) { + case OP_EVAL_CODE: + printf("eval code %p\n", inst->code); + break; + case OP_CALL_BUILTIN: + printf("call builtin %p\n", inst->callback); + break; + case OP_NUMBER: + printf("number %lf\n", inst->number); + break; + case OP_JUMP_IF: + printf("if [next %zu]\n", inst->pc); + break; + case OP_JUMP: + printf("jump to %zu\n", inst->pc); + break; + case OP_NOP: + printf("nop\n"); + } + } +} +#endif + +static bool eval_code(struct forth_ctx *ctx, + const struct forth_code *code, + struct forth_vars *vars, + int recursion_limit) +{ + const struct forth_inst *inst; + + if (recursion_limit == 0) { + lwan_status_error("recursion limit reached"); + return false; + } + +#if DUMP_CODE + dump_code(code); +#endif + + LWAN_ARRAY_FOREACH (code, inst) { + switch (inst->opcode) { + case OP_EVAL_CODE: + if (UNLIKELY(!eval_code(ctx, inst->code, vars, recursion_limit - 1))) + return false; + break; + case OP_CALL_BUILTIN: + if (UNLIKELY(!inst->callback(ctx, vars))) + return false; + break; + case OP_NUMBER: + PUSH_D(inst->number); + break; + case OP_JUMP_IF: + if (POP_D() == 0.0) + inst = forth_code_get_elem(code, inst->pc); + break; + case OP_JUMP: + inst = forth_code_get_elem(code, inst->pc); + break; + case OP_NOP: + break; + } + } + + return true; +} + +bool forth_run(struct forth_ctx *ctx, struct forth_vars *vars) +{ + return eval_code(ctx, &ctx->main->code, vars, 100); +} + +static struct forth_inst *new_inst(struct forth_ctx *ctx) +{ + /* FIXME: if last instruction is NOP, maybe we can reuse it? */ + + if (UNLIKELY(!ctx->defining_word)) + return NULL; + + return forth_code_append(&ctx->defining_word->code); +} + +static bool emit_word_call(struct forth_ctx *ctx, struct forth_word *word) +{ + struct forth_inst *inst = new_inst(ctx); + if (UNLIKELY(!inst)) + return false; + + if (word->is_builtin) { + *inst = (struct forth_inst){.callback = word->callback, + .opcode = OP_CALL_BUILTIN}; + } else { + *inst = + (struct forth_inst){.code = &word->code, .opcode = OP_EVAL_CODE}; + } + + return true; +} + +static bool emit_number(struct forth_ctx *ctx, double number) +{ + struct forth_inst *inst = new_inst(ctx); + if (UNLIKELY(!inst)) + return false; + + *inst = (struct forth_inst){.number = number, .opcode = OP_NUMBER}; + return true; +} + +static bool emit_jump_if(struct forth_ctx *ctx) +{ + struct forth_inst *inst = new_inst(ctx); + if (UNLIKELY(!inst)) + return false; + + *inst = (struct forth_inst){.opcode = OP_JUMP_IF}; + return true; +} + +static bool emit_jump(struct forth_ctx *ctx) +{ + struct forth_inst *inst = new_inst(ctx); + if (UNLIKELY(!inst)) + return false; + + *inst = (struct forth_inst){.opcode = OP_JUMP}; + return true; +} + +static bool emit_nop(struct forth_ctx *ctx) +{ + struct forth_inst *inst = new_inst(ctx); + if (UNLIKELY(!inst)) + return false; + + *inst = (struct forth_inst){.opcode = OP_NOP}; + return true; +} + +static const char* parse_single_line_comment(struct forth_ctx *ctx, + const char *code) +{ + while (*code && *code != '\n') + code++; + return code; +} + +static const char *parse_begin_parens_comment(struct forth_ctx *ctx, + const char *code) +{ + if (UNLIKELY(ctx->flags & IS_INSIDE_COMMENT)) + return NULL; + + ctx->flags |= IS_INSIDE_COMMENT; + return code; +} + +static const char *parse_begin_word_def(struct forth_ctx *ctx, const char *code) +{ + if (UNLIKELY(ctx->flags & IS_INSIDE_WORD_DEF)) + return NULL; + + ctx->flags |= IS_INSIDE_WORD_DEF; + ctx->defining_word = NULL; + return code; +} + +static const char *parse_end_word_def(struct forth_ctx *ctx, const char *code) +{ + if (UNLIKELY(!(ctx->flags & IS_INSIDE_WORD_DEF))) + return NULL; + + ctx->flags &= ~IS_INSIDE_WORD_DEF; + + if (UNLIKELY(!ctx->defining_word)) + return NULL; + + ctx->defining_word = ctx->main; + return code; +} + +static bool parse_number(const char *ptr, size_t len, double *number) +{ + char *endptr; + + errno = 0; + *number = strtod(strndupa(ptr, len), &endptr); + + if (errno != 0) + return false; + + if (*endptr != '\0') + return false; + + return true; +} + +static struct forth_word *new_word(struct forth_ctx *ctx, + const char *name, + size_t len, + bool (*callback)(struct forth_ctx *, + struct forth_vars *)) +{ + struct forth_word *word = malloc(sizeof(*word) + len + 1); + if (UNLIKELY(!word)) + return NULL; + + if (callback) { + word->is_builtin = true; + word->callback = callback; + } else { + word->is_builtin = false; + forth_code_init(&word->code); + } + + strncpy(word->name, name, len); + word->name[len] = '\0'; + + if (!hash_add(ctx->words, word->name, word)) + return word; + + free(word); + return NULL; +} + +static struct forth_word * +lookup_word(struct forth_ctx *ctx, const char *name, size_t len) +{ + return hash_find(ctx->words, strndupa(name, len)); +} + +static bool is_redefining_word(const struct forth_ctx *ctx, + const char *word, + const size_t word_len) +{ + if (UNLIKELY(!ctx->defining_word)) { + lwan_status_error("Can't redefine word \"%.*s\"", (int)word_len, + word); + return true; + } + + return false; +} + +static const char *found_word(struct forth_ctx *ctx, + const char *code, + const char *word, + size_t word_len) +{ + if (ctx->flags & IS_INSIDE_COMMENT) { + if (word_len == 1 && *word == ')') + ctx->flags &= ~IS_INSIDE_COMMENT; + return code; + } + + if (word_len == 1) { + if (UNLIKELY(is_redefining_word(ctx, word, word_len))) + return NULL; + + switch (*word) { + case '\\': + return parse_single_line_comment(ctx, code); + case ':': + return parse_begin_word_def(ctx, code); + case ';': + if (ctx->r_stack.pos) { + lwan_status_error("Unmatched if/then/else"); + return false; + } + + return parse_end_word_def(ctx, code); + case '(': + return parse_begin_parens_comment(ctx, code); + case ')': + lwan_status_error("Comment closed without opening"); + return NULL; /* handled above; can't reuse word for non-comment + purposes */ + } + } + + if (word_len == 2 && !strncmp(word, "if", 2)) { + if (UNLIKELY(is_redefining_word(ctx, word, word_len))) + return NULL; + + PUSH_R((int32_t)forth_code_len(&ctx->defining_word->code)); + + emit_jump_if(ctx); + + return code; + } + if (word_len == 4 && (!strncmp(word, "else", 4) || !strncmp(word, "then", 4))) { + if (UNLIKELY(is_redefining_word(ctx, word, word_len))) + return NULL; + + double v = POP_R(); + if (UNLIKELY(v != v)) { + lwan_status_error("Unbalanced if/else/then"); + return NULL; + } + + struct forth_inst *inst = + forth_code_get_elem(&ctx->defining_word->code, (int32_t)v); + + inst->pc = forth_code_len(&ctx->defining_word->code); + + if (*word == 'e') { + PUSH_R((int32_t)inst->pc); + emit_jump(ctx); + } else { + emit_nop(ctx); + } + + return code; + } + + double number; + if (parse_number(word, word_len, &number)) { + if (LIKELY(ctx->defining_word)) + return emit_number(ctx, number) ? code : NULL; + + lwan_status_error("Can't redefine number %lf", number); + return NULL; + } + + struct forth_word *w = lookup_word(ctx, word, word_len); + if (ctx->defining_word) { + if (LIKELY(w)) + return emit_word_call(ctx, w) ? code : NULL; + + lwan_status_error("Word \"%.*s\" not defined yet, can't call", + (int)word_len, word); + return NULL; /* word not defined yet */ + } + + if (LIKELY(w != NULL)) { /* redefining word not supported */ + lwan_status_error("Can't redefine word \"%.*s\"", (int)word_len, word); + return NULL; + } + + w = new_word(ctx, word, word_len, NULL); + if (UNLIKELY(!w)) { /* can't create new word */ + lwan_status_error("Can't create new word"); + return NULL; + } + + ctx->defining_word = w; + return code; +} + +bool forth_parse_string(struct forth_ctx *ctx, const char *code) +{ + assert(ctx); + + while (*code) { + while (isspace(*code)) + code++; + + const char *word_ptr = code; + + while (true) { + if (*code == '\0') { + if (word_ptr == code) + return true; + break; + } + if (isspace(*code)) + break; + if (!isprint(*code)) + return false; + code++; + } + + assert(code > word_ptr); + + code = found_word(ctx, code, word_ptr, (size_t)(code - word_ptr)); + if (!code) + return false; + + if (*code == '\0') + break; + + code++; + } + + return true; +} + +struct forth_builtin { + const char *name; + size_t name_len; + bool (*callback)(struct forth_ctx *, struct forth_vars *vars); + + void *padding; /* FIXME LWAN_SECTION_FOREACH needs this */ +}; + +#define BUILTIN_DETAIL(name_, id_, struct_id_) \ + static bool id_(struct forth_ctx *, struct forth_vars *); \ + static const struct forth_builtin __attribute__(( \ + used, section(LWAN_SECTION_NAME(forth_builtin)))) struct_id_ = { \ + .name = name_, \ + .name_len = sizeof(name_) - 1, \ + .callback = id_, \ + }; \ + static bool id_(struct forth_ctx *ctx, struct forth_vars *vars) + +#define BUILTIN(name_) BUILTIN_DETAIL(name_, LWAN_TMP_ID, LWAN_TMP_ID) + +BUILTIN("x") +{ + PUSH_D(vars->x); + return true; +} +BUILTIN("y") +{ + PUSH_D(vars->y); + return true; +} +BUILTIN("t") +{ + PUSH_D(vars->t); + return true; +} +BUILTIN("dt") +{ + PUSH_D(vars->dt); + return true; +} + +BUILTIN("mx") +{ + /* stub */ + PUSH_D(0.0); + return true; +} + +BUILTIN("my") +{ + /* stub */ + PUSH_D(0.0); + return true; +} + +BUILTIN("button") +{ + /* stub */ + POP_D(); + PUSH_D(0.0); + return true; +} + +BUILTIN("buttons") +{ + /* stub */ + PUSH_D(0.0); + return true; +} + +BUILTIN("audio") +{ + /* stub */ + POP_D(); + return true; +} + +BUILTIN("sample") +{ + /* stub */ + POP_D(); + POP_D(); + PUSH_D(0); + PUSH_D(0); + PUSH_D(0); + return true; +} + +BUILTIN("bwsample") +{ + /* stub */ + POP_D(); + POP_D(); + PUSH_D(0); + return true; +} + +BUILTIN("push") +{ + PUSH_R(POP_D()); + return true; +} +BUILTIN("pop") +{ + PUSH_D(POP_R()); + return true; +} + +BUILTIN(">r") +{ + PUSH_R(POP_D()); + return true; +} + +BUILTIN("r>") +{ + PUSH_D(POP_R()); + return true; +} + +BUILTIN("r@") +{ + double v = POP_R(); + PUSH_R(v); + PUSH_D(v); + return true; +} + +BUILTIN("@") +{ + double slot = POP_D(); + PUSH_D(LOAD(slot)); + return true; +} + +BUILTIN("!") +{ + double v1 = POP_D(); + double v2 = POP_D(); + STORE(v2, v1); + return true; +} + +BUILTIN("dup") +{ + double v = POP_D(); + PUSH_D(v); + PUSH_D(v); + return true; +} + +BUILTIN("over") +{ + double v1 = POP_D(); + double v2 = POP_D(); + PUSH_D(v2); + PUSH_D(v1); + PUSH_D(v2); + return true; +} + +BUILTIN("2dup") +{ + double v1 = POP_D(); + double v2 = POP_D(); + PUSH_D(v2); + PUSH_D(v1); + PUSH_D(v2); + PUSH_D(v1); + return true; +} + +BUILTIN("z+") +{ + double v1 = POP_D(); + double v2 = POP_D(); + double v3 = POP_D(); + double v4 = POP_D(); + PUSH_D(v2 + v4); + PUSH_D(v1 + v3); + return true; +} + +BUILTIN("z*") +{ + double v1 = POP_D(); + double v2 = POP_D(); + double v3 = POP_D(); + double v4 = POP_D(); + PUSH_D(v4 * v2 - v3 * v1); + PUSH_D(v4 * v1 + v3 * v2); + return true; +} + +BUILTIN("drop") +{ + POP_D(); + return true; +} + +BUILTIN("swap") +{ + double v1 = POP_D(); + double v2 = POP_D(); + PUSH_D(v1); + PUSH_D(v2); + return true; +} + +BUILTIN("rot") +{ + double v1 = POP_D(); + double v2 = POP_D(); + double v3 = POP_D(); + PUSH_D(v2); + PUSH_D(v1); + PUSH_D(v3); + return true; +} + +BUILTIN("-rot") +{ + double v1 = POP_D(); + double v2 = POP_D(); + double v3 = POP_D(); + PUSH_D(v1); + PUSH_D(v3); + PUSH_D(v2); + return true; +} + +BUILTIN("=") +{ + double v1 = POP_D(); + double v2 = POP_D(); + PUSH_D(v1 == v2 ? 1.0 : 0.0); + return true; +} + +BUILTIN("<>") +{ + double v1 = POP_D(); + double v2 = POP_D(); + PUSH_D(v1 != v2 ? 1.0 : 0.0); + return true; +} + +BUILTIN(">") +{ + double v1 = POP_D(); + double v2 = POP_D(); + PUSH_D(v1 > v2 ? 1.0 : 0.0); + return true; +} + +BUILTIN("<") +{ + double v1 = POP_D(); + double v2 = POP_D(); + PUSH_D(v1 < v2 ? 1.0 : 0.0); + return true; +} + +BUILTIN(">=") +{ + double v1 = POP_D(); + double v2 = POP_D(); + PUSH_D(v1 >= v2 ? 1.0 : 0.0); + return true; +} + +BUILTIN("<=") +{ + double v1 = POP_D(); + double v2 = POP_D(); + PUSH_D(v1 <= v2 ? 1.0 : 0.0); + return true; +} + +BUILTIN("+") +{ + PUSH_D(POP_D() + POP_D()); + return true; +} + +BUILTIN("*") +{ + PUSH_D(POP_D() * POP_D()); + return true; +} + +BUILTIN("-") +{ + double v = POP_D(); + PUSH_D(POP_D() - v); + return true; +} + +BUILTIN("/") +{ + double v = POP_D(); + if (v == 0.0) + PUSH_D(INFINITY); + else + PUSH_D(POP_D() / v); + + return true; +} + +BUILTIN("mod") +{ + double v = POP_D(); + PUSH_D(fmod(POP_D(), v)); + return true; +} + +BUILTIN("pow") +{ + double v = POP_D(); + PUSH_D(pow(fabs(POP_D()), v)); + return true; +} + +BUILTIN("**") +{ + double v = POP_D(); + PUSH_D(pow(fabs(POP_D()), v)); + return true; +} + +BUILTIN("atan2") +{ + double v = POP_D(); + PUSH_D(atan2(POP_D(), v)); + return true; +} + +BUILTIN("and") +{ + double v = POP_D(); + PUSH_D((POP_D() != 0.0 && v != 0.0) ? 1.0 : 0.0); + return true; +} + +BUILTIN("or") +{ + double v = POP_D(); + PUSH_D((POP_D() != 0.0 || v != 0.0) ? 1.0 : 0.0); + return true; +} + +BUILTIN("not") +{ + PUSH_D(POP_D() != 0.0 ? 0.0 : 1.0); + return true; +} + +BUILTIN("min") +{ + double v1 = POP_D(); + double v2 = POP_D(); + PUSH_D(v1 > v2 ? v2 : v1); + return true; +} + +BUILTIN("max") +{ + double v1 = POP_D(); + double v2 = POP_D(); + PUSH_D(v1 > v2 ? v1 : v2); + return true; +} + +BUILTIN("negate") +{ + PUSH_D(-POP_D()); + return true; +} + +BUILTIN("sin") +{ + PUSH_D(sin(POP_D())); + return true; +} + +BUILTIN("cos") +{ + PUSH_D(cos(POP_D())); + return true; +} + +BUILTIN("tan") +{ + PUSH_D(tan(POP_D())); + return true; +} + +BUILTIN("log") +{ + PUSH_D(log(fabs(POP_D()))); + return true; +} + +BUILTIN("exp") +{ + PUSH_D(log(POP_D())); + return true; +} + +BUILTIN("sqrt") +{ + PUSH_D(sqrt(fabs(POP_D()))); + return true; +} + +BUILTIN("floor") +{ + PUSH_D(floor(POP_D())); + return true; +} + +BUILTIN("ceil") +{ + PUSH_D(ceil(POP_D())); + return true; +} + +BUILTIN("abs") +{ + PUSH_D(fabs(POP_D())); + return true; +} + +BUILTIN("pi") +{ + PUSH_D(M_PI); + return true; +} + +BUILTIN("random") +{ + PUSH_D(drand48()); + return true; +} + +__attribute__((no_sanitize_address)) static void +register_builtins(struct forth_ctx *ctx) +{ + const struct forth_builtin *iter; + + LWAN_SECTION_FOREACH(forth_builtin, iter) { + if (!new_word(ctx, iter->name, iter->name_len, iter->callback)) { + lwan_status_critical("could not register forth word: %s", + iter->name); + } + } +} + +static void word_free(void *ptr) +{ + struct forth_word *word = ptr; + + if (!word->is_builtin) + forth_code_reset(&word->code); + free(word); +} + +struct forth_ctx *forth_new(void) +{ + struct forth_ctx *ctx = malloc(sizeof(*ctx)); + + if (!ctx) + return NULL; + + ctx->flags = 0; + + ctx->words = hash_str_new(NULL, word_free); + if (!ctx->words) { + free(ctx); + return NULL; + } + + struct forth_word *word = new_word(ctx, " ", 1, NULL); + if (!word) { + free(ctx); + return NULL; + } + + ctx->main = word; + ctx->defining_word = word; + + ctx->r_stack.pos = 0; + ctx->d_stack.pos = 0; + + register_builtins(ctx); + + return ctx; +} + +void forth_free(struct forth_ctx *ctx) +{ + if (!ctx) + return; + + hash_unref(ctx->words); + free(ctx); +} + +#if defined(FUZZ_TEST) +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + struct forth_ctx *ctx = forth_new(); + if (!ctx) + return 1; + + char *input = strndup((const char *)data, size); + if (!input) { + forth_free(ctx); + return 1; + } + + if (!forth_parse_string(ctx, input)) { + forth_free(ctx); + free(input); + return 1; + } + + free(input); + + struct forth_vars vars = {.x = 1, .y = 0}; + forth_run(ctx, &vars); + + forth_free(ctx); + + return 0; +} +#elif defined(MAIN) +int main(int argc, char *argv[]) +{ + struct forth_ctx *ctx = forth_new(); + if (!ctx) + return 1; + + if (!forth_parse_string(ctx, ": nice 60 5 4 + + ; : juanita 400 10 5 5 + + + ; x if nice else juanita then 2 * 4 / 2 *")) { + lwan_status_critical("could not parse forth program"); + forth_free(ctx); + return 1; + } + + struct forth_vars vars = {.x = 1, .y = 0}; + if (forth_run(ctx, &vars)) { + lwan_status_debug("top of d-stack: %lf", POP_D()); + } + + forth_free(ctx); + + return 0; +} +#endif diff --git a/src/samples/forthsalon/main.c b/src/samples/forthsalon/main.c new file mode 100644 index 000000000..7a1117cad --- /dev/null +++ b/src/samples/forthsalon/main.c @@ -0,0 +1,32 @@ +/* + * lwan - web server + * Copyright (c) 2025 L. A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "lwan.h" + +LWAN_HANDLER_ROUTE(hello_world, "/") +{ + static const char message[] = "Hello, World!"; + + response->mime_type = "text/plain"; + lwan_strbuf_set_static(response->buffer, message, sizeof(message) - 1); + + return HTTP_OK; +} + +int main(void) { return lwan_main(); } From 48ff78ee1b4a6b62abf0bf5aa595315c969be7ab Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 27 Jan 2025 18:11:08 -0800 Subject: [PATCH 2393/2505] Implement if/else/then as compiler-mode words This cleans up found_word() quite a bit, by moving the stuff to handle conditionals to words that can be called during compilation time, bringing the implementation closer to an actual FORTH. --- src/samples/forthsalon/forth.c | 102 +++++++++++++++++++-------------- 1 file changed, 59 insertions(+), 43 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 40c2f3670..5a512c777 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -73,6 +73,7 @@ struct forth_word { struct forth_code code; }; bool is_builtin; + bool is_compiler; char name[]; }; @@ -357,7 +358,8 @@ static struct forth_word *new_word(struct forth_ctx *ctx, const char *name, size_t len, bool (*callback)(struct forth_ctx *, - struct forth_vars *)) + struct forth_vars *), + bool compiler) { struct forth_word *word = malloc(sizeof(*word) + len + 1); if (UNLIKELY(!word)) @@ -371,6 +373,8 @@ static struct forth_word *new_word(struct forth_ctx *ctx, forth_code_init(&word->code); } + word->is_compiler = compiler; + strncpy(word->name, name, len); word->name[len] = '\0'; @@ -436,41 +440,6 @@ static const char *found_word(struct forth_ctx *ctx, } } - if (word_len == 2 && !strncmp(word, "if", 2)) { - if (UNLIKELY(is_redefining_word(ctx, word, word_len))) - return NULL; - - PUSH_R((int32_t)forth_code_len(&ctx->defining_word->code)); - - emit_jump_if(ctx); - - return code; - } - if (word_len == 4 && (!strncmp(word, "else", 4) || !strncmp(word, "then", 4))) { - if (UNLIKELY(is_redefining_word(ctx, word, word_len))) - return NULL; - - double v = POP_R(); - if (UNLIKELY(v != v)) { - lwan_status_error("Unbalanced if/else/then"); - return NULL; - } - - struct forth_inst *inst = - forth_code_get_elem(&ctx->defining_word->code, (int32_t)v); - - inst->pc = forth_code_len(&ctx->defining_word->code); - - if (*word == 'e') { - PUSH_R((int32_t)inst->pc); - emit_jump(ctx); - } else { - emit_nop(ctx); - } - - return code; - } - double number; if (parse_number(word, word_len, &number)) { if (LIKELY(ctx->defining_word)) @@ -482,8 +451,10 @@ static const char *found_word(struct forth_ctx *ctx, struct forth_word *w = lookup_word(ctx, word, word_len); if (ctx->defining_word) { - if (LIKELY(w)) - return emit_word_call(ctx, w) ? code : NULL; + if (LIKELY(w)) { + bool success = w->is_compiler ? w->callback(ctx, NULL) : emit_word_call(ctx, w); + return success ? code : NULL; + } lwan_status_error("Word \"%.*s\" not defined yet, can't call", (int)word_len, word); @@ -495,7 +466,7 @@ static const char *found_word(struct forth_ctx *ctx, return NULL; } - w = new_word(ctx, word, word_len, NULL); + w = new_word(ctx, word, word_len, NULL, false); if (UNLIKELY(!w)) { /* can't create new word */ lwan_status_error("Can't create new word"); return NULL; @@ -547,21 +518,66 @@ struct forth_builtin { const char *name; size_t name_len; bool (*callback)(struct forth_ctx *, struct forth_vars *vars); + bool compiler; void *padding; /* FIXME LWAN_SECTION_FOREACH needs this */ }; -#define BUILTIN_DETAIL(name_, id_, struct_id_) \ +#define BUILTIN_DETAIL(name_, id_, struct_id_, compiler_) \ static bool id_(struct forth_ctx *, struct forth_vars *); \ static const struct forth_builtin __attribute__(( \ used, section(LWAN_SECTION_NAME(forth_builtin)))) struct_id_ = { \ .name = name_, \ .name_len = sizeof(name_) - 1, \ .callback = id_, \ + .compiler = compiler_, \ }; \ static bool id_(struct forth_ctx *ctx, struct forth_vars *vars) -#define BUILTIN(name_) BUILTIN_DETAIL(name_, LWAN_TMP_ID, LWAN_TMP_ID) +#define BUILTIN(name_) BUILTIN_DETAIL(name_, LWAN_TMP_ID, LWAN_TMP_ID, false) +#define BUILTIN_COMPILER(name_) BUILTIN_DETAIL(name_, LWAN_TMP_ID, LWAN_TMP_ID, true) + +BUILTIN_COMPILER("if") +{ + if (UNLIKELY(is_redefining_word(ctx, "if", 4))) + return false; + + PUSH_R((int32_t)forth_code_len(&ctx->defining_word->code)); + + emit_jump_if(ctx); + + return true; +} + +static bool builtin_else_then(struct forth_ctx *ctx, struct forth_vars *vars, bool is_then) +{ + if (UNLIKELY(is_redefining_word(ctx, is_then ? "then" : "else", 4))) + return false; + + double v = POP_R(); + if (UNLIKELY(v != v)) { + lwan_status_error("Unbalanced if/else/then"); + return false; + } + + struct forth_inst *inst = + forth_code_get_elem(&ctx->defining_word->code, (int32_t)v); + + inst->pc = forth_code_len(&ctx->defining_word->code); + + if (is_then) { + emit_nop(ctx); + } else { + PUSH_R((int32_t)inst->pc); + emit_jump(ctx); + } + + return true; +} + +BUILTIN_COMPILER("else") { return builtin_else_then(ctx, vars, false); } + +BUILTIN_COMPILER("then") { return builtin_else_then(ctx, vars, true); } BUILTIN("x") { @@ -994,7 +1010,7 @@ register_builtins(struct forth_ctx *ctx) const struct forth_builtin *iter; LWAN_SECTION_FOREACH(forth_builtin, iter) { - if (!new_word(ctx, iter->name, iter->name_len, iter->callback)) { + if (!new_word(ctx, iter->name, iter->name_len, iter->callback, iter->compiler)) { lwan_status_critical("could not register forth word: %s", iter->name); } @@ -1025,7 +1041,7 @@ struct forth_ctx *forth_new(void) return NULL; } - struct forth_word *word = new_word(ctx, " ", 1, NULL); + struct forth_word *word = new_word(ctx, " ", 1, NULL, false); if (!word) { free(ctx); return NULL; From 4e7ef06eb6f8ae4dad31167e64d8c3810bf2ddfc Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 27 Jan 2025 21:43:40 -0800 Subject: [PATCH 2394/2505] Use compiler builtins for 1-letter words too --- src/samples/forthsalon/forth.c | 204 ++++++++++++++++----------------- 1 file changed, 96 insertions(+), 108 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 5a512c777..d6ad32efe 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -70,6 +70,7 @@ DEFINE_ARRAY_TYPE(forth_code, struct forth_inst) struct forth_word { union { bool (*callback)(struct forth_ctx *ctx, struct forth_vars *vars); + const char *(*callback_compiler)(struct forth_ctx *ctx, const char *code); struct forth_code code; }; bool is_builtin; @@ -296,48 +297,6 @@ static bool emit_nop(struct forth_ctx *ctx) return true; } -static const char* parse_single_line_comment(struct forth_ctx *ctx, - const char *code) -{ - while (*code && *code != '\n') - code++; - return code; -} - -static const char *parse_begin_parens_comment(struct forth_ctx *ctx, - const char *code) -{ - if (UNLIKELY(ctx->flags & IS_INSIDE_COMMENT)) - return NULL; - - ctx->flags |= IS_INSIDE_COMMENT; - return code; -} - -static const char *parse_begin_word_def(struct forth_ctx *ctx, const char *code) -{ - if (UNLIKELY(ctx->flags & IS_INSIDE_WORD_DEF)) - return NULL; - - ctx->flags |= IS_INSIDE_WORD_DEF; - ctx->defining_word = NULL; - return code; -} - -static const char *parse_end_word_def(struct forth_ctx *ctx, const char *code) -{ - if (UNLIKELY(!(ctx->flags & IS_INSIDE_WORD_DEF))) - return NULL; - - ctx->flags &= ~IS_INSIDE_WORD_DEF; - - if (UNLIKELY(!ctx->defining_word)) - return NULL; - - ctx->defining_word = ctx->main; - return code; -} - static bool parse_number(const char *ptr, size_t len, double *number) { char *endptr; @@ -357,8 +316,7 @@ static bool parse_number(const char *ptr, size_t len, double *number) static struct forth_word *new_word(struct forth_ctx *ctx, const char *name, size_t len, - bool (*callback)(struct forth_ctx *, - struct forth_vars *), + void *callback, bool compiler) { struct forth_word *word = malloc(sizeof(*word) + len + 1); @@ -367,7 +325,11 @@ static struct forth_word *new_word(struct forth_ctx *ctx, if (callback) { word->is_builtin = true; - word->callback = callback; + if (compiler) { + word->callback_compiler = callback; + } else { + word->callback = callback; + } } else { word->is_builtin = false; forth_code_init(&word->code); @@ -391,19 +353,6 @@ lookup_word(struct forth_ctx *ctx, const char *name, size_t len) return hash_find(ctx->words, strndupa(name, len)); } -static bool is_redefining_word(const struct forth_ctx *ctx, - const char *word, - const size_t word_len) -{ - if (UNLIKELY(!ctx->defining_word)) { - lwan_status_error("Can't redefine word \"%.*s\"", (int)word_len, - word); - return true; - } - - return false; -} - static const char *found_word(struct forth_ctx *ctx, const char *code, const char *word, @@ -415,31 +364,6 @@ static const char *found_word(struct forth_ctx *ctx, return code; } - if (word_len == 1) { - if (UNLIKELY(is_redefining_word(ctx, word, word_len))) - return NULL; - - switch (*word) { - case '\\': - return parse_single_line_comment(ctx, code); - case ':': - return parse_begin_word_def(ctx, code); - case ';': - if (ctx->r_stack.pos) { - lwan_status_error("Unmatched if/then/else"); - return false; - } - - return parse_end_word_def(ctx, code); - case '(': - return parse_begin_parens_comment(ctx, code); - case ')': - lwan_status_error("Comment closed without opening"); - return NULL; /* handled above; can't reuse word for non-comment - purposes */ - } - } - double number; if (parse_number(word, word_len, &number)) { if (LIKELY(ctx->defining_word)) @@ -452,8 +376,9 @@ static const char *found_word(struct forth_ctx *ctx, struct forth_word *w = lookup_word(ctx, word, word_len); if (ctx->defining_word) { if (LIKELY(w)) { - bool success = w->is_compiler ? w->callback(ctx, NULL) : emit_word_call(ctx, w); - return success ? code : NULL; + if (w->is_compiler) + return w->callback_compiler(ctx, code); + return emit_word_call(ctx, w) ? code : NULL; } lwan_status_error("Word \"%.*s\" not defined yet, can't call", @@ -499,7 +424,6 @@ bool forth_parse_string(struct forth_ctx *ctx, const char *code) code++; } - assert(code > word_ptr); code = found_word(ctx, code, word_ptr, (size_t)(code - word_ptr)); if (!code) @@ -517,47 +441,105 @@ bool forth_parse_string(struct forth_ctx *ctx, const char *code) struct forth_builtin { const char *name; size_t name_len; - bool (*callback)(struct forth_ctx *, struct forth_vars *vars); + union { + bool (*callback)(struct forth_ctx *, struct forth_vars *vars); + const char *(*callback_compiler)(struct forth_ctx *, const char *); + }; bool compiler; - - void *padding; /* FIXME LWAN_SECTION_FOREACH needs this */ }; -#define BUILTIN_DETAIL(name_, id_, struct_id_, compiler_) \ +#define BUILTIN_DETAIL(name_, id_, struct_id_) \ static bool id_(struct forth_ctx *, struct forth_vars *); \ - static const struct forth_builtin __attribute__(( \ - used, section(LWAN_SECTION_NAME(forth_builtin)))) struct_id_ = { \ + static const struct forth_builtin __attribute__((used)) \ + __attribute__((section(LWAN_SECTION_NAME(forth_builtin)))) \ + __attribute__((aligned(8))) struct_id_ = { \ .name = name_, \ .name_len = sizeof(name_) - 1, \ .callback = id_, \ - .compiler = compiler_, \ }; \ static bool id_(struct forth_ctx *ctx, struct forth_vars *vars) -#define BUILTIN(name_) BUILTIN_DETAIL(name_, LWAN_TMP_ID, LWAN_TMP_ID, false) -#define BUILTIN_COMPILER(name_) BUILTIN_DETAIL(name_, LWAN_TMP_ID, LWAN_TMP_ID, true) +#define BUILTIN_COMPILER_DETAIL(name_, id_, struct_id_) \ + static const char *id_(struct forth_ctx *, const char *); \ + static const struct forth_builtin __attribute__((used)) \ + __attribute__((section(LWAN_SECTION_NAME(forth_compiler_builtin)))) \ + __attribute__((aligned(8))) struct_id_ = { \ + .name = name_, \ + .name_len = sizeof(name_) - 1, \ + .callback_compiler = id_, \ + }; \ + static const char *id_(struct forth_ctx *ctx, const char *code) -BUILTIN_COMPILER("if") +#define BUILTIN(name_) BUILTIN_DETAIL(name_, LWAN_TMP_ID, LWAN_TMP_ID) +#define BUILTIN_COMPILER(name_) BUILTIN_COMPILER_DETAIL(name_, LWAN_TMP_ID, LWAN_TMP_ID) + +BUILTIN_COMPILER("\\") { - if (UNLIKELY(is_redefining_word(ctx, "if", 4))) - return false; + while (*code && *code != '\n') + code++; + return code; +} + +BUILTIN_COMPILER(":") +{ + if (UNLIKELY(ctx->flags & IS_INSIDE_WORD_DEF)) + return NULL; + + ctx->flags |= IS_INSIDE_WORD_DEF; + ctx->defining_word = NULL; + return code; +} +BUILTIN_COMPILER(";") +{ + if (ctx->r_stack.pos) { + lwan_status_error("Unmatched if/then/else"); + return NULL; + } + + if (UNLIKELY(!(ctx->flags & IS_INSIDE_WORD_DEF))) + return NULL; + + ctx->flags &= ~IS_INSIDE_WORD_DEF; + + if (UNLIKELY(!ctx->defining_word)) + return NULL; + + ctx->defining_word = ctx->main; + return code; +} + +BUILTIN_COMPILER("(") +{ + if (UNLIKELY(ctx->flags & IS_INSIDE_COMMENT)) + return NULL; + + ctx->flags |= IS_INSIDE_COMMENT; + return code; +} + +BUILTIN_COMPILER(")") +{ + lwan_status_error("Comment closed without opening"); + return NULL; /* handled above; can't reuse word for non-comment + purposes */ +} + +BUILTIN_COMPILER("if") +{ PUSH_R((int32_t)forth_code_len(&ctx->defining_word->code)); emit_jump_if(ctx); - return true; + return code; } -static bool builtin_else_then(struct forth_ctx *ctx, struct forth_vars *vars, bool is_then) +static const char* builtin_else_then(struct forth_ctx *ctx, const char *code, bool is_then) { - if (UNLIKELY(is_redefining_word(ctx, is_then ? "then" : "else", 4))) - return false; - double v = POP_R(); if (UNLIKELY(v != v)) { lwan_status_error("Unbalanced if/else/then"); - return false; + return NULL; } struct forth_inst *inst = @@ -572,12 +554,12 @@ static bool builtin_else_then(struct forth_ctx *ctx, struct forth_vars *vars, bo emit_jump(ctx); } - return true; + return code; } -BUILTIN_COMPILER("else") { return builtin_else_then(ctx, vars, false); } +BUILTIN_COMPILER("else") { return builtin_else_then(ctx, code, false); } -BUILTIN_COMPILER("then") { return builtin_else_then(ctx, vars, true); } +BUILTIN_COMPILER("then") { return builtin_else_then(ctx, code, true); } BUILTIN("x") { @@ -1010,7 +992,13 @@ register_builtins(struct forth_ctx *ctx) const struct forth_builtin *iter; LWAN_SECTION_FOREACH(forth_builtin, iter) { - if (!new_word(ctx, iter->name, iter->name_len, iter->callback, iter->compiler)) { + if (!new_word(ctx, iter->name, iter->name_len, iter->callback, false)) { + lwan_status_critical("could not register forth word: %s", + iter->name); + } + } + LWAN_SECTION_FOREACH(forth_compiler_builtin, iter) { + if (!new_word(ctx, iter->name, iter->name_len, iter->callback_compiler, true)) { lwan_status_critical("could not register forth word: %s", iter->name); } From e6702b8ae252d313c99b9cdaa408dc4383b3c0ac Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 27 Jan 2025 21:45:45 -0800 Subject: [PATCH 2395/2505] Assert that no compile-time word will be used during runtime --- src/samples/forthsalon/forth.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index d6ad32efe..41c4656b6 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -242,6 +242,8 @@ static struct forth_inst *new_inst(struct forth_ctx *ctx) static bool emit_word_call(struct forth_ctx *ctx, struct forth_word *word) { + assert(!word->is_compiler); + struct forth_inst *inst = new_inst(ctx); if (UNLIKELY(!inst)) return false; From c5f3ea21d625d21813f862e8e9d44052608541e9 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 27 Jan 2025 21:55:56 -0800 Subject: [PATCH 2396/2505] Simplify parsing of comments --- src/samples/forthsalon/forth.c | 44 ++++++++++++---------------------- 1 file changed, 15 insertions(+), 29 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 41c4656b6..70165051b 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -37,11 +37,6 @@ #include "lwan-array.h" #include "lwan-private.h" -enum flags { - IS_INSIDE_COMMENT = 1 << 0, - IS_INSIDE_WORD_DEF = 1 << 1, -}; - enum forth_opcode { OP_CALL_BUILTIN, OP_EVAL_CODE, @@ -91,7 +86,7 @@ struct forth_ctx { double memory[64]; - enum flags flags; + bool is_inside_word_def; }; struct forth_vars { @@ -360,12 +355,6 @@ static const char *found_word(struct forth_ctx *ctx, const char *word, size_t word_len) { - if (ctx->flags & IS_INSIDE_COMMENT) { - if (word_len == 1 && *word == ')') - ctx->flags &= ~IS_INSIDE_COMMENT; - return code; - } - double number; if (parse_number(word, word_len, &number)) { if (LIKELY(ctx->defining_word)) @@ -484,10 +473,12 @@ BUILTIN_COMPILER("\\") BUILTIN_COMPILER(":") { - if (UNLIKELY(ctx->flags & IS_INSIDE_WORD_DEF)) + if (UNLIKELY(ctx->is_inside_word_def)) { + lwan_status_error("Already defining word"); return NULL; + } - ctx->flags |= IS_INSIDE_WORD_DEF; + ctx->is_inside_word_def = true; ctx->defining_word = NULL; return code; } @@ -499,13 +490,17 @@ BUILTIN_COMPILER(";") return NULL; } - if (UNLIKELY(!(ctx->flags & IS_INSIDE_WORD_DEF))) + if (UNLIKELY(!ctx->is_inside_word_def)) { + lwan_status_error("Ending word without defining one"); return NULL; + } - ctx->flags &= ~IS_INSIDE_WORD_DEF; + ctx->is_inside_word_def = false; - if (UNLIKELY(!ctx->defining_word)) + if (UNLIKELY(!ctx->defining_word)) { + lwan_status_error("No word provided"); return NULL; + } ctx->defining_word = ctx->main; return code; @@ -513,20 +508,11 @@ BUILTIN_COMPILER(";") BUILTIN_COMPILER("(") { - if (UNLIKELY(ctx->flags & IS_INSIDE_COMMENT)) - return NULL; - - ctx->flags |= IS_INSIDE_COMMENT; + while (*code && *code != ')') + code++; return code; } -BUILTIN_COMPILER(")") -{ - lwan_status_error("Comment closed without opening"); - return NULL; /* handled above; can't reuse word for non-comment - purposes */ -} - BUILTIN_COMPILER("if") { PUSH_R((int32_t)forth_code_len(&ctx->defining_word->code)); @@ -1023,7 +1009,7 @@ struct forth_ctx *forth_new(void) if (!ctx) return NULL; - ctx->flags = 0; + ctx->is_inside_word_def = false; ctx->words = hash_str_new(NULL, word_free); if (!ctx->words) { From f6e985250024ba4a4d3178515e36e519b9043a8d Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 27 Jan 2025 22:32:06 -0800 Subject: [PATCH 2397/2505] Limit word names to 64 characters --- src/samples/forthsalon/forth.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 70165051b..13c7cbcce 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -316,6 +316,9 @@ static struct forth_word *new_word(struct forth_ctx *ctx, void *callback, bool compiler) { + if (len > 64) + return NULL; + struct forth_word *word = malloc(sizeof(*word) + len + 1); if (UNLIKELY(!word)) return NULL; From 645f16115c30a02a2089a63113aa97c862b740ad Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 28 Jan 2025 09:04:47 -0800 Subject: [PATCH 2398/2505] Fix @/! when stack is empty Both loading from the memory (@) and storing (!) require popping the address from the stack. If it's empty, POP_D() will yield NaN -- making the conversion to int32_t yield an out-of-bounds value. Handle this condition. This also gets rid of the STORE() and LOAD() macro, that were only used inside one function each. --- src/samples/forthsalon/forth.c | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 13c7cbcce..5ea7a1863 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -84,7 +84,7 @@ struct forth_ctx { size_t pos; } r_stack, d_stack; - double memory[64]; + double memory[16]; bool is_inside_word_def; }; @@ -126,20 +126,6 @@ struct forth_vars { } \ v; \ }) -#define LOAD(addr_) \ - ({ \ - size_t v = (size_t)(int32_t)(addr_); \ - if (v > N_ELEMENTS(ctx->memory)) \ - return false; \ - ctx->memory[v]; \ - }) -#define STORE(addr_, value_) \ - ({ \ - size_t v = (size_t)(int32_t)(addr_); \ - if (v > N_ELEMENTS(ctx->memory)) \ - return false; \ - ctx->memory[v] = (value_); \ - }) #if DUMP_CODE static void dump_code(const struct forth_code *code) @@ -662,16 +648,20 @@ BUILTIN("r@") BUILTIN("@") { - double slot = POP_D(); - PUSH_D(LOAD(slot)); + int32_t slot = (int32_t)POP_D(); + if (UNLIKELY(slot < 0)) + return false; + PUSH_D(ctx->memory[slot % (int32_t)N_ELEMENTS(ctx->memory)]); return true; } BUILTIN("!") { - double v1 = POP_D(); - double v2 = POP_D(); - STORE(v2, v1); + double v = POP_D(); + int32_t slot = (int32_t)POP_D(); + if (UNLIKELY(slot < 0)) + return false; + ctx->memory[slot % (int32_t)N_ELEMENTS(ctx->memory)] = v; return true; } From 03fa9abb306e05e0b993ee39910bf5f52b2bb28b Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 28 Jan 2025 09:29:34 -0800 Subject: [PATCH 2399/2505] Fix compilation warnings in the forth implementation --- src/samples/forthsalon/forth.c | 44 +++++++++++++++++----------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 5ea7a1863..2ee3296ac 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -100,32 +100,32 @@ struct forth_vars { return false; \ ctx->d_stack.values[ctx->d_stack.pos++] = (value_); \ }) -#define POP_D(value_) \ - ({ \ - double v; \ - if (LIKELY(ctx->d_stack.pos > 0)) { \ - v = ctx->d_stack.values[--ctx->d_stack.pos]; \ - } else { \ - v = NAN; \ - } \ - v; \ - }) + #define PUSH_R(value_) \ ({ \ if (UNLIKELY(ctx->r_stack.pos >= N_ELEMENTS(ctx->r_stack.values))) \ return false; \ ctx->r_stack.values[ctx->r_stack.pos++] = (value_); \ }) -#define POP_R(value_) \ - ({ \ - double v; \ - if (LIKELY(ctx->r_stack.pos > 0)) { \ - v = ctx->r_stack.values[--ctx->r_stack.pos]; \ - } else { \ - v = NAN; \ - } \ - v; \ - }) + +#define POP_D() pop_d(ctx) + +#define POP_R() pop_r(ctx) + +static inline double pop_d(struct forth_ctx *ctx) +{ + if (ctx->d_stack.pos > 0) + return ctx->d_stack.values[--ctx->d_stack.pos]; + return (double)NAN; +} + +static inline double pop_r(struct forth_ctx *ctx) +{ + if (ctx->r_stack.pos > 0) + return ctx->r_stack.values[--ctx->r_stack.pos]; + return (double)NAN; +} + #if DUMP_CODE static void dump_code(const struct forth_code *code) @@ -514,13 +514,13 @@ BUILTIN_COMPILER("if") static const char* builtin_else_then(struct forth_ctx *ctx, const char *code, bool is_then) { double v = POP_R(); - if (UNLIKELY(v != v)) { + if (UNLIKELY(isnan(v))) { lwan_status_error("Unbalanced if/else/then"); return NULL; } struct forth_inst *inst = - forth_code_get_elem(&ctx->defining_word->code, (int32_t)v); + forth_code_get_elem(&ctx->defining_word->code, (size_t)(int32_t)v); inst->pc = forth_code_len(&ctx->defining_word->code); From c21dad13dd6dd1a53a7a10f277c131ce708a6d74 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 28 Jan 2025 09:29:48 -0800 Subject: [PATCH 2400/2505] Wire up Forth implementation to the build system This is for testing purposes only. --- src/samples/CMakeLists.txt | 1 + src/samples/forthsalon/CMakeLists.txt | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/samples/CMakeLists.txt b/src/samples/CMakeLists.txt index a79c45c48..4cd119ab4 100644 --- a/src/samples/CMakeLists.txt +++ b/src/samples/CMakeLists.txt @@ -8,6 +8,7 @@ if (NOT ${CMAKE_BUILD_TYPE} MATCHES "Coverage") add_subdirectory(pastebin) add_subdirectory(smolsite) add_subdirectory(send-money-json-api) + add_subdirectory(forthsalon) endif() add_subdirectory(techempower) diff --git a/src/samples/forthsalon/CMakeLists.txt b/src/samples/forthsalon/CMakeLists.txt index 97acea732..5e40e967a 100644 --- a/src/samples/forthsalon/CMakeLists.txt +++ b/src/samples/forthsalon/CMakeLists.txt @@ -1,8 +1,11 @@ -add_executable(forthsalon - main.c +add_executable(forth + forth.c ) -target_link_libraries(forthsalon +target_compile_options(forth PRIVATE -DMAIN) + +target_link_libraries(forth ${LWAN_COMMON_LIBS} ${ADDITIONAL_LIBRARIES} + m ) From 0317ad9328865ad370bf2ca5cd84e8268e2f443a Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 29 Jan 2025 18:26:15 -0800 Subject: [PATCH 2401/2505] Default to dumping forth code before executing it --- src/samples/forthsalon/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/samples/forthsalon/CMakeLists.txt b/src/samples/forthsalon/CMakeLists.txt index 5e40e967a..85671890b 100644 --- a/src/samples/forthsalon/CMakeLists.txt +++ b/src/samples/forthsalon/CMakeLists.txt @@ -2,7 +2,7 @@ add_executable(forth forth.c ) -target_compile_options(forth PRIVATE -DMAIN) +target_compile_options(forth PRIVATE -DMAIN -DDUMP_CODE) target_link_libraries(forth ${LWAN_COMMON_LIBS} From efaa3fd77dfe8ed0f1684d798638662c0a85875a Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 29 Jan 2025 18:27:17 -0800 Subject: [PATCH 2402/2505] Use strchr() to implement \ and ( --- src/samples/forthsalon/forth.c | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 2ee3296ac..4b7e2d59a 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -404,7 +404,6 @@ bool forth_parse_string(struct forth_ctx *ctx, const char *code) code++; } - code = found_word(ctx, code, word_ptr, (size_t)(code - word_ptr)); if (!code) return false; @@ -451,13 +450,19 @@ struct forth_builtin { static const char *id_(struct forth_ctx *ctx, const char *code) #define BUILTIN(name_) BUILTIN_DETAIL(name_, LWAN_TMP_ID, LWAN_TMP_ID) -#define BUILTIN_COMPILER(name_) BUILTIN_COMPILER_DETAIL(name_, LWAN_TMP_ID, LWAN_TMP_ID) +#define BUILTIN_COMPILER(name_) \ + BUILTIN_COMPILER_DETAIL(name_, LWAN_TMP_ID, LWAN_TMP_ID) BUILTIN_COMPILER("\\") { - while (*code && *code != '\n') - code++; - return code; + code = strchr(code, '\n'); + return code ? code + 1 : NULL; +} + +BUILTIN_COMPILER("(") +{ + code = strchr(code, ')'); + return code ? code + 1 : NULL; } BUILTIN_COMPILER(":") @@ -495,13 +500,6 @@ BUILTIN_COMPILER(";") return code; } -BUILTIN_COMPILER("(") -{ - while (*code && *code != ')') - code++; - return code; -} - BUILTIN_COMPILER("if") { PUSH_R((int32_t)forth_code_len(&ctx->defining_word->code)); From 7881e4a7308a7d7f25b27511ba5c0125f9261f52 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 30 Jan 2025 14:25:42 -0800 Subject: [PATCH 2403/2505] Generate GIF from Forth Haiku This is using a hardcoded Haiku but this will eventually be something that users will be able to upload and edit and mix, similar to the Forth Salon website. --- src/samples/forthsalon/CMakeLists.txt | 13 + src/samples/forthsalon/forth.c | 34 +- src/samples/forthsalon/forth.h | 39 ++ src/samples/forthsalon/gif.h | 863 ++++++++++++++++++++++++++ src/samples/forthsalon/main.c | 81 ++- 5 files changed, 1015 insertions(+), 15 deletions(-) create mode 100644 src/samples/forthsalon/forth.h create mode 100644 src/samples/forthsalon/gif.h diff --git a/src/samples/forthsalon/CMakeLists.txt b/src/samples/forthsalon/CMakeLists.txt index 85671890b..e41d52402 100644 --- a/src/samples/forthsalon/CMakeLists.txt +++ b/src/samples/forthsalon/CMakeLists.txt @@ -9,3 +9,16 @@ target_link_libraries(forth ${ADDITIONAL_LIBRARIES} m ) + +add_executable(forthsalon + forth.c + main.c +) + +ADD_DEFINITIONS(-fstack-usage) + +target_link_libraries(forthsalon + ${LWAN_COMMON_LIBS} + ${ADDITIONAL_LIBRARIES} + m +) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 4b7e2d59a..2403f8361 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -14,7 +14,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ /* @@ -37,6 +38,8 @@ #include "lwan-array.h" #include "lwan-private.h" +#include "forth.h" + enum forth_opcode { OP_CALL_BUILTIN, OP_EVAL_CODE, @@ -65,7 +68,8 @@ DEFINE_ARRAY_TYPE(forth_code, struct forth_inst) struct forth_word { union { bool (*callback)(struct forth_ctx *ctx, struct forth_vars *vars); - const char *(*callback_compiler)(struct forth_ctx *ctx, const char *code); + const char *(*callback_compiler)(struct forth_ctx *ctx, + const char *code); struct forth_code code; }; bool is_builtin; @@ -89,11 +93,6 @@ struct forth_ctx { bool is_inside_word_def; }; -struct forth_vars { - double x, y; - int t, dt; -}; - #define PUSH_D(value_) \ ({ \ if (UNLIKELY(ctx->d_stack.pos >= N_ELEMENTS(ctx->d_stack.values))) \ @@ -126,7 +125,6 @@ static inline double pop_r(struct forth_ctx *ctx) return (double)NAN; } - #if DUMP_CODE static void dump_code(const struct forth_code *code) { @@ -181,7 +179,8 @@ static bool eval_code(struct forth_ctx *ctx, LWAN_ARRAY_FOREACH (code, inst) { switch (inst->opcode) { case OP_EVAL_CODE: - if (UNLIKELY(!eval_code(ctx, inst->code, vars, recursion_limit - 1))) + if (UNLIKELY( + !eval_code(ctx, inst->code, vars, recursion_limit - 1))) return false; break; case OP_CALL_BUILTIN: @@ -509,7 +508,8 @@ BUILTIN_COMPILER("if") return code; } -static const char* builtin_else_then(struct forth_ctx *ctx, const char *code, bool is_then) +static const char * +builtin_else_then(struct forth_ctx *ctx, const char *code, bool is_then) { double v = POP_R(); if (UNLIKELY(isnan(v))) { @@ -1034,6 +1034,16 @@ void forth_free(struct forth_ctx *ctx) free(ctx); } +size_t forth_d_stack_len(const struct forth_ctx *ctx) +{ + return ctx->d_stack.pos; +} + +double forth_d_stack_pop(struct forth_ctx *ctx) +{ + return POP_D(); +} + #if defined(FUZZ_TEST) int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { @@ -1069,7 +1079,9 @@ int main(int argc, char *argv[]) if (!ctx) return 1; - if (!forth_parse_string(ctx, ": nice 60 5 4 + + ; : juanita 400 10 5 5 + + + ; x if nice else juanita then 2 * 4 / 2 *")) { + if (!forth_parse_string(ctx, + ": nice 60 5 4 + + ; : juanita 400 10 5 5 + + + ; " + "x if nice else juanita then 2 * 4 / 2 *")) { lwan_status_critical("could not parse forth program"); forth_free(ctx); return 1; diff --git a/src/samples/forthsalon/forth.h b/src/samples/forthsalon/forth.h new file mode 100644 index 000000000..0d4fc63c6 --- /dev/null +++ b/src/samples/forthsalon/forth.h @@ -0,0 +1,39 @@ +/* + * lwan - web server + * Copyright (c) 2025 L. A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#pragma once + +struct forth_ctx; + +struct forth_vars { + double x, y; + double t, dt; +}; + +bool forth_run(struct forth_ctx *ctx, struct forth_vars *vars); +bool forth_parse_string(struct forth_ctx *ctx, const char *code); +void forth_free(struct forth_ctx *ctx); +struct forth_ctx *forth_new(void); +size_t forth_d_stack_len(const struct forth_ctx *ctx); +double forth_d_stack_pop(struct forth_ctx *ctx); + + + + diff --git a/src/samples/forthsalon/gif.h b/src/samples/forthsalon/gif.h new file mode 100644 index 000000000..2c52cfb1b --- /dev/null +++ b/src/samples/forthsalon/gif.h @@ -0,0 +1,863 @@ +// +// gif.h +// by Charlie Tangora +// Public domain. +// Email me : ctangora -at- gmail -dot- com +// +// This file offers a simple, very limited way to create animated GIFs directly in code. +// +// Those looking for particular cleverness are likely to be disappointed; it's pretty +// much a straight-ahead implementation of the GIF format with optional Floyd-Steinberg +// dithering. (It does at least use delta encoding - only the changed portions of each +// frame are saved.) +// +// So resulting files are often quite large. The hope is that it will be handy nonetheless +// as a quick and easily-integrated way for programs to spit out animations. +// +// Only RGBA8 is currently supported as an input format. (The alpha is ignored.) +// +// If capturing a buffer with a bottom-left origin (such as OpenGL), define GIF_FLIP_VERT +// to automatically flip the buffer data when writing the image (the buffer itself is +// unchanged. +// +// USAGE: +// Create a GifWriter struct. Pass it to GifBegin() to initialize and write the header. +// Pass subsequent frames to GifWriteFrame(). +// Finally, call GifEnd() to close the file handle and free memory. +// + +#ifndef gif_h +#define gif_h + +/* FIXME(lpereira): Someday I need to go through these warnings and fix them up. */ +#pragma GCC diagnostic ignored "-Wconversion" +#pragma GCC diagnostic ignored "-Wpointer-sign" +#pragma GCC diagnostic ignored "-Warith-conversion" + +#include // for memcpy and bzero +#include // for integer typedefs +#include // for bool macros + +// Define these macros to hook into a custom memory allocator. +// TEMP_MALLOC and TEMP_FREE will only be called in stack fashion - frees in the reverse order of mallocs +// and any temp memory allocated by a function will be freed before it exits. +// MALLOC and FREE are used only by GifBegin and GifEnd respectively (to allocate a buffer the size of the image, which +// is used to find changed pixels for delta-encoding.) + +#ifndef GIF_TEMP_MALLOC +#include +#define GIF_TEMP_MALLOC malloc +#endif + +#ifndef GIF_TEMP_FREE +#include +#define GIF_TEMP_FREE free +#endif + +#ifndef GIF_MALLOC +#include +#define GIF_MALLOC malloc +#endif + +#ifndef GIF_FREE +#include +#define GIF_FREE free +#endif + +const int kGifTransIndex = 0; + +typedef struct +{ + int bitDepth; + + uint8_t r[256]; + uint8_t g[256]; + uint8_t b[256]; + + // k-d tree over RGB space, organized in heap fashion + // i.e. left child of node i is node i*2, right child is node i*2+1 + // nodes 256-511 are implicitly the leaves, containing a color + uint8_t treeSplitElt[256]; + uint8_t treeSplit[256]; +} GifPalette; + +// max, min, and abs functions +int GifIMax(int l, int r) { return l>r?l:r; } +int GifIMin(int l, int r) { return l (1<bitDepth)-1) + { + int ind = treeRoot-(1<bitDepth); + if(ind == kGifTransIndex) return; + + // check whether this color is better than the current winner + int r_err = r - ((int32_t)pPal->r[ind]); + int g_err = g - ((int32_t)pPal->g[ind]); + int b_err = b - ((int32_t)pPal->b[ind]); + int diff = GifIAbs(r_err)+GifIAbs(g_err)+GifIAbs(b_err); + + if(diff < *bestDiff) + { + *bestInd = ind; + *bestDiff = diff; + } + + return; + } + + // take the appropriate color (r, g, or b) for this node of the k-d tree + int comps[3]; comps[0] = r; comps[1] = g; comps[2] = b; + int splitComp = comps[pPal->treeSplitElt[treeRoot]]; + + int splitPos = pPal->treeSplit[treeRoot]; + if(splitPos > splitComp) + { + // check the left subtree + GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot*2); + if( *bestDiff > splitPos - splitComp ) + { + // cannot prove there's not a better value in the right subtree, check that too + GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot*2+1); + } + } + else + { + GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot*2+1); + if( *bestDiff > splitComp - splitPos ) + { + GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot*2); + } + } +} + +void GifSwapPixels(uint8_t* image, int pixA, int pixB) +{ + uint8_t rA = image[pixA*4]; + uint8_t gA = image[pixA*4+1]; + uint8_t bA = image[pixA*4+2]; + uint8_t aA = image[pixA*4+3]; + + uint8_t rB = image[pixB*4]; + uint8_t gB = image[pixB*4+1]; + uint8_t bB = image[pixB*4+2]; + uint8_t aB = image[pixA*4+3]; + + image[pixA*4] = rB; + image[pixA*4+1] = gB; + image[pixA*4+2] = bB; + image[pixA*4+3] = aB; + + image[pixB*4] = rA; + image[pixB*4+1] = gA; + image[pixB*4+2] = bA; + image[pixB*4+3] = aA; +} + +// just the partition operation from quicksort +int GifPartition(uint8_t* image, const int left, const int right, const int elt, int pivotValue) +{ + int storeIndex = left; + bool split = 0; + for(int ii=left; ii neededCenter) + GifPartitionByMedian(image, left, pivotIndex, com, neededCenter); + + if(pivotIndex < neededCenter) + GifPartitionByMedian(image, pivotIndex+1, right, com, neededCenter); + } +} + +// Just partition around a given pivot, returning the split point +int GifPartitionByMean(uint8_t* image, int left, int right, int com, int neededMean) +{ + if(left < right-1) + { + return GifPartition(image, left, right-1, com, neededMean); + } + return left; +} + +// Builds a palette by creating a balanced k-d tree of all pixels in the image +void GifSplitPalette(uint8_t* image, int numPixels, int treeNode, int treeLevel, bool buildForDither, GifPalette* pal) +{ + if(numPixels == 0) + return; + + int numColors = (1 << pal->bitDepth); + + // base case, bottom of the tree + if(treeNode >= numColors) + { + int entry = treeNode - numColors; + + if(buildForDither) + { + // Dithering needs at least one color as dark as anything + // in the image and at least one brightest color - + // otherwise it builds up error and produces strange artifacts + if( entry == 1 ) + { + // special case: the darkest color in the image + uint32_t r=255, g=255, b=255; + for(int ii=0; iir[entry] = (uint8_t)r; + pal->g[entry] = (uint8_t)g; + pal->b[entry] = (uint8_t)b; + + return; + } + + if( entry == numColors-1 ) + { + // special case: the lightest color in the image + uint32_t r=0, g=0, b=0; + for(int ii=0; iir[entry] = (uint8_t)r; + pal->g[entry] = (uint8_t)g; + pal->b[entry] = (uint8_t)b; + + return; + } + } + + // otherwise, take the average of all colors in this subcube + uint64_t r=0, g=0, b=0; + for(int ii=0; iir[entry] = (uint8_t)r; + pal->g[entry] = (uint8_t)g; + pal->b[entry] = (uint8_t)b; + + return; + } + + // Find the axis with the largest range + int minR = 255, maxR = 0; + int minG = 255, maxG = 0; + int minB = 255, maxB = 0; + for(int ii=0; ii maxR) maxR = r; + if(r < minR) minR = r; + + if(g > maxG) maxG = g; + if(g < minG) minG = g; + + if(b > maxB) maxB = b; + if(b < minB) minB = b; + } + + int rRange = maxR - minR; + int gRange = maxG - minG; + int bRange = maxB - minB; + + // and split along that axis. (incidentally, this means this isn't a "proper" k-d tree but I don't know what else to call it) + int splitCom = 1; int rangeMin = minG; int rangeMax = maxG; + if(bRange > gRange) { splitCom = 2; rangeMin = minB; rangeMax = maxB; } + if(rRange > bRange && rRange > gRange) { splitCom = 0; rangeMin = minR; rangeMax = maxR; } + + int subPixelsA = numPixels / 2; + + GifPartitionByMedian(image, 0, numPixels, splitCom, subPixelsA); + int splitValue = image[subPixelsA*4+splitCom]; + + // if the split is very unbalanced, split at the mean instead of the median to preserve rare colors + int splitUnbalance = GifIAbs( (splitValue - rangeMin) - (rangeMax - splitValue) ); + if( splitUnbalance > (1536 >> treeLevel) ) + { + splitValue = rangeMin + (rangeMax-rangeMin) / 2; + subPixelsA = GifPartitionByMean(image, 0, numPixels, splitCom, splitValue); + } + + // add the bottom node for the transparency index + if( treeNode == numColors/2 ) + { + subPixelsA = 0; + splitValue = 0; + } + + int subPixelsB = numPixels-subPixelsA; + pal->treeSplitElt[treeNode] = (uint8_t)splitCom; + pal->treeSplit[treeNode] = (uint8_t)splitValue; + + GifSplitPalette(image, subPixelsA, treeNode*2, treeLevel+1, buildForDither, pal); + GifSplitPalette(image+subPixelsA*4, subPixelsB, treeNode*2+1, treeLevel+1, buildForDither, pal); +} + +// Finds all pixels that have changed from the previous image and +// moves them to the fromt of th buffer. +// This allows us to build a palette optimized for the colors of the +// changed pixels only. +int GifPickChangedPixels( const uint8_t* lastFrame, uint8_t* frame, int numPixels ) +{ + int numChanged = 0; + uint8_t* writeIter = frame; + + for (int ii=0; iibitDepth = bitDepth; + + // SplitPalette is destructive (it sorts the pixels by color) so + // we must create a copy of the image for it to destroy + size_t imageSize = (size_t)(width * height * 4 * sizeof(uint8_t)); + uint8_t* destroyableImage = (uint8_t*)GIF_TEMP_MALLOC(imageSize); + memcpy(destroyableImage, nextFrame, imageSize); + + int numPixels = (int)(width * height); + if(lastFrame) + numPixels = GifPickChangedPixels(lastFrame, destroyableImage, numPixels); + + GifSplitPalette(destroyableImage, numPixels, 1, 0, buildForDither, pPal); + + GIF_TEMP_FREE(destroyableImage); + + // add the bottom node for the transparency index + pPal->treeSplit[1 << (bitDepth-1)] = 0; + pPal->treeSplitElt[1 << (bitDepth-1)] = 0; + + pPal->r[0] = pPal->g[0] = pPal->b[0] = 0; +} + +// Implements Floyd-Steinberg dithering, writes palette value to alpha +void GifDitherImage( const uint8_t* lastFrame, const uint8_t* nextFrame, uint8_t* outFrame, uint32_t width, uint32_t height, GifPalette* pPal ) +{ + int numPixels = (int)(width * height); + + // quantPixels initially holds color*256 for all pixels + // The extra 8 bits of precision allow for sub-single-color error values + // to be propagated + int32_t *quantPixels = (int32_t *)GIF_TEMP_MALLOC(sizeof(int32_t) * (size_t)numPixels * 4); + + for( int ii=0; iir[bestInd]) * 256; + int32_t g_err = nextPix[1] - (int32_t)(pPal->g[bestInd]) * 256; + int32_t b_err = nextPix[2] - (int32_t)(pPal->b[bestInd]) * 256; + + nextPix[0] = pPal->r[bestInd]; + nextPix[1] = pPal->g[bestInd]; + nextPix[2] = pPal->b[bestInd]; + nextPix[3] = bestInd; + + // Propagate the error to the four adjacent locations + // that we haven't touched yet + int quantloc_7 = (int)(yy * width + xx + 1); + int quantloc_3 = (int)(yy * width + width + xx - 1); + int quantloc_5 = (int)(yy * width + width + xx); + int quantloc_1 = (int)(yy * width + width + xx + 1); + + if(quantloc_7 < numPixels) + { + int32_t* pix7 = quantPixels+4*quantloc_7; + pix7[0] += GifIMax( -pix7[0], r_err * 7 / 16 ); + pix7[1] += GifIMax( -pix7[1], g_err * 7 / 16 ); + pix7[2] += GifIMax( -pix7[2], b_err * 7 / 16 ); + } + + if(quantloc_3 < numPixels) + { + int32_t* pix3 = quantPixels+4*quantloc_3; + pix3[0] += GifIMax( -pix3[0], r_err * 3 / 16 ); + pix3[1] += GifIMax( -pix3[1], g_err * 3 / 16 ); + pix3[2] += GifIMax( -pix3[2], b_err * 3 / 16 ); + } + + if(quantloc_5 < numPixels) + { + int32_t* pix5 = quantPixels+4*quantloc_5; + pix5[0] += GifIMax( -pix5[0], r_err * 5 / 16 ); + pix5[1] += GifIMax( -pix5[1], g_err * 5 / 16 ); + pix5[2] += GifIMax( -pix5[2], b_err * 5 / 16 ); + } + + if(quantloc_1 < numPixels) + { + int32_t* pix1 = quantPixels+4*quantloc_1; + pix1[0] += GifIMax( -pix1[0], r_err / 16 ); + pix1[1] += GifIMax( -pix1[1], g_err / 16 ); + pix1[2] += GifIMax( -pix1[2], b_err / 16 ); + } + } + } + + // Copy the palettized result to the output buffer + for( int ii=0; iir[bestInd]; + outFrame[1] = pPal->g[bestInd]; + outFrame[2] = pPal->b[bestInd]; + outFrame[3] = (uint8_t)bestInd; + } + + if(lastFrame) lastFrame += 4; + outFrame += 4; + nextFrame += 4; + } +} + +// Simple structure to write out the LZW-compressed portion of the image +// one bit at a time +typedef struct +{ + uint32_t chunkIndex; + uint8_t chunk[256]; // bytes are written in here until we have 256 of them, then written to the file + + uint8_t bitIndex; // how many bits in the partial byte written so far + uint8_t byte; // current partial byte + + uint8_t padding[2]; // make padding explicit +} GifBitStatus; + +// insert a single bit +void GifWriteBit( GifBitStatus* stat, uint32_t bit ) +{ + bit = bit & 1; + bit = bit << stat->bitIndex; + stat->byte |= bit; + + ++stat->bitIndex; + if( stat->bitIndex > 7 ) + { + // move the newly-finished byte to the chunk buffer + stat->chunk[stat->chunkIndex++] = stat->byte; + // and start a new byte + stat->bitIndex = 0; + stat->byte = 0; + } +} + +// write all bytes so far to the file +void GifWriteChunk( struct lwan_strbuf* f, GifBitStatus* stat ) +{ + lwan_strbuf_append_char(f, (int)stat->chunkIndex); + lwan_strbuf_append_str(f, stat->chunk, stat->chunkIndex); + + stat->bitIndex = 0; + stat->byte = 0; + stat->chunkIndex = 0; +} + +void GifWriteCode( struct lwan_strbuf* f, GifBitStatus* stat, uint32_t code, uint32_t length ) +{ + for( uint32_t ii=0; ii> 1; + + if( stat->chunkIndex == 255 ) + { + GifWriteChunk(f, stat); + } + } +} + +// The LZW dictionary is a 256-ary tree constructed as the file is encoded, +// this is one node +typedef struct +{ + uint16_t m_next[256]; +} GifLzwNode; + +// write a 256-color (8-bit) image palette to the file +void GifWritePalette( const GifPalette* pPal, struct lwan_strbuf* f ) +{ + lwan_strbuf_append_char(f, 0); // first color: transparency + lwan_strbuf_append_char(f, 0); + lwan_strbuf_append_char(f, 0); + + for(int ii=1; ii<(1 << pPal->bitDepth); ++ii) + { + uint32_t r = pPal->r[ii]; + uint32_t g = pPal->g[ii]; + uint32_t b = pPal->b[ii]; + + lwan_strbuf_append_char(f, r); + lwan_strbuf_append_char(f, g); + lwan_strbuf_append_char(f, b); + } +} + +// write the image header, LZW-compress and write out the image +void GifWriteLzwImage(struct lwan_strbuf* f, uint8_t* image, uint32_t left, uint32_t top, uint32_t width, uint32_t height, uint32_t delay, GifPalette* pPal) +{ + // graphics control extension + lwan_strbuf_append_char(f, 0x21); + lwan_strbuf_append_char(f, 0xf9); + lwan_strbuf_append_char(f, 0x04); + lwan_strbuf_append_char(f, 0x05); // leave prev frame in place, this frame has transparency + lwan_strbuf_append_char(f, (delay & 0xff)); + lwan_strbuf_append_char(f, ((delay>>8) & 0xff)); + lwan_strbuf_append_char(f, kGifTransIndex); // transparent color index + lwan_strbuf_append_char(f, 0); + + lwan_strbuf_append_char(f, 0x2c); // image descriptor block + + lwan_strbuf_append_char(f, left & 0xff); // corner of image in canvas space + lwan_strbuf_append_char(f, (left >> 8) & 0xff); + lwan_strbuf_append_char(f, top & 0xff); + lwan_strbuf_append_char(f, (top >> 8) & 0xff); + + lwan_strbuf_append_char(f, width & 0xff); // width and height of image + lwan_strbuf_append_char(f, (width >> 8) & 0xff); + lwan_strbuf_append_char(f, height & 0xff); + lwan_strbuf_append_char(f, (height >> 8) & 0xff); + + //lwan_strbuf_append_char(f, 0); // no local color table, no transparency + //lwan_strbuf_append_char(f, 0x80); // no local color table, but transparency + + lwan_strbuf_append_char(f, 0x80 + pPal->bitDepth-1); // local color table present, 2 ^ bitDepth entries + GifWritePalette(pPal, f); + + const int minCodeSize = pPal->bitDepth; + const uint32_t clearCode = 1 << pPal->bitDepth; + + lwan_strbuf_append_char(f, minCodeSize); // min code size 8 bits + + GifLzwNode* codetree = (GifLzwNode*)GIF_TEMP_MALLOC(sizeof(GifLzwNode)*4096); + + memset(codetree, 0, sizeof(GifLzwNode)*4096); + int32_t curCode = -1; + uint32_t codeSize = (uint32_t)minCodeSize + 1; + uint32_t maxCode = clearCode+1; + + GifBitStatus stat; + stat.byte = 0; + stat.bitIndex = 0; + stat.chunkIndex = 0; + + GifWriteCode(f, &stat, clearCode, codeSize); // start with a fresh LZW dictionary + + for(uint32_t yy=0; yy= (1ul << codeSize) ) + { + // dictionary entry count has broken a size barrier, + // we need more bits for codes + codeSize++; + } + if( maxCode == 4095 ) + { + // the dictionary is full, clear it out and begin anew + GifWriteCode(f, &stat, clearCode, codeSize); // clear tree + + memset(codetree, 0, sizeof(GifLzwNode)*4096); + codeSize = (uint32_t)(minCodeSize + 1); + maxCode = clearCode+1; + } + + curCode = nextValue; + } + } + } + + // compression footer + GifWriteCode(f, &stat, (uint32_t)curCode, codeSize); + GifWriteCode(f, &stat, clearCode, codeSize); + GifWriteCode(f, &stat, clearCode + 1, (uint32_t)minCodeSize + 1); + + // write out the last partial chunk + while( stat.bitIndex ) GifWriteBit(&stat, 0); + if( stat.chunkIndex ) GifWriteChunk(f, &stat); + + lwan_strbuf_append_char(f, 0); // image block terminator + + GIF_TEMP_FREE(codetree); +} + +typedef struct +{ + struct lwan_strbuf* f; + uint8_t* oldImage; + bool firstFrame; + + uint8_t padding[7]; // make padding explicit +} GifWriter; + +// Creates a gif file. +// The input GIFWriter is assumed to be uninitialized. +// The delay value is the time between frames in hundredths of a second - note that not all viewers pay much attention to this value. +bool GifBegin( GifWriter* writer, struct lwan_strbuf *f, uint32_t width, uint32_t height, uint32_t delay, int32_t bitDepth, bool dither ) +{ + (void)bitDepth; (void)dither; // Mute "Unused argument" warnings + + if(!f) return false; + + writer->f = f; + writer->firstFrame = true; + + // allocate + writer->oldImage = (uint8_t*)GIF_MALLOC(width*height*4); + + lwan_strbuf_append_strz(writer->f, "GIF89a"); + + // screen descriptor + lwan_strbuf_append_char(writer->f, width & 0xff); + lwan_strbuf_append_char(writer->f, (width >> 8) & 0xff); + lwan_strbuf_append_char(writer->f, height & 0xff); + lwan_strbuf_append_char(writer->f, (height >> 8) & 0xff); + + lwan_strbuf_append_char(writer->f, 0xf0); // there is an unsorted global color table of 2 entries + lwan_strbuf_append_char(writer->f, 0); // background color + lwan_strbuf_append_char(writer->f, 0); // pixels are square (we need to specify this because it's 1989) + + // now the "global" palette (really just a dummy palette) + // color 0: black + lwan_strbuf_append_char(writer->f, 0); + lwan_strbuf_append_char(writer->f, 0); + lwan_strbuf_append_char(writer->f, 0); + // color 1: also black + lwan_strbuf_append_char(writer->f, 0); + lwan_strbuf_append_char(writer->f, 0); + lwan_strbuf_append_char(writer->f, 0); + + if( delay != 0 ) + { + // animation header + lwan_strbuf_append_char(writer->f, 0x21); // extension + lwan_strbuf_append_char(writer->f, 0xff); // application specific + lwan_strbuf_append_char(writer->f, 11); // length 11 + lwan_strbuf_append_strz(writer->f, "NETSCAPE2.0"); // yes, really + lwan_strbuf_append_char(writer->f, 3); // 3 bytes of NETSCAPE2.0 data + + lwan_strbuf_append_char(writer->f, 1); // this is the Netscape 2.0 sub-block ID and it must be 1, otherwise some viewers error + lwan_strbuf_append_char(writer->f, 0); // loop infinitely (byte 0) + lwan_strbuf_append_char(writer->f, 0); // loop infinitely (byte 1) + + lwan_strbuf_append_char(writer->f, 0); // block terminator + } + + return true; +} + +// Writes out a new frame to a GIF in progress. +// The GIFWriter should have been created by GIFBegin. +// AFAIK, it is legal to use different bit depths for different frames of an image - +// this may be handy to save bits in animations that don't change much. +bool GifWriteFrame( GifWriter* writer, const uint8_t* image, uint32_t width, uint32_t height, uint32_t delay, int bitDepth, bool dither) +{ + if(!writer->f) return false; + + const uint8_t* oldImage = writer->firstFrame? NULL : writer->oldImage; + writer->firstFrame = false; + + GifPalette pal; + GifMakePalette((dither? NULL : oldImage), image, width, height, bitDepth, dither, &pal); + + if(dither) + GifDitherImage(oldImage, image, writer->oldImage, width, height, &pal); + else + GifThresholdImage(oldImage, image, writer->oldImage, width, height, &pal); + + GifWriteLzwImage(writer->f, writer->oldImage, 0, 0, width, height, delay, &pal); + + return true; +} + +// Writes the EOF code, closes the file handle, and frees temp memory used by a GIF. +// Many if not most viewers will still display a GIF properly if the EOF code is missing, +// but it's still a good idea to write it out. +bool GifEnd( GifWriter* writer ) +{ + if(!writer->f) return false; + + lwan_strbuf_append_char(writer->f, 0x3b); // end of file + GIF_FREE(writer->oldImage); + + writer->f = NULL; + writer->oldImage = NULL; + + return true; +} + +#endif diff --git a/src/samples/forthsalon/main.c b/src/samples/forthsalon/main.c index 7a1117cad..ce0c571c5 100644 --- a/src/samples/forthsalon/main.c +++ b/src/samples/forthsalon/main.c @@ -17,14 +17,87 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include + #include "lwan.h" +#include "forth.h" +#include "gif.h" + +/* Twister by boomlinde + * https://forthsalon.appspot.com/haiku-view/ag5mb3J0aHNhbG9uLWhyZHISCxIFSGFpa3UYgICAvJXxgwsM + */ +static const char twister[] = ": t' t pi * 2 / ;\n" +": l * + sin ;\n" +": r t' 1 y t' + 4 l + 1.57 ;\n" +": x' x 4 * 2 - t' y 3 l + ;\n" +": v 2dup x' >= swap x' < * -rot swap - l ;\n" +": a r 4 l ; : b r 1 l ;\n" +": c r 2 l ; : d r 3 l ;\n" +"0 d a v a b v b c v c d v 0.1 0.2"; + +static void destroy_forth_ctx(void *p) { forth_free(p); } +static void destroy_gif_writer(void *p) { GifEnd(p); } -LWAN_HANDLER_ROUTE(hello_world, "/") +LWAN_HANDLER_ROUTE(twister, "/") { - static const char message[] = "Hello, World!"; + struct forth_ctx *f = forth_new(); + double current_time = (int32_t)time(NULL); + + coro_defer(request->conn->coro, destroy_forth_ctx, f); + + if (!forth_parse_string(f, twister)) + return HTTP_INTERNAL_ERROR; + + uint8_t *frame_buffer = coro_malloc(request->conn->coro, 64 * 64 * 4); + if (!frame_buffer) + return HTTP_INTERNAL_ERROR; + + response->mime_type = "image/gif"; + + if (!lwan_response_set_chunked(request, HTTP_OK)) + return HTTP_INTERNAL_ERROR; + + GifWriter writer = {}; + coro_defer(request->conn->coro, destroy_gif_writer, &writer); + + GifBegin(&writer, response->buffer, 64, 64, 2, 8, true); + + for (int frame = 0; frame < 1000; frame++) { + for (int x = 0; x < 64; x++) { + for (int y = 0; y < 64; y++) { + uint8_t *pixel = &frame_buffer[4 * (y * 64 + x)]; + + struct forth_vars vars = { + .x = x / 64., + .y = y / 64., + .t = current_time, + }; + if (!forth_run(f, &vars)) + return HTTP_INTERNAL_ERROR; + switch (forth_d_stack_len(f)) { + case 3: + pixel[3] = 0; + pixel[2] = (uint8_t)(round(forth_d_stack_pop(f) * 255.)); + pixel[1] = (uint8_t)(round(forth_d_stack_pop(f) * 255.)); + pixel[0] = (uint8_t)(round(forth_d_stack_pop(f) * 255.)); + break; + case 4: + pixel[3] = (uint8_t)(round(forth_d_stack_pop(f) * 255.)); + pixel[2] = (uint8_t)(round(forth_d_stack_pop(f) * 255.)); + pixel[1] = (uint8_t)(round(forth_d_stack_pop(f) * 255.)); + pixel[0] = (uint8_t)(round(forth_d_stack_pop(f) * 255.)); + break; + default: + return HTTP_INTERNAL_ERROR; + } + } + } - response->mime_type = "text/plain"; - lwan_strbuf_set_static(response->buffer, message, sizeof(message) - 1); + GifWriteFrame(&writer, frame_buffer, 64, 64, 2, 8, true); + lwan_response_send_chunk(request); + lwan_request_sleep(request, 16); + current_time += .016; + } return HTTP_OK; } From f8f4650ae6e1acde8c2f7f867b66f788289dd5fd Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 31 Jan 2025 00:46:23 -0800 Subject: [PATCH 2404/2505] Fix "/" builtin when dividing by zero Stack effects should be the same for every arithmetic built-in. --- src/samples/forthsalon/forth.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 2403f8361..d0bae9669 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -821,10 +821,12 @@ BUILTIN("-") BUILTIN("/") { double v = POP_D(); - if (v == 0.0) + if (v == 0.0) { + POP_D(); PUSH_D(INFINITY); - else + } else { PUSH_D(POP_D() / v); + } return true; } From 5542fa629de3ee29d25ac6828634ce9f3f3824aa Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 31 Jan 2025 09:27:49 -0800 Subject: [PATCH 2405/2505] Simplify how words are marked builtin/compiler --- src/samples/forthsalon/forth.c | 62 ++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index d0bae9669..1db52d134 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -65,6 +65,15 @@ struct forth_inst { DEFINE_ARRAY_TYPE(forth_code, struct forth_inst) +struct forth_builtin { + const char *name; + size_t name_len; + union { + bool (*callback)(struct forth_ctx *, struct forth_vars *vars); + const char *(*callback_compiler)(struct forth_ctx *, const char *); + }; +}; + struct forth_word { union { bool (*callback)(struct forth_ctx *ctx, struct forth_vars *vars); @@ -72,8 +81,7 @@ struct forth_word { const char *code); struct forth_code code; }; - bool is_builtin; - bool is_compiler; + const struct forth_builtin *builtin; char name[]; }; @@ -125,6 +133,18 @@ static inline double pop_r(struct forth_ctx *ctx) return (double)NAN; } +static inline bool is_word_builtin(const struct forth_word *w) +{ + return !!w->builtin; +} + +static inline bool is_word_compiler(const struct forth_word *w) +{ + const struct forth_builtin *b = w->builtin; + return b && b >= SECTION_START_SYMBOL(forth_compiler_builtin, b) && + b < SECTION_STOP_SYMBOL(forth_compiler_builtin, b); +} + #if DUMP_CODE static void dump_code(const struct forth_code *code) { @@ -222,13 +242,13 @@ static struct forth_inst *new_inst(struct forth_ctx *ctx) static bool emit_word_call(struct forth_ctx *ctx, struct forth_word *word) { - assert(!word->is_compiler); + assert(!is_word_compiler(word)); struct forth_inst *inst = new_inst(ctx); if (UNLIKELY(!inst)) return false; - if (word->is_builtin) { + if (is_word_builtin(word)) { *inst = (struct forth_inst){.callback = word->callback, .opcode = OP_CALL_BUILTIN}; } else { @@ -299,7 +319,7 @@ static struct forth_word *new_word(struct forth_ctx *ctx, const char *name, size_t len, void *callback, - bool compiler) + const struct forth_builtin *builtin) { if (len > 64) return NULL; @@ -309,18 +329,12 @@ static struct forth_word *new_word(struct forth_ctx *ctx, return NULL; if (callback) { - word->is_builtin = true; - if (compiler) { - word->callback_compiler = callback; - } else { - word->callback = callback; - } + word->callback = callback; } else { - word->is_builtin = false; forth_code_init(&word->code); } - word->is_compiler = compiler; + word->builtin = builtin; strncpy(word->name, name, len); word->name[len] = '\0'; @@ -355,7 +369,7 @@ static const char *found_word(struct forth_ctx *ctx, struct forth_word *w = lookup_word(ctx, word, word_len); if (ctx->defining_word) { if (LIKELY(w)) { - if (w->is_compiler) + if (is_word_compiler(w)) return w->callback_compiler(ctx, code); return emit_word_call(ctx, w) ? code : NULL; } @@ -370,7 +384,7 @@ static const char *found_word(struct forth_ctx *ctx, return NULL; } - w = new_word(ctx, word, word_len, NULL, false); + w = new_word(ctx, word, word_len, NULL, NULL); if (UNLIKELY(!w)) { /* can't create new word */ lwan_status_error("Can't create new word"); return NULL; @@ -416,16 +430,6 @@ bool forth_parse_string(struct forth_ctx *ctx, const char *code) return true; } -struct forth_builtin { - const char *name; - size_t name_len; - union { - bool (*callback)(struct forth_ctx *, struct forth_vars *vars); - const char *(*callback_compiler)(struct forth_ctx *, const char *); - }; - bool compiler; -}; - #define BUILTIN_DETAIL(name_, id_, struct_id_) \ static bool id_(struct forth_ctx *, struct forth_vars *); \ static const struct forth_builtin __attribute__((used)) \ @@ -973,13 +977,13 @@ register_builtins(struct forth_ctx *ctx) const struct forth_builtin *iter; LWAN_SECTION_FOREACH(forth_builtin, iter) { - if (!new_word(ctx, iter->name, iter->name_len, iter->callback, false)) { + if (!new_word(ctx, iter->name, iter->name_len, iter->callback, iter)) { lwan_status_critical("could not register forth word: %s", iter->name); } } LWAN_SECTION_FOREACH(forth_compiler_builtin, iter) { - if (!new_word(ctx, iter->name, iter->name_len, iter->callback_compiler, true)) { + if (!new_word(ctx, iter->name, iter->name_len, iter->callback_compiler, iter)) { lwan_status_critical("could not register forth word: %s", iter->name); } @@ -990,7 +994,7 @@ static void word_free(void *ptr) { struct forth_word *word = ptr; - if (!word->is_builtin) + if (!is_word_builtin(word)) forth_code_reset(&word->code); free(word); } @@ -1010,7 +1014,7 @@ struct forth_ctx *forth_new(void) return NULL; } - struct forth_word *word = new_word(ctx, " ", 1, NULL, false); + struct forth_word *word = new_word(ctx, " ", 1, NULL, NULL); if (!word) { free(ctx); return NULL; From 53f49a1f7eb855fc5dfaf1fbb87710b8f053cfbd Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 31 Jan 2025 09:30:40 -0800 Subject: [PATCH 2406/2505] Move runtime context to the begininng of the forth_ctx struct --- src/samples/forthsalon/forth.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 1db52d134..212bd9a92 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -86,18 +86,17 @@ struct forth_word { }; struct forth_ctx { - struct forth_word *defining_word; - struct forth_word *main; - - struct hash *words; - struct { - double values[256]; size_t pos; + double values[256]; } r_stack, d_stack; double memory[16]; + struct forth_word *defining_word; + struct forth_word *main; + struct hash *words; + bool is_inside_word_def; }; From 73d859e7f298ecd75f254df76671b6109c9de0d6 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 31 Jan 2025 09:44:31 -0800 Subject: [PATCH 2407/2505] Statically check if the program is correct For instance, an instruction that pops 2 items from the stack can't work if there's just a single item there. Ensure this is the case while compiling. --- src/samples/forthsalon/forth.c | 250 +++++++++++++++++++++++++-------- 1 file changed, 188 insertions(+), 62 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 212bd9a92..e37597ee6 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -72,6 +72,10 @@ struct forth_builtin { bool (*callback)(struct forth_ctx *, struct forth_vars *vars); const char *(*callback_compiler)(struct forth_ctx *, const char *); }; + int d_pushes; + int d_pops; + int r_pushes; + int r_pops; }; struct forth_word { @@ -82,6 +86,8 @@ struct forth_word { struct forth_code code; }; const struct forth_builtin *builtin; + int d_stack_len; + int r_stack_len; char name[]; }; @@ -144,6 +150,106 @@ static inline bool is_word_compiler(const struct forth_word *w) b < SECTION_STOP_SYMBOL(forth_compiler_builtin, b); } +static const struct forth_builtin *find_builtin_by_callback(void *callback) +{ + const struct forth_builtin *iter; + + LWAN_SECTION_FOREACH(forth_builtin, iter) { + if (iter->callback == callback) + return iter; + } + LWAN_SECTION_FOREACH(forth_compiler_builtin, iter) { + if (iter->callback_compiler == callback) + return iter; + } + + return NULL; +} + +static const struct forth_word *find_word_by_code(const struct forth_ctx *ctx, + const struct forth_code *code) +{ + struct hash_iter iter; + const void *name, *value; + + hash_iter_init(ctx->words, &iter); + while (hash_iter_next(&iter, &name, &value)) { + const struct forth_word *word = value; + if (&word->code == code) + return word; + } + + return NULL; +} + +static bool check_stack_effects(const struct forth_ctx *ctx, + struct forth_word *w) +{ + const struct forth_inst *inst; + int items_in_d_stack = 0; + int items_in_r_stack = 0; + + assert(!is_word_builtin(w)); + + LWAN_ARRAY_FOREACH(&w->code, inst) { + switch (inst->opcode) { + case OP_EVAL_CODE: { + const struct forth_word *cw = find_word_by_code(ctx, inst->code); + if (UNLIKELY(!cw)) { + lwan_status_critical("Can't find builtin word by user code"); + return false; + } + + items_in_d_stack += cw->d_stack_len; + items_in_r_stack += cw->r_stack_len; + break; + } + case OP_CALL_BUILTIN: { + const struct forth_builtin *b = find_builtin_by_callback(inst->callback); + if (UNLIKELY(!b)) { + lwan_status_critical("Can't find builtin word by callback"); + return false; + } + + if (items_in_d_stack < b->d_pops) { + lwan_status_error("Word `%.*s' requires %d item(s) in the D stack", + (int)b->name_len, b->name, b->d_pops); + return false; + } + if (items_in_r_stack < b->r_pops) { + lwan_status_error("Word `%.*s' requires %d item(s) in the R stack", + (int)b->name_len, b->name, b->r_pops); + return false; + } + + items_in_d_stack -= b->d_pops; + items_in_d_stack += b->d_pushes; + items_in_r_stack -= b->r_pops; + items_in_r_stack += b->r_pushes; + break; + } + case OP_NUMBER: + items_in_d_stack++; + break; + case OP_JUMP_IF: + if (!items_in_d_stack) { + lwan_status_error("Word `if' requires 1 item(s) in the D stack"); + return false; + } + items_in_d_stack--; + break; + case OP_NOP: + case OP_JUMP: + continue; + } + } + + w->d_stack_len = items_in_d_stack; + w->r_stack_len = items_in_r_stack; + + return true; +} + #if DUMP_CODE static void dump_code(const struct forth_code *code) { @@ -203,8 +309,7 @@ static bool eval_code(struct forth_ctx *ctx, return false; break; case OP_CALL_BUILTIN: - if (UNLIKELY(!inst->callback(ctx, vars))) - return false; + inst->callback(ctx, vars); break; case OP_NUMBER: PUSH_D(inst->number); @@ -334,6 +439,8 @@ static struct forth_word *new_word(struct forth_ctx *ctx, } word->builtin = builtin; + word->d_stack_len = 0; + word->r_stack_len = 0; strncpy(word->name, name, len); word->name[len] = '\0'; @@ -426,10 +533,14 @@ bool forth_parse_string(struct forth_ctx *ctx, const char *code) code++; } + if (!check_stack_effects(ctx, ctx->main)) + return false; + return true; } -#define BUILTIN_DETAIL(name_, id_, struct_id_) \ +#define BUILTIN_DETAIL(name_, id_, struct_id_, d_pushes_, d_pops_, r_pushes_, \ + r_pops_) \ static bool id_(struct forth_ctx *, struct forth_vars *); \ static const struct forth_builtin __attribute__((used)) \ __attribute__((section(LWAN_SECTION_NAME(forth_builtin)))) \ @@ -437,6 +548,10 @@ bool forth_parse_string(struct forth_ctx *ctx, const char *code) .name = name_, \ .name_len = sizeof(name_) - 1, \ .callback = id_, \ + .d_pushes = d_pushes_, \ + .d_pops = d_pops_, \ + .r_pushes = r_pushes_, \ + .r_pops = r_pops_, \ }; \ static bool id_(struct forth_ctx *ctx, struct forth_vars *vars) @@ -451,7 +566,12 @@ bool forth_parse_string(struct forth_ctx *ctx, const char *code) }; \ static const char *id_(struct forth_ctx *ctx, const char *code) -#define BUILTIN(name_) BUILTIN_DETAIL(name_, LWAN_TMP_ID, LWAN_TMP_ID) +#define BUILTIN(name_, d_pushes_, d_pops_) \ + BUILTIN_DETAIL(name_, LWAN_TMP_ID, LWAN_TMP_ID, d_pushes_, d_pops_, 0, 0) +#define BUILTIN_R(name_, d_pushes_, d_pops_, r_pushes_, r_pops_) \ + BUILTIN_DETAIL(name_, LWAN_TMP_ID, LWAN_TMP_ID, d_pushes_, d_pops_, \ + r_pushes_, r_pops_) + #define BUILTIN_COMPILER(name_) \ BUILTIN_COMPILER_DETAIL(name_, LWAN_TMP_ID, LWAN_TMP_ID) @@ -491,6 +611,11 @@ BUILTIN_COMPILER(";") return NULL; } + if (UNLIKELY(!check_stack_effects(ctx, ctx->defining_word))) { + lwan_status_error("Stack effect checks failed"); + return NULL; + } + ctx->is_inside_word_def = false; if (UNLIKELY(!ctx->defining_word)) { @@ -539,42 +664,42 @@ BUILTIN_COMPILER("else") { return builtin_else_then(ctx, code, false); } BUILTIN_COMPILER("then") { return builtin_else_then(ctx, code, true); } -BUILTIN("x") +BUILTIN("x", 1, 0) { PUSH_D(vars->x); return true; } -BUILTIN("y") +BUILTIN("y", 1, 0) { PUSH_D(vars->y); return true; } -BUILTIN("t") +BUILTIN("t", 1, 0) { PUSH_D(vars->t); return true; } -BUILTIN("dt") +BUILTIN("dt", 1, 0) { PUSH_D(vars->dt); return true; } -BUILTIN("mx") +BUILTIN("mx", 1, 0) { /* stub */ PUSH_D(0.0); return true; } -BUILTIN("my") +BUILTIN("my", 1, 0) { /* stub */ PUSH_D(0.0); return true; } -BUILTIN("button") +BUILTIN("button", 1, 1) { /* stub */ POP_D(); @@ -582,21 +707,21 @@ BUILTIN("button") return true; } -BUILTIN("buttons") +BUILTIN("buttons", 1, 0) { /* stub */ PUSH_D(0.0); return true; } -BUILTIN("audio") +BUILTIN("audio", 0, 1) { /* stub */ POP_D(); return true; } -BUILTIN("sample") +BUILTIN("sample", 3, 2) { /* stub */ POP_D(); @@ -607,7 +732,7 @@ BUILTIN("sample") return true; } -BUILTIN("bwsample") +BUILTIN("bwsample", 1, 2) { /* stub */ POP_D(); @@ -616,30 +741,31 @@ BUILTIN("bwsample") return true; } -BUILTIN("push") +BUILTIN_R("push", 0, 1, 1, 0) { PUSH_R(POP_D()); return true; } -BUILTIN("pop") + +BUILTIN_R("pop", 1, 0, 0, 1) { PUSH_D(POP_R()); return true; } -BUILTIN(">r") +BUILTIN_R(">r", 0, 1, 1, 0) { PUSH_R(POP_D()); return true; } -BUILTIN("r>") +BUILTIN_R("r>", 1, 0, 0, 1) { PUSH_D(POP_R()); return true; } -BUILTIN("r@") +BUILTIN_R("r@", 1, 0, 1, 1) { double v = POP_R(); PUSH_R(v); @@ -647,7 +773,7 @@ BUILTIN("r@") return true; } -BUILTIN("@") +BUILTIN("@", 1, 1) { int32_t slot = (int32_t)POP_D(); if (UNLIKELY(slot < 0)) @@ -656,7 +782,7 @@ BUILTIN("@") return true; } -BUILTIN("!") +BUILTIN("!", 0, 2) { double v = POP_D(); int32_t slot = (int32_t)POP_D(); @@ -666,7 +792,7 @@ BUILTIN("!") return true; } -BUILTIN("dup") +BUILTIN("dup", 2, 1) { double v = POP_D(); PUSH_D(v); @@ -674,7 +800,7 @@ BUILTIN("dup") return true; } -BUILTIN("over") +BUILTIN("over", 3, 2) { double v1 = POP_D(); double v2 = POP_D(); @@ -684,7 +810,7 @@ BUILTIN("over") return true; } -BUILTIN("2dup") +BUILTIN("2dup", 4, 2) { double v1 = POP_D(); double v2 = POP_D(); @@ -695,7 +821,7 @@ BUILTIN("2dup") return true; } -BUILTIN("z+") +BUILTIN("z+", 2, 4) { double v1 = POP_D(); double v2 = POP_D(); @@ -706,7 +832,7 @@ BUILTIN("z+") return true; } -BUILTIN("z*") +BUILTIN("z*", 2, 4) { double v1 = POP_D(); double v2 = POP_D(); @@ -717,13 +843,13 @@ BUILTIN("z*") return true; } -BUILTIN("drop") +BUILTIN("drop", 0, 1) { POP_D(); return true; } -BUILTIN("swap") +BUILTIN("swap", 2, 2) { double v1 = POP_D(); double v2 = POP_D(); @@ -732,7 +858,7 @@ BUILTIN("swap") return true; } -BUILTIN("rot") +BUILTIN("rot", 3, 3) { double v1 = POP_D(); double v2 = POP_D(); @@ -743,7 +869,7 @@ BUILTIN("rot") return true; } -BUILTIN("-rot") +BUILTIN("-rot", 3, 3) { double v1 = POP_D(); double v2 = POP_D(); @@ -754,7 +880,7 @@ BUILTIN("-rot") return true; } -BUILTIN("=") +BUILTIN("=", 1, 2) { double v1 = POP_D(); double v2 = POP_D(); @@ -762,7 +888,7 @@ BUILTIN("=") return true; } -BUILTIN("<>") +BUILTIN("<>", 1, 2) { double v1 = POP_D(); double v2 = POP_D(); @@ -770,7 +896,7 @@ BUILTIN("<>") return true; } -BUILTIN(">") +BUILTIN(">", 1, 2) { double v1 = POP_D(); double v2 = POP_D(); @@ -778,7 +904,7 @@ BUILTIN(">") return true; } -BUILTIN("<") +BUILTIN("<", 1, 2) { double v1 = POP_D(); double v2 = POP_D(); @@ -786,7 +912,7 @@ BUILTIN("<") return true; } -BUILTIN(">=") +BUILTIN(">=", 1, 2) { double v1 = POP_D(); double v2 = POP_D(); @@ -794,7 +920,7 @@ BUILTIN(">=") return true; } -BUILTIN("<=") +BUILTIN("<=", 1, 2) { double v1 = POP_D(); double v2 = POP_D(); @@ -802,26 +928,26 @@ BUILTIN("<=") return true; } -BUILTIN("+") +BUILTIN("+", 1, 2) { PUSH_D(POP_D() + POP_D()); return true; } -BUILTIN("*") +BUILTIN("*", 1, 2) { PUSH_D(POP_D() * POP_D()); return true; } -BUILTIN("-") +BUILTIN("-", 1, 2) { double v = POP_D(); PUSH_D(POP_D() - v); return true; } -BUILTIN("/") +BUILTIN("/", 1, 2) { double v = POP_D(); if (v == 0.0) { @@ -834,55 +960,55 @@ BUILTIN("/") return true; } -BUILTIN("mod") +BUILTIN("mod", 1, 2) { double v = POP_D(); PUSH_D(fmod(POP_D(), v)); return true; } -BUILTIN("pow") +BUILTIN("pow", 1, 2) { double v = POP_D(); PUSH_D(pow(fabs(POP_D()), v)); return true; } -BUILTIN("**") +BUILTIN("**", 1, 2) { double v = POP_D(); PUSH_D(pow(fabs(POP_D()), v)); return true; } -BUILTIN("atan2") +BUILTIN("atan2", 1, 2) { double v = POP_D(); PUSH_D(atan2(POP_D(), v)); return true; } -BUILTIN("and") +BUILTIN("and", 1, 2) { double v = POP_D(); PUSH_D((POP_D() != 0.0 && v != 0.0) ? 1.0 : 0.0); return true; } -BUILTIN("or") +BUILTIN("or", 1, 2) { double v = POP_D(); PUSH_D((POP_D() != 0.0 || v != 0.0) ? 1.0 : 0.0); return true; } -BUILTIN("not") +BUILTIN("not", 1, 1) { PUSH_D(POP_D() != 0.0 ? 0.0 : 1.0); return true; } -BUILTIN("min") +BUILTIN("min", 1, 2) { double v1 = POP_D(); double v2 = POP_D(); @@ -890,7 +1016,7 @@ BUILTIN("min") return true; } -BUILTIN("max") +BUILTIN("max", 1, 2) { double v1 = POP_D(); double v2 = POP_D(); @@ -898,73 +1024,73 @@ BUILTIN("max") return true; } -BUILTIN("negate") +BUILTIN("negate", 1, 1) { PUSH_D(-POP_D()); return true; } -BUILTIN("sin") +BUILTIN("sin", 1, 1) { PUSH_D(sin(POP_D())); return true; } -BUILTIN("cos") +BUILTIN("cos", 1, 1) { PUSH_D(cos(POP_D())); return true; } -BUILTIN("tan") +BUILTIN("tan", 1, 1) { PUSH_D(tan(POP_D())); return true; } -BUILTIN("log") +BUILTIN("log", 1, 1) { PUSH_D(log(fabs(POP_D()))); return true; } -BUILTIN("exp") +BUILTIN("exp", 1, 1) { PUSH_D(log(POP_D())); return true; } -BUILTIN("sqrt") +BUILTIN("sqrt", 1, 1) { PUSH_D(sqrt(fabs(POP_D()))); return true; } -BUILTIN("floor") +BUILTIN("floor", 1, 1) { PUSH_D(floor(POP_D())); return true; } -BUILTIN("ceil") +BUILTIN("ceil", 1, 1) { PUSH_D(ceil(POP_D())); return true; } -BUILTIN("abs") +BUILTIN("abs", 1, 1) { PUSH_D(fabs(POP_D())); return true; } -BUILTIN("pi") +BUILTIN("pi", 1, 0) { PUSH_D(M_PI); return true; } -BUILTIN("random") +BUILTIN("random", 1, 0) { PUSH_D(drand48()); return true; From 9e2221ff369a0cb8e96838d97b303c979f2b9f43 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 31 Jan 2025 09:52:10 -0800 Subject: [PATCH 2408/2505] Remove stack bounds check This shouldn't be necessary since we're now relying on check_stack_effects() to statically verify the program is valid, stack use-wise. This of course needs some fuzzing before it's declared stable, but -- oh, well, this whole thing is experimental anyway. --- src/samples/forthsalon/forth.c | 53 +++++++++------------------------- 1 file changed, 14 insertions(+), 39 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index e37597ee6..cc4465efb 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -106,37 +106,12 @@ struct forth_ctx { bool is_inside_word_def; }; -#define PUSH_D(value_) \ - ({ \ - if (UNLIKELY(ctx->d_stack.pos >= N_ELEMENTS(ctx->d_stack.values))) \ - return false; \ - ctx->d_stack.values[ctx->d_stack.pos++] = (value_); \ - }) - -#define PUSH_R(value_) \ - ({ \ - if (UNLIKELY(ctx->r_stack.pos >= N_ELEMENTS(ctx->r_stack.values))) \ - return false; \ - ctx->r_stack.values[ctx->r_stack.pos++] = (value_); \ - }) - -#define POP_D() pop_d(ctx) - -#define POP_R() pop_r(ctx) - -static inline double pop_d(struct forth_ctx *ctx) -{ - if (ctx->d_stack.pos > 0) - return ctx->d_stack.values[--ctx->d_stack.pos]; - return (double)NAN; -} - -static inline double pop_r(struct forth_ctx *ctx) -{ - if (ctx->r_stack.pos > 0) - return ctx->r_stack.values[--ctx->r_stack.pos]; - return (double)NAN; -} +#define PUSH_D(value_) ({ ctx->d_stack.values[ctx->d_stack.pos++] = (value_); }) +#define PUSH_R(value_) ({ ctx->r_stack.values[ctx->r_stack.pos++] = (value_); }) +#define DROP_D() ({ ctx->d_stack.pos--; }) +#define DROP_R() ({ ctx->r_stack.pos--; }) +#define POP_D() ({ DROP_D(); ctx->d_stack.values[ctx->d_stack.pos]; }) +#define POP_R() ({ DROP_R(); ctx->r_stack.values[ctx->r_stack.pos]; }) static inline bool is_word_builtin(const struct forth_word *w) { @@ -702,7 +677,7 @@ BUILTIN("my", 1, 0) BUILTIN("button", 1, 1) { /* stub */ - POP_D(); + DROP_D(); PUSH_D(0.0); return true; } @@ -717,15 +692,15 @@ BUILTIN("buttons", 1, 0) BUILTIN("audio", 0, 1) { /* stub */ - POP_D(); + DROP_D(); return true; } BUILTIN("sample", 3, 2) { /* stub */ - POP_D(); - POP_D(); + DROP_D(); + DROP_D(); PUSH_D(0); PUSH_D(0); PUSH_D(0); @@ -735,8 +710,8 @@ BUILTIN("sample", 3, 2) BUILTIN("bwsample", 1, 2) { /* stub */ - POP_D(); - POP_D(); + DROP_D(); + DROP_D(); PUSH_D(0); return true; } @@ -845,7 +820,7 @@ BUILTIN("z*", 2, 4) BUILTIN("drop", 0, 1) { - POP_D(); + DROP_D(); return true; } @@ -951,7 +926,7 @@ BUILTIN("/", 1, 2) { double v = POP_D(); if (v == 0.0) { - POP_D(); + DROP_D(); PUSH_D(INFINITY); } else { PUSH_D(POP_D() / v); From a4201dcbd0d2ca70e59055631dbd2c62bdcb8c93 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 31 Jan 2025 09:59:38 -0800 Subject: [PATCH 2409/2505] Ensure GifWriter can be used by coro deferred callbacks --- src/samples/forthsalon/main.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/samples/forthsalon/main.c b/src/samples/forthsalon/main.c index ce0c571c5..b833a9ae4 100644 --- a/src/samples/forthsalon/main.c +++ b/src/samples/forthsalon/main.c @@ -36,7 +36,11 @@ static const char twister[] = ": t' t pi * 2 / ;\n" "0 d a v a b v b c v c d v 0.1 0.2"; static void destroy_forth_ctx(void *p) { forth_free(p); } -static void destroy_gif_writer(void *p) { GifEnd(p); } +static void destroy_gif_writer(void *p) +{ + GifEnd(p); + free(p); +} LWAN_HANDLER_ROUTE(twister, "/") { @@ -57,10 +61,10 @@ LWAN_HANDLER_ROUTE(twister, "/") if (!lwan_response_set_chunked(request, HTTP_OK)) return HTTP_INTERNAL_ERROR; - GifWriter writer = {}; - coro_defer(request->conn->coro, destroy_gif_writer, &writer); + GifWriter *writer = coro_malloc_full(request->conn->coro, sizeof(*writer), + destroy_gif_writer); - GifBegin(&writer, response->buffer, 64, 64, 2, 8, true); + GifBegin(writer, response->buffer, 64, 64, 2, 8, true); for (int frame = 0; frame < 1000; frame++) { for (int x = 0; x < 64; x++) { @@ -93,7 +97,7 @@ LWAN_HANDLER_ROUTE(twister, "/") } } - GifWriteFrame(&writer, frame_buffer, 64, 64, 2, 8, true); + GifWriteFrame(writer, frame_buffer, 64, 64, 2, 8, true); lwan_response_send_chunk(request); lwan_request_sleep(request, 16); current_time += .016; From e6642b758b64181a138c2960a462f9dc5e704d13 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 31 Jan 2025 13:55:19 -0800 Subject: [PATCH 2410/2505] Make built-in non-compiler handlers return void Since the code is now statically verified during compilation time, checks for the execution of every built-in word isn't necessary anymore and we can simplify their callbacks. --- src/samples/forthsalon/forth.c | 201 ++++++--------------------------- 1 file changed, 33 insertions(+), 168 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index cc4465efb..f0a69a8e4 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -57,7 +57,7 @@ struct forth_inst { union { double number; struct forth_code *code; - bool (*callback)(struct forth_ctx *ctx, struct forth_vars *vars); + void (*callback)(struct forth_ctx *ctx, struct forth_vars *vars); size_t pc; }; enum forth_opcode opcode; @@ -69,7 +69,7 @@ struct forth_builtin { const char *name; size_t name_len; union { - bool (*callback)(struct forth_ctx *, struct forth_vars *vars); + void (*callback)(struct forth_ctx *, struct forth_vars *vars); const char *(*callback_compiler)(struct forth_ctx *, const char *); }; int d_pushes; @@ -80,7 +80,7 @@ struct forth_builtin { struct forth_word { union { - bool (*callback)(struct forth_ctx *ctx, struct forth_vars *vars); + void (*callback)(struct forth_ctx *ctx, struct forth_vars *vars); const char *(*callback_compiler)(struct forth_ctx *ctx, const char *code); struct forth_code code; @@ -516,7 +516,7 @@ bool forth_parse_string(struct forth_ctx *ctx, const char *code) #define BUILTIN_DETAIL(name_, id_, struct_id_, d_pushes_, d_pops_, r_pushes_, \ r_pops_) \ - static bool id_(struct forth_ctx *, struct forth_vars *); \ + static void id_(struct forth_ctx *, struct forth_vars *); \ static const struct forth_builtin __attribute__((used)) \ __attribute__((section(LWAN_SECTION_NAME(forth_builtin)))) \ __attribute__((aligned(8))) struct_id_ = { \ @@ -528,7 +528,7 @@ bool forth_parse_string(struct forth_ctx *ctx, const char *code) .r_pushes = r_pushes_, \ .r_pops = r_pops_, \ }; \ - static bool id_(struct forth_ctx *ctx, struct forth_vars *vars) + static void id_(struct forth_ctx *ctx, struct forth_vars *vars) #define BUILTIN_COMPILER_DETAIL(name_, id_, struct_id_) \ static const char *id_(struct forth_ctx *, const char *); \ @@ -639,39 +639,21 @@ BUILTIN_COMPILER("else") { return builtin_else_then(ctx, code, false); } BUILTIN_COMPILER("then") { return builtin_else_then(ctx, code, true); } -BUILTIN("x", 1, 0) -{ - PUSH_D(vars->x); - return true; -} -BUILTIN("y", 1, 0) -{ - PUSH_D(vars->y); - return true; -} -BUILTIN("t", 1, 0) -{ - PUSH_D(vars->t); - return true; -} -BUILTIN("dt", 1, 0) -{ - PUSH_D(vars->dt); - return true; -} +BUILTIN("x", 1, 0) { PUSH_D(vars->x); } +BUILTIN("y", 1, 0) { PUSH_D(vars->y); } +BUILTIN("t", 1, 0) { PUSH_D(vars->t); } +BUILTIN("dt", 1, 0) { PUSH_D(vars->dt); } BUILTIN("mx", 1, 0) { /* stub */ PUSH_D(0.0); - return true; } BUILTIN("my", 1, 0) { /* stub */ PUSH_D(0.0); - return true; } BUILTIN("button", 1, 1) @@ -679,21 +661,18 @@ BUILTIN("button", 1, 1) /* stub */ DROP_D(); PUSH_D(0.0); - return true; } BUILTIN("buttons", 1, 0) { /* stub */ PUSH_D(0.0); - return true; } BUILTIN("audio", 0, 1) { /* stub */ DROP_D(); - return true; } BUILTIN("sample", 3, 2) @@ -704,7 +683,6 @@ BUILTIN("sample", 3, 2) PUSH_D(0); PUSH_D(0); PUSH_D(0); - return true; } BUILTIN("bwsample", 1, 2) @@ -713,58 +691,34 @@ BUILTIN("bwsample", 1, 2) DROP_D(); DROP_D(); PUSH_D(0); - return true; } -BUILTIN_R("push", 0, 1, 1, 0) -{ - PUSH_R(POP_D()); - return true; -} +BUILTIN_R("push", 0, 1, 1, 0) { PUSH_R(POP_D()); } -BUILTIN_R("pop", 1, 0, 0, 1) -{ - PUSH_D(POP_R()); - return true; -} +BUILTIN_R("pop", 1, 0, 0, 1) { PUSH_D(POP_R()); } -BUILTIN_R(">r", 0, 1, 1, 0) -{ - PUSH_R(POP_D()); - return true; -} +BUILTIN_R(">r", 0, 1, 1, 0) { PUSH_R(POP_D()); } -BUILTIN_R("r>", 1, 0, 0, 1) -{ - PUSH_D(POP_R()); - return true; -} +BUILTIN_R("r>", 1, 0, 0, 1) { PUSH_D(POP_R()); } BUILTIN_R("r@", 1, 0, 1, 1) { double v = POP_R(); PUSH_R(v); PUSH_D(v); - return true; } BUILTIN("@", 1, 1) { - int32_t slot = (int32_t)POP_D(); - if (UNLIKELY(slot < 0)) - return false; - PUSH_D(ctx->memory[slot % (int32_t)N_ELEMENTS(ctx->memory)]); - return true; + uint32_t slot = (uint32_t)POP_D(); + PUSH_D(ctx->memory[slot % (uint32_t)N_ELEMENTS(ctx->memory)]); } BUILTIN("!", 0, 2) { double v = POP_D(); - int32_t slot = (int32_t)POP_D(); - if (UNLIKELY(slot < 0)) - return false; - ctx->memory[slot % (int32_t)N_ELEMENTS(ctx->memory)] = v; - return true; + uint32_t slot = (uint32_t)POP_D(); + ctx->memory[slot % (uint32_t)N_ELEMENTS(ctx->memory)] = v; } BUILTIN("dup", 2, 1) @@ -772,7 +726,6 @@ BUILTIN("dup", 2, 1) double v = POP_D(); PUSH_D(v); PUSH_D(v); - return true; } BUILTIN("over", 3, 2) @@ -782,7 +735,6 @@ BUILTIN("over", 3, 2) PUSH_D(v2); PUSH_D(v1); PUSH_D(v2); - return true; } BUILTIN("2dup", 4, 2) @@ -793,7 +745,6 @@ BUILTIN("2dup", 4, 2) PUSH_D(v1); PUSH_D(v2); PUSH_D(v1); - return true; } BUILTIN("z+", 2, 4) @@ -804,7 +755,6 @@ BUILTIN("z+", 2, 4) double v4 = POP_D(); PUSH_D(v2 + v4); PUSH_D(v1 + v3); - return true; } BUILTIN("z*", 2, 4) @@ -815,14 +765,9 @@ BUILTIN("z*", 2, 4) double v4 = POP_D(); PUSH_D(v4 * v2 - v3 * v1); PUSH_D(v4 * v1 + v3 * v2); - return true; } -BUILTIN("drop", 0, 1) -{ - DROP_D(); - return true; -} +BUILTIN("drop", 0, 1) { DROP_D(); } BUILTIN("swap", 2, 2) { @@ -830,7 +775,6 @@ BUILTIN("swap", 2, 2) double v2 = POP_D(); PUSH_D(v1); PUSH_D(v2); - return true; } BUILTIN("rot", 3, 3) @@ -841,7 +785,6 @@ BUILTIN("rot", 3, 3) PUSH_D(v2); PUSH_D(v1); PUSH_D(v3); - return true; } BUILTIN("-rot", 3, 3) @@ -852,7 +795,6 @@ BUILTIN("-rot", 3, 3) PUSH_D(v1); PUSH_D(v3); PUSH_D(v2); - return true; } BUILTIN("=", 1, 2) @@ -860,7 +802,6 @@ BUILTIN("=", 1, 2) double v1 = POP_D(); double v2 = POP_D(); PUSH_D(v1 == v2 ? 1.0 : 0.0); - return true; } BUILTIN("<>", 1, 2) @@ -868,7 +809,6 @@ BUILTIN("<>", 1, 2) double v1 = POP_D(); double v2 = POP_D(); PUSH_D(v1 != v2 ? 1.0 : 0.0); - return true; } BUILTIN(">", 1, 2) @@ -876,7 +816,6 @@ BUILTIN(">", 1, 2) double v1 = POP_D(); double v2 = POP_D(); PUSH_D(v1 > v2 ? 1.0 : 0.0); - return true; } BUILTIN("<", 1, 2) @@ -884,7 +823,6 @@ BUILTIN("<", 1, 2) double v1 = POP_D(); double v2 = POP_D(); PUSH_D(v1 < v2 ? 1.0 : 0.0); - return true; } BUILTIN(">=", 1, 2) @@ -892,7 +830,6 @@ BUILTIN(">=", 1, 2) double v1 = POP_D(); double v2 = POP_D(); PUSH_D(v1 >= v2 ? 1.0 : 0.0); - return true; } BUILTIN("<=", 1, 2) @@ -900,26 +837,16 @@ BUILTIN("<=", 1, 2) double v1 = POP_D(); double v2 = POP_D(); PUSH_D(v1 <= v2 ? 1.0 : 0.0); - return true; } -BUILTIN("+", 1, 2) -{ - PUSH_D(POP_D() + POP_D()); - return true; -} +BUILTIN("+", 1, 2) { PUSH_D(POP_D() + POP_D()); } -BUILTIN("*", 1, 2) -{ - PUSH_D(POP_D() * POP_D()); - return true; -} +BUILTIN("*", 1, 2) { PUSH_D(POP_D() * POP_D()); } BUILTIN("-", 1, 2) { double v = POP_D(); PUSH_D(POP_D() - v); - return true; } BUILTIN("/", 1, 2) @@ -931,64 +858,51 @@ BUILTIN("/", 1, 2) } else { PUSH_D(POP_D() / v); } - - return true; } BUILTIN("mod", 1, 2) { double v = POP_D(); PUSH_D(fmod(POP_D(), v)); - return true; } BUILTIN("pow", 1, 2) { double v = POP_D(); PUSH_D(pow(fabs(POP_D()), v)); - return true; } BUILTIN("**", 1, 2) { double v = POP_D(); PUSH_D(pow(fabs(POP_D()), v)); - return true; } BUILTIN("atan2", 1, 2) { double v = POP_D(); PUSH_D(atan2(POP_D(), v)); - return true; } BUILTIN("and", 1, 2) { double v = POP_D(); PUSH_D((POP_D() != 0.0 && v != 0.0) ? 1.0 : 0.0); - return true; } BUILTIN("or", 1, 2) { double v = POP_D(); PUSH_D((POP_D() != 0.0 || v != 0.0) ? 1.0 : 0.0); - return true; } -BUILTIN("not", 1, 1) -{ - PUSH_D(POP_D() != 0.0 ? 0.0 : 1.0); - return true; -} +BUILTIN("not", 1, 1) { PUSH_D(POP_D() != 0.0 ? 0.0 : 1.0); } BUILTIN("min", 1, 2) { double v1 = POP_D(); double v2 = POP_D(); PUSH_D(v1 > v2 ? v2 : v1); - return true; } BUILTIN("max", 1, 2) @@ -996,80 +910,31 @@ BUILTIN("max", 1, 2) double v1 = POP_D(); double v2 = POP_D(); PUSH_D(v1 > v2 ? v1 : v2); - return true; } -BUILTIN("negate", 1, 1) -{ - PUSH_D(-POP_D()); - return true; -} +BUILTIN("negate", 1, 1) { PUSH_D(-POP_D()); } -BUILTIN("sin", 1, 1) -{ - PUSH_D(sin(POP_D())); - return true; -} +BUILTIN("sin", 1, 1) { PUSH_D(sin(POP_D())); } -BUILTIN("cos", 1, 1) -{ - PUSH_D(cos(POP_D())); - return true; -} +BUILTIN("cos", 1, 1) { PUSH_D(cos(POP_D())); } -BUILTIN("tan", 1, 1) -{ - PUSH_D(tan(POP_D())); - return true; -} +BUILTIN("tan", 1, 1) { PUSH_D(tan(POP_D())); } -BUILTIN("log", 1, 1) -{ - PUSH_D(log(fabs(POP_D()))); - return true; -} +BUILTIN("log", 1, 1) { PUSH_D(log(fabs(POP_D()))); } -BUILTIN("exp", 1, 1) -{ - PUSH_D(log(POP_D())); - return true; -} +BUILTIN("exp", 1, 1) { PUSH_D(log(POP_D())); } -BUILTIN("sqrt", 1, 1) -{ - PUSH_D(sqrt(fabs(POP_D()))); - return true; -} +BUILTIN("sqrt", 1, 1) { PUSH_D(sqrt(fabs(POP_D()))); } -BUILTIN("floor", 1, 1) -{ - PUSH_D(floor(POP_D())); - return true; -} +BUILTIN("floor", 1, 1) { PUSH_D(floor(POP_D())); } -BUILTIN("ceil", 1, 1) -{ - PUSH_D(ceil(POP_D())); - return true; -} +BUILTIN("ceil", 1, 1) { PUSH_D(ceil(POP_D())); } -BUILTIN("abs", 1, 1) -{ - PUSH_D(fabs(POP_D())); - return true; -} +BUILTIN("abs", 1, 1) { PUSH_D(fabs(POP_D())); } -BUILTIN("pi", 1, 0) -{ - PUSH_D(M_PI); - return true; -} +BUILTIN("pi", 1, 0) { PUSH_D(M_PI); } -BUILTIN("random", 1, 0) -{ - PUSH_D(drand48()); - return true; -} +BUILTIN("random", 1, 0) { PUSH_D(drand48()); } __attribute__((no_sanitize_address)) static void register_builtins(struct forth_ctx *ctx) From b4fc25cf941732b856dbb49bdd7dd6d3963ccc5e Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 31 Jan 2025 17:57:13 -0800 Subject: [PATCH 2411/2505] Inline all user-defined words This makes the stack effect checks actually work across word calls. --- src/samples/forthsalon/forth.c | 75 ++++++++++++++++++++++++++-------- 1 file changed, 58 insertions(+), 17 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index f0a69a8e4..68c3d8954 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -262,16 +262,10 @@ static void dump_code(const struct forth_code *code) static bool eval_code(struct forth_ctx *ctx, const struct forth_code *code, - struct forth_vars *vars, - int recursion_limit) + struct forth_vars *vars) { const struct forth_inst *inst; - if (recursion_limit == 0) { - lwan_status_error("recursion limit reached"); - return false; - } - #if DUMP_CODE dump_code(code); #endif @@ -279,10 +273,8 @@ static bool eval_code(struct forth_ctx *ctx, LWAN_ARRAY_FOREACH (code, inst) { switch (inst->opcode) { case OP_EVAL_CODE: - if (UNLIKELY( - !eval_code(ctx, inst->code, vars, recursion_limit - 1))) - return false; - break; + lwan_status_critical("Unreachable"); + __builtin_unreachable(); case OP_CALL_BUILTIN: inst->callback(ctx, vars); break; @@ -306,7 +298,7 @@ static bool eval_code(struct forth_ctx *ctx, bool forth_run(struct forth_ctx *ctx, struct forth_vars *vars) { - return eval_code(ctx, &ctx->main->code, vars, 100); + return eval_code(ctx, &ctx->main->code, vars); } static struct forth_inst *new_inst(struct forth_ctx *ctx) @@ -475,6 +467,57 @@ static const char *found_word(struct forth_ctx *ctx, return code; } +static bool inline_calls_code(struct forth_ctx *ctx, + const struct forth_code *orig_code, + struct forth_code *new_code) +{ + const struct forth_inst *inst; + + LWAN_ARRAY_FOREACH (orig_code, inst) { + if (inst->opcode == OP_EVAL_CODE) { + if (!inline_calls_code(ctx, inst->code, new_code)) + return false; + } else { + struct forth_inst *new_inst = forth_code_append(new_code); + if (!new_inst) + return false; + + *new_inst = *inst; + + if (inst->opcode == OP_JUMP_IF) { + PUSH_R((uint32_t)forth_code_len(new_code) - 1); + } else if (inst->opcode == OP_JUMP) { + struct forth_inst *if_inst = + forth_code_get_elem(new_code, (uint32_t)POP_R()); + if_inst->pc = forth_code_len(new_code) - 1; + PUSH_R((int32_t)forth_code_len(new_code) - 1); + } else if (inst->opcode == OP_NOP) { + struct forth_inst *else_inst = + forth_code_get_elem(new_code, (uint32_t)POP_R()); + else_inst->pc = forth_code_len(new_code) - 1; + } + } + } + + return true; +} + +static bool inline_calls(struct forth_ctx *ctx) +{ + struct forth_code new_main; + + forth_code_init(&new_main); + if (!inline_calls_code(ctx, &ctx->main->code, &new_main)) { + forth_code_reset(&new_main); + return false; + } + + forth_code_reset(&ctx->main->code); + ctx->main->code = new_main; + + return true; +} + bool forth_parse_string(struct forth_ctx *ctx, const char *code) { assert(ctx); @@ -508,6 +551,9 @@ bool forth_parse_string(struct forth_ctx *ctx, const char *code) code++; } + if (!inline_calls(ctx)) + return false; + if (!check_stack_effects(ctx, ctx->main)) return false; @@ -586,11 +632,6 @@ BUILTIN_COMPILER(";") return NULL; } - if (UNLIKELY(!check_stack_effects(ctx, ctx->defining_word))) { - lwan_status_error("Stack effect checks failed"); - return NULL; - } - ctx->is_inside_word_def = false; if (UNLIKELY(!ctx->defining_word)) { From 7a1e3a8409033d59cf0435512fb30303665f7b3d Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 31 Jan 2025 17:57:46 -0800 Subject: [PATCH 2412/2505] Dump R and D stacks after sample forth invocation --- src/samples/forthsalon/forth.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 68c3d8954..ecb607c2e 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -1101,7 +1101,15 @@ int main(int argc, char *argv[]) struct forth_vars vars = {.x = 1, .y = 0}; if (forth_run(ctx, &vars)) { - lwan_status_debug("top of d-stack: %lf", POP_D()); + lwan_status_debug("D stack:"); + for (size_t i = 0; i < ctx->d_stack.pos; i++) { + lwan_status_debug(" %lf", POP_D()); + } + + lwan_status_debug("R stack:"); + for (size_t i = 0; i < ctx->r_stack.pos; i++) { + lwan_status_debug(" %lf", POP_R()); + } } forth_free(ctx); From a72e6760a392072bca287ce5ce2dbc9ae7a2f14e Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 31 Jan 2025 17:58:07 -0800 Subject: [PATCH 2413/2505] Add endpoint to benchmark forth implementation --- src/samples/forthsalon/main.c | 54 +++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/samples/forthsalon/main.c b/src/samples/forthsalon/main.c index b833a9ae4..37936df22 100644 --- a/src/samples/forthsalon/main.c +++ b/src/samples/forthsalon/main.c @@ -42,6 +42,60 @@ static void destroy_gif_writer(void *p) free(p); } +static struct timespec current_precise_monotonic_timespec(void) +{ + struct timespec now; + + if (UNLIKELY(clock_gettime(CLOCK_MONOTONIC, &now) < 0)) { + lwan_status_perror("clock_gettime"); + return (struct timespec){}; + } + + return now; +} + +static double elapsed_time_ms(const struct timespec then) +{ + const struct timespec now = current_precise_monotonic_timespec(); + struct timespec diff = { + .tv_sec = now.tv_sec - then.tv_sec, + .tv_nsec = now.tv_nsec - then.tv_nsec, + }; + + if (diff.tv_nsec < 0) { + diff.tv_sec--; + diff.tv_nsec += 1000000000l; + } + + return (double)diff.tv_sec / 1000.0 + (double)diff.tv_nsec / 1000000.0; +} + +LWAN_HANDLER_ROUTE(benchmark, "/benchmark") +{ + struct forth_ctx *f = forth_new(); + coro_defer(request->conn->coro, destroy_forth_ctx, f); + + if (!forth_parse_string(f, twister)) + return HTTP_INTERNAL_ERROR; + + struct timespec before = current_precise_monotonic_timespec(); + for (int i = 0; i < 100000; i++) { + struct forth_vars vars = { + .x = i / 64., + .y = i / 64., + .t = 0, + }; + if (!forth_run(f, &vars)) + return HTTP_INTERNAL_ERROR; + } + + response->mime_type = "text/plain"; + lwan_strbuf_printf(response->buffer, "elapsed time: %lfms", + elapsed_time_ms(before)); + + return HTTP_OK; +} + LWAN_HANDLER_ROUTE(twister, "/") { struct forth_ctx *f = forth_new(); From e0b303b9be68c934e8ee067a6e469467efdf458a Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 31 Jan 2025 19:24:18 -0800 Subject: [PATCH 2414/2505] Use a jump stack local to an inline_calls_code() callframe --- src/samples/forthsalon/forth.c | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index ecb607c2e..fa265c8df 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -472,11 +472,27 @@ static bool inline_calls_code(struct forth_ctx *ctx, struct forth_code *new_code) { const struct forth_inst *inst; + size_t jump_stack[64]; + size_t *j = jump_stack; + +#define JS_PUSH(val_) \ + ({ \ + if (j > (jump_stack + 64)) \ + return false; \ + *j++ = (val_); \ + }) +#define JS_POP(val_) \ + ({ \ + if (j <= jump_stack) \ + return false; \ + *--j; \ + }) LWAN_ARRAY_FOREACH (orig_code, inst) { if (inst->opcode == OP_EVAL_CODE) { - if (!inline_calls_code(ctx, inst->code, new_code)) + if (!inline_calls_code(ctx, inst->code, new_code)) { return false; + } } else { struct forth_inst *new_inst = forth_code_append(new_code); if (!new_inst) @@ -485,20 +501,23 @@ static bool inline_calls_code(struct forth_ctx *ctx, *new_inst = *inst; if (inst->opcode == OP_JUMP_IF) { - PUSH_R((uint32_t)forth_code_len(new_code) - 1); + JS_PUSH(forth_code_len(new_code) - 1); } else if (inst->opcode == OP_JUMP) { struct forth_inst *if_inst = - forth_code_get_elem(new_code, (uint32_t)POP_R()); + forth_code_get_elem(new_code, JS_POP()); if_inst->pc = forth_code_len(new_code) - 1; - PUSH_R((int32_t)forth_code_len(new_code) - 1); + JS_PUSH(forth_code_len(new_code) - 1); } else if (inst->opcode == OP_NOP) { struct forth_inst *else_inst = - forth_code_get_elem(new_code, (uint32_t)POP_R()); + forth_code_get_elem(new_code, JS_POP()); else_inst->pc = forth_code_len(new_code) - 1; } } } +#undef JS_PUSH +#undef JS_POP + return true; } From acd8c1ff13bf2a07601d95c33bc6dc3e02d79350 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 31 Jan 2025 21:07:04 -0800 Subject: [PATCH 2415/2505] Ensure forth_run() resets the stack pointers before starting --- src/samples/forthsalon/forth.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index fa265c8df..c1bf56495 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -298,6 +298,8 @@ static bool eval_code(struct forth_ctx *ctx, bool forth_run(struct forth_ctx *ctx, struct forth_vars *vars) { + ctx->d_stack.pos = 0; + ctx->r_stack.pos = 0; return eval_code(ctx, &ctx->main->code, vars); } From c1eeae6e6ff7853b8ed900cfb9b7804773686314 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 31 Jan 2025 21:08:52 -0800 Subject: [PATCH 2416/2505] Stack effects should also check for potential stack overflow --- src/samples/forthsalon/forth.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index c1bf56495..1f9fc040f 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -217,6 +217,15 @@ static bool check_stack_effects(const struct forth_ctx *ctx, case OP_JUMP: continue; } + + if (UNLIKELY(items_in_d_stack >= (int)N_ELEMENTS(ctx->d_stack.values))) { + lwan_status_error("Program would cause a stack overflow in the D stack"); + return false; + } + if (UNLIKELY(items_in_r_stack >= (int)N_ELEMENTS(ctx->d_stack.values))) { + lwan_status_error("Program would cause a stack overflow in the R stack"); + return false; + } } w->d_stack_len = items_in_d_stack; From 03b058aa7b8eade6cb4d1078b17a0d9b060387cf Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 31 Jan 2025 21:10:18 -0800 Subject: [PATCH 2417/2505] Sprinkle some UNLIKELY() macros in check_stack_effects() --- src/samples/forthsalon/forth.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 1f9fc040f..5d0449125 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -186,12 +186,12 @@ static bool check_stack_effects(const struct forth_ctx *ctx, return false; } - if (items_in_d_stack < b->d_pops) { + if (UNLIKELY(items_in_d_stack < b->d_pops)) { lwan_status_error("Word `%.*s' requires %d item(s) in the D stack", (int)b->name_len, b->name, b->d_pops); return false; } - if (items_in_r_stack < b->r_pops) { + if (UNLIKELY(items_in_r_stack < b->r_pops)) { lwan_status_error("Word `%.*s' requires %d item(s) in the R stack", (int)b->name_len, b->name, b->r_pops); return false; @@ -207,7 +207,7 @@ static bool check_stack_effects(const struct forth_ctx *ctx, items_in_d_stack++; break; case OP_JUMP_IF: - if (!items_in_d_stack) { + if (UNLIKELY(!items_in_d_stack)) { lwan_status_error("Word `if' requires 1 item(s) in the D stack"); return false; } From 0bf65ef69a123620d9bd2d3a6c9f01f22897c000 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 2 Feb 2025 11:14:03 -0800 Subject: [PATCH 2418/2505] Use tail-call instruction dispatch for the forth runtime This yields a ~2x performance boost in the benchmark. This hasn't been fully tested yet, and there's a good chance it'll blow up in your face. This also needs some cleaning up; currently, we're generating an IR, which is the old instruction set, and then converting it to this new scheme. It was just easier to perform some of the transforms. --- src/samples/forthsalon/forth.c | 590 +++++++++++++++++++++++---------- src/samples/forthsalon/forth.h | 14 +- src/samples/forthsalon/main.c | 16 +- 3 files changed, 424 insertions(+), 196 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 5d0449125..6c411cb62 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -51,25 +51,42 @@ enum forth_opcode { struct forth_ctx; struct forth_vars; -struct forth_code; +struct forth_ir_code; +union forth_inst; -struct forth_inst { +struct forth_ir { union { double number; - struct forth_code *code; - void (*callback)(struct forth_ctx *ctx, struct forth_vars *vars); + struct forth_ir_code *code; + void (*callback)(union forth_inst *, + double *d_stack, + double *r_stack, + struct forth_vars *vars); size_t pc; }; enum forth_opcode opcode; }; -DEFINE_ARRAY_TYPE(forth_code, struct forth_inst) +union forth_inst { + void (*callback)(union forth_inst *, + double *d_stack, + double *r_stack, + struct forth_vars *vars); + double number; + size_t pc; +}; + +DEFINE_ARRAY_TYPE(forth_ir_code, struct forth_ir) +DEFINE_ARRAY_TYPE(forth_code, union forth_inst) struct forth_builtin { const char *name; size_t name_len; union { - void (*callback)(struct forth_ctx *, struct forth_vars *vars); + void (*callback)(union forth_inst *, + double *d_stack, + double *r_stack, + struct forth_vars *vars); const char *(*callback_compiler)(struct forth_ctx *, const char *); }; int d_pushes; @@ -80,10 +97,13 @@ struct forth_builtin { struct forth_word { union { - void (*callback)(struct forth_ctx *ctx, struct forth_vars *vars); + void (*callback)(union forth_inst *, + double *d_stack, + double *r_stack, + struct forth_vars *vars); const char *(*callback_compiler)(struct forth_ctx *ctx, const char *code); - struct forth_code code; + struct forth_ir_code code; }; const struct forth_builtin *builtin; int d_stack_len; @@ -92,12 +112,18 @@ struct forth_word { }; struct forth_ctx { - struct { - size_t pos; - double values[256]; - } r_stack, d_stack; + union { + struct { + double d_stack[256]; + double r_stack[256]; + }; + struct { + size_t j_stack[32]; + size_t *j; + }; + }; - double memory[16]; + struct forth_code main_code; struct forth_word *defining_word; struct forth_word *main; @@ -106,12 +132,12 @@ struct forth_ctx { bool is_inside_word_def; }; -#define PUSH_D(value_) ({ ctx->d_stack.values[ctx->d_stack.pos++] = (value_); }) -#define PUSH_R(value_) ({ ctx->r_stack.values[ctx->r_stack.pos++] = (value_); }) -#define DROP_D() ({ ctx->d_stack.pos--; }) -#define DROP_R() ({ ctx->r_stack.pos--; }) -#define POP_D() ({ DROP_D(); ctx->d_stack.values[ctx->d_stack.pos]; }) -#define POP_R() ({ DROP_R(); ctx->r_stack.values[ctx->r_stack.pos]; }) +#define PUSH_D(value_) ({ *d_stack = (value_); d_stack++; }) +#define PUSH_R(value_) ({ *r_stack = (value_); r_stack++; }) +#define DROP_D() ({ d_stack--; }) +#define DROP_R() ({ r_stack--; }) +#define POP_D() ({ DROP_D(); *d_stack; }) +#define POP_R() ({ DROP_R(); *r_stack; }) static inline bool is_word_builtin(const struct forth_word *w) { @@ -142,7 +168,7 @@ static const struct forth_builtin *find_builtin_by_callback(void *callback) } static const struct forth_word *find_word_by_code(const struct forth_ctx *ctx, - const struct forth_code *code) + const struct forth_ir_code *code) { struct hash_iter iter; const void *name, *value; @@ -160,16 +186,19 @@ static const struct forth_word *find_word_by_code(const struct forth_ctx *ctx, static bool check_stack_effects(const struct forth_ctx *ctx, struct forth_word *w) { - const struct forth_inst *inst; + /* FIXME: this isn't correct when we have JUMP_IF and JUMP + * instructions: the number of items in the stacks isn't reset + * to the beginning of either if/else block. */ + const struct forth_ir *ir; int items_in_d_stack = 0; int items_in_r_stack = 0; assert(!is_word_builtin(w)); - LWAN_ARRAY_FOREACH(&w->code, inst) { - switch (inst->opcode) { + LWAN_ARRAY_FOREACH(&w->code, ir) { + switch (ir->opcode) { case OP_EVAL_CODE: { - const struct forth_word *cw = find_word_by_code(ctx, inst->code); + const struct forth_word *cw = find_word_by_code(ctx, ir->code); if (UNLIKELY(!cw)) { lwan_status_critical("Can't find builtin word by user code"); return false; @@ -180,7 +209,7 @@ static bool check_stack_effects(const struct forth_ctx *ctx, break; } case OP_CALL_BUILTIN: { - const struct forth_builtin *b = find_builtin_by_callback(inst->callback); + const struct forth_builtin *b = find_builtin_by_callback(ir->callback); if (UNLIKELY(!b)) { lwan_status_critical("Can't find builtin word by callback"); return false; @@ -218,11 +247,11 @@ static bool check_stack_effects(const struct forth_ctx *ctx, continue; } - if (UNLIKELY(items_in_d_stack >= (int)N_ELEMENTS(ctx->d_stack.values))) { + if (UNLIKELY(items_in_d_stack >= 256)) { lwan_status_error("Program would cause a stack overflow in the D stack"); return false; } - if (UNLIKELY(items_in_r_stack >= (int)N_ELEMENTS(ctx->d_stack.values))) { + if (UNLIKELY(items_in_r_stack >= 256)) { lwan_status_error("Program would cause a stack overflow in the R stack"); return false; } @@ -234,33 +263,33 @@ static bool check_stack_effects(const struct forth_ctx *ctx, return true; } -#if DUMP_CODE -static void dump_code(const struct forth_code *code) +#if defined(DUMP_CODE) +static void dump_code(const struct forth_ir_code *code) { - const struct forth_inst *inst; + const struct forth_ir *ir; size_t i = 0; printf("dumping code @ %p\n", code); - LWAN_ARRAY_FOREACH (code, inst) { + LWAN_ARRAY_FOREACH (code, ir) { printf("%08zu ", i); i++; - switch (inst->opcode) { + switch (ir->opcode) { case OP_EVAL_CODE: - printf("eval code %p\n", inst->code); + printf("eval code %p\n", ir->code); break; case OP_CALL_BUILTIN: - printf("call builtin %p\n", inst->callback); + printf("call builtin %p\n", ir->callback); break; case OP_NUMBER: - printf("number %lf\n", inst->number); + printf("number %lf\n", ir->number); break; case OP_JUMP_IF: - printf("if [next %zu]\n", inst->pc); + printf("if [next %zu]\n", ir->pc); break; case OP_JUMP: - printf("jump to %zu\n", inst->pc); + printf("jump to %zu\n", ir->pc); break; case OP_NOP: printf("nop\n"); @@ -269,73 +298,37 @@ static void dump_code(const struct forth_code *code) } #endif -static bool eval_code(struct forth_ctx *ctx, - const struct forth_code *code, - struct forth_vars *vars) -{ - const struct forth_inst *inst; - -#if DUMP_CODE - dump_code(code); -#endif - - LWAN_ARRAY_FOREACH (code, inst) { - switch (inst->opcode) { - case OP_EVAL_CODE: - lwan_status_critical("Unreachable"); - __builtin_unreachable(); - case OP_CALL_BUILTIN: - inst->callback(ctx, vars); - break; - case OP_NUMBER: - PUSH_D(inst->number); - break; - case OP_JUMP_IF: - if (POP_D() == 0.0) - inst = forth_code_get_elem(code, inst->pc); - break; - case OP_JUMP: - inst = forth_code_get_elem(code, inst->pc); - break; - case OP_NOP: - break; - } - } - - return true; -} - bool forth_run(struct forth_ctx *ctx, struct forth_vars *vars) { - ctx->d_stack.pos = 0; - ctx->r_stack.pos = 0; - return eval_code(ctx, &ctx->main->code, vars); + union forth_inst *instr = forth_code_get_elem(&ctx->main_code, 0); + instr->callback(instr, ctx->d_stack, ctx->r_stack, vars); + return true; } -static struct forth_inst *new_inst(struct forth_ctx *ctx) +static struct forth_ir *new_ir(struct forth_ctx *ctx) { - /* FIXME: if last instruction is NOP, maybe we can reuse it? */ + /* FIXME: if last irruction is NOP, maybe we can reuse it? */ if (UNLIKELY(!ctx->defining_word)) return NULL; - return forth_code_append(&ctx->defining_word->code); + return forth_ir_code_append(&ctx->defining_word->code); } static bool emit_word_call(struct forth_ctx *ctx, struct forth_word *word) { assert(!is_word_compiler(word)); - struct forth_inst *inst = new_inst(ctx); - if (UNLIKELY(!inst)) + struct forth_ir *ir = new_ir(ctx); + if (UNLIKELY(!ir)) return false; if (is_word_builtin(word)) { - *inst = (struct forth_inst){.callback = word->callback, - .opcode = OP_CALL_BUILTIN}; + *ir = (struct forth_ir){.callback = word->callback, + .opcode = OP_CALL_BUILTIN}; } else { - *inst = - (struct forth_inst){.code = &word->code, .opcode = OP_EVAL_CODE}; + *ir = + (struct forth_ir){.code = &word->code, .opcode = OP_EVAL_CODE}; } return true; @@ -343,41 +336,41 @@ static bool emit_word_call(struct forth_ctx *ctx, struct forth_word *word) static bool emit_number(struct forth_ctx *ctx, double number) { - struct forth_inst *inst = new_inst(ctx); - if (UNLIKELY(!inst)) + struct forth_ir *ir = new_ir(ctx); + if (UNLIKELY(!ir)) return false; - *inst = (struct forth_inst){.number = number, .opcode = OP_NUMBER}; + *ir = (struct forth_ir){.number = number, .opcode = OP_NUMBER}; return true; } static bool emit_jump_if(struct forth_ctx *ctx) { - struct forth_inst *inst = new_inst(ctx); - if (UNLIKELY(!inst)) + struct forth_ir *ir = new_ir(ctx); + if (UNLIKELY(!ir)) return false; - *inst = (struct forth_inst){.opcode = OP_JUMP_IF}; + *ir = (struct forth_ir){.opcode = OP_JUMP_IF}; return true; } static bool emit_jump(struct forth_ctx *ctx) { - struct forth_inst *inst = new_inst(ctx); - if (UNLIKELY(!inst)) + struct forth_ir *ir = new_ir(ctx); + if (UNLIKELY(!ir)) return false; - *inst = (struct forth_inst){.opcode = OP_JUMP}; + *ir = (struct forth_ir){.opcode = OP_JUMP}; return true; } static bool emit_nop(struct forth_ctx *ctx) { - struct forth_inst *inst = new_inst(ctx); - if (UNLIKELY(!inst)) + struct forth_ir *ir = new_ir(ctx); + if (UNLIKELY(!ir)) return false; - *inst = (struct forth_inst){.opcode = OP_NOP}; + *ir = (struct forth_ir){.opcode = OP_NOP}; return true; } @@ -413,7 +406,7 @@ static struct forth_word *new_word(struct forth_ctx *ctx, if (callback) { word->callback = callback; } else { - forth_code_init(&word->code); + forth_ir_code_init(&word->code); } word->builtin = builtin; @@ -460,16 +453,16 @@ static const char *found_word(struct forth_ctx *ctx, lwan_status_error("Word \"%.*s\" not defined yet, can't call", (int)word_len, word); - return NULL; /* word not defined yet */ + return NULL; } - if (LIKELY(w != NULL)) { /* redefining word not supported */ + if (LIKELY(w != NULL)) { lwan_status_error("Can't redefine word \"%.*s\"", (int)word_len, word); return NULL; } w = new_word(ctx, word, word_len, NULL, NULL); - if (UNLIKELY(!w)) { /* can't create new word */ + if (UNLIKELY(!w)) { lwan_status_error("Can't create new word"); return NULL; } @@ -479,10 +472,10 @@ static const char *found_word(struct forth_ctx *ctx, } static bool inline_calls_code(struct forth_ctx *ctx, - const struct forth_code *orig_code, - struct forth_code *new_code) + const struct forth_ir_code *orig_code, + struct forth_ir_code *new_code) { - const struct forth_inst *inst; + const struct forth_ir *ir; size_t jump_stack[64]; size_t *j = jump_stack; @@ -499,29 +492,29 @@ static bool inline_calls_code(struct forth_ctx *ctx, *--j; \ }) - LWAN_ARRAY_FOREACH (orig_code, inst) { - if (inst->opcode == OP_EVAL_CODE) { - if (!inline_calls_code(ctx, inst->code, new_code)) { + LWAN_ARRAY_FOREACH (orig_code, ir) { + if (ir->opcode == OP_EVAL_CODE) { + if (!inline_calls_code(ctx, ir->code, new_code)) { return false; } } else { - struct forth_inst *new_inst = forth_code_append(new_code); - if (!new_inst) + struct forth_ir *new_ir = forth_ir_code_append(new_code); + if (!new_ir) return false; - *new_inst = *inst; - - if (inst->opcode == OP_JUMP_IF) { - JS_PUSH(forth_code_len(new_code) - 1); - } else if (inst->opcode == OP_JUMP) { - struct forth_inst *if_inst = - forth_code_get_elem(new_code, JS_POP()); - if_inst->pc = forth_code_len(new_code) - 1; - JS_PUSH(forth_code_len(new_code) - 1); - } else if (inst->opcode == OP_NOP) { - struct forth_inst *else_inst = - forth_code_get_elem(new_code, JS_POP()); - else_inst->pc = forth_code_len(new_code) - 1; + *new_ir = *ir; + + if (ir->opcode == OP_JUMP_IF) { + JS_PUSH(forth_ir_code_len(new_code) - 1); + } else if (ir->opcode == OP_JUMP) { + struct forth_ir *if_ir = + forth_ir_code_get_elem(new_code, JS_POP()); + if_ir->pc = forth_ir_code_len(new_code) - 1; + JS_PUSH(forth_ir_code_len(new_code) - 1); + } else if (ir->opcode == OP_NOP) { + struct forth_ir *else_ir = + forth_ir_code_get_elem(new_code, JS_POP()); + else_ir->pc = forth_ir_code_len(new_code) - 1; } } } @@ -534,24 +527,124 @@ static bool inline_calls_code(struct forth_ctx *ctx, static bool inline_calls(struct forth_ctx *ctx) { - struct forth_code new_main; + struct forth_ir_code new_main; - forth_code_init(&new_main); + forth_ir_code_init(&new_main); if (!inline_calls_code(ctx, &ctx->main->code, &new_main)) { - forth_code_reset(&new_main); + forth_ir_code_reset(&new_main); return false; } - forth_code_reset(&ctx->main->code); + forth_ir_code_reset(&ctx->main->code); ctx->main->code = new_main; return true; } +static void op_number(union forth_inst *inst, + double *d_stack, + double *r_stack, + struct forth_vars *vars) +{ + *d_stack++ = inst[1].number; + return inst[2].callback(&inst[2], d_stack, r_stack, vars); +} + +static void op_jump_if(union forth_inst *inst, + double *d_stack, + double *r_stack, + struct forth_vars *vars) +{ + size_t pc = (*--d_stack == 0.0) ? inst[1].pc : 2; + return inst[pc].callback(&inst[pc], d_stack, r_stack, vars); +} + +static void op_jump(union forth_inst *inst, + double *d_stack, + double *r_stack, + struct forth_vars *vars) +{ + size_t pc = inst[1].pc; + return inst[pc].callback(&inst[pc], d_stack, r_stack, vars); +} + +static void op_nop(union forth_inst *inst, + double *d_stack, + double *r_stack, + struct forth_vars *vars) +{ + return inst[1].callback(&inst[1], d_stack, r_stack, vars); +} + +static void op_halt(union forth_inst *inst, + double *d_stack, + double *r_stack, + struct forth_vars *vars) +{ + vars->final_d_stack_ptr = d_stack; + vars->final_r_stack_ptr = r_stack; +} + +#define EMIT(arg) \ + do { \ + union forth_inst *inst = forth_code_append(&ctx->main_code); \ + if (UNLIKELY(!inst)) \ + goto out; \ + *inst = (union forth_inst){arg}; \ + } while (0) + +static bool ir_to_inst(struct forth_ctx *ctx) +{ + const struct forth_ir *ir; + + forth_code_init(&ctx->main_code); + + LWAN_ARRAY_FOREACH (&ctx->main->code, ir) { + switch (ir->opcode) { + case OP_NUMBER: + EMIT(.callback = op_number); + EMIT(.number = ir->number); + break; + case OP_JUMP_IF: + assert(ir->pc); + EMIT(.callback = op_jump_if); + EMIT(.pc = ir->pc - 1); + break; + case OP_JUMP: + assert(ir->pc); + EMIT(.callback = op_jump); + EMIT(.pc = ir->pc - 1); + break; + case OP_NOP: + EMIT(.callback = op_nop); + break; + case OP_CALL_BUILTIN: + EMIT(.callback = ir->callback); + break; + case OP_EVAL_CODE: + __builtin_unreachable(); + } + } + + EMIT(.callback = op_halt); + + forth_ir_code_reset(&ctx->main->code); + + return true; + +out: + forth_code_reset(&ctx->main_code); + return false; +} + +#undef EMIT + bool forth_parse_string(struct forth_ctx *ctx, const char *code) { assert(ctx); + ctx->j = ctx->j_stack; + while (*code) { while (isspace(*code)) code++; @@ -584,15 +677,23 @@ bool forth_parse_string(struct forth_ctx *ctx, const char *code) if (!inline_calls(ctx)) return false; +#if defined(DUMP_CODE) + dump_code(&ctx->main->code); +#endif + if (!check_stack_effects(ctx, ctx->main)) return false; + if (!ir_to_inst(ctx)) + return false; + return true; } #define BUILTIN_DETAIL(name_, id_, struct_id_, d_pushes_, d_pops_, r_pushes_, \ r_pops_) \ - static void id_(struct forth_ctx *, struct forth_vars *); \ + static ALWAYS_INLINE void id_(union forth_inst *inst, double *d_stack, \ + double *r_stack, struct forth_vars *vars); \ static const struct forth_builtin __attribute__((used)) \ __attribute__((section(LWAN_SECTION_NAME(forth_builtin)))) \ __attribute__((aligned(8))) struct_id_ = { \ @@ -604,7 +705,8 @@ bool forth_parse_string(struct forth_ctx *ctx, const char *code) .r_pushes = r_pushes_, \ .r_pops = r_pops_, \ }; \ - static void id_(struct forth_ctx *ctx, struct forth_vars *vars) + static ALWAYS_INLINE void id_(union forth_inst *inst, double *d_stack, \ + double *r_stack, struct forth_vars *vars) #define BUILTIN_COMPILER_DETAIL(name_, id_, struct_id_) \ static const char *id_(struct forth_ctx *, const char *); \ @@ -652,7 +754,7 @@ BUILTIN_COMPILER(":") BUILTIN_COMPILER(";") { - if (ctx->r_stack.pos) { + if (ctx->j != ctx->j_stack) { lwan_status_error("Unmatched if/then/else"); return NULL; } @@ -675,7 +777,7 @@ BUILTIN_COMPILER(";") BUILTIN_COMPILER("if") { - PUSH_R((int32_t)forth_code_len(&ctx->defining_word->code)); + *ctx->j++ = forth_ir_code_len(&ctx->defining_word->code); emit_jump_if(ctx); @@ -685,21 +787,15 @@ BUILTIN_COMPILER("if") static const char * builtin_else_then(struct forth_ctx *ctx, const char *code, bool is_then) { - double v = POP_R(); - if (UNLIKELY(isnan(v))) { - lwan_status_error("Unbalanced if/else/then"); - return NULL; - } - - struct forth_inst *inst = - forth_code_get_elem(&ctx->defining_word->code, (size_t)(int32_t)v); + struct forth_ir *ir = + forth_ir_code_get_elem(&ctx->defining_word->code, *--ctx->j); - inst->pc = forth_code_len(&ctx->defining_word->code); + ir->pc = forth_ir_code_len(&ctx->defining_word->code); if (is_then) { emit_nop(ctx); } else { - PUSH_R((int32_t)inst->pc); + *ctx->j++ = ir->pc; emit_jump(ctx); } @@ -710,21 +806,41 @@ BUILTIN_COMPILER("else") { return builtin_else_then(ctx, code, false); } BUILTIN_COMPILER("then") { return builtin_else_then(ctx, code, true); } -BUILTIN("x", 1, 0) { PUSH_D(vars->x); } -BUILTIN("y", 1, 0) { PUSH_D(vars->y); } -BUILTIN("t", 1, 0) { PUSH_D(vars->t); } -BUILTIN("dt", 1, 0) { PUSH_D(vars->dt); } +#define NEXT() return inst[1].callback(&inst[1], d_stack, r_stack, vars) + +BUILTIN("x", 1, 0) +{ + PUSH_D(vars->x); + NEXT(); +} +BUILTIN("y", 1, 0) +{ + PUSH_D(vars->y); + NEXT(); +} +BUILTIN("t", 1, 0) +{ + PUSH_D(vars->t); + NEXT(); +} +BUILTIN("dt", 1, 0) +{ + PUSH_D(vars->dt); + NEXT(); +} BUILTIN("mx", 1, 0) { /* stub */ PUSH_D(0.0); + NEXT(); } BUILTIN("my", 1, 0) { /* stub */ PUSH_D(0.0); + NEXT(); } BUILTIN("button", 1, 1) @@ -732,18 +848,21 @@ BUILTIN("button", 1, 1) /* stub */ DROP_D(); PUSH_D(0.0); + NEXT(); } BUILTIN("buttons", 1, 0) { /* stub */ PUSH_D(0.0); + NEXT(); } BUILTIN("audio", 0, 1) { /* stub */ DROP_D(); + NEXT(); } BUILTIN("sample", 3, 2) @@ -754,6 +873,7 @@ BUILTIN("sample", 3, 2) PUSH_D(0); PUSH_D(0); PUSH_D(0); + NEXT(); } BUILTIN("bwsample", 1, 2) @@ -762,34 +882,54 @@ BUILTIN("bwsample", 1, 2) DROP_D(); DROP_D(); PUSH_D(0); + NEXT(); } -BUILTIN_R("push", 0, 1, 1, 0) { PUSH_R(POP_D()); } +BUILTIN_R("push", 0, 1, 1, 0) +{ + PUSH_R(POP_D()); + NEXT(); +} -BUILTIN_R("pop", 1, 0, 0, 1) { PUSH_D(POP_R()); } +BUILTIN_R("pop", 1, 0, 0, 1) +{ + PUSH_D(POP_R()); + NEXT(); +} -BUILTIN_R(">r", 0, 1, 1, 0) { PUSH_R(POP_D()); } +BUILTIN_R(">r", 0, 1, 1, 0) +{ + PUSH_R(POP_D()); + NEXT(); +} -BUILTIN_R("r>", 1, 0, 0, 1) { PUSH_D(POP_R()); } +BUILTIN_R("r>", 1, 0, 0, 1) +{ + PUSH_D(POP_R()); + NEXT(); +} BUILTIN_R("r@", 1, 0, 1, 1) { double v = POP_R(); PUSH_R(v); PUSH_D(v); + NEXT(); } BUILTIN("@", 1, 1) { uint32_t slot = (uint32_t)POP_D(); - PUSH_D(ctx->memory[slot % (uint32_t)N_ELEMENTS(ctx->memory)]); + PUSH_D(vars->memory[slot % (uint32_t)N_ELEMENTS(vars->memory)]); + NEXT(); } BUILTIN("!", 0, 2) { double v = POP_D(); uint32_t slot = (uint32_t)POP_D(); - ctx->memory[slot % (uint32_t)N_ELEMENTS(ctx->memory)] = v; + vars->memory[slot % (uint32_t)N_ELEMENTS(vars->memory)] = v; + NEXT(); } BUILTIN("dup", 2, 1) @@ -797,6 +937,7 @@ BUILTIN("dup", 2, 1) double v = POP_D(); PUSH_D(v); PUSH_D(v); + NEXT(); } BUILTIN("over", 3, 2) @@ -806,6 +947,7 @@ BUILTIN("over", 3, 2) PUSH_D(v2); PUSH_D(v1); PUSH_D(v2); + NEXT(); } BUILTIN("2dup", 4, 2) @@ -816,6 +958,7 @@ BUILTIN("2dup", 4, 2) PUSH_D(v1); PUSH_D(v2); PUSH_D(v1); + NEXT(); } BUILTIN("z+", 2, 4) @@ -826,6 +969,7 @@ BUILTIN("z+", 2, 4) double v4 = POP_D(); PUSH_D(v2 + v4); PUSH_D(v1 + v3); + NEXT(); } BUILTIN("z*", 2, 4) @@ -836,9 +980,14 @@ BUILTIN("z*", 2, 4) double v4 = POP_D(); PUSH_D(v4 * v2 - v3 * v1); PUSH_D(v4 * v1 + v3 * v2); + NEXT(); } -BUILTIN("drop", 0, 1) { DROP_D(); } +BUILTIN("drop", 0, 1) +{ + DROP_D(); + NEXT(); +} BUILTIN("swap", 2, 2) { @@ -846,6 +995,7 @@ BUILTIN("swap", 2, 2) double v2 = POP_D(); PUSH_D(v1); PUSH_D(v2); + NEXT(); } BUILTIN("rot", 3, 3) @@ -856,6 +1006,7 @@ BUILTIN("rot", 3, 3) PUSH_D(v2); PUSH_D(v1); PUSH_D(v3); + NEXT(); } BUILTIN("-rot", 3, 3) @@ -866,6 +1017,7 @@ BUILTIN("-rot", 3, 3) PUSH_D(v1); PUSH_D(v3); PUSH_D(v2); + NEXT(); } BUILTIN("=", 1, 2) @@ -873,6 +1025,7 @@ BUILTIN("=", 1, 2) double v1 = POP_D(); double v2 = POP_D(); PUSH_D(v1 == v2 ? 1.0 : 0.0); + NEXT(); } BUILTIN("<>", 1, 2) @@ -880,6 +1033,7 @@ BUILTIN("<>", 1, 2) double v1 = POP_D(); double v2 = POP_D(); PUSH_D(v1 != v2 ? 1.0 : 0.0); + NEXT(); } BUILTIN(">", 1, 2) @@ -887,6 +1041,7 @@ BUILTIN(">", 1, 2) double v1 = POP_D(); double v2 = POP_D(); PUSH_D(v1 > v2 ? 1.0 : 0.0); + NEXT(); } BUILTIN("<", 1, 2) @@ -894,6 +1049,7 @@ BUILTIN("<", 1, 2) double v1 = POP_D(); double v2 = POP_D(); PUSH_D(v1 < v2 ? 1.0 : 0.0); + NEXT(); } BUILTIN(">=", 1, 2) @@ -901,6 +1057,7 @@ BUILTIN(">=", 1, 2) double v1 = POP_D(); double v2 = POP_D(); PUSH_D(v1 >= v2 ? 1.0 : 0.0); + NEXT(); } BUILTIN("<=", 1, 2) @@ -908,16 +1065,26 @@ BUILTIN("<=", 1, 2) double v1 = POP_D(); double v2 = POP_D(); PUSH_D(v1 <= v2 ? 1.0 : 0.0); + NEXT(); } -BUILTIN("+", 1, 2) { PUSH_D(POP_D() + POP_D()); } +BUILTIN("+", 1, 2) +{ + PUSH_D(POP_D() + POP_D()); + NEXT(); +} -BUILTIN("*", 1, 2) { PUSH_D(POP_D() * POP_D()); } +BUILTIN("*", 1, 2) +{ + PUSH_D(POP_D() * POP_D()); + NEXT(); +} BUILTIN("-", 1, 2) { double v = POP_D(); PUSH_D(POP_D() - v); + NEXT(); } BUILTIN("/", 1, 2) @@ -929,51 +1096,63 @@ BUILTIN("/", 1, 2) } else { PUSH_D(POP_D() / v); } + NEXT(); } BUILTIN("mod", 1, 2) { double v = POP_D(); PUSH_D(fmod(POP_D(), v)); + NEXT(); } BUILTIN("pow", 1, 2) { double v = POP_D(); PUSH_D(pow(fabs(POP_D()), v)); + NEXT(); } BUILTIN("**", 1, 2) { double v = POP_D(); PUSH_D(pow(fabs(POP_D()), v)); + NEXT(); } BUILTIN("atan2", 1, 2) { double v = POP_D(); PUSH_D(atan2(POP_D(), v)); + NEXT(); } BUILTIN("and", 1, 2) { double v = POP_D(); PUSH_D((POP_D() != 0.0 && v != 0.0) ? 1.0 : 0.0); + NEXT(); } BUILTIN("or", 1, 2) { double v = POP_D(); PUSH_D((POP_D() != 0.0 || v != 0.0) ? 1.0 : 0.0); + NEXT(); } -BUILTIN("not", 1, 1) { PUSH_D(POP_D() != 0.0 ? 0.0 : 1.0); } +BUILTIN("not", 1, 1) +{ + PUSH_D(POP_D() != 0.0 ? 0.0 : 1.0); + NEXT(); +} BUILTIN("min", 1, 2) { double v1 = POP_D(); double v2 = POP_D(); PUSH_D(v1 > v2 ? v2 : v1); + NEXT(); } BUILTIN("max", 1, 2) @@ -981,31 +1160,82 @@ BUILTIN("max", 1, 2) double v1 = POP_D(); double v2 = POP_D(); PUSH_D(v1 > v2 ? v1 : v2); + NEXT(); } -BUILTIN("negate", 1, 1) { PUSH_D(-POP_D()); } +BUILTIN("negate", 1, 1) +{ + PUSH_D(-POP_D()); + NEXT(); +} -BUILTIN("sin", 1, 1) { PUSH_D(sin(POP_D())); } +BUILTIN("sin", 1, 1) +{ + PUSH_D(sin(POP_D())); + NEXT(); +} -BUILTIN("cos", 1, 1) { PUSH_D(cos(POP_D())); } +BUILTIN("cos", 1, 1) +{ + PUSH_D(cos(POP_D())); + NEXT(); +} -BUILTIN("tan", 1, 1) { PUSH_D(tan(POP_D())); } +BUILTIN("tan", 1, 1) +{ + PUSH_D(tan(POP_D())); + NEXT(); +} -BUILTIN("log", 1, 1) { PUSH_D(log(fabs(POP_D()))); } +BUILTIN("log", 1, 1) +{ + PUSH_D(log(fabs(POP_D()))); + NEXT(); +} -BUILTIN("exp", 1, 1) { PUSH_D(log(POP_D())); } +BUILTIN("exp", 1, 1) +{ + PUSH_D(log(POP_D())); + NEXT(); +} -BUILTIN("sqrt", 1, 1) { PUSH_D(sqrt(fabs(POP_D()))); } +BUILTIN("sqrt", 1, 1) +{ + PUSH_D(sqrt(fabs(POP_D()))); + NEXT(); +} + +BUILTIN("floor", 1, 1) +{ + PUSH_D(floor(POP_D())); + NEXT(); +} -BUILTIN("floor", 1, 1) { PUSH_D(floor(POP_D())); } +BUILTIN("ceil", 1, 1) +{ + PUSH_D(ceil(POP_D())); + NEXT(); +} -BUILTIN("ceil", 1, 1) { PUSH_D(ceil(POP_D())); } +BUILTIN("abs", 1, 1) +{ + PUSH_D(fabs(POP_D())); + NEXT(); +} -BUILTIN("abs", 1, 1) { PUSH_D(fabs(POP_D())); } +BUILTIN("pi", 1, 0) +{ + PUSH_D(M_PI); + NEXT(); +} -BUILTIN("pi", 1, 0) { PUSH_D(M_PI); } +BUILTIN("random", 1, 0) +{ + PUSH_D(drand48()); + NEXT(); +} -BUILTIN("random", 1, 0) { PUSH_D(drand48()); } +#undef NEXT __attribute__((no_sanitize_address)) static void register_builtins(struct forth_ctx *ctx) @@ -1031,7 +1261,7 @@ static void word_free(void *ptr) struct forth_word *word = ptr; if (!is_word_builtin(word)) - forth_code_reset(&word->code); + forth_ir_code_reset(&word->code); free(word); } @@ -1059,9 +1289,6 @@ struct forth_ctx *forth_new(void) ctx->main = word; ctx->defining_word = word; - ctx->r_stack.pos = 0; - ctx->d_stack.pos = 0; - register_builtins(ctx); return ctx; @@ -1073,17 +1300,21 @@ void forth_free(struct forth_ctx *ctx) return; hash_unref(ctx->words); + forth_code_reset(&ctx->main_code); free(ctx); } -size_t forth_d_stack_len(const struct forth_ctx *ctx) +size_t forth_d_stack_len(const struct forth_ctx *ctx, + const struct forth_vars *vars) { - return ctx->d_stack.pos; + return (size_t)(vars->final_d_stack_ptr - ctx->d_stack); } -double forth_d_stack_pop(struct forth_ctx *ctx) +double forth_d_stack_pop(struct forth_vars *vars) { - return POP_D(); + vars->final_d_stack_ptr--; + double v = *vars->final_d_stack_ptr; + return v; } #if defined(FUZZ_TEST) @@ -1131,14 +1362,9 @@ int main(int argc, char *argv[]) struct forth_vars vars = {.x = 1, .y = 0}; if (forth_run(ctx, &vars)) { - lwan_status_debug("D stack:"); - for (size_t i = 0; i < ctx->d_stack.pos; i++) { - lwan_status_debug(" %lf", POP_D()); - } - - lwan_status_debug("R stack:"); - for (size_t i = 0; i < ctx->r_stack.pos; i++) { - lwan_status_debug(" %lf", POP_R()); + lwan_status_debug("D stack: %zu elems", forth_d_stack_len(ctx, &vars)); + for (size_t len = forth_d_stack_len(ctx, &vars); len; len--) { + lwan_status_debug(" %lf", forth_d_stack_pop(&vars)); } } diff --git a/src/samples/forthsalon/forth.h b/src/samples/forthsalon/forth.h index 0d4fc63c6..f942b7bb7 100644 --- a/src/samples/forthsalon/forth.h +++ b/src/samples/forthsalon/forth.h @@ -25,15 +25,17 @@ struct forth_ctx; struct forth_vars { double x, y; double t, dt; + + double memory[16]; + + double *final_r_stack_ptr; + double *final_d_stack_ptr; }; bool forth_run(struct forth_ctx *ctx, struct forth_vars *vars); bool forth_parse_string(struct forth_ctx *ctx, const char *code); void forth_free(struct forth_ctx *ctx); struct forth_ctx *forth_new(void); -size_t forth_d_stack_len(const struct forth_ctx *ctx); -double forth_d_stack_pop(struct forth_ctx *ctx); - - - - +size_t forth_d_stack_len(const struct forth_ctx *ctx, + const struct forth_vars *vars); +double forth_d_stack_pop(struct forth_vars *vars); diff --git a/src/samples/forthsalon/main.c b/src/samples/forthsalon/main.c index 37936df22..adeb78f6a 100644 --- a/src/samples/forthsalon/main.c +++ b/src/samples/forthsalon/main.c @@ -132,18 +132,18 @@ LWAN_HANDLER_ROUTE(twister, "/") }; if (!forth_run(f, &vars)) return HTTP_INTERNAL_ERROR; - switch (forth_d_stack_len(f)) { + switch (forth_d_stack_len(f, &vars)) { case 3: pixel[3] = 0; - pixel[2] = (uint8_t)(round(forth_d_stack_pop(f) * 255.)); - pixel[1] = (uint8_t)(round(forth_d_stack_pop(f) * 255.)); - pixel[0] = (uint8_t)(round(forth_d_stack_pop(f) * 255.)); + pixel[2] = (uint8_t)(round(forth_d_stack_pop(&vars) * 255.)); + pixel[1] = (uint8_t)(round(forth_d_stack_pop(&vars) * 255.)); + pixel[0] = (uint8_t)(round(forth_d_stack_pop(&vars) * 255.)); break; case 4: - pixel[3] = (uint8_t)(round(forth_d_stack_pop(f) * 255.)); - pixel[2] = (uint8_t)(round(forth_d_stack_pop(f) * 255.)); - pixel[1] = (uint8_t)(round(forth_d_stack_pop(f) * 255.)); - pixel[0] = (uint8_t)(round(forth_d_stack_pop(f) * 255.)); + pixel[3] = (uint8_t)(round(forth_d_stack_pop(&vars) * 255.)); + pixel[2] = (uint8_t)(round(forth_d_stack_pop(&vars) * 255.)); + pixel[1] = (uint8_t)(round(forth_d_stack_pop(&vars) * 255.)); + pixel[0] = (uint8_t)(round(forth_d_stack_pop(&vars) * 255.)); break; default: return HTTP_INTERNAL_ERROR; From 0b7c3ed5360ae37c8528e6ebc05386b77c3c3b27 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 2 Feb 2025 13:31:34 -0800 Subject: [PATCH 2419/2505] Simplify forth IR emitting code --- src/samples/forthsalon/forth.c | 84 ++++++++-------------------------- 1 file changed, 20 insertions(+), 64 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 6c411cb62..3f63e28c2 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -315,64 +315,13 @@ static struct forth_ir *new_ir(struct forth_ctx *ctx) return forth_ir_code_append(&ctx->defining_word->code); } -static bool emit_word_call(struct forth_ctx *ctx, struct forth_word *word) -{ - assert(!is_word_compiler(word)); - - struct forth_ir *ir = new_ir(ctx); - if (UNLIKELY(!ir)) - return false; - - if (is_word_builtin(word)) { - *ir = (struct forth_ir){.callback = word->callback, - .opcode = OP_CALL_BUILTIN}; - } else { - *ir = - (struct forth_ir){.code = &word->code, .opcode = OP_EVAL_CODE}; - } - - return true; -} - -static bool emit_number(struct forth_ctx *ctx, double number) -{ - struct forth_ir *ir = new_ir(ctx); - if (UNLIKELY(!ir)) - return false; - - *ir = (struct forth_ir){.number = number, .opcode = OP_NUMBER}; - return true; -} - -static bool emit_jump_if(struct forth_ctx *ctx) -{ - struct forth_ir *ir = new_ir(ctx); - if (UNLIKELY(!ir)) - return false; - - *ir = (struct forth_ir){.opcode = OP_JUMP_IF}; - return true; -} - -static bool emit_jump(struct forth_ctx *ctx) -{ - struct forth_ir *ir = new_ir(ctx); - if (UNLIKELY(!ir)) - return false; - - *ir = (struct forth_ir){.opcode = OP_JUMP}; - return true; -} - -static bool emit_nop(struct forth_ctx *ctx) -{ - struct forth_ir *ir = new_ir(ctx); - if (UNLIKELY(!ir)) - return false; - - *ir = (struct forth_ir){.opcode = OP_NOP}; - return true; -} +#define EMIT_IR(...) \ + do { \ + struct forth_ir *ir_inst = new_ir(ctx); \ + if (UNLIKELY(!ir_inst)) \ + return NULL; \ + *ir_inst = (struct forth_ir){__VA_ARGS__}; \ + } while (0) static bool parse_number(const char *ptr, size_t len, double *number) { @@ -436,8 +385,10 @@ static const char *found_word(struct forth_ctx *ctx, { double number; if (parse_number(word, word_len, &number)) { - if (LIKELY(ctx->defining_word)) - return emit_number(ctx, number) ? code : NULL; + if (LIKELY(ctx->defining_word)) { + EMIT_IR(.number = number, .opcode = OP_NUMBER); + return code; + } lwan_status_error("Can't redefine number %lf", number); return NULL; @@ -448,7 +399,12 @@ static const char *found_word(struct forth_ctx *ctx, if (LIKELY(w)) { if (is_word_compiler(w)) return w->callback_compiler(ctx, code); - return emit_word_call(ctx, w) ? code : NULL; + + if (is_word_builtin(w)) + EMIT_IR(.callback = w->callback, .opcode = OP_CALL_BUILTIN); + else + EMIT_IR(.code = &w->code, .opcode = OP_EVAL_CODE); + return code; } lwan_status_error("Word \"%.*s\" not defined yet, can't call", @@ -779,7 +735,7 @@ BUILTIN_COMPILER("if") { *ctx->j++ = forth_ir_code_len(&ctx->defining_word->code); - emit_jump_if(ctx); + EMIT_IR(.opcode = OP_JUMP_IF); return code; } @@ -793,10 +749,10 @@ builtin_else_then(struct forth_ctx *ctx, const char *code, bool is_then) ir->pc = forth_ir_code_len(&ctx->defining_word->code); if (is_then) { - emit_nop(ctx); + EMIT_IR(.opcode = OP_NOP); } else { *ctx->j++ = ir->pc; - emit_jump(ctx); + EMIT_IR(.opcode = OP_JUMP); } return code; From e0e8233a4875784b0fc306daf2b7946db6e5fd7f Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 2 Feb 2025 13:36:41 -0800 Subject: [PATCH 2420/2505] Disable fuzzing with Mayhem This doesn't really seem to work anymore and with oss-fuzz already working, I don't think this is necessary. --- .github/{workflows => disabled-workflows}/mayhem.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{workflows => disabled-workflows}/mayhem.yml (100%) diff --git a/.github/workflows/mayhem.yml b/.github/disabled-workflows/mayhem.yml similarity index 100% rename from .github/workflows/mayhem.yml rename to .github/disabled-workflows/mayhem.yml From eafe3492d6c5f71fc905b010d22203e32ddf8097 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 2 Feb 2025 13:50:24 -0800 Subject: [PATCH 2421/2505] Always print the contents of the stack, even in release builds --- src/samples/forthsalon/forth.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 3f63e28c2..0c12310fb 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -1318,9 +1318,9 @@ int main(int argc, char *argv[]) struct forth_vars vars = {.x = 1, .y = 0}; if (forth_run(ctx, &vars)) { - lwan_status_debug("D stack: %zu elems", forth_d_stack_len(ctx, &vars)); + printf("D stack: %zu elems", forth_d_stack_len(ctx, &vars)); for (size_t len = forth_d_stack_len(ctx, &vars); len; len--) { - lwan_status_debug(" %lf", forth_d_stack_pop(&vars)); + printf(" %lf", forth_d_stack_pop(&vars)); } } From 175e41fe4f0f22dd23a7633ac04d3d8f82a17b39 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 2 Feb 2025 14:18:43 -0800 Subject: [PATCH 2422/2505] Reduce sizes of R and D stacks --- src/samples/forthsalon/forth.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 0c12310fb..3d4a5e9c7 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -114,8 +114,8 @@ struct forth_word { struct forth_ctx { union { struct { - double d_stack[256]; - double r_stack[256]; + double d_stack[32]; + double r_stack[32]; }; struct { size_t j_stack[32]; @@ -247,11 +247,11 @@ static bool check_stack_effects(const struct forth_ctx *ctx, continue; } - if (UNLIKELY(items_in_d_stack >= 256)) { + if (UNLIKELY(items_in_d_stack >= (int)N_ELEMENTS(ctx->d_stack))) { lwan_status_error("Program would cause a stack overflow in the D stack"); return false; } - if (UNLIKELY(items_in_r_stack >= 256)) { + if (UNLIKELY(items_in_r_stack >= (int)N_ELEMENTS(ctx->r_stack))) { lwan_status_error("Program would cause a stack overflow in the R stack"); return false; } From 6ee3af7f94839ed56527866400d690857dd42904 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 2 Feb 2025 14:28:51 -0800 Subject: [PATCH 2423/2505] No need to generate stack usage for the forthsalon sample anymore --- src/samples/forthsalon/CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/samples/forthsalon/CMakeLists.txt b/src/samples/forthsalon/CMakeLists.txt index e41d52402..fb521b165 100644 --- a/src/samples/forthsalon/CMakeLists.txt +++ b/src/samples/forthsalon/CMakeLists.txt @@ -15,8 +15,6 @@ add_executable(forthsalon main.c ) -ADD_DEFINITIONS(-fstack-usage) - target_link_libraries(forthsalon ${LWAN_COMMON_LIBS} ${ADDITIONAL_LIBRARIES} From d39585e43e2c0ec0c5e79c96798ffae105013e16 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 2 Feb 2025 16:34:34 -0800 Subject: [PATCH 2424/2505] Move stack manipulation macros closer to where it's used --- src/samples/forthsalon/forth.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 3d4a5e9c7..1fcab9503 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -132,12 +132,6 @@ struct forth_ctx { bool is_inside_word_def; }; -#define PUSH_D(value_) ({ *d_stack = (value_); d_stack++; }) -#define PUSH_R(value_) ({ *r_stack = (value_); r_stack++; }) -#define DROP_D() ({ d_stack--; }) -#define DROP_R() ({ r_stack--; }) -#define POP_D() ({ DROP_D(); *d_stack; }) -#define POP_R() ({ DROP_R(); *r_stack; }) static inline bool is_word_builtin(const struct forth_word *w) { @@ -762,6 +756,13 @@ BUILTIN_COMPILER("else") { return builtin_else_then(ctx, code, false); } BUILTIN_COMPILER("then") { return builtin_else_then(ctx, code, true); } +#define PUSH_D(value_) ({ *d_stack = (value_); d_stack++; }) +#define PUSH_R(value_) ({ *r_stack = (value_); r_stack++; }) +#define DROP_D() ({ d_stack--; }) +#define DROP_R() ({ r_stack--; }) +#define POP_D() ({ DROP_D(); *d_stack; }) +#define POP_R() ({ DROP_R(); *r_stack; }) + #define NEXT() return inst[1].callback(&inst[1], d_stack, r_stack, vars) BUILTIN("x", 1, 0) From 8c0a89d4c9ace10eb72d9854c25839e146915889 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 2 Feb 2025 19:37:25 -0800 Subject: [PATCH 2425/2505] Define EMIT_IT() closer to where it's used --- src/samples/forthsalon/forth.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 1fcab9503..1332edd75 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -309,14 +309,6 @@ static struct forth_ir *new_ir(struct forth_ctx *ctx) return forth_ir_code_append(&ctx->defining_word->code); } -#define EMIT_IR(...) \ - do { \ - struct forth_ir *ir_inst = new_ir(ctx); \ - if (UNLIKELY(!ir_inst)) \ - return NULL; \ - *ir_inst = (struct forth_ir){__VA_ARGS__}; \ - } while (0) - static bool parse_number(const char *ptr, size_t len, double *number) { char *endptr; @@ -372,6 +364,14 @@ lookup_word(struct forth_ctx *ctx, const char *name, size_t len) return hash_find(ctx->words, strndupa(name, len)); } +#define EMIT_IR(...) \ + do { \ + struct forth_ir *ir_inst = new_ir(ctx); \ + if (UNLIKELY(!ir_inst)) \ + return NULL; \ + *ir_inst = (struct forth_ir){__VA_ARGS__}; \ + } while (0) + static const char *found_word(struct forth_ctx *ctx, const char *code, const char *word, From c5e05ac3d6e96daa521d3a8320e8414261ed1317 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 2 Feb 2025 19:37:39 -0800 Subject: [PATCH 2426/2505] Forth BUILTINs don't need to be inline This is a refactoring artifact when each function had a wrapper function. --- src/samples/forthsalon/forth.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 1332edd75..01d826f35 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -642,8 +642,8 @@ bool forth_parse_string(struct forth_ctx *ctx, const char *code) #define BUILTIN_DETAIL(name_, id_, struct_id_, d_pushes_, d_pops_, r_pushes_, \ r_pops_) \ - static ALWAYS_INLINE void id_(union forth_inst *inst, double *d_stack, \ - double *r_stack, struct forth_vars *vars); \ + static void id_(union forth_inst *inst, double *d_stack, double *r_stack, \ + struct forth_vars *vars); \ static const struct forth_builtin __attribute__((used)) \ __attribute__((section(LWAN_SECTION_NAME(forth_builtin)))) \ __attribute__((aligned(8))) struct_id_ = { \ @@ -655,8 +655,8 @@ bool forth_parse_string(struct forth_ctx *ctx, const char *code) .r_pushes = r_pushes_, \ .r_pops = r_pops_, \ }; \ - static ALWAYS_INLINE void id_(union forth_inst *inst, double *d_stack, \ - double *r_stack, struct forth_vars *vars) + static void id_(union forth_inst *inst, double *d_stack, double *r_stack, \ + struct forth_vars *vars) #define BUILTIN_COMPILER_DETAIL(name_, id_, struct_id_) \ static const char *id_(struct forth_ctx *, const char *); \ From 182367bf419e92c5456c257a1401ac79746fd5e5 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 2 Feb 2025 19:38:10 -0800 Subject: [PATCH 2427/2505] Better error messages when compiling forth code --- src/samples/forthsalon/forth.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 01d826f35..2c918b998 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -401,13 +401,13 @@ static const char *found_word(struct forth_ctx *ctx, return code; } - lwan_status_error("Word \"%.*s\" not defined yet, can't call", + lwan_status_error("Undefined word: \"%.*s\"", (int)word_len, word); return NULL; } if (LIKELY(w != NULL)) { - lwan_status_error("Can't redefine word \"%.*s\"", (int)word_len, word); + lwan_status_error("Word already defined: \"%.*s\"", (int)word_len, word); return NULL; } From 008ad228f3db2b463c18b5e11b0253d3d1a83b14 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 5 Feb 2025 10:53:38 -0800 Subject: [PATCH 2428/2505] Allow usage of bin2hex generated header to not use a constructor function --- src/bin/tools/bin2hex.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/bin/tools/bin2hex.c b/src/bin/tools/bin2hex.c index 3c84d23b0..7dd498b43 100644 --- a/src/bin/tools/bin2hex.c +++ b/src/bin/tools/bin2hex.c @@ -96,7 +96,7 @@ static int bin2hex(const char *path, const char *identifier) printf("\n/* Contents of %s available through %s_value */\n", path, identifier); - printf("#if defined(__GNUC__) || defined(__clang__)\n"); + printf("#if !defined(NO_INCBIN)\n"); r |= bin2hex_incbin(path, identifier); printf("#else\n"); r |= bin2hex_mmap(path, identifier); @@ -124,6 +124,12 @@ int main(int argc, char *argv[]) printf("#pragma once\n\n"); printf("#include \"lwan-private.h\"\n"); + printf("#if defined(NO_INCBIN)\n"); + printf(" /* do nothing */\n"); + printf("#elif defined(__GNUC__) || defined(__clang__)\n"); + printf("#define WANT_INCBIN\n"); + printf("#endif\n"); + for (arg = 1; arg < argc; arg += 2) { const char *path = argv[arg]; const char *identifier = argv[arg + 1]; @@ -135,7 +141,7 @@ int main(int argc, char *argv[]) } } - printf("#if defined(__GNUC__) || defined(__clang__)\n"); + printf("#if !defined(NO_INCBIN)\n"); printf("LWAN_CONSTRUCTOR(bin2hex_%016lx, 0)\n", (uintptr_t)argv); printf("{\n"); for (arg = 1; arg < argc; arg += 2) { From e00ee92f1a89523558b223c29d5447a224a6e730 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 5 Feb 2025 10:54:34 -0800 Subject: [PATCH 2429/2505] Actually use exp() in the exp builtin --- src/samples/forthsalon/forth.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 2c918b998..6d1c5bb06 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -1152,7 +1152,7 @@ BUILTIN("log", 1, 1) BUILTIN("exp", 1, 1) { - PUSH_D(log(POP_D())); + PUSH_D(exp(POP_D())); NEXT(); } From e8d94b17dd072a1eef6314d55f13945b4fedcc76 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 5 Feb 2025 10:55:54 -0800 Subject: [PATCH 2430/2505] Begin writing a Forth Haiku to C compiler This is still very crude and needs a lot of cleanup, but the basic idea is here and seems to be working. --- src/samples/forthsalon/CMakeLists.txt | 18 +++ src/samples/forthsalon/forth-jit.h | 112 +++++++++++++ src/samples/forthsalon/forth.c | 221 ++++++++++++++++++++++++-- 3 files changed, 334 insertions(+), 17 deletions(-) create mode 100644 src/samples/forthsalon/forth-jit.h diff --git a/src/samples/forthsalon/CMakeLists.txt b/src/samples/forthsalon/CMakeLists.txt index fb521b165..932ec9306 100644 --- a/src/samples/forthsalon/CMakeLists.txt +++ b/src/samples/forthsalon/CMakeLists.txt @@ -15,8 +15,26 @@ add_executable(forthsalon main.c ) +target_compile_options(forthsalon PRIVATE -DDUMP_CODE) + target_link_libraries(forthsalon ${LWAN_COMMON_LIBS} ${ADDITIONAL_LIBRARIES} m ) + +add_custom_command( + OUTPUT ${CMAKE_BINARY_DIR}/forth-jit-inc.h + COMMAND bin2hex + ${CMAKE_SOURCE_DIR}/src/samples/forthsalon/forth-jit.h forth_jit + > + ${CMAKE_BINARY_DIR}/forth-jit-inc.h + DEPENDS ${CMAKE_SOURCE_DIR}/src/samples/forthsalon/forth-jit.h + bin2hex + COMMENT "Bundling data for forthsalon sample" +) +add_custom_target(forth_jit_data + DEPENDS ${CMAKE_BINARY_DIR}/forth-jit-inc.h +) +add_dependencies(forth forth_jit_data) +add_dependencies(forthsalon forth_jit_data) diff --git a/src/samples/forthsalon/forth-jit.h b/src/samples/forthsalon/forth-jit.h new file mode 100644 index 000000000..e0a2d8a9e --- /dev/null +++ b/src/samples/forthsalon/forth-jit.h @@ -0,0 +1,112 @@ +/* This file is used by the Forth haiku-to-C compiler as part of the Lwan + * web server project, and is placed in the public domain, or in the Creative + * Commons CC0 license (at your option). */ + +#include +#include + +/* Stubs */ +static inline double op_dt(void) { return 0; } +static inline double op_mx(void) { return 0; } +static inline double op_my(void) { return 0; } +static inline double op_button(double b) { return 0; } +static inline double op_buttons() { return 0; } +static inline double op_audio(double a) { return 0; } +static inline void op_sample(double a, double b, double *aa, double *bb, double *cc) {} +static inline double op_bwsample(double a, double b) { return 0; } + +static inline void op_dup(double a, double *aa, double *bb) { *aa = *bb = a; } + +static inline void +op_over(double a, double b, double *aa, double *bb, double *cc) +{ + *cc = a; + *bb = b; + *aa = a; +} + +static inline void +op_2dup(double a, double b, double *aa, double *bb, double *cc, double *dd) +{ + *aa = a; + *bb = b; + *cc = a; + *dd = b; +} + +static inline void +op_zadd(double a, double b, double c, double d, double *aa, double *bb) +{ + *aa = c + a; + *bb = d + b; +} + +static inline void +op_zmult(double a, double b, double c, double d, double *aa, double *bb) +{ + *aa = a * c - b * d; + *bb = a * d - b * c; +} + +static inline void op_swap(double a, double b, double *aa, double *bb) +{ + *aa = b; + *bb = a; +} + +static inline void +op_rot(double a, double b, double c, double *aa, double *bb, double *cc) +{ + *aa = b; + *bb = c; + *cc = a; +} + +static inline void +op_minusrot(double a, double b, double c, double *aa, double *bb, double *cc) +{ + *aa = c; + *bb = a; + *cc = b; +} + +static inline double op_neq(double a, double b) { return a != b ? 1.0 : 0.0; } +static inline double op_eq(double a, double b) { return a == b ? 1.0 : 0.0; } +static inline double op_gt(double a, double b) { return a > b ? 1.0 : 0.0; } +static inline double op_gte(double a, double b) { return a >= b ? 1.0 : 0.0; } +static inline double op_lt(double a, double b) { return a < b ? 1.0 : 0.0; } +static inline double op_lte(double a, double b) { return a <= b ? 1.0 : 0.0; } +static inline double op_add(double a, double b) { return a + b; } + +static inline double op_mult(double a, double b) { return a + b; } +static inline double op_sub(double a, double b) { return a - b; } +static inline double op_div(double a, double b) +{ + return b == 0.0 ? INFINITY : a / b; +} +static inline double op_fmod(double a, double b) { return fmod(a, b); } +static inline double op_pow(double a, double b) { return pow(fabs(a), b); } +static inline double op_atan2(double a, double b) { return atan2(a, b); } +static inline double op_and(double a, double b) +{ + return (a != 0.0 && b != 0.0) ? 1.0 : 0.0; +} +static inline double op_or(double a, double b) +{ + return (a != 0.0 || b != 0.0) ? 1.0 : 0.0; +} +static inline double op_not(double a) { return a != 0.0 ? 0.0 : 1.0; } +static inline double op_min(double a, double b) { return a < b ? a : b; } +static inline double op_max(double a, double b) { return a > b ? a : b; } +static inline double op_negate(double a) { return -a; } +static inline double op_sin(double a) { return sin(a); } +static inline double op_cos(double a) { return cos(a); } +static inline double op_tan(double a) { return tan(a); } +static inline double op_log(double a) { return log(fabs(a)); } +static inline double op_exp(double a) { return exp(a); } +static inline double op_sqrt(double a) { return sqrt(a); } +static inline double op_floor(double a) { return floor(a); } +static inline double op_ceil(double a) { return ceil(a); } +static inline double op_abs(double a) { return fabs(a); } +static inline double op_pi(void) { return M_PI; } +static inline double op_random(void) { return drand48(); } diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 6d1c5bb06..ec2db6414 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -40,6 +40,9 @@ #include "forth.h" +#define NO_INCBIN +#include "forth-jit-inc.h" + enum forth_opcode { OP_CALL_BUILTIN, OP_EVAL_CODE, @@ -82,6 +85,7 @@ DEFINE_ARRAY_TYPE(forth_code, union forth_inst) struct forth_builtin { const char *name; size_t name_len; + const char *c_name; union { void (*callback)(union forth_inst *, double *d_stack, @@ -258,7 +262,7 @@ static bool check_stack_effects(const struct forth_ctx *ctx, } #if defined(DUMP_CODE) -static void dump_code(const struct forth_ir_code *code) +static void dump_code_ir(const struct forth_ir_code *code) { const struct forth_ir *ir; size_t i = 0; @@ -290,6 +294,202 @@ static void dump_code(const struct forth_ir_code *code) } } } + +#define JS_PUSH(val_) \ + ({ \ + if (j > (jump_stack + 64)) \ + return false; \ + *j++ = (val_); \ + }) +#define JS_POP(val_) \ + ({ \ + if (j <= jump_stack) \ + return false; \ + *--j; \ + }) + +static const char *c_builtin_name(const struct forth_builtin *b, + char buffer[static 64]) +{ + /* FIXME add op_* names to forth_builtin; maybe do this during new_word()? */ + if (streq(b->name, "+")) + return "op_add"; + if (streq(b->name, "-")) + return "op_sub"; + if (streq(b->name, "/")) + return "op_div"; + if (streq(b->name, "*")) + return "op_mult"; + if (streq(b->name, "<>")) + return "op_diff"; + if (streq(b->name, "=")) + return "op_eq"; + if (streq(b->name, ">")) + return "op_gt"; + if (streq(b->name, ">=")) + return "op_gte"; + if (streq(b->name, "<")) + return "op_lt"; + if (streq(b->name, "<=")) + return "op_lte"; + if (streq(b->name, "**")) + return "op_pow"; + if (streq(b->name, "%")) + return "op_mod"; + if (streq(b->name, ">r")) + return "op_tor"; + if (streq(b->name, "r>")) + return "op_fromr"; + if (streq(b->name, "r@")) + return "op_rtord"; + if (streq(b->name, "@")) + return "op_recall"; + if (streq(b->name, "!")) + return "op_store"; + if (streq(b->name, "2dup")) + return "op_2dup"; + if (streq(b->name, "z+")) + return "op_zplus"; + if (streq(b->name, "z*")) + return "op_zmult"; + if (streq(b->name, "-rot")) + return "op_minusrot"; + int ret = snprintf(buffer, 64, "op_%s", b->name); + return (ret < 0 || ret > 64) ? NULL : buffer; +} + + +#define GET_TMP(num_) \ + ({ \ + int n = (num_); \ + const char *out; \ + if (n > last_undeclared) { \ + out = "double tmp"; \ + last_undeclared = n; \ + } else { \ + out = "tmp"; \ + } \ + out; \ + }) + +static bool dump_code_c(const struct forth_ir_code *code) +{ + size_t jump_stack[64]; + size_t *j = jump_stack; + char name_buffer[64]; + int last_tmp = 0; + int last_undeclared = -1; + const struct forth_ir *ir; + size_t i = 0; + + printf("dumping code @ %p\n", code); + + fwrite(forth_jit_value.value, forth_jit_value.len, 1, stdout); + printf("void compute(double x, double y, double t, double *r, double *g, " + "double *b) {\n"); + + LWAN_ARRAY_FOREACH (code, ir) { + switch (ir->opcode) { + case OP_EVAL_CODE: + __builtin_unreachable(); + case OP_CALL_BUILTIN: { + const struct forth_builtin *b = + find_builtin_by_callback(ir->callback); + last_tmp -= b->d_pops; + + if (b->d_pushes == 0) { + printf(" %s(", c_builtin_name(b, name_buffer)); + for (int arg = 0; arg < b->d_pops; arg++) { + printf("tmp%d, ", last_tmp + arg - 1); + } + printf(");\n"); + } else if (b->d_pushes == 1) { + if (streq(b->name, "t") || streq(b->name, "x") || + streq(b->name, "y")) { + int t = last_tmp++; + printf(" %s%d = %s;\n", GET_TMP(t), t, b->name); + } else { + int t = last_tmp++; + printf(" %s%d = %s(", GET_TMP(t), t, + c_builtin_name(b, name_buffer)); + for (int arg = 0; arg < b->d_pops; arg++) { + t = last_tmp + arg - 1; + if (arg == b->d_pops - 1) { + printf("tmp%d", t); + } else { + printf("tmp%d, ", t); + } + } + printf(");\n"); + } + + } else { + printf(" %s(", c_builtin_name(b, name_buffer)); + for (int arg = 0; arg < b->d_pops; arg++) { + printf("tmp%d, ", last_tmp + arg - 1); + } + for (int out_arg = 0; out_arg < b->d_pushes; out_arg++) { + int t = last_tmp + out_arg - 1; + if (out_arg == b->d_pushes - 1) { + printf("&tmp%d", t); + } else { + printf("&tmp%d, ", t); + } + } + last_tmp += b->d_pushes; + printf(");\n"); + } + + break; + } + case OP_NUMBER: { + int t = last_tmp++; + printf(" %s%d = %lf;\n", GET_TMP(t), t, ir->number); + break; + } + case OP_JUMP_IF: + printf(" if (tmp%d == 0.0) {\n", --last_tmp); + JS_PUSH((size_t)last_tmp); + JS_PUSH((size_t)last_undeclared); + break; + case OP_JUMP: + printf(" } else {\n"); + last_undeclared = (int)JS_POP(); + last_tmp = (int)JS_POP(); + break; + case OP_NOP: + printf(" }\n"); + break; + } + + i++; + } + + switch (last_tmp) { + case 3: + printf(" *r = tmp2;\n"); + printf(" *g = tmp1;\n"); + printf(" *b = tmp0;\n"); + break; + case 4: + printf(" *r = tmp3;\n"); + printf(" *g = tmp2;\n"); + printf(" *b = tmp1;\n"); + break; + default: + printf(" *r = *g = *b = 0.0;\n"); + } + + printf("}\n"); + + return true; +} + +static void dump_code(const struct forth_ir_code *code) +{ + dump_code_ir(code); + dump_code_c(code); +} #endif bool forth_run(struct forth_ctx *ctx, struct forth_vars *vars) @@ -429,19 +629,6 @@ static bool inline_calls_code(struct forth_ctx *ctx, size_t jump_stack[64]; size_t *j = jump_stack; -#define JS_PUSH(val_) \ - ({ \ - if (j > (jump_stack + 64)) \ - return false; \ - *j++ = (val_); \ - }) -#define JS_POP(val_) \ - ({ \ - if (j <= jump_stack) \ - return false; \ - *--j; \ - }) - LWAN_ARRAY_FOREACH (orig_code, ir) { if (ir->opcode == OP_EVAL_CODE) { if (!inline_calls_code(ctx, ir->code, new_code)) { @@ -469,9 +656,6 @@ static bool inline_calls_code(struct forth_ctx *ctx, } } -#undef JS_PUSH -#undef JS_POP - return true; } @@ -640,6 +824,8 @@ bool forth_parse_string(struct forth_ctx *ctx, const char *code) return true; } +#define C_NAME(id_) #id_ + #define BUILTIN_DETAIL(name_, id_, struct_id_, d_pushes_, d_pops_, r_pushes_, \ r_pops_) \ static void id_(union forth_inst *inst, double *d_stack, double *r_stack, \ @@ -649,6 +835,7 @@ bool forth_parse_string(struct forth_ctx *ctx, const char *code) __attribute__((aligned(8))) struct_id_ = { \ .name = name_, \ .name_len = sizeof(name_) - 1, \ + .c_name = C_NAME(id_), \ .callback = id_, \ .d_pushes = d_pushes_, \ .d_pops = d_pops_, \ From b2fe883099c068ba371e93ae156f8dd4dcd7c85f Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 5 Feb 2025 16:31:37 -0800 Subject: [PATCH 2431/2505] JIT op_sample stub should set output variables to 0 --- src/samples/forthsalon/forth-jit.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/samples/forthsalon/forth-jit.h b/src/samples/forthsalon/forth-jit.h index e0a2d8a9e..0947e776e 100644 --- a/src/samples/forthsalon/forth-jit.h +++ b/src/samples/forthsalon/forth-jit.h @@ -12,7 +12,9 @@ static inline double op_my(void) { return 0; } static inline double op_button(double b) { return 0; } static inline double op_buttons() { return 0; } static inline double op_audio(double a) { return 0; } -static inline void op_sample(double a, double b, double *aa, double *bb, double *cc) {} +static inline void op_sample(double a, double b, double *aa, double *bb, double *cc) { + *aa = *bb = *cc = 0; +} static inline double op_bwsample(double a, double b) { return 0; } static inline void op_dup(double a, double *aa, double *bb) { *aa = *bb = a; } From 672c0b1919cee9f27ae61a2325a2c6d07c9bfbd8 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 5 Feb 2025 16:39:11 -0800 Subject: [PATCH 2432/2505] Restore last_undeclared when leaving if/else block --- src/samples/forthsalon/forth.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index ec2db6414..2c7285cb4 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -456,9 +456,11 @@ static bool dump_code_c(const struct forth_ir_code *code) printf(" } else {\n"); last_undeclared = (int)JS_POP(); last_tmp = (int)JS_POP(); + JS_PUSH((size_t)last_undeclared); break; case OP_NOP: printf(" }\n"); + last_undeclared = (int)JS_POP(); break; } From 2ea5ae95276509c4ce87a9a97db59eccbae22740 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 5 Feb 2025 19:34:27 -0800 Subject: [PATCH 2433/2505] Add README to forthsalon sample --- src/samples/forthsalon/README | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 src/samples/forthsalon/README diff --git a/src/samples/forthsalon/README b/src/samples/forthsalon/README new file mode 100644 index 000000000..eb184dc02 --- /dev/null +++ b/src/samples/forthsalon/README @@ -0,0 +1,2 @@ + +This is (very) experimental and is prone to crashing. From 034ca19f73e62a2a385d4669721a0b645b4f1cd6 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 17 Feb 2025 10:09:31 -0800 Subject: [PATCH 2434/2505] Use -mtls-dialect=gnu2 in release builds, if available TLS is seldomly used in Lwan (it's used mostly for the per-thread PRNG state used by lwan_random_uint64()), but let's try this thing! In-depth description of methods to grab the TLS here: https://yosefk.com/blog/cxx-thread-local-storage-performance.html --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 461b18742..2be1da768 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -286,6 +286,8 @@ if (${CMAKE_BUILD_TYPE} MATCHES "Rel") enable_c_flag_if_avail(-ffat-lto-objects C_FLAGS_REL LWAN_HAVE_LTO_FAT_OBJS) enable_c_flag_if_avail(-mcrc32 C_FLAGS_REL LWAN_HAVE_BUILTIN_IA32_CRC32) + + enable_c_flag_if_avail(-mtls-dialect=gnu2 C_FLAGS_REL LWAN_HAVE_GNU2_TLS_DIALECT) endif () if (${CMAKE_BUILD_TYPE} MATCHES "Deb") From 4569354fbd28f2e06ff3ff7621eec2ce63cc782d Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 23 Feb 2025 10:06:15 -0800 Subject: [PATCH 2435/2505] Macros to lazily-initialize globals: either once, or thread-local This makes it easier to declare a lazily-initialized value by implementing only a function that initializes and returns a value; the rest is hidden by the macro. For instance, to declare a lazily-initialized thread-local, one would write: LWAN_LAZY_THREAD_LOCAL(bool, is_something_supported) { /* Perform computation to determine if something * is supported */ return true; } This function will be called only once per calling thread. Likewise with the lazy global macro (LWAN_LAZY_GLOBAL), where values are initialized using pthread_once. --- src/lib/lwan-private.h | 33 +++++++++++++++++++++ src/lib/lwan-socket.c | 42 +++++++++------------------ src/lib/lwan-status.c | 24 +++------------ src/samples/techempower/techempower.c | 32 +++++++++----------- 4 files changed, 63 insertions(+), 68 deletions(-) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 9b2a0523e..19d1eccd4 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -28,6 +28,39 @@ #define DEFAULT_BUFFER_SIZE 4096 #define DEFAULT_HEADERS_SIZE 4096 +#define LWAN_LAZY_GLOBAL(type_, name_) \ + static type_ lazy_global_##name_; \ + static type_ new_lazy_global_##name_(void); \ + static inline void initialize_lazy_global_##name_(void) \ + { \ + lazy_global_##name_ = new_lazy_global_##name_(); \ + } \ + static inline type_ name_(void) \ + { \ + static pthread_once_t once = PTHREAD_ONCE_INIT; \ + pthread_once(&once, initialize_lazy_global_##name_); \ + return lazy_global_##name_; \ + } \ + static inline type_ new_lazy_global_##name_(void) + +#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) +/* Workaround for: + * https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=15216 */ +#define LWAN_LAZY_THREAD_LOCAL(type_, name_) static inline type_ name_(void) +#else +#define LWAN_LAZY_THREAD_LOCAL(type_, name_) \ + static type_ new_lazy_thread_local_##name_(void); \ + static inline type_ name_(void) \ + { \ + static __thread type_ val; \ + static __thread bool initialized; \ + if (!initialized) \ + val = new_lazy_thread_local_##name_(); \ + return val; \ + } \ + static inline type_ new_lazy_thread_local_##name_(void) +#endif + struct lwan_constructor_callback_info { void (*func)(struct lwan *); int prio; diff --git a/src/lib/lwan-socket.c b/src/lib/lwan-socket.c index fc6d95378..28ec47640 100644 --- a/src/lib/lwan-socket.c +++ b/src/lib/lwan-socket.c @@ -38,37 +38,29 @@ #include "sd-daemon.h" #ifdef __linux__ - -static bool reno_supported; -static void init_reno_supported(void) +LWAN_LAZY_GLOBAL(bool, is_reno_supported) { FILE *allowed; - - reno_supported = false; + bool supported = false; allowed = fopen("/proc/sys/net/ipv4/tcp_allowed_congestion_control", "re"); - if (!allowed) - return; - - char line[4096]; - if (fgets(line, sizeof(line), allowed)) { - if (strstr(line, "reno")) - reno_supported = true; + if (allowed) { + char line[4096]; + if (fgets(line, sizeof(line), allowed)) { + if (strstr(line, "reno")) + supported = true; + } + fclose(allowed); } - fclose(allowed); -} -static bool is_reno_supported(void) -{ - static pthread_once_t reno_supported_once = PTHREAD_ONCE_INIT; - pthread_once(&reno_supported_once, init_reno_supported); - return reno_supported; + return supported; } #endif -static int backlog_size; -static void init_backlog_size(void) +LWAN_LAZY_GLOBAL(int, get_backlog_size) { + int backlog_size = SOMAXCONN; + #ifdef __linux__ FILE *somaxconn; @@ -81,14 +73,6 @@ static void init_backlog_size(void) } #endif - if (!backlog_size) - backlog_size = SOMAXCONN; -} - -static int get_backlog_size(void) -{ - static pthread_once_t backlog_size_once = PTHREAD_ONCE_INIT; - pthread_once(&backlog_size_once, init_backlog_size); return backlog_size; } diff --git a/src/lib/lwan-status.c b/src/lib/lwan-status.c index 8115e9424..464f6ae25 100644 --- a/src/lib/lwan-status.c +++ b/src/lib/lwan-status.c @@ -142,23 +142,13 @@ static inline char *strerror_thunk_r(int error_number, char *buffer, size_t len) } #ifndef NDEBUG -static long gettid_cached(void) + +LWAN_LAZY_THREAD_LOCAL(long, gettid_cached) { -#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) - /* Workaround for: - * https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=15216 */ return gettid(); -#else - static __thread long tid; - - if (!tid) - tid = gettid(); - - return tid; -#endif } -static const char *get_thread_emoji(void) +LWAN_LAZY_THREAD_LOCAL(const char *, get_thread_emoji) { static const char *emojis[] = { "🐶", "🐱", "🐭", "🐹", "🐰", "🦊", "🐻", "🐼", "🐨", "🐯", "🦁", "🐮", @@ -171,14 +161,8 @@ static const char *get_thread_emoji(void) "🐩", "🐈", "🐓", "🦃", "🦤", "🦚", "🦜", "🦢", "🦩", "🕊", "🐇", "🦝", "🦨", "🦡", "🦫", "🦦", "🦥", "🐁", "🐀", "🐿", "🦔", "🐉", "🐲", }; - static __thread const char *emoji; static unsigned int last_emoji_id; - - if (!emoji) { - emoji = emojis[ATOMIC_INC(last_emoji_id) % (int)N_ELEMENTS(emojis)]; - } - - return emoji; + return emojis[ATOMIC_INC(last_emoji_id) % (int)N_ELEMENTS(emojis)]; } #endif diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index f94223625..7a2a541af 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -126,28 +126,22 @@ static const struct json_obj_descr queries_array_desc = db_json_desc, N_ELEMENTS(db_json_desc)); -static struct db *get_db(void) +LWAN_LAZY_THREAD_LOCAL(struct db *, get_db) { - static __thread struct db *database; - - if (!database) { - switch (db_connection_params.type) { - case DB_CONN_MYSQL: - database = db_connect_mysql(db_connection_params.mysql.hostname, - db_connection_params.mysql.user, - db_connection_params.mysql.password, - db_connection_params.mysql.database); - break; - case DB_CONN_SQLITE: - database = db_connect_sqlite(db_connection_params.sqlite.path, true, - db_connection_params.sqlite.pragmas); - break; - } - if (!database) - lwan_status_critical("Could not connect to the database"); + switch (db_connection_params.type) { + case DB_CONN_MYSQL: + return db_connect_mysql(db_connection_params.mysql.hostname, + db_connection_params.mysql.user, + db_connection_params.mysql.password, + db_connection_params.mysql.database); + + case DB_CONN_SQLITE: + return db_connect_sqlite(db_connection_params.sqlite.path, true, + db_connection_params.sqlite.pragmas); } - return database; + lwan_status_critical("Could not connect to the database"); + __builtin_unreachable(); } static int append_to_strbuf(const char *bytes, size_t len, void *data) From 42937580f49fca3ba5e56d100c6fa95857a0d03e Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 23 Feb 2025 10:13:23 -0800 Subject: [PATCH 2436/2505] Ensure get_db() crits when TWFB benchmark harness fails to connect --- src/samples/techempower/techempower.c | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/samples/techempower/techempower.c b/src/samples/techempower/techempower.c index 7a2a541af..6b9ed8efd 100644 --- a/src/samples/techempower/techempower.c +++ b/src/samples/techempower/techempower.c @@ -128,20 +128,30 @@ static const struct json_obj_descr queries_array_desc = LWAN_LAZY_THREAD_LOCAL(struct db *, get_db) { + struct db *db; + switch (db_connection_params.type) { case DB_CONN_MYSQL: - return db_connect_mysql(db_connection_params.mysql.hostname, - db_connection_params.mysql.user, - db_connection_params.mysql.password, - db_connection_params.mysql.database); + db = db_connect_mysql(db_connection_params.mysql.hostname, + db_connection_params.mysql.user, + db_connection_params.mysql.password, + db_connection_params.mysql.database); + break; case DB_CONN_SQLITE: - return db_connect_sqlite(db_connection_params.sqlite.path, true, - db_connection_params.sqlite.pragmas); + db = db_connect_sqlite(db_connection_params.sqlite.path, true, + db_connection_params.sqlite.pragmas); + break; + default: + __builtin_unreachable(); + } + + if (!db) { + lwan_status_critical("Could not connect to the database"); + __builtin_unreachable(); } - lwan_status_critical("Could not connect to the database"); - __builtin_unreachable(); + return db; } static int append_to_strbuf(const char *bytes, size_t len, void *data) From 71e4fe01dc8669cd5f6125b3da183df4b4b69762 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 23 Feb 2025 13:16:49 -0800 Subject: [PATCH 2437/2505] Ensure lazily-initialized TLS variables are indeed initialized once --- src/lib/lwan-private.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 19d1eccd4..8b194b141 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -54,8 +54,10 @@ { \ static __thread type_ val; \ static __thread bool initialized; \ - if (!initialized) \ + if (UNLIKELY(!initialized)) { \ val = new_lazy_thread_local_##name_(); \ + initialized = true; \ + } \ return val; \ } \ static inline type_ new_lazy_thread_local_##name_(void) From d851482bf0a1d0ce839a7c6eac346fb1cf3afe75 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 23 Feb 2025 13:19:56 -0800 Subject: [PATCH 2438/2505] Mark lazily-initialized variable initializers as cold/no-inline This keeps them out of the way until it's time to call them. --- src/lib/lwan-private.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 8b194b141..30edade4a 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -35,13 +35,13 @@ { \ lazy_global_##name_ = new_lazy_global_##name_(); \ } \ - static inline type_ name_(void) \ + static type_ name_(void) \ { \ static pthread_once_t once = PTHREAD_ONCE_INIT; \ pthread_once(&once, initialize_lazy_global_##name_); \ return lazy_global_##name_; \ } \ - static inline type_ new_lazy_global_##name_(void) + __attribute__((cold, noinline)) static type_ new_lazy_global_##name_(void) #if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) /* Workaround for: @@ -50,7 +50,7 @@ #else #define LWAN_LAZY_THREAD_LOCAL(type_, name_) \ static type_ new_lazy_thread_local_##name_(void); \ - static inline type_ name_(void) \ + static type_ name_(void) \ { \ static __thread type_ val; \ static __thread bool initialized; \ @@ -60,7 +60,8 @@ } \ return val; \ } \ - static inline type_ new_lazy_thread_local_##name_(void) + __attribute__((cold, \ + noinline)) static type_ new_lazy_thread_local_##name_(void) #endif struct lwan_constructor_callback_info { From e75f53f91e1c26a49b2688a6923fcdd20acf11e9 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 24 Feb 2025 23:09:34 -0800 Subject: [PATCH 2439/2505] More tweaks to function attributes for lazily-initialized values --- src/lib/lwan-private.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index 30edade4a..c6a7952a1 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -31,7 +31,7 @@ #define LWAN_LAZY_GLOBAL(type_, name_) \ static type_ lazy_global_##name_; \ static type_ new_lazy_global_##name_(void); \ - static inline void initialize_lazy_global_##name_(void) \ + __attribute__((cold)) static void initialize_lazy_global_##name_(void) \ { \ lazy_global_##name_ = new_lazy_global_##name_(); \ } \ @@ -41,7 +41,9 @@ pthread_once(&once, initialize_lazy_global_##name_); \ return lazy_global_##name_; \ } \ - __attribute__((cold, noinline)) static type_ new_lazy_global_##name_(void) + __attribute__(( \ + cold, \ + always_inline)) static inline type_ new_lazy_global_##name_(void) #if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) /* Workaround for: From 47f8739be205e3472963f28f4afb06c33bbd64f6 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 27 Feb 2025 13:04:15 -0800 Subject: [PATCH 2440/2505] Support get_backlog_size() on BSD-based systems --- src/lib/lwan-socket.c | 10 ++++++++-- src/lib/missing/sys/sysctl.h | 24 ++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 src/lib/missing/sys/sysctl.h diff --git a/src/lib/lwan-socket.c b/src/lib/lwan-socket.c index 28ec47640..71b38a026 100644 --- a/src/lib/lwan-socket.c +++ b/src/lib/lwan-socket.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -62,15 +63,20 @@ LWAN_LAZY_GLOBAL(int, get_backlog_size) int backlog_size = SOMAXCONN; #ifdef __linux__ - FILE *somaxconn; + FILE *somaxconn = fopen("/proc/sys/net/core/somaxconn", "re"); - somaxconn = fopen("/proc/sys/net/core/somaxconn", "re"); if (somaxconn) { int tmp; if (fscanf(somaxconn, "%d", &tmp) == 1) backlog_size = tmp; fclose(somaxconn); } +#elifdef KIPC_SOMAXCONN + int mib[] = {CTL_KERN, KERN_IPC, KIPC_SOMAXCONN, -1}; + int tmp; + + if (!sysctl(mib, N_ELEMENTS(mib), NULL, NULL, &tmp, sizeof(tmp))) + backlog_size = tmp; #endif return backlog_size; diff --git a/src/lib/missing/sys/sysctl.h b/src/lib/missing/sys/sysctl.h new file mode 100644 index 000000000..be07a41e2 --- /dev/null +++ b/src/lib/missing/sys/sysctl.h @@ -0,0 +1,24 @@ +/* + * lwan - web server + * Copyright (c) 2025 L. A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__OpenBSD__) +#include_next +#endif + From 5008f1fa730b15ce92317c01b42f29b8a2f9e529 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 27 Feb 2025 13:20:03 -0800 Subject: [PATCH 2441/2505] Support OpenBSD in get_backlog_size() The OpenBSD mib is different from macOS and FreeBSD, because of course it is different. --- src/lib/lwan-socket.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-socket.c b/src/lib/lwan-socket.c index 71b38a026..fa499ad24 100644 --- a/src/lib/lwan-socket.c +++ b/src/lib/lwan-socket.c @@ -71,8 +71,13 @@ LWAN_LAZY_GLOBAL(int, get_backlog_size) backlog_size = tmp; fclose(somaxconn); } -#elifdef KIPC_SOMAXCONN +#elif defined(KIPC_SOMAXCONN) || defined(KERN_SOMAXCONN) + +#if defined(KERN_SOMAXCONN) /* OpenBSD */ + int mib[] = {CTL_KERN, KERN_SOMAXCONN, -1}; +#else /* FreeBSD, macOS */ int mib[] = {CTL_KERN, KERN_IPC, KIPC_SOMAXCONN, -1}; +#endif int tmp; if (!sysctl(mib, N_ELEMENTS(mib), NULL, NULL, &tmp, sizeof(tmp))) From 5cbb6722babd0d7cfdf1ba2f90d9d6b8cc7f9f50 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 27 Feb 2025 13:36:20 -0800 Subject: [PATCH 2442/2505] Address some compile warnings on FreeBSD --- src/lib/lwan-socket.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/lwan-socket.c b/src/lib/lwan-socket.c index fa499ad24..2f4016bd9 100644 --- a/src/lib/lwan-socket.c +++ b/src/lib/lwan-socket.c @@ -78,7 +78,7 @@ LWAN_LAZY_GLOBAL(int, get_backlog_size) #else /* FreeBSD, macOS */ int mib[] = {CTL_KERN, KERN_IPC, KIPC_SOMAXCONN, -1}; #endif - int tmp; + int tmp = 0; if (!sysctl(mib, N_ELEMENTS(mib), NULL, NULL, &tmp, sizeof(tmp))) backlog_size = tmp; @@ -255,6 +255,8 @@ static int set_socket_options(const struct lwan *l, int fd) if (is_reno_supported()) setsockopt(fd, IPPROTO_TCP, TCP_CONGESTION, "reno", 4); +#else + (void)l; #endif return fd; From e6b8a70be1824b4147b754b6dfea33ef3ec7f004 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 27 Feb 2025 15:19:57 -0800 Subject: [PATCH 2443/2505] Use lazy-global to determine temporary directory --- src/lib/lwan-request.c | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index fc1fa3ba8..1a8cc64ab 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1077,12 +1077,9 @@ static const char *is_dir_good_for_tmp(const char *v) return v; } -static const char *temp_dir; static const size_t body_buffer_temp_file_thresh = 1<<20; -__attribute__((cold)) -static const char * -get_temp_dir(void) +LWAN_LAZY_GLOBAL(const char *, get_temp_dir) { const char *tmpdir; @@ -1112,22 +1109,17 @@ get_temp_dir(void) return NULL; } -LWAN_CONSTRUCTOR(initialize_temp_dir, 0) -{ - temp_dir = get_temp_dir(); -} - static int create_temp_file(void) { char template[PATH_MAX]; mode_t prev_mask; int ret; - if (UNLIKELY(!temp_dir)) + if (UNLIKELY(!get_temp_dir())) return -ENOENT; #if defined(O_TMPFILE) - int fd = open(temp_dir, + int fd = open(get_temp_dir(), O_TMPFILE | O_CREAT | O_RDWR | O_EXCL | O_CLOEXEC | O_NOFOLLOW | O_NOATIME, S_IRUSR | S_IWUSR); @@ -1135,7 +1127,7 @@ static int create_temp_file(void) return fd; #endif - ret = snprintf(template, sizeof(template), "%s/lwanXXXXXX", temp_dir); + ret = snprintf(template, sizeof(template), "%s/lwanXXXXXX", get_temp_dir()); if (UNLIKELY(ret < 0 || ret >= (int)sizeof(template))) return -EOVERFLOW; From 7aac55590f5f5f3a2170f47e904788580bf34fc2 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 27 Feb 2025 15:21:17 -0800 Subject: [PATCH 2444/2505] Add support for __attribute__((access)) This is a GCC extension that lets one mark how a function parameter is used, especially if it is a pointer to a buffer with another parameter used with the length. You can specify, for instance, that a parameter is read-only, giving a bit more protection against sloppy usage of const and extra checks that require the value to be initialized before calling the function. Details: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-access-function-attribute --- CMakeLists.txt | 1 + src/cmake/lwan-build-config.h.cmake | 3 +++ src/lib/lwan.h | 6 ++++++ 3 files changed, 10 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2be1da768..40ce71a79 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -217,6 +217,7 @@ endif() # # Check for GCC builtin functions # +check_c_source_compiles("__attribute__((access(read_only, 1))) int main(char *p) { return 0; }" LWAN_HAVE_ACCESS_ATTRIBUTE) check_c_source_compiles("int main(void) { __builtin_cpu_init(); }" LWAN_HAVE_BUILTIN_CPU_INIT) check_c_source_compiles("int main(void) { __builtin_expect_with_probability(0, 0, 0); }" LWAN_HAVE_BUILTIN_EXPECT_PROBABILITY) check_c_source_compiles("int main(void) { __builtin_clzll(0); }" LWAN_HAVE_BUILTIN_CLZLL) diff --git a/src/cmake/lwan-build-config.h.cmake b/src/cmake/lwan-build-config.h.cmake index 7cf31ab5f..f8c4cfcfe 100644 --- a/src/cmake/lwan-build-config.h.cmake +++ b/src/cmake/lwan-build-config.h.cmake @@ -60,6 +60,9 @@ #cmakedefine LWAN_HAVE_BUILTIN_FPCLASSIFY #cmakedefine LWAN_HAVE_BUILTIN_EXPECT_PROBABILITY +/* GCC extensions */ +#cmakedefine LWAN_HAVE_ACCESS_ATTRIBUTE + /* C11 _Static_assert() */ #cmakedefine LWAN_HAVE_STATIC_ASSERT diff --git a/src/lib/lwan.h b/src/lib/lwan.h index 47c574bc3..e107e9fef 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -163,6 +163,12 @@ static ALWAYS_INLINE uint64_t string_as_uint64(const char *s) #define LWAN_ARRAY_PARAM(length) [static length] #endif +#if defined(LWAN_HAVE_ACCESS_ATTRIBUTE) +#define LWAN_ACCESS_PARAM(...) __attribute__((access(__VA_ARGS__))) +#else +#define LWAN_ACCESS_PARAM(...) +#endif + #include "lwan-http-status.h" #define GENERATE_ENUM_ITEM(id, code, short, long) HTTP_ ## id = code, From 9951929061f8c66717000f8c088a1eea69a09c76 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 27 Feb 2025 15:23:36 -0800 Subject: [PATCH 2445/2505] Sprinkle some LWAN_ACCESS_PARAM around The whole codebase could get more of these kinds of annotations, but let's start small. --- src/lib/lwan-request.c | 8 ++++++++ src/lib/lwan.h | 11 ++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 1a8cc64ab..b89c64620 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -78,6 +78,7 @@ struct proxy_header_v2 { }; +LWAN_ACCESS_PARAM(read_only, 1) static bool parse_ascii_port(char *port, unsigned short *out) { @@ -100,6 +101,7 @@ parse_ascii_port(char *port, unsigned short *out) return true; } +LWAN_ACCESS_PARAM(read_write, 1) static char * strsep_char(char *strp, const char *end, char delim) { @@ -119,6 +121,7 @@ strsep_char(char *strp, const char *end, char delim) return ptr + 1; } +LWAN_ACCESS_PARAM(read_only, 2) static char * parse_proxy_protocol_v1(struct lwan_request *request, char *buffer) { @@ -185,6 +188,7 @@ parse_proxy_protocol_v1(struct lwan_request *request, char *buffer) return buffer + size; } +LWAN_ACCESS_PARAM(read_only, 2) static char *parse_proxy_protocol_v2(struct lwan_request *request, char *buffer) { struct proxy_header_v2 *hdr = (struct proxy_header_v2 *)buffer; @@ -245,6 +249,7 @@ static char *parse_proxy_protocol_v2(struct lwan_request *request, char *buffer) __builtin_expect(value1, value2) #endif +LWAN_ACCESS_PARAM(read_only, 2) static ALWAYS_INLINE char *identify_http_method(struct lwan_request *request, char *buffer) { @@ -264,6 +269,7 @@ static ALWAYS_INLINE char *identify_http_method(struct lwan_request *request, return NULL; } +LWAN_ACCESS_PARAM(read_write, 1) __attribute__((nonnull(1))) static ssize_t url_decode(char *str) { static const unsigned char tbl1[256] = { @@ -331,6 +337,8 @@ __attribute__((nonnull(1))) static ssize_t url_decode(char *str) return (ssize_t)strlen(str); } +LWAN_ACCESS_PARAM(read_only, 1) +LWAN_ACCESS_PARAM(read_only, 2) static int key_value_compare(const void *a, const void *b) { return strcmp(((const struct lwan_key_value *)a)->key, diff --git a/src/lib/lwan.h b/src/lib/lwan.h index e107e9fef..d59227964 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -570,11 +570,13 @@ size_t lwan_prepare_response_header(struct lwan_request *request, enum lwan_http_status status, char header_buffer[], size_t header_buffer_size) - __attribute__((warn_unused_result)); + __attribute__((warn_unused_result)) + LWAN_ACCESS_PARAM(write_only, 3, 4); const char *lwan_request_get_post_param(struct lwan_request *request, const char *key) __attribute__((warn_unused_result, pure)); + const char *lwan_request_get_query_param(struct lwan_request *request, const char *key) __attribute__((warn_unused_result, pure)); @@ -628,12 +630,15 @@ const char *lwan_request_get_host(struct lwan_request *request); const char * lwan_request_get_remote_address(const struct lwan_request *request, char buffer LWAN_ARRAY_PARAM(INET6_ADDRSTRLEN)) - __attribute__((warn_unused_result)); + __attribute__((warn_unused_result)) + LWAN_ACCESS_PARAM(write_only, 2); const char *lwan_request_get_remote_address_and_port( const struct lwan_request *request, char buffer LWAN_ARRAY_PARAM(INET6_ADDRSTRLEN), uint16_t *port) - __attribute__((warn_unused_result)); + __attribute__((warn_unused_result)) + LWAN_ACCESS_PARAM(write_only, 2) + LWAN_ACCESS_PARAM(write_only, 3); static inline enum lwan_request_flags lwan_request_get_method(const struct lwan_request *request) From 729d11ec563cc9052a9b10fb0b462f5fe8623610 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 27 Feb 2025 15:29:25 -0800 Subject: [PATCH 2446/2505] Use lazy-global for Lua method table --- src/lib/lwan-lua.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/lib/lwan-lua.c b/src/lib/lwan-lua.c index 400a46544..5561280a3 100644 --- a/src/lib/lwan-lua.c +++ b/src/lib/lwan-lua.c @@ -423,15 +423,17 @@ static int luaopen_log(lua_State *L) } DEFINE_ARRAY_TYPE(lwan_lua_method_array, luaL_reg) -static struct lwan_lua_method_array lua_methods; -LWAN_CONSTRUCTOR(register_lua_methods, 0) +LWAN_LAZY_GLOBAL(luaL_reg *, lua_methods) { + struct lwan_lua_method_array methods; const struct lwan_lua_method_info *info; luaL_reg *r; + lwan_lua_method_array_init(&methods); + LWAN_SECTION_FOREACH(lwan_lua_method, info) { - r = lwan_lua_method_array_append(&lua_methods); + r = lwan_lua_method_array_append(&methods); if (!r) { lwan_status_critical("Could not register Lua method `%s`", info->name); @@ -441,12 +443,14 @@ LWAN_CONSTRUCTOR(register_lua_methods, 0) r->func = info->func; } - r = lwan_lua_method_array_append(&lua_methods); + r = lwan_lua_method_array_append(&methods); if (!r) lwan_status_critical("Could not add Lua method sentinel"); r->name = NULL; r->func = NULL; + + return lwan_lua_method_array_get_array(&methods); } const char *lwan_lua_state_last_error(lua_State *L) @@ -466,7 +470,7 @@ lua_State *lwan_lua_create_state(const char *script_file, const char *script) luaopen_log(L); luaL_newmetatable(L, request_metatable_name); - luaL_register(L, NULL, lwan_lua_method_array_get_array(&lua_methods)); + luaL_register(L, NULL, lua_methods()); lua_setfield(L, -1, "__index"); if (script_file) { From bde3257662eefc9f12bb976c97002221630f052c Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 27 Feb 2025 15:49:37 -0800 Subject: [PATCH 2447/2505] Check if address family from systemd socket is AF_INET or AF_INET6 --- src/lib/lwan-socket.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/lib/lwan-socket.c b/src/lib/lwan-socket.c index 2f4016bd9..213bbeec2 100644 --- a/src/lib/lwan-socket.c +++ b/src/lib/lwan-socket.c @@ -292,12 +292,13 @@ static int setup_socket_normally(const struct lwan *l, static int from_systemd_socket(const struct lwan *l, int fd) { - if (!sd_is_socket_inet(fd, AF_UNSPEC, SOCK_STREAM, 1, 0)) { - lwan_status_critical("Passed file descriptor is not a " - "listening TCP socket"); + if (sd_is_socket_inet(fd, AF_INET, SOCK_STREAM, 1, 0) == 1 || + sd_is_socket_inet(fd, AF_INET6, SOCK_STREAM, 1, 0) == 1) { + return set_socket_options(l, set_socket_flags(fd)); } - return set_socket_options(l, set_socket_flags(fd)); + lwan_status_critical("Passed file descriptor is not a " + "listening TCP socket"); } int lwan_create_listen_socket(const struct lwan *l, From b60f38676c11b383bb6e5c09b40dbb771d4d22e9 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 27 Feb 2025 16:45:07 -0800 Subject: [PATCH 2448/2505] Fix some compilation warnings with Clang --- src/lib/lwan-cache.c | 2 +- src/lib/lwan-mod-fastcgi.c | 12 ++++++++---- src/lib/lwan-mod-serve-files.c | 2 +- src/lib/lwan-private.h | 3 ++- src/lib/lwan-request.c | 3 +++ src/lib/lwan.c | 2 +- src/samples/forthsalon/forth.c | 13 +++++++------ src/samples/pastebin/main.c | 8 +++++--- 8 files changed, 28 insertions(+), 17 deletions(-) diff --git a/src/lib/lwan-cache.c b/src/lib/lwan-cache.c index cb7ec180a..5e5808809 100644 --- a/src/lib/lwan-cache.c +++ b/src/lib/lwan-cache.c @@ -90,7 +90,7 @@ static ALWAYS_INLINE void *identity_key_copy(const void *key) return (void *)key; } -static void identity_key_free(void *key) +static void identity_key_free(void *key __attribute__((unused))) { } diff --git a/src/lib/lwan-mod-fastcgi.c b/src/lib/lwan-mod-fastcgi.c index 4d93f61c7..3ccc77e72 100644 --- a/src/lib/lwan-mod-fastcgi.c +++ b/src/lib/lwan-mod-fastcgi.c @@ -162,8 +162,10 @@ add_int_param(struct lwan_strbuf *strbuf, const char *key, ssize_t value) return add_param_len(strbuf, key, strlen(key), p, len); } -static struct cache_entry * -create_script_name(const void *keyptr, void *context, void *create_contex) +static struct cache_entry *create_script_name(const void *keyptr, + void *context, + void *create_contex + __attribute__((unused))) { struct private_data *pd = context; struct script_name_cache_entry *entry; @@ -194,7 +196,8 @@ create_script_name(const void *keyptr, void *context, void *create_contex) if (!entry->script_filename) goto free_script_name; - if (strncmp(entry->script_filename, pd->script_path, strlen(pd->script_path))) + if (strncmp(entry->script_filename, pd->script_path, + strlen(pd->script_path))) goto free_script_filename; return &entry->base; @@ -209,7 +212,8 @@ create_script_name(const void *keyptr, void *context, void *create_contex) return NULL; } -static void destroy_script_name(struct cache_entry *entry, void *context) +static void destroy_script_name(struct cache_entry *entry, + void *context __attribute__((unused))) { struct script_name_cache_entry *snce = (struct script_name_cache_entry *)entry; diff --git a/src/lib/lwan-mod-serve-files.c b/src/lib/lwan-mod-serve-files.c index 2c0b1e673..4680fc9c7 100644 --- a/src/lib/lwan-mod-serve-files.c +++ b/src/lib/lwan-mod-serve-files.c @@ -489,7 +489,7 @@ static int try_open_compressed(const char *relpath, return -ENOENT; } -static bool mmap_fd(const struct serve_files_priv *priv, +static bool mmap_fd(const struct serve_files_priv *priv __attribute__((unused)), int fd, const size_t size, struct lwan_value *value) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index c6a7952a1..e49c54012 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -80,7 +80,8 @@ struct lwan_constructor_callback_info { .func = lwan_constructor_##name_, \ .prio = (prio_), \ }; \ - static ALWAYS_INLINE void lwan_constructor_##name_(struct lwan *l) + static ALWAYS_INLINE void lwan_constructor_##name_( \ + struct lwan *l __attribute__((unused))) struct lwan_request_parser_helper { struct lwan_value *buffer; /* The whole request buffer */ diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index b89c64620..6adbaf80b 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -272,6 +272,8 @@ static ALWAYS_INLINE char *identify_http_method(struct lwan_request *request, LWAN_ACCESS_PARAM(read_write, 1) __attribute__((nonnull(1))) static ssize_t url_decode(char *str) { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Winitializer-overrides" static const unsigned char tbl1[256] = { [0 ... 255] = 255, ['0'] = 0 << 4, ['1'] = 1 << 4, ['2'] = 2 << 4, ['3'] = 3 << 4, ['4'] = 4 << 4, ['5'] = 5 << 4, ['6'] = 6 << 4, @@ -287,6 +289,7 @@ __attribute__((nonnull(1))) static ssize_t url_decode(char *str) ['e'] = 14, ['f'] = 15, ['A'] = 10, ['B'] = 11, ['C'] = 12, ['D'] = 13, ['E'] = 14, ['F'] = 15, }; +#pragma GCC diagnostic pop const char *inptr = str; char *outptr = str; diff --git a/src/lib/lwan.c b/src/lib/lwan.c index 8936553df..3fc2ed154 100644 --- a/src/lib/lwan.c +++ b/src/lib/lwan.c @@ -1002,7 +1002,7 @@ void lwan_shutdown(struct lwan *l) lwan_readahead_shutdown(); } -void lwan_main_loop(struct lwan *l) +void lwan_main_loop(struct lwan *l __attribute__((unused))) { lwan_status_info("Ready to serve"); diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 2c7285cb4..94da50d41 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -380,7 +380,6 @@ static bool dump_code_c(const struct forth_ir_code *code) int last_tmp = 0; int last_undeclared = -1; const struct forth_ir *ir; - size_t i = 0; printf("dumping code @ %p\n", code); @@ -463,8 +462,6 @@ static bool dump_code_c(const struct forth_ir_code *code) last_undeclared = (int)JS_POP(); break; } - - i++; } switch (last_tmp) { @@ -712,7 +709,7 @@ static void op_nop(union forth_inst *inst, return inst[1].callback(&inst[1], d_stack, r_stack, vars); } -static void op_halt(union forth_inst *inst, +static void op_halt(union forth_inst *inst __attribute__((unused)), double *d_stack, double *r_stack, struct forth_vars *vars) @@ -856,7 +853,8 @@ bool forth_parse_string(struct forth_ctx *ctx, const char *code) .name_len = sizeof(name_) - 1, \ .callback_compiler = id_, \ }; \ - static const char *id_(struct forth_ctx *ctx, const char *code) + static const char *id_(struct forth_ctx *ctx __attribute__((unused)), \ + const char *code) #define BUILTIN(name_, d_pushes_, d_pops_) \ BUILTIN_DETAIL(name_, LWAN_TMP_ID, LWAN_TMP_ID, d_pushes_, d_pops_, 0, 0) @@ -1238,7 +1236,7 @@ BUILTIN("/", 1, 2) double v = POP_D(); if (v == 0.0) { DROP_D(); - PUSH_D(INFINITY); + PUSH_D(__builtin_inf()); } else { PUSH_D(POP_D() / v); } @@ -1494,6 +1492,9 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) #elif defined(MAIN) int main(int argc, char *argv[]) { + (void)argc; + (void)argv; + struct forth_ctx *ctx = forth_new(); if (!ctx) return 1; diff --git a/src/samples/pastebin/main.c b/src/samples/pastebin/main.c index 3b677c4e4..fc13832a5 100644 --- a/src/samples/pastebin/main.c +++ b/src/samples/pastebin/main.c @@ -39,8 +39,9 @@ struct paste { char value[]; }; -static struct cache_entry * -create_paste(const void *key, void *cache_ctx, void *create_ctx) +static struct cache_entry *create_paste(const void *key __attribute__((unused)), + void *cache_ctx __attribute__((unused)), + void *create_ctx) { const struct lwan_value *body = create_ctx; size_t alloc_size; @@ -60,7 +61,8 @@ create_paste(const void *key, void *cache_ctx, void *create_ctx) return (struct cache_entry *)paste; } -static void destroy_paste(struct cache_entry *entry, void *context) +static void destroy_paste(struct cache_entry *entry, + void *context __attribute__((unused))) { free(entry); } From f9a164e0046616ed8777d3dfbbe0f8f55af73808 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 27 Feb 2025 18:39:03 -0800 Subject: [PATCH 2449/2505] Check for f_type in struct statfs instead of checking for OS --- CMakeLists.txt | 4 ++++ src/cmake/lwan-build-config.h.cmake | 1 + src/lib/lwan-request.c | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 40ce71a79..283cb0153 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -236,6 +236,10 @@ check_c_source_compiles("#include int main(void) { setsockopt(0, SOL_SOCKET, SO_INCOMING_CPU, NULL, 0); }" LWAN_HAVE_SO_INCOMING_CPU) +check_c_source_compiles("#include +int main(void) { + struct statfs sfs = {.f_type = 0}; +}" LWAN_HAVE_STATFS_F_TYPE) # # Look for Valgrind header diff --git a/src/cmake/lwan-build-config.h.cmake b/src/cmake/lwan-build-config.h.cmake index f8c4cfcfe..b46d9f307 100644 --- a/src/cmake/lwan-build-config.h.cmake +++ b/src/cmake/lwan-build-config.h.cmake @@ -50,6 +50,7 @@ #cmakedefine LWAN_HAVE_STPCPY #cmakedefine LWAN_HAVE_EVENTFD #cmakedefine LWAN_HAVE_MINCORE +#cmakedefine LWAN_HAVE_STATFS_F_TYPE /* Compiler builtins for specific CPU instruction support */ #cmakedefine LWAN_HAVE_BUILTIN_CLZLL diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 6adbaf80b..633a2aba8 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -1076,7 +1076,7 @@ static const char *is_dir_good_for_tmp(const char *v) v); } -#ifndef __OpenBSD__ /* OpenBSD doesn't have f_type */ +#ifdef LWAN_HAVE_STATFS_F_TYPE struct statfs sb; if (!statfs(v, &sb) && sb.f_type == TMPFS_MAGIC) { lwan_status_warning("%s is a tmpfs filesystem, " From 7b3367b487431fe6ab9e631fefc51d01e84b90fb Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 27 Feb 2025 18:39:24 -0800 Subject: [PATCH 2450/2505] Remove Clang-only pragmas to disable some warnings This warning is useful in the general sense, but not in this one here. So leave them for now until I figure out a way to make it work with both GCC and Clang the same way (without having to manually specify all 512 elements). --- src/lib/lwan-request.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/lib/lwan-request.c b/src/lib/lwan-request.c index 633a2aba8..839a6c1ca 100644 --- a/src/lib/lwan-request.c +++ b/src/lib/lwan-request.c @@ -272,8 +272,6 @@ static ALWAYS_INLINE char *identify_http_method(struct lwan_request *request, LWAN_ACCESS_PARAM(read_write, 1) __attribute__((nonnull(1))) static ssize_t url_decode(char *str) { -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Winitializer-overrides" static const unsigned char tbl1[256] = { [0 ... 255] = 255, ['0'] = 0 << 4, ['1'] = 1 << 4, ['2'] = 2 << 4, ['3'] = 3 << 4, ['4'] = 4 << 4, ['5'] = 5 << 4, ['6'] = 6 << 4, @@ -289,7 +287,6 @@ __attribute__((nonnull(1))) static ssize_t url_decode(char *str) ['e'] = 14, ['f'] = 15, ['A'] = 10, ['B'] = 11, ['C'] = 12, ['D'] = 13, ['E'] = 14, ['F'] = 15, }; -#pragma GCC diagnostic pop const char *inptr = str; char *outptr = str; From 46a8d0c2c0005ddc9cf28154814b3fa8ada08a35 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 27 Feb 2025 23:45:52 -0800 Subject: [PATCH 2451/2505] Properly check for NULL in strv_extend_n() --- src/lib/sd-daemon.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/sd-daemon.c b/src/lib/sd-daemon.c index 3f93b864b..c9274dc0c 100644 --- a/src/lib/sd-daemon.c +++ b/src/lib/sd-daemon.c @@ -121,7 +121,7 @@ static int strv_extend_n(char ***p, const char *s, int n) { return -EINVAL; *p = calloc((size_t)n, sizeof(char *)); - if (!p) + if (!*p) return -ENOMEM; size_t s_size = strlen(s) + 1; From 23abc75fca8c3cb6be095b8ae43f1be0f6260061 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 1 Mar 2025 21:57:57 -0800 Subject: [PATCH 2452/2505] Get rid of forth_ir struct and use forth_inst union directly Things are still broken but the code raw code dumping before inlining seems happy. --- src/samples/forthsalon/forth.c | 488 +++++++++++++++------------------ 1 file changed, 214 insertions(+), 274 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 94da50d41..5ff67cf4e 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -54,32 +54,18 @@ enum forth_opcode { struct forth_ctx; struct forth_vars; -struct forth_ir_code; union forth_inst; -struct forth_ir { - union { - double number; - struct forth_ir_code *code; - void (*callback)(union forth_inst *, - double *d_stack, - double *r_stack, - struct forth_vars *vars); - size_t pc; - }; - enum forth_opcode opcode; -}; - union forth_inst { void (*callback)(union forth_inst *, double *d_stack, double *r_stack, struct forth_vars *vars); + struct forth_code *code; double number; size_t pc; }; -DEFINE_ARRAY_TYPE(forth_ir_code, struct forth_ir) DEFINE_ARRAY_TYPE(forth_code, union forth_inst) struct forth_builtin { @@ -107,7 +93,7 @@ struct forth_word { struct forth_vars *vars); const char *(*callback_compiler)(struct forth_ctx *ctx, const char *code); - struct forth_ir_code code; + struct forth_code code; }; const struct forth_builtin *builtin; int d_stack_len; @@ -122,13 +108,11 @@ struct forth_ctx { double r_stack[32]; }; struct { - size_t j_stack[32]; - size_t *j; + union forth_inst *j_stack[32]; + union forth_inst **j; }; }; - struct forth_code main_code; - struct forth_word *defining_word; struct forth_word *main; struct hash *words; @@ -165,20 +149,57 @@ static const struct forth_builtin *find_builtin_by_callback(void *callback) return NULL; } -static const struct forth_word *find_word_by_code(const struct forth_ctx *ctx, - const struct forth_ir_code *code) +static void op_number(union forth_inst *inst, + double *d_stack, + double *r_stack, + struct forth_vars *vars) { - struct hash_iter iter; - const void *name, *value; + *d_stack++ = inst[1].number; + return inst[2].callback(&inst[2], d_stack, r_stack, vars); +} - hash_iter_init(ctx->words, &iter); - while (hash_iter_next(&iter, &name, &value)) { - const struct forth_word *word = value; - if (&word->code == code) - return word; - } +static void op_jump_if(union forth_inst *inst, + double *d_stack, + double *r_stack, + struct forth_vars *vars) +{ + size_t pc = (*--d_stack == 0.0) ? inst[1].pc : 2; + return inst[pc].callback(&inst[pc], d_stack, r_stack, vars); +} - return NULL; +static void op_jump(union forth_inst *inst, + double *d_stack, + double *r_stack, + struct forth_vars *vars) +{ + size_t pc = inst[1].pc; + return inst[pc].callback(&inst[pc], d_stack, r_stack, vars); +} + +static void op_nop(union forth_inst *inst, + double *d_stack, + double *r_stack, + struct forth_vars *vars) +{ + return inst[1].callback(&inst[1], d_stack, r_stack, vars); +} + +static void op_halt(union forth_inst *inst __attribute__((unused)), + double *d_stack, + double *r_stack, + struct forth_vars *vars) +{ + vars->final_d_stack_ptr = d_stack; + vars->final_r_stack_ptr = r_stack; +} + +static void op_eval_code(union forth_inst *inst __attribute__((unused)), + double *d_stack, + double *r_stack, + struct forth_vars *vars) +{ + lwan_status_critical("eval_code instruction executed after inlining"); + __builtin_unreachable(); } static bool check_stack_effects(const struct forth_ctx *ctx, @@ -187,63 +208,62 @@ static bool check_stack_effects(const struct forth_ctx *ctx, /* FIXME: this isn't correct when we have JUMP_IF and JUMP * instructions: the number of items in the stacks isn't reset * to the beginning of either if/else block. */ - const struct forth_ir *ir; + const union forth_inst *inst; int items_in_d_stack = 0; int items_in_r_stack = 0; assert(!is_word_builtin(w)); - LWAN_ARRAY_FOREACH(&w->code, ir) { - switch (ir->opcode) { - case OP_EVAL_CODE: { - const struct forth_word *cw = find_word_by_code(ctx, ir->code); - if (UNLIKELY(!cw)) { - lwan_status_critical("Can't find builtin word by user code"); - return false; - } - - items_in_d_stack += cw->d_stack_len; - items_in_r_stack += cw->r_stack_len; - break; - } - case OP_CALL_BUILTIN: { - const struct forth_builtin *b = find_builtin_by_callback(ir->callback); - if (UNLIKELY(!b)) { - lwan_status_critical("Can't find builtin word by callback"); - return false; - } - - if (UNLIKELY(items_in_d_stack < b->d_pops)) { - lwan_status_error("Word `%.*s' requires %d item(s) in the D stack", - (int)b->name_len, b->name, b->d_pops); - return false; - } - if (UNLIKELY(items_in_r_stack < b->r_pops)) { - lwan_status_error("Word `%.*s' requires %d item(s) in the R stack", - (int)b->name_len, b->name, b->r_pops); - return false; - } - - items_in_d_stack -= b->d_pops; - items_in_d_stack += b->d_pushes; - items_in_r_stack -= b->r_pops; - items_in_r_stack += b->r_pushes; - break; - } - case OP_NUMBER: + LWAN_ARRAY_FOREACH(&w->code, inst) { + if (inst->callback == op_number) { items_in_d_stack++; - break; - case OP_JUMP_IF: + inst++; /* skip number immediate */ + continue; + } + if (inst->callback == op_jump_if) { if (UNLIKELY(!items_in_d_stack)) { lwan_status_error("Word `if' requires 1 item(s) in the D stack"); return false; } items_in_d_stack--; - break; - case OP_NOP: - case OP_JUMP: + inst++; /* skip pc immediate */ + continue; + } + if (inst->callback == op_jump) { + inst++; /* skip pc immediate */ + continue; + } + if (inst->callback == op_halt || inst->callback == op_nop) { + /* no immediates for these operations */ continue; } + if (inst->callback == op_eval_code) { + lwan_status_critical("eval_code instruction shouldn't appear here"); + return false; + } + + /* all other built-ins */ + const struct forth_builtin *b = find_builtin_by_callback(inst->callback); + if (UNLIKELY(!b)) { + lwan_status_critical("Can't find builtin word by callback"); + return false; + } + + if (UNLIKELY(items_in_d_stack < b->d_pops)) { + lwan_status_error("Word `%.*s' requires %d item(s) in the D stack", + (int)b->name_len, b->name, b->d_pops); + return false; + } + if (UNLIKELY(items_in_r_stack < b->r_pops)) { + lwan_status_error("Word `%.*s' requires %d item(s) in the R stack", + (int)b->name_len, b->name, b->r_pops); + return false; + } + + items_in_d_stack -= b->d_pops; + items_in_d_stack += b->d_pushes; + items_in_r_stack -= b->r_pops; + items_in_r_stack += b->r_pushes; if (UNLIKELY(items_in_d_stack >= (int)N_ELEMENTS(ctx->d_stack))) { lwan_status_error("Program would cause a stack overflow in the D stack"); @@ -261,40 +281,6 @@ static bool check_stack_effects(const struct forth_ctx *ctx, return true; } -#if defined(DUMP_CODE) -static void dump_code_ir(const struct forth_ir_code *code) -{ - const struct forth_ir *ir; - size_t i = 0; - - printf("dumping code @ %p\n", code); - - LWAN_ARRAY_FOREACH (code, ir) { - printf("%08zu ", i); - i++; - - switch (ir->opcode) { - case OP_EVAL_CODE: - printf("eval code %p\n", ir->code); - break; - case OP_CALL_BUILTIN: - printf("call builtin %p\n", ir->callback); - break; - case OP_NUMBER: - printf("number %lf\n", ir->number); - break; - case OP_JUMP_IF: - printf("if [next %zu]\n", ir->pc); - break; - case OP_JUMP: - printf("jump to %zu\n", ir->pc); - break; - case OP_NOP: - printf("nop\n"); - } - } -} - #define JS_PUSH(val_) \ ({ \ if (j > (jump_stack + 64)) \ @@ -308,6 +294,55 @@ static void dump_code_ir(const struct forth_ir_code *code) *--j; \ }) +#if defined(DUMP_CODE) +static void dump_code(const struct forth_code *code) +{ + const union forth_inst *inst; + + printf("dumping code @ %p\n", code); + + LWAN_ARRAY_FOREACH (code, inst) { + printf("%08zu ", forth_code_get_elem_index(code, (union forth_inst *)inst)); + + if (inst->callback == op_number) { + inst++; + printf("number %lf\n", inst->number); + continue; + } + if (inst->callback == op_jump_if) { + inst++; + printf("if [next %zu]\n", inst->pc); + continue; + } + if (inst->callback == op_jump) { + inst++; + printf("jump to %zu\n", inst->pc); + continue; + } + if (inst->callback == op_nop) { + printf("nop\n"); + continue; + } + if (inst->callback == op_halt) { + printf("halt\n"); + continue; + } + if (UNLIKELY(inst->callback == op_eval_code)) { + lwan_status_critical("eval_code shouldn't exist here"); + __builtin_unreachable(); + } + + const struct forth_builtin *b = find_builtin_by_callback(inst->callback); + if (b) { + printf("call builtin '%s'\n", b->name); + } else { + printf("*** inconsistency; value = %zu ***\n", inst->pc); + } + } +} +#endif + +#if 0 static const char *c_builtin_name(const struct forth_builtin *b, char buffer[static 64]) { @@ -493,21 +528,11 @@ static void dump_code(const struct forth_ir_code *code) bool forth_run(struct forth_ctx *ctx, struct forth_vars *vars) { - union forth_inst *instr = forth_code_get_elem(&ctx->main_code, 0); + union forth_inst *instr = forth_code_get_elem(&ctx->main->code, 0); instr->callback(instr, ctx->d_stack, ctx->r_stack, vars); return true; } -static struct forth_ir *new_ir(struct forth_ctx *ctx) -{ - /* FIXME: if last irruction is NOP, maybe we can reuse it? */ - - if (UNLIKELY(!ctx->defining_word)) - return NULL; - - return forth_ir_code_append(&ctx->defining_word->code); -} - static bool parse_number(const char *ptr, size_t len, double *number) { char *endptr; @@ -540,7 +565,7 @@ static struct forth_word *new_word(struct forth_ctx *ctx, if (callback) { word->callback = callback; } else { - forth_ir_code_init(&word->code); + forth_code_init(&word->code); } word->builtin = builtin; @@ -563,13 +588,15 @@ lookup_word(struct forth_ctx *ctx, const char *name, size_t len) return hash_find(ctx->words, strndupa(name, len)); } -#define EMIT_IR(...) \ - do { \ - struct forth_ir *ir_inst = new_ir(ctx); \ - if (UNLIKELY(!ir_inst)) \ +#define EMIT(arg) \ + ({ \ + union forth_inst *emitted = \ + forth_code_append(&ctx->defining_word->code); \ + if (UNLIKELY(!emitted)) \ return NULL; \ - *ir_inst = (struct forth_ir){__VA_ARGS__}; \ - } while (0) + *emitted = (union forth_inst){arg}; \ + emitted; \ + }) static const char *found_word(struct forth_ctx *ctx, const char *code, @@ -579,7 +606,8 @@ static const char *found_word(struct forth_ctx *ctx, double number; if (parse_number(word, word_len, &number)) { if (LIKELY(ctx->defining_word)) { - EMIT_IR(.number = number, .opcode = OP_NUMBER); + EMIT(.callback = op_number); + EMIT(.number = number); return code; } @@ -593,10 +621,12 @@ static const char *found_word(struct forth_ctx *ctx, if (is_word_compiler(w)) return w->callback_compiler(ctx, code); - if (is_word_builtin(w)) - EMIT_IR(.callback = w->callback, .opcode = OP_CALL_BUILTIN); - else - EMIT_IR(.code = &w->code, .opcode = OP_EVAL_CODE); + if (is_word_builtin(w)) { + EMIT(.callback = w->callback); + } else { + EMIT(.callback = op_eval_code); + EMIT(.code = &w->code); + } return code; } @@ -621,36 +651,50 @@ static const char *found_word(struct forth_ctx *ctx, } static bool inline_calls_code(struct forth_ctx *ctx, - const struct forth_ir_code *orig_code, - struct forth_ir_code *new_code) + const struct forth_code *orig_code, + struct forth_code *new_code) { - const struct forth_ir *ir; + const union forth_inst *inst; size_t jump_stack[64]; size_t *j = jump_stack; - LWAN_ARRAY_FOREACH (orig_code, ir) { - if (ir->opcode == OP_EVAL_CODE) { - if (!inline_calls_code(ctx, ir->code, new_code)) { + LWAN_ARRAY_FOREACH (orig_code, inst) { + if (inst->callback == op_eval_code) { + inst++; + if (!inline_calls_code(ctx, inst->code, new_code)) return false; - } } else { - struct forth_ir *new_ir = forth_ir_code_append(new_code); - if (!new_ir) + bool has_imm = false; + union forth_inst *new_inst = forth_code_append(new_code); + if (!new_inst) return false; - *new_ir = *ir; - - if (ir->opcode == OP_JUMP_IF) { - JS_PUSH(forth_ir_code_len(new_code) - 1); - } else if (ir->opcode == OP_JUMP) { - struct forth_ir *if_ir = - forth_ir_code_get_elem(new_code, JS_POP()); - if_ir->pc = forth_ir_code_len(new_code) - 1; - JS_PUSH(forth_ir_code_len(new_code) - 1); - } else if (ir->opcode == OP_NOP) { - struct forth_ir *else_ir = - forth_ir_code_get_elem(new_code, JS_POP()); - else_ir->pc = forth_ir_code_len(new_code) - 1; + *new_inst = *inst; + + if (inst->callback == op_jump_if) { + JS_PUSH(forth_code_len(new_code)); + has_imm = true; + } else if (inst->callback == op_jump) { + union forth_inst *if_inst = + forth_code_get_elem(new_code, JS_POP()); + if_inst->pc = forth_code_len(new_code) + 1 /* jump imm */; + JS_PUSH(forth_code_len(new_code)); + has_imm = true; + } else if (inst->callback == op_nop) { + union forth_inst *else_inst = + forth_code_get_elem(new_code, JS_POP()); + else_inst->pc = forth_code_len(new_code); + } else if (inst->callback == op_number) { + has_imm = true; + } + + if (has_imm) { + new_inst = forth_code_append(new_code); + if (!new_inst) + return false; + + inst++; + *new_inst = *inst; } } } @@ -660,118 +704,20 @@ static bool inline_calls_code(struct forth_ctx *ctx, static bool inline_calls(struct forth_ctx *ctx) { - struct forth_ir_code new_main; + struct forth_code new_main; - forth_ir_code_init(&new_main); + forth_code_init(&new_main); if (!inline_calls_code(ctx, &ctx->main->code, &new_main)) { - forth_ir_code_reset(&new_main); + forth_code_reset(&new_main); return false; } - forth_ir_code_reset(&ctx->main->code); + forth_code_reset(&ctx->main->code); ctx->main->code = new_main; return true; } -static void op_number(union forth_inst *inst, - double *d_stack, - double *r_stack, - struct forth_vars *vars) -{ - *d_stack++ = inst[1].number; - return inst[2].callback(&inst[2], d_stack, r_stack, vars); -} - -static void op_jump_if(union forth_inst *inst, - double *d_stack, - double *r_stack, - struct forth_vars *vars) -{ - size_t pc = (*--d_stack == 0.0) ? inst[1].pc : 2; - return inst[pc].callback(&inst[pc], d_stack, r_stack, vars); -} - -static void op_jump(union forth_inst *inst, - double *d_stack, - double *r_stack, - struct forth_vars *vars) -{ - size_t pc = inst[1].pc; - return inst[pc].callback(&inst[pc], d_stack, r_stack, vars); -} - -static void op_nop(union forth_inst *inst, - double *d_stack, - double *r_stack, - struct forth_vars *vars) -{ - return inst[1].callback(&inst[1], d_stack, r_stack, vars); -} - -static void op_halt(union forth_inst *inst __attribute__((unused)), - double *d_stack, - double *r_stack, - struct forth_vars *vars) -{ - vars->final_d_stack_ptr = d_stack; - vars->final_r_stack_ptr = r_stack; -} - -#define EMIT(arg) \ - do { \ - union forth_inst *inst = forth_code_append(&ctx->main_code); \ - if (UNLIKELY(!inst)) \ - goto out; \ - *inst = (union forth_inst){arg}; \ - } while (0) - -static bool ir_to_inst(struct forth_ctx *ctx) -{ - const struct forth_ir *ir; - - forth_code_init(&ctx->main_code); - - LWAN_ARRAY_FOREACH (&ctx->main->code, ir) { - switch (ir->opcode) { - case OP_NUMBER: - EMIT(.callback = op_number); - EMIT(.number = ir->number); - break; - case OP_JUMP_IF: - assert(ir->pc); - EMIT(.callback = op_jump_if); - EMIT(.pc = ir->pc - 1); - break; - case OP_JUMP: - assert(ir->pc); - EMIT(.callback = op_jump); - EMIT(.pc = ir->pc - 1); - break; - case OP_NOP: - EMIT(.callback = op_nop); - break; - case OP_CALL_BUILTIN: - EMIT(.callback = ir->callback); - break; - case OP_EVAL_CODE: - __builtin_unreachable(); - } - } - - EMIT(.callback = op_halt); - - forth_ir_code_reset(&ctx->main->code); - - return true; - -out: - forth_code_reset(&ctx->main_code); - return false; -} - -#undef EMIT - bool forth_parse_string(struct forth_ctx *ctx, const char *code) { assert(ctx); @@ -807,6 +753,8 @@ bool forth_parse_string(struct forth_ctx *ctx, const char *code) code++; } + EMIT(.callback = op_halt); + if (!inline_calls(ctx)) return false; @@ -817,9 +765,6 @@ bool forth_parse_string(struct forth_ctx *ctx, const char *code) if (!check_stack_effects(ctx, ctx->main)) return false; - if (!ir_to_inst(ctx)) - return false; - return true; } @@ -914,33 +859,29 @@ BUILTIN_COMPILER(";") BUILTIN_COMPILER("if") { - *ctx->j++ = forth_ir_code_len(&ctx->defining_word->code); - - EMIT_IR(.opcode = OP_JUMP_IF); - + EMIT(.callback = op_jump_if); + *ctx->j++ = EMIT(.pc = 0); return code; } static const char * builtin_else_then(struct forth_ctx *ctx, const char *code, bool is_then) { - struct forth_ir *ir = - forth_ir_code_get_elem(&ctx->defining_word->code, *--ctx->j); - - ir->pc = forth_ir_code_len(&ctx->defining_word->code); + union forth_inst *prev_pc_imm = *--ctx->j; if (is_then) { - EMIT_IR(.opcode = OP_NOP); + EMIT(.callback = op_nop); } else { - *ctx->j++ = ir->pc; - EMIT_IR(.opcode = OP_JUMP); + EMIT(.callback = op_jump); + *ctx->j++ = EMIT(.pc = 0); } + prev_pc_imm->pc = forth_code_len(&ctx->defining_word->code); + return code; } BUILTIN_COMPILER("else") { return builtin_else_then(ctx, code, false); } - BUILTIN_COMPILER("then") { return builtin_else_then(ctx, code, true); } #define PUSH_D(value_) ({ *d_stack = (value_); d_stack++; }) @@ -1405,7 +1346,7 @@ static void word_free(void *ptr) struct forth_word *word = ptr; if (!is_word_builtin(word)) - forth_ir_code_reset(&word->code); + forth_code_reset(&word->code); free(word); } @@ -1444,7 +1385,6 @@ void forth_free(struct forth_ctx *ctx) return; hash_unref(ctx->words); - forth_code_reset(&ctx->main_code); free(ctx); } @@ -1501,7 +1441,7 @@ int main(int argc, char *argv[]) if (!forth_parse_string(ctx, ": nice 60 5 4 + + ; : juanita 400 10 5 5 + + + ; " - "x if nice else juanita then 2 * 4 / 2 *")) { + "x if nice else juanita then 2 * 4 / 2 *")) { lwan_status_critical("could not parse forth program"); forth_free(ctx); return 1; From 4b5f93477c50a8a40b29b2588fb632d6e5ec2e10 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 1 Mar 2025 22:42:19 -0800 Subject: [PATCH 2453/2505] Fix jumps in Forth after moving to cont passing-style dispatch They have to be relative now. --- src/samples/forthsalon/forth.c | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 5ff67cf4e..26c5aeeee 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -302,7 +302,8 @@ static void dump_code(const struct forth_code *code) printf("dumping code @ %p\n", code); LWAN_ARRAY_FOREACH (code, inst) { - printf("%08zu ", forth_code_get_elem_index(code, (union forth_inst *)inst)); + printf("%08zu ", + forth_code_get_elem_index(code, (union forth_inst *)inst)); if (inst->callback == op_number) { inst++; @@ -311,12 +312,16 @@ static void dump_code(const struct forth_code *code) } if (inst->callback == op_jump_if) { inst++; - printf("if [next %zu]\n", inst->pc); + printf("if [next +%zu, abs %zu]\n", inst->pc, + forth_code_get_elem_index(code, (union forth_inst *)inst) + + inst->pc); continue; } if (inst->callback == op_jump) { inst++; - printf("jump to %zu\n", inst->pc); + printf("jump to +%zu, abs %zu\n", inst->pc, + forth_code_get_elem_index(code, (union forth_inst *)inst) + + inst->pc); continue; } if (inst->callback == op_nop) { @@ -332,7 +337,8 @@ static void dump_code(const struct forth_code *code) __builtin_unreachable(); } - const struct forth_builtin *b = find_builtin_by_callback(inst->callback); + const struct forth_builtin *b = + find_builtin_by_callback(inst->callback); if (b) { printf("call builtin '%s'\n", b->name); } else { @@ -650,8 +656,7 @@ static const char *found_word(struct forth_ctx *ctx, return code; } -static bool inline_calls_code(struct forth_ctx *ctx, - const struct forth_code *orig_code, +static bool inline_calls_code(const struct forth_code *orig_code, struct forth_code *new_code) { const union forth_inst *inst; @@ -661,7 +666,7 @@ static bool inline_calls_code(struct forth_ctx *ctx, LWAN_ARRAY_FOREACH (orig_code, inst) { if (inst->callback == op_eval_code) { inst++; - if (!inline_calls_code(ctx, inst->code, new_code)) + if (!inline_calls_code(inst->code, new_code)) return false; } else { bool has_imm = false; @@ -677,13 +682,16 @@ static bool inline_calls_code(struct forth_ctx *ctx, } else if (inst->callback == op_jump) { union forth_inst *if_inst = forth_code_get_elem(new_code, JS_POP()); - if_inst->pc = forth_code_len(new_code) + 1 /* jump imm */; + if_inst->pc = forth_code_len(new_code) + 1 /* jump imm */ - + forth_code_get_elem_index(new_code, if_inst); + JS_PUSH(forth_code_len(new_code)); has_imm = true; } else if (inst->callback == op_nop) { union forth_inst *else_inst = forth_code_get_elem(new_code, JS_POP()); - else_inst->pc = forth_code_len(new_code); + else_inst->pc = forth_code_len(new_code) - + forth_code_get_elem_index(new_code, else_inst); } else if (inst->callback == op_number) { has_imm = true; } @@ -707,7 +715,7 @@ static bool inline_calls(struct forth_ctx *ctx) struct forth_code new_main; forth_code_init(&new_main); - if (!inline_calls_code(ctx, &ctx->main->code, &new_main)) { + if (!inline_calls_code(&ctx->main->code, &new_main)) { forth_code_reset(&new_main); return false; } From 6f457c4e587704dcc36c135d8d7c56c6f60068df Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 1 Mar 2025 22:49:20 -0800 Subject: [PATCH 2454/2505] Crashes have been fixed in the Forth implementation, remove warning in README --- src/samples/forthsalon/README | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 src/samples/forthsalon/README diff --git a/src/samples/forthsalon/README b/src/samples/forthsalon/README deleted file mode 100644 index eb184dc02..000000000 --- a/src/samples/forthsalon/README +++ /dev/null @@ -1,2 +0,0 @@ - -This is (very) experimental and is prone to crashing. From a379b2d197ff209861d1268bc0ae1b026ce4e950 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 1 Mar 2025 22:55:18 -0800 Subject: [PATCH 2455/2505] Add some consistency checks for if/else/then words --- src/samples/forthsalon/forth.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 26c5aeeee..d785f7988 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -867,6 +867,11 @@ BUILTIN_COMPILER(";") BUILTIN_COMPILER("if") { + if ((size_t)(ctx->j - ctx->j_stack) >= N_ELEMENTS(ctx->j_stack)) { + lwan_status_error("Too many nested 'if' words"); + return NULL; + } + EMIT(.callback = op_jump_if); *ctx->j++ = EMIT(.pc = 0); return code; @@ -875,12 +880,23 @@ BUILTIN_COMPILER("if") static const char * builtin_else_then(struct forth_ctx *ctx, const char *code, bool is_then) { + if (ctx->j == ctx->j_stack) { + lwan_status_error("'%s' before 'if'", is_then ? "then" : "else"); + return NULL; + } + union forth_inst *prev_pc_imm = *--ctx->j; if (is_then) { EMIT(.callback = op_nop); } else { EMIT(.callback = op_jump); + + if ((size_t)(ctx->j - ctx->j_stack) >= N_ELEMENTS(ctx->j_stack)) { + lwan_status_error("Else is too deep"); + return NULL; + } + *ctx->j++ = EMIT(.pc = 0); } From 2f534a7422cca6e2ff7aaaae2f9806dc80009ebe Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 1 Mar 2025 23:09:34 -0800 Subject: [PATCH 2456/2505] Fix inlining of `if` instructions when processing `else` instructions --- src/samples/forthsalon/forth.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index d785f7988..1e2aa55e8 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -682,8 +682,8 @@ static bool inline_calls_code(const struct forth_code *orig_code, } else if (inst->callback == op_jump) { union forth_inst *if_inst = forth_code_get_elem(new_code, JS_POP()); - if_inst->pc = forth_code_len(new_code) + 1 /* jump imm */ - - forth_code_get_elem_index(new_code, if_inst); + if_inst->pc = forth_code_len(new_code) + + forth_code_get_elem_index(new_code, if_inst) - 2; JS_PUSH(forth_code_len(new_code)); has_imm = true; From 55e2688081c7cbc2d9ae04f2846a7fbf72c5f652 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 1 Mar 2025 23:14:46 -0800 Subject: [PATCH 2457/2505] Run forth debug app with two different inputs Makes it easier to test programs with if/else/then --- src/samples/forthsalon/forth.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 1e2aa55e8..7eecd2698 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -1471,7 +1471,17 @@ int main(int argc, char *argv[]) return 1; } - struct forth_vars vars = {.x = 1, .y = 0}; + printf("running with x=0\n"); + struct forth_vars vars = {.x = 0, .y = 0}; + if (forth_run(ctx, &vars)) { + printf("D stack: %zu elems", forth_d_stack_len(ctx, &vars)); + for (size_t len = forth_d_stack_len(ctx, &vars); len; len--) { + printf(" %lf", forth_d_stack_pop(&vars)); + } + } + + printf("\nrunning with x=1\n"); + vars = (struct forth_vars){.x = 1, .y = 0}; if (forth_run(ctx, &vars)) { printf("D stack: %zu elems", forth_d_stack_len(ctx, &vars)); for (size_t len = forth_d_stack_len(ctx, &vars); len; len--) { From fe6d593747c37ff7dae7a209ac22c985b6304a1a Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 1 Mar 2025 23:20:23 -0800 Subject: [PATCH 2458/2505] Fix jump offsets in dump_code() --- src/samples/forthsalon/forth.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 7eecd2698..9e2513590 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -311,17 +311,17 @@ static void dump_code(const struct forth_code *code) continue; } if (inst->callback == op_jump_if) { - inst++; - printf("if [next +%zu, abs %zu]\n", inst->pc, + printf("if [next +%zu, abs %zu]\n", inst[1].pc, forth_code_get_elem_index(code, (union forth_inst *)inst) + - inst->pc); + inst[1].pc); + inst++; continue; } if (inst->callback == op_jump) { - inst++; - printf("jump to +%zu, abs %zu\n", inst->pc, + printf("jump to +%zu, abs %zu\n", inst[1].pc, forth_code_get_elem_index(code, (union forth_inst *)inst) + - inst->pc); + inst[1].pc); + inst++; continue; } if (inst->callback == op_nop) { From 205653e86294994717111309da16e99847c5a9e3 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 2 Mar 2025 00:40:42 -0800 Subject: [PATCH 2459/2505] It's unlikely a divide by 0 can occur --- src/samples/forthsalon/forth.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 9e2513590..25d9fe3fb 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -1199,7 +1199,7 @@ BUILTIN("-", 1, 2) BUILTIN("/", 1, 2) { double v = POP_D(); - if (v == 0.0) { + if (UNLIKELY(v == 0.0)) { DROP_D(); PUSH_D(__builtin_inf()); } else { From d3d9f373799c5c23cb9b193d121581bf8163e021 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 2 Mar 2025 00:40:56 -0800 Subject: [PATCH 2460/2505] `c_name' field was set but unused, remove it --- src/samples/forthsalon/forth.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 25d9fe3fb..812ea82f8 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -71,7 +71,6 @@ DEFINE_ARRAY_TYPE(forth_code, union forth_inst) struct forth_builtin { const char *name; size_t name_len; - const char *c_name; union { void (*callback)(union forth_inst *, double *d_stack, @@ -776,8 +775,6 @@ bool forth_parse_string(struct forth_ctx *ctx, const char *code) return true; } -#define C_NAME(id_) #id_ - #define BUILTIN_DETAIL(name_, id_, struct_id_, d_pushes_, d_pops_, r_pushes_, \ r_pops_) \ static void id_(union forth_inst *inst, double *d_stack, double *r_stack, \ @@ -787,7 +784,6 @@ bool forth_parse_string(struct forth_ctx *ctx, const char *code) __attribute__((aligned(8))) struct_id_ = { \ .name = name_, \ .name_len = sizeof(name_) - 1, \ - .c_name = C_NAME(id_), \ .callback = id_, \ .d_pushes = d_pushes_, \ .d_pops = d_pops_, \ From c3e59c0ca5880b3876fabd7872c3bc02ce415d48 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 2 Mar 2025 08:07:19 -0800 Subject: [PATCH 2461/2505] Remove unused enum forth_opcode --- src/samples/forthsalon/forth.c | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 812ea82f8..f8316aac0 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -43,15 +43,6 @@ #define NO_INCBIN #include "forth-jit-inc.h" -enum forth_opcode { - OP_CALL_BUILTIN, - OP_EVAL_CODE, - OP_NUMBER, - OP_JUMP_IF, - OP_JUMP, - OP_NOP, -}; - struct forth_ctx; struct forth_vars; union forth_inst; From 7ea57531d41840f2d4bc2837db21feef5c920864 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 2 Mar 2025 08:07:33 -0800 Subject: [PATCH 2462/2505] Can't store pointers to lwan_array elements while it's being built Reallocations may occur and the pointer might move. It's safer to store indices. --- src/samples/forthsalon/forth.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index f8316aac0..d05ac9576 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -98,8 +98,8 @@ struct forth_ctx { double r_stack[32]; }; struct { - union forth_inst *j_stack[32]; - union forth_inst **j; + size_t j_stack[63]; + size_t *j; }; }; @@ -591,7 +591,7 @@ lookup_word(struct forth_ctx *ctx, const char *name, size_t len) if (UNLIKELY(!emitted)) \ return NULL; \ *emitted = (union forth_inst){arg}; \ - emitted; \ + forth_code_get_elem_index(&ctx->defining_word->code, emitted); \ }) static const char *found_word(struct forth_ctx *ctx, @@ -872,7 +872,8 @@ builtin_else_then(struct forth_ctx *ctx, const char *code, bool is_then) return NULL; } - union forth_inst *prev_pc_imm = *--ctx->j; + union forth_inst *prev_pc_imm = + forth_code_get_elem(&ctx->defining_word->code, *--ctx->j); if (is_then) { EMIT(.callback = op_nop); From 1c6c3281e6ec6641241cc70228cc11ea0ec46363 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 2 Mar 2025 08:17:32 -0800 Subject: [PATCH 2463/2505] Get rid of `nop' instruction when inlining --- src/samples/forthsalon/forth.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index d05ac9576..b66e6d294 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -171,7 +171,8 @@ static void op_nop(union forth_inst *inst, double *r_stack, struct forth_vars *vars) { - return inst[1].callback(&inst[1], d_stack, r_stack, vars); + lwan_status_critical("nop instruction executed after inlining"); + __builtin_unreachable(); } static void op_halt(union forth_inst *inst __attribute__((unused)), @@ -660,11 +661,15 @@ static bool inline_calls_code(const struct forth_code *orig_code, return false; } else { bool has_imm = false; - union forth_inst *new_inst = forth_code_append(new_code); - if (!new_inst) - return false; + union forth_inst *new_inst; + + if (inst->callback != op_nop) { + new_inst = forth_code_append(new_code); + if (!new_inst) + return false; - *new_inst = *inst; + *new_inst = *inst; + } if (inst->callback == op_jump_if) { JS_PUSH(forth_code_len(new_code)); @@ -681,7 +686,7 @@ static bool inline_calls_code(const struct forth_code *orig_code, union forth_inst *else_inst = forth_code_get_elem(new_code, JS_POP()); else_inst->pc = forth_code_len(new_code) - - forth_code_get_elem_index(new_code, else_inst); + forth_code_get_elem_index(new_code, else_inst) + 1; } else if (inst->callback == op_number) { has_imm = true; } From b3ebe9e6423214fb2a8bcea6c3d56ae1a7fbb526 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 2 Mar 2025 08:29:02 -0800 Subject: [PATCH 2464/2505] `nop' shouldn't appear when dump_code() is called --- src/samples/forthsalon/forth.c | 37 +++++++++++++--------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index b66e6d294..c5cfd8385 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -299,41 +299,32 @@ static void dump_code(const struct forth_code *code) if (inst->callback == op_number) { inst++; printf("number %lf\n", inst->number); - continue; - } - if (inst->callback == op_jump_if) { + } else if (inst->callback == op_jump_if) { printf("if [next +%zu, abs %zu]\n", inst[1].pc, forth_code_get_elem_index(code, (union forth_inst *)inst) + inst[1].pc); inst++; - continue; - } - if (inst->callback == op_jump) { + } else if (inst->callback == op_jump) { printf("jump to +%zu, abs %zu\n", inst[1].pc, forth_code_get_elem_index(code, (union forth_inst *)inst) + inst[1].pc); inst++; - continue; - } - if (inst->callback == op_nop) { - printf("nop\n"); - continue; - } - if (inst->callback == op_halt) { + } else if (inst->callback == op_halt) { printf("halt\n"); - continue; - } - if (UNLIKELY(inst->callback == op_eval_code)) { + } else if (UNLIKELY(inst->callback == op_eval_code)) { lwan_status_critical("eval_code shouldn't exist here"); __builtin_unreachable(); - } - - const struct forth_builtin *b = - find_builtin_by_callback(inst->callback); - if (b) { - printf("call builtin '%s'\n", b->name); + } else if (UNLIKELY(inst->callback == op_nop)) { + lwan_status_critical("nop shouldn't exist here"); + __builtin_unreachable(); } else { - printf("*** inconsistency; value = %zu ***\n", inst->pc); + const struct forth_builtin *b = + find_builtin_by_callback(inst->callback); + if (b) { + printf("call builtin '%s'\n", b->name); + } else { + printf("*** inconsistency; value = %zu ***\n", inst->pc); + } } } } From 844f7d03f45dad12a6194a0f771b2d86fde753ef Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 2 Mar 2025 08:32:43 -0800 Subject: [PATCH 2465/2505] Remove Forth-to-C converter for now Fully supporting this would be slightly tricky and I'm too lazy right now to make it happen. The main reason is that I'd have to write a fork server before Lwan threads are created, to call $CC, and I'd rather focus on other things right now. --- src/samples/forthsalon/CMakeLists.txt | 16 --- src/samples/forthsalon/forth-jit.h | 114 ---------------- src/samples/forthsalon/forth.c | 187 -------------------------- 3 files changed, 317 deletions(-) delete mode 100644 src/samples/forthsalon/forth-jit.h diff --git a/src/samples/forthsalon/CMakeLists.txt b/src/samples/forthsalon/CMakeLists.txt index 932ec9306..1d710e45d 100644 --- a/src/samples/forthsalon/CMakeLists.txt +++ b/src/samples/forthsalon/CMakeLists.txt @@ -22,19 +22,3 @@ target_link_libraries(forthsalon ${ADDITIONAL_LIBRARIES} m ) - -add_custom_command( - OUTPUT ${CMAKE_BINARY_DIR}/forth-jit-inc.h - COMMAND bin2hex - ${CMAKE_SOURCE_DIR}/src/samples/forthsalon/forth-jit.h forth_jit - > - ${CMAKE_BINARY_DIR}/forth-jit-inc.h - DEPENDS ${CMAKE_SOURCE_DIR}/src/samples/forthsalon/forth-jit.h - bin2hex - COMMENT "Bundling data for forthsalon sample" -) -add_custom_target(forth_jit_data - DEPENDS ${CMAKE_BINARY_DIR}/forth-jit-inc.h -) -add_dependencies(forth forth_jit_data) -add_dependencies(forthsalon forth_jit_data) diff --git a/src/samples/forthsalon/forth-jit.h b/src/samples/forthsalon/forth-jit.h deleted file mode 100644 index 0947e776e..000000000 --- a/src/samples/forthsalon/forth-jit.h +++ /dev/null @@ -1,114 +0,0 @@ -/* This file is used by the Forth haiku-to-C compiler as part of the Lwan - * web server project, and is placed in the public domain, or in the Creative - * Commons CC0 license (at your option). */ - -#include -#include - -/* Stubs */ -static inline double op_dt(void) { return 0; } -static inline double op_mx(void) { return 0; } -static inline double op_my(void) { return 0; } -static inline double op_button(double b) { return 0; } -static inline double op_buttons() { return 0; } -static inline double op_audio(double a) { return 0; } -static inline void op_sample(double a, double b, double *aa, double *bb, double *cc) { - *aa = *bb = *cc = 0; -} -static inline double op_bwsample(double a, double b) { return 0; } - -static inline void op_dup(double a, double *aa, double *bb) { *aa = *bb = a; } - -static inline void -op_over(double a, double b, double *aa, double *bb, double *cc) -{ - *cc = a; - *bb = b; - *aa = a; -} - -static inline void -op_2dup(double a, double b, double *aa, double *bb, double *cc, double *dd) -{ - *aa = a; - *bb = b; - *cc = a; - *dd = b; -} - -static inline void -op_zadd(double a, double b, double c, double d, double *aa, double *bb) -{ - *aa = c + a; - *bb = d + b; -} - -static inline void -op_zmult(double a, double b, double c, double d, double *aa, double *bb) -{ - *aa = a * c - b * d; - *bb = a * d - b * c; -} - -static inline void op_swap(double a, double b, double *aa, double *bb) -{ - *aa = b; - *bb = a; -} - -static inline void -op_rot(double a, double b, double c, double *aa, double *bb, double *cc) -{ - *aa = b; - *bb = c; - *cc = a; -} - -static inline void -op_minusrot(double a, double b, double c, double *aa, double *bb, double *cc) -{ - *aa = c; - *bb = a; - *cc = b; -} - -static inline double op_neq(double a, double b) { return a != b ? 1.0 : 0.0; } -static inline double op_eq(double a, double b) { return a == b ? 1.0 : 0.0; } -static inline double op_gt(double a, double b) { return a > b ? 1.0 : 0.0; } -static inline double op_gte(double a, double b) { return a >= b ? 1.0 : 0.0; } -static inline double op_lt(double a, double b) { return a < b ? 1.0 : 0.0; } -static inline double op_lte(double a, double b) { return a <= b ? 1.0 : 0.0; } -static inline double op_add(double a, double b) { return a + b; } - -static inline double op_mult(double a, double b) { return a + b; } -static inline double op_sub(double a, double b) { return a - b; } -static inline double op_div(double a, double b) -{ - return b == 0.0 ? INFINITY : a / b; -} -static inline double op_fmod(double a, double b) { return fmod(a, b); } -static inline double op_pow(double a, double b) { return pow(fabs(a), b); } -static inline double op_atan2(double a, double b) { return atan2(a, b); } -static inline double op_and(double a, double b) -{ - return (a != 0.0 && b != 0.0) ? 1.0 : 0.0; -} -static inline double op_or(double a, double b) -{ - return (a != 0.0 || b != 0.0) ? 1.0 : 0.0; -} -static inline double op_not(double a) { return a != 0.0 ? 0.0 : 1.0; } -static inline double op_min(double a, double b) { return a < b ? a : b; } -static inline double op_max(double a, double b) { return a > b ? a : b; } -static inline double op_negate(double a) { return -a; } -static inline double op_sin(double a) { return sin(a); } -static inline double op_cos(double a) { return cos(a); } -static inline double op_tan(double a) { return tan(a); } -static inline double op_log(double a) { return log(fabs(a)); } -static inline double op_exp(double a) { return exp(a); } -static inline double op_sqrt(double a) { return sqrt(a); } -static inline double op_floor(double a) { return floor(a); } -static inline double op_ceil(double a) { return ceil(a); } -static inline double op_abs(double a) { return fabs(a); } -static inline double op_pi(void) { return M_PI; } -static inline double op_random(void) { return drand48(); } diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index c5cfd8385..afcc2ff2d 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -40,9 +40,6 @@ #include "forth.h" -#define NO_INCBIN -#include "forth-jit-inc.h" - struct forth_ctx; struct forth_vars; union forth_inst; @@ -330,190 +327,6 @@ static void dump_code(const struct forth_code *code) } #endif -#if 0 -static const char *c_builtin_name(const struct forth_builtin *b, - char buffer[static 64]) -{ - /* FIXME add op_* names to forth_builtin; maybe do this during new_word()? */ - if (streq(b->name, "+")) - return "op_add"; - if (streq(b->name, "-")) - return "op_sub"; - if (streq(b->name, "/")) - return "op_div"; - if (streq(b->name, "*")) - return "op_mult"; - if (streq(b->name, "<>")) - return "op_diff"; - if (streq(b->name, "=")) - return "op_eq"; - if (streq(b->name, ">")) - return "op_gt"; - if (streq(b->name, ">=")) - return "op_gte"; - if (streq(b->name, "<")) - return "op_lt"; - if (streq(b->name, "<=")) - return "op_lte"; - if (streq(b->name, "**")) - return "op_pow"; - if (streq(b->name, "%")) - return "op_mod"; - if (streq(b->name, ">r")) - return "op_tor"; - if (streq(b->name, "r>")) - return "op_fromr"; - if (streq(b->name, "r@")) - return "op_rtord"; - if (streq(b->name, "@")) - return "op_recall"; - if (streq(b->name, "!")) - return "op_store"; - if (streq(b->name, "2dup")) - return "op_2dup"; - if (streq(b->name, "z+")) - return "op_zplus"; - if (streq(b->name, "z*")) - return "op_zmult"; - if (streq(b->name, "-rot")) - return "op_minusrot"; - int ret = snprintf(buffer, 64, "op_%s", b->name); - return (ret < 0 || ret > 64) ? NULL : buffer; -} - - -#define GET_TMP(num_) \ - ({ \ - int n = (num_); \ - const char *out; \ - if (n > last_undeclared) { \ - out = "double tmp"; \ - last_undeclared = n; \ - } else { \ - out = "tmp"; \ - } \ - out; \ - }) - -static bool dump_code_c(const struct forth_ir_code *code) -{ - size_t jump_stack[64]; - size_t *j = jump_stack; - char name_buffer[64]; - int last_tmp = 0; - int last_undeclared = -1; - const struct forth_ir *ir; - - printf("dumping code @ %p\n", code); - - fwrite(forth_jit_value.value, forth_jit_value.len, 1, stdout); - printf("void compute(double x, double y, double t, double *r, double *g, " - "double *b) {\n"); - - LWAN_ARRAY_FOREACH (code, ir) { - switch (ir->opcode) { - case OP_EVAL_CODE: - __builtin_unreachable(); - case OP_CALL_BUILTIN: { - const struct forth_builtin *b = - find_builtin_by_callback(ir->callback); - last_tmp -= b->d_pops; - - if (b->d_pushes == 0) { - printf(" %s(", c_builtin_name(b, name_buffer)); - for (int arg = 0; arg < b->d_pops; arg++) { - printf("tmp%d, ", last_tmp + arg - 1); - } - printf(");\n"); - } else if (b->d_pushes == 1) { - if (streq(b->name, "t") || streq(b->name, "x") || - streq(b->name, "y")) { - int t = last_tmp++; - printf(" %s%d = %s;\n", GET_TMP(t), t, b->name); - } else { - int t = last_tmp++; - printf(" %s%d = %s(", GET_TMP(t), t, - c_builtin_name(b, name_buffer)); - for (int arg = 0; arg < b->d_pops; arg++) { - t = last_tmp + arg - 1; - if (arg == b->d_pops - 1) { - printf("tmp%d", t); - } else { - printf("tmp%d, ", t); - } - } - printf(");\n"); - } - - } else { - printf(" %s(", c_builtin_name(b, name_buffer)); - for (int arg = 0; arg < b->d_pops; arg++) { - printf("tmp%d, ", last_tmp + arg - 1); - } - for (int out_arg = 0; out_arg < b->d_pushes; out_arg++) { - int t = last_tmp + out_arg - 1; - if (out_arg == b->d_pushes - 1) { - printf("&tmp%d", t); - } else { - printf("&tmp%d, ", t); - } - } - last_tmp += b->d_pushes; - printf(");\n"); - } - - break; - } - case OP_NUMBER: { - int t = last_tmp++; - printf(" %s%d = %lf;\n", GET_TMP(t), t, ir->number); - break; - } - case OP_JUMP_IF: - printf(" if (tmp%d == 0.0) {\n", --last_tmp); - JS_PUSH((size_t)last_tmp); - JS_PUSH((size_t)last_undeclared); - break; - case OP_JUMP: - printf(" } else {\n"); - last_undeclared = (int)JS_POP(); - last_tmp = (int)JS_POP(); - JS_PUSH((size_t)last_undeclared); - break; - case OP_NOP: - printf(" }\n"); - last_undeclared = (int)JS_POP(); - break; - } - } - - switch (last_tmp) { - case 3: - printf(" *r = tmp2;\n"); - printf(" *g = tmp1;\n"); - printf(" *b = tmp0;\n"); - break; - case 4: - printf(" *r = tmp3;\n"); - printf(" *g = tmp2;\n"); - printf(" *b = tmp1;\n"); - break; - default: - printf(" *r = *g = *b = 0.0;\n"); - } - - printf("}\n"); - - return true; -} - -static void dump_code(const struct forth_ir_code *code) -{ - dump_code_ir(code); - dump_code_c(code); -} -#endif - bool forth_run(struct forth_ctx *ctx, struct forth_vars *vars) { union forth_inst *instr = forth_code_get_elem(&ctx->main->code, 0); From d4b0700cee3808558d79a1448b6ae5b158953d0e Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 2 Mar 2025 08:38:10 -0800 Subject: [PATCH 2466/2505] `nop' instruction shouldn't appear in code after inlining --- src/samples/forthsalon/forth.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index afcc2ff2d..545f631dd 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -221,12 +221,15 @@ static bool check_stack_effects(const struct forth_ctx *ctx, inst++; /* skip pc immediate */ continue; } - if (inst->callback == op_halt || inst->callback == op_nop) { - /* no immediates for these operations */ - continue; + if (inst->callback == op_halt) { + continue; /* no immediate for halt */ } - if (inst->callback == op_eval_code) { - lwan_status_critical("eval_code instruction shouldn't appear here"); + if (UNLIKELY(inst->callback == op_eval_code)) { + lwan_status_critical("eval_code after inlining"); + return false; + } + if (UNLIKELY(inst->callback == op_nop)) { + lwan_status_critical("nop after inlining"); return false; } From 81f279b96a42304c2413fb25dd311afd7700411a Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 2 Mar 2025 08:40:37 -0800 Subject: [PATCH 2467/2505] Critical status ends the program, so no need to do anything after --- src/samples/forthsalon/forth.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 545f631dd..820bad8c8 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -226,18 +226,18 @@ static bool check_stack_effects(const struct forth_ctx *ctx, } if (UNLIKELY(inst->callback == op_eval_code)) { lwan_status_critical("eval_code after inlining"); - return false; + __builtin_unreachable(); } if (UNLIKELY(inst->callback == op_nop)) { lwan_status_critical("nop after inlining"); - return false; + __builtin_unreachable(); } /* all other built-ins */ const struct forth_builtin *b = find_builtin_by_callback(inst->callback); if (UNLIKELY(!b)) { lwan_status_critical("Can't find builtin word by callback"); - return false; + __builtin_unreachable(); } if (UNLIKELY(items_in_d_stack < b->d_pops)) { @@ -1267,8 +1267,7 @@ int main(int argc, char *argv[]) ": nice 60 5 4 + + ; : juanita 400 10 5 5 + + + ; " "x if nice else juanita then 2 * 4 / 2 *")) { lwan_status_critical("could not parse forth program"); - forth_free(ctx); - return 1; + __builtin_unreachable(); } printf("running with x=0\n"); From b571cee73f2d32ecce2f18f28573047e509efc63 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 2 Mar 2025 10:14:25 -0800 Subject: [PATCH 2468/2505] Use __attribute__((musttail)) whenever available Clang already supports it, GCC will support in GCC 15 (I still have 14.2 here). Although both compilers can generate a `jmp` instruction instead of a `call` instruction for each Forth instruction, that's not guaranteed, so using the `musttail` is the way to go. Once the compiler supports it, it'll be used automatically. --- src/samples/forthsalon/forth.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 820bad8c8..1508fe0e4 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -40,6 +40,14 @@ #include "forth.h" +#define TAIL_CALL return +#if defined __has_attribute +# if __has_attribute (musttail) +# undef TAIL_CALL +# define TAIL_CALL __attribute__((musttail)) return +# endif +#endif + struct forth_ctx; struct forth_vars; union forth_inst; @@ -142,7 +150,7 @@ static void op_number(union forth_inst *inst, struct forth_vars *vars) { *d_stack++ = inst[1].number; - return inst[2].callback(&inst[2], d_stack, r_stack, vars); + TAIL_CALL inst[2].callback(&inst[2], d_stack, r_stack, vars); } static void op_jump_if(union forth_inst *inst, @@ -151,7 +159,7 @@ static void op_jump_if(union forth_inst *inst, struct forth_vars *vars) { size_t pc = (*--d_stack == 0.0) ? inst[1].pc : 2; - return inst[pc].callback(&inst[pc], d_stack, r_stack, vars); + TAIL_CALL inst[pc].callback(&inst[pc], d_stack, r_stack, vars); } static void op_jump(union forth_inst *inst, @@ -160,7 +168,7 @@ static void op_jump(union forth_inst *inst, struct forth_vars *vars) { size_t pc = inst[1].pc; - return inst[pc].callback(&inst[pc], d_stack, r_stack, vars); + TAIL_CALL inst[pc].callback(&inst[pc], d_stack, r_stack, vars); } static void op_nop(union forth_inst *inst, @@ -715,7 +723,7 @@ BUILTIN_COMPILER("then") { return builtin_else_then(ctx, code, true); } #define POP_D() ({ DROP_D(); *d_stack; }) #define POP_R() ({ DROP_R(); *r_stack; }) -#define NEXT() return inst[1].callback(&inst[1], d_stack, r_stack, vars) +#define NEXT() TAIL_CALL inst[1].callback(&inst[1], d_stack, r_stack, vars) BUILTIN("x", 1, 0) { From a1d0fd1f991fd6a335103d5369d854dc10aef270 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 2 Mar 2025 14:17:57 -0800 Subject: [PATCH 2469/2505] `struct forth_word` doesn't need to store callbacks --- src/samples/forthsalon/forth.c | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 1508fe0e4..d3ebce7a5 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -81,15 +81,7 @@ struct forth_builtin { }; struct forth_word { - union { - void (*callback)(union forth_inst *, - double *d_stack, - double *r_stack, - struct forth_vars *vars); - const char *(*callback_compiler)(struct forth_ctx *ctx, - const char *code); - struct forth_code code; - }; + struct forth_code code; const struct forth_builtin *builtin; int d_stack_len; int r_stack_len; @@ -374,9 +366,7 @@ static struct forth_word *new_word(struct forth_ctx *ctx, if (UNLIKELY(!word)) return NULL; - if (callback) { - word->callback = callback; - } else { + if (!callback) { forth_code_init(&word->code); } @@ -430,11 +420,11 @@ static const char *found_word(struct forth_ctx *ctx, struct forth_word *w = lookup_word(ctx, word, word_len); if (ctx->defining_word) { if (LIKELY(w)) { - if (is_word_compiler(w)) - return w->callback_compiler(ctx, code); - if (is_word_builtin(w)) { - EMIT(.callback = w->callback); + if (is_word_compiler(w)) + return w->builtin->callback_compiler(ctx, code); + + EMIT(.callback = w->builtin->callback); } else { EMIT(.callback = op_eval_code); EMIT(.code = &w->code); From b688f42ea12d1810395a8f6d994c607a55f1677e Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 2 Mar 2025 14:23:19 -0800 Subject: [PATCH 2470/2505] Reduce per-word memory usage slightly --- src/samples/forthsalon/forth.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index d3ebce7a5..ee69f2a53 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -81,8 +81,10 @@ struct forth_builtin { }; struct forth_word { - struct forth_code code; - const struct forth_builtin *builtin; + union { + struct forth_code code; + const struct forth_builtin *builtin; + }; int d_stack_len; int r_stack_len; char name[]; @@ -110,13 +112,14 @@ struct forth_ctx { static inline bool is_word_builtin(const struct forth_word *w) { - return !!w->builtin; + return w->d_stack_len < 0 && w->r_stack_len < 0; } static inline bool is_word_compiler(const struct forth_word *w) { const struct forth_builtin *b = w->builtin; - return b && b >= SECTION_START_SYMBOL(forth_compiler_builtin, b) && + return is_word_builtin(w) && + b >= SECTION_START_SYMBOL(forth_compiler_builtin, b) && b < SECTION_STOP_SYMBOL(forth_compiler_builtin, b); } @@ -371,8 +374,8 @@ static struct forth_word *new_word(struct forth_ctx *ctx, } word->builtin = builtin; - word->d_stack_len = 0; - word->r_stack_len = 0; + word->d_stack_len = builtin ? -1 : 0; + word->r_stack_len = builtin ? -1 : 0; strncpy(word->name, name, len); word->name[len] = '\0'; From 19c57a91e1f461eeb24f55310c8c3bdee2d544f8 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 2 Mar 2025 14:34:12 -0800 Subject: [PATCH 2471/2505] Ensure all happy paths lead to op_halt being emitted --- src/samples/forthsalon/forth.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index ee69f2a53..c12930998 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -544,7 +544,7 @@ bool forth_parse_string(struct forth_ctx *ctx, const char *code) while (true) { if (*code == '\0') { if (word_ptr == code) - return true; + goto finish; break; } if (isspace(*code)) @@ -564,6 +564,7 @@ bool forth_parse_string(struct forth_ctx *ctx, const char *code) code++; } +finish: EMIT(.callback = op_halt); if (!inline_calls(ctx)) From 85428f0b161ea426403c576d2555e0182216227d Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 2 Mar 2025 14:45:01 -0800 Subject: [PATCH 2472/2505] Emit error if word definition has not ended --- src/samples/forthsalon/forth.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index c12930998..a6e8eac2c 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -565,6 +565,11 @@ bool forth_parse_string(struct forth_ctx *ctx, const char *code) } finish: + if (ctx->is_inside_word_def) { + lwan_status_error("Word definition not finished"); + return false; + } + EMIT(.callback = op_halt); if (!inline_calls(ctx)) From 55a38a317e58d31ae05de4ba28deebedd483c2e0 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 2 Mar 2025 14:53:04 -0800 Subject: [PATCH 2473/2505] Prevent inline_calls_code() from recursing indefinitely --- src/samples/forthsalon/forth.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index a6e8eac2c..676620182 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -456,16 +456,22 @@ static const char *found_word(struct forth_ctx *ctx, } static bool inline_calls_code(const struct forth_code *orig_code, - struct forth_code *new_code) + struct forth_code *new_code, + int nested) { const union forth_inst *inst; size_t jump_stack[64]; size_t *j = jump_stack; + if (!nested) { + lwan_status_error("Recursion limit reached while inlining"); + return false; + } + LWAN_ARRAY_FOREACH (orig_code, inst) { if (inst->callback == op_eval_code) { inst++; - if (!inline_calls_code(inst->code, new_code)) + if (!inline_calls_code(inst->code, new_code, nested - 1)) return false; } else { bool has_imm = false; @@ -518,7 +524,7 @@ static bool inline_calls(struct forth_ctx *ctx) struct forth_code new_main; forth_code_init(&new_main); - if (!inline_calls_code(&ctx->main->code, &new_main)) { + if (!inline_calls_code(&ctx->main->code, &new_main, 100)) { forth_code_reset(&new_main); return false; } @@ -660,13 +666,12 @@ BUILTIN_COMPILER(";") return NULL; } - ctx->is_inside_word_def = false; - if (UNLIKELY(!ctx->defining_word)) { lwan_status_error("No word provided"); return NULL; } + ctx->is_inside_word_def = false; ctx->defining_word = ctx->main; return code; } From 5c544c4b490a6505efb5396e8a9230dc9a44d892 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 2 Mar 2025 17:13:06 -0800 Subject: [PATCH 2474/2505] Don't copy word names for built-in words --- src/samples/forthsalon/forth.c | 61 +++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 20 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 676620182..eeb343cb9 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -48,10 +48,6 @@ # endif #endif -struct forth_ctx; -struct forth_vars; -union forth_inst; - union forth_inst { void (*callback)(union forth_inst *, double *d_stack, @@ -87,6 +83,9 @@ struct forth_word { }; int d_stack_len; int r_stack_len; + + /* This is only valid if !is_word_builtin(word); otherwise, + * access the name through builtin->name. */ char name[]; }; @@ -356,11 +355,26 @@ static bool parse_number(const char *ptr, size_t len, double *number) return true; } -static struct forth_word *new_word(struct forth_ctx *ctx, - const char *name, - size_t len, - void *callback, - const struct forth_builtin *builtin) +static struct forth_word *new_builtin_word(struct forth_ctx *ctx, + const struct forth_builtin *builtin) +{ + struct forth_word *word = malloc(sizeof(*word)); + if (UNLIKELY(!word)) + return NULL; + + word->builtin = builtin; + word->d_stack_len = -1; + word->r_stack_len = -1; + + if (!hash_add(ctx->words, builtin->name, word)) + return word; + + free(word); + return NULL; +} + +static struct forth_word * +new_user_word(struct forth_ctx *ctx, const char *name, size_t len) { if (len > 64) return NULL; @@ -369,17 +383,15 @@ static struct forth_word *new_word(struct forth_ctx *ctx, if (UNLIKELY(!word)) return NULL; - if (!callback) { - forth_code_init(&word->code); - } - - word->builtin = builtin; - word->d_stack_len = builtin ? -1 : 0; - word->r_stack_len = builtin ? -1 : 0; + forth_code_init(&word->code); strncpy(word->name, name, len); word->name[len] = '\0'; + word->d_stack_len = 0; + word->r_stack_len = 0; + word->builtin = NULL; + if (!hash_add(ctx->words, word->name, word)) return word; @@ -387,6 +399,15 @@ static struct forth_word *new_word(struct forth_ctx *ctx, return NULL; } +static struct forth_word *new_word(struct forth_ctx *ctx, + const char *name, + size_t len, + const struct forth_builtin *builtin) +{ + return builtin ? new_builtin_word(ctx, builtin) + : new_user_word(ctx, name, len); +} + static struct forth_word * lookup_word(struct forth_ctx *ctx, const char *name, size_t len) { @@ -445,7 +466,7 @@ static const char *found_word(struct forth_ctx *ctx, return NULL; } - w = new_word(ctx, word, word_len, NULL, NULL); + w = new_word(ctx, word, word_len, NULL); if (UNLIKELY(!w)) { lwan_status_error("Can't create new word"); return NULL; @@ -1164,13 +1185,13 @@ register_builtins(struct forth_ctx *ctx) const struct forth_builtin *iter; LWAN_SECTION_FOREACH(forth_builtin, iter) { - if (!new_word(ctx, iter->name, iter->name_len, iter->callback, iter)) { + if (!new_word(ctx, iter->name, iter->name_len, iter)) { lwan_status_critical("could not register forth word: %s", iter->name); } } LWAN_SECTION_FOREACH(forth_compiler_builtin, iter) { - if (!new_word(ctx, iter->name, iter->name_len, iter->callback_compiler, iter)) { + if (!new_word(ctx, iter->name, iter->name_len, iter)) { lwan_status_critical("could not register forth word: %s", iter->name); } @@ -1201,7 +1222,7 @@ struct forth_ctx *forth_new(void) return NULL; } - struct forth_word *word = new_word(ctx, " ", 1, NULL, NULL); + struct forth_word *word = new_word(ctx, " ", 1, NULL); if (!word) { free(ctx); return NULL; From ef631c53b150299e812f95ce4baadfe4f08aba13 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 2 Mar 2025 17:18:16 -0800 Subject: [PATCH 2475/2505] Remove `name_len` from `struct forth_builtin` --- src/samples/forthsalon/forth.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index eeb343cb9..f002d6532 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -62,7 +62,7 @@ DEFINE_ARRAY_TYPE(forth_code, union forth_inst) struct forth_builtin { const char *name; - size_t name_len; + union { void (*callback)(union forth_inst *, double *d_stack, @@ -70,6 +70,7 @@ struct forth_builtin { struct forth_vars *vars); const char *(*callback_compiler)(struct forth_ctx *, const char *); }; + int d_pushes; int d_pops; int r_pushes; @@ -81,6 +82,7 @@ struct forth_word { struct forth_code code; const struct forth_builtin *builtin; }; + int d_stack_len; int r_stack_len; @@ -243,13 +245,13 @@ static bool check_stack_effects(const struct forth_ctx *ctx, } if (UNLIKELY(items_in_d_stack < b->d_pops)) { - lwan_status_error("Word `%.*s' requires %d item(s) in the D stack", - (int)b->name_len, b->name, b->d_pops); + lwan_status_error("Word `%s' requires %d item(s) in the D stack", + b->name, b->d_pops); return false; } if (UNLIKELY(items_in_r_stack < b->r_pops)) { - lwan_status_error("Word `%.*s' requires %d item(s) in the R stack", - (int)b->name_len, b->name, b->r_pops); + lwan_status_error("Word `%s' requires %d item(s) in the R stack", + b->name, b->r_pops); return false; } @@ -401,11 +403,10 @@ new_user_word(struct forth_ctx *ctx, const char *name, size_t len) static struct forth_word *new_word(struct forth_ctx *ctx, const char *name, - size_t len, const struct forth_builtin *builtin) { return builtin ? new_builtin_word(ctx, builtin) - : new_user_word(ctx, name, len); + : new_user_word(ctx, name, strlen(name)); } static struct forth_word * @@ -466,7 +467,7 @@ static const char *found_word(struct forth_ctx *ctx, return NULL; } - w = new_word(ctx, word, word_len, NULL); + w = new_word(ctx, strndupa(word, word_len), NULL); if (UNLIKELY(!w)) { lwan_status_error("Can't create new word"); return NULL; @@ -620,7 +621,6 @@ bool forth_parse_string(struct forth_ctx *ctx, const char *code) __attribute__((section(LWAN_SECTION_NAME(forth_builtin)))) \ __attribute__((aligned(8))) struct_id_ = { \ .name = name_, \ - .name_len = sizeof(name_) - 1, \ .callback = id_, \ .d_pushes = d_pushes_, \ .d_pops = d_pops_, \ @@ -636,7 +636,6 @@ bool forth_parse_string(struct forth_ctx *ctx, const char *code) __attribute__((section(LWAN_SECTION_NAME(forth_compiler_builtin)))) \ __attribute__((aligned(8))) struct_id_ = { \ .name = name_, \ - .name_len = sizeof(name_) - 1, \ .callback_compiler = id_, \ }; \ static const char *id_(struct forth_ctx *ctx __attribute__((unused)), \ @@ -1185,13 +1184,13 @@ register_builtins(struct forth_ctx *ctx) const struct forth_builtin *iter; LWAN_SECTION_FOREACH(forth_builtin, iter) { - if (!new_word(ctx, iter->name, iter->name_len, iter)) { + if (!new_word(ctx, iter->name, iter)) { lwan_status_critical("could not register forth word: %s", iter->name); } } LWAN_SECTION_FOREACH(forth_compiler_builtin, iter) { - if (!new_word(ctx, iter->name, iter->name_len, iter)) { + if (!new_word(ctx, iter->name, iter)) { lwan_status_critical("could not register forth word: %s", iter->name); } @@ -1222,7 +1221,7 @@ struct forth_ctx *forth_new(void) return NULL; } - struct forth_word *word = new_word(ctx, " ", 1, NULL); + struct forth_word *word = new_word(ctx, " ", NULL); if (!word) { free(ctx); return NULL; From 641dec1a25b44bfc8ff7e1b13bebccd0406c43d8 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 2 Mar 2025 17:24:22 -0800 Subject: [PATCH 2476/2505] Simplify new_user_word() slightly --- src/samples/forthsalon/forth.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index f002d6532..ee0c358e8 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -376,9 +376,10 @@ static struct forth_word *new_builtin_word(struct forth_ctx *ctx, } static struct forth_word * -new_user_word(struct forth_ctx *ctx, const char *name, size_t len) +new_user_word(struct forth_ctx *ctx, const char *name) { - if (len > 64) + const size_t len = strlen(name); + if (UNLIKELY(len > 64)) return NULL; struct forth_word *word = malloc(sizeof(*word) + len + 1); @@ -406,7 +407,7 @@ static struct forth_word *new_word(struct forth_ctx *ctx, const struct forth_builtin *builtin) { return builtin ? new_builtin_word(ctx, builtin) - : new_user_word(ctx, name, strlen(name)); + : new_user_word(ctx, name); } static struct forth_word * From 95b4997d08dcd1a77b7df9f0ad49854dd986876c Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 2 Mar 2025 17:29:08 -0800 Subject: [PATCH 2477/2505] Add some assertions for `items_in_{d,r}_stack` --- src/samples/forthsalon/forth.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index ee0c358e8..4b7bf7645 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -270,6 +270,9 @@ static bool check_stack_effects(const struct forth_ctx *ctx, } } + assert(items_in_d_stack >= 0); + assert(items_in_r_stack >= 0); + w->d_stack_len = items_in_d_stack; w->r_stack_len = items_in_r_stack; From f8a91ad9a04d2105c94739ffbe9dadeb30bf6d00 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 2 Mar 2025 17:33:30 -0800 Subject: [PATCH 2478/2505] No need to dereference iter within register_builtins() --- src/samples/forthsalon/forth.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 4b7bf7645..50298cc20 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -1188,13 +1188,13 @@ register_builtins(struct forth_ctx *ctx) const struct forth_builtin *iter; LWAN_SECTION_FOREACH(forth_builtin, iter) { - if (!new_word(ctx, iter->name, iter)) { + if (!new_word(ctx, NULL, iter)) { lwan_status_critical("could not register forth word: %s", iter->name); } } LWAN_SECTION_FOREACH(forth_compiler_builtin, iter) { - if (!new_word(ctx, iter->name, iter)) { + if (!new_word(ctx, NULL, iter)) { lwan_status_critical("could not register forth word: %s", iter->name); } From 99081660efb665f65df358f7fde3ef56377864de Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 2 Mar 2025 17:35:22 -0800 Subject: [PATCH 2479/2505] Fix compiler warning in new_user_word() --- src/samples/forthsalon/forth.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 50298cc20..f8bc59bf9 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -391,7 +391,7 @@ new_user_word(struct forth_ctx *ctx, const char *name) forth_code_init(&word->code); - strncpy(word->name, name, len); + memcpy(word->name, name, len); word->name[len] = '\0'; word->d_stack_len = 0; From 34f09977bb8aa9029515005fd7e2373f075b84fa Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 2 Mar 2025 21:25:06 -0800 Subject: [PATCH 2480/2505] Better error messages in found_word() --- src/samples/forthsalon/forth.c | 45 ++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index f8bc59bf9..3e43e44c1 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -436,38 +436,41 @@ static const char *found_word(struct forth_ctx *ctx, { double number; if (parse_number(word, word_len, &number)) { - if (LIKELY(ctx->defining_word)) { - EMIT(.callback = op_number); - EMIT(.number = number); - return code; + if (UNLIKELY(!ctx->defining_word)) { + lwan_status_error("Can't redefine number %lf", number); + return NULL; } - lwan_status_error("Can't redefine number %lf", number); - return NULL; + EMIT(.callback = op_number); + EMIT(.number = number); + + return code; } struct forth_word *w = lookup_word(ctx, word, word_len); if (ctx->defining_word) { - if (LIKELY(w)) { - if (is_word_builtin(w)) { - if (is_word_compiler(w)) - return w->builtin->callback_compiler(ctx, code); + if (UNLIKELY(!w)) { + lwan_status_error("Undefined word: \"%.*s\"", (int)word_len, word); + return NULL; + } - EMIT(.callback = w->builtin->callback); - } else { - EMIT(.callback = op_eval_code); - EMIT(.code = &w->code); - } - return code; + if (is_word_builtin(w)) { + if (is_word_compiler(w)) + return w->builtin->callback_compiler(ctx, code); + + EMIT(.callback = w->builtin->callback); + } else { + EMIT(.callback = op_eval_code); + EMIT(.code = &w->code); } - lwan_status_error("Undefined word: \"%.*s\"", - (int)word_len, word); - return NULL; + return code; } - if (LIKELY(w != NULL)) { - lwan_status_error("Word already defined: \"%.*s\"", (int)word_len, word); + if (UNLIKELY(w != NULL)) { + lwan_status_error("Can't redefine %sword \"%.*s\"", + is_word_builtin(w) ? "built-in " : "", + (int)word_len, word); return NULL; } From e3179b18bfe884c45896ea303b8668057bfdb02d Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 2 Mar 2025 21:27:03 -0800 Subject: [PATCH 2481/2505] Reduce number of string copies within found_word() --- src/samples/forthsalon/forth.c | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 3e43e44c1..dc7f54831 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -344,12 +344,12 @@ bool forth_run(struct forth_ctx *ctx, struct forth_vars *vars) return true; } -static bool parse_number(const char *ptr, size_t len, double *number) +static bool parse_number(const char *ptr, double *number) { char *endptr; errno = 0; - *number = strtod(strndupa(ptr, len), &endptr); + *number = strtod(ptr, &endptr); if (errno != 0) return false; @@ -413,12 +413,6 @@ static struct forth_word *new_word(struct forth_ctx *ctx, : new_user_word(ctx, name); } -static struct forth_word * -lookup_word(struct forth_ctx *ctx, const char *name, size_t len) -{ - return hash_find(ctx->words, strndupa(name, len)); -} - #define EMIT(arg) \ ({ \ union forth_inst *emitted = \ @@ -434,8 +428,10 @@ static const char *found_word(struct forth_ctx *ctx, const char *word, size_t word_len) { + const char *word_copy = strndupa(word, word_len); + double number; - if (parse_number(word, word_len, &number)) { + if (parse_number(word_copy, &number)) { if (UNLIKELY(!ctx->defining_word)) { lwan_status_error("Can't redefine number %lf", number); return NULL; @@ -447,7 +443,7 @@ static const char *found_word(struct forth_ctx *ctx, return code; } - struct forth_word *w = lookup_word(ctx, word, word_len); + struct forth_word *w = hash_find(ctx->words, word_copy); if (ctx->defining_word) { if (UNLIKELY(!w)) { lwan_status_error("Undefined word: \"%.*s\"", (int)word_len, word); @@ -474,7 +470,7 @@ static const char *found_word(struct forth_ctx *ctx, return NULL; } - w = new_word(ctx, strndupa(word, word_len), NULL); + w = new_word(ctx, word_copy, NULL); if (UNLIKELY(!w)) { lwan_status_error("Can't create new word"); return NULL; From cd1a839c4f3467ef313bf27c09c4b3ee3e6ba4cf Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 3 Mar 2025 18:12:43 -0800 Subject: [PATCH 2482/2505] Don't set word->builtin for user words This worked by chance before because both builtin and code are in the same union and the representation of an empty lwan_array is all zeroes. --- src/samples/forthsalon/forth.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index dc7f54831..ae93d08a7 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -389,14 +389,12 @@ new_user_word(struct forth_ctx *ctx, const char *name) if (UNLIKELY(!word)) return NULL; - forth_code_init(&word->code); - memcpy(word->name, name, len); word->name[len] = '\0'; + forth_code_init(&word->code); word->d_stack_len = 0; word->r_stack_len = 0; - word->builtin = NULL; if (!hash_add(ctx->words, word->name, word)) return word; From 34605bda4055c264ec0e2ad642c36d6c425d5a0b Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 4 Mar 2025 11:28:23 -0800 Subject: [PATCH 2483/2505] Remove `is_inside_word_def` flag from forth_ctx We can calculate something that's close enough by observing ctx->defining_word. --- src/samples/forthsalon/forth.c | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index ae93d08a7..5609c4d65 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -106,10 +106,12 @@ struct forth_ctx { struct forth_word *defining_word; struct forth_word *main; struct hash *words; - - bool is_inside_word_def; }; +static inline bool is_inside_word_def(const struct forth_ctx *ctx) +{ + return !ctx->defining_word || ctx->defining_word != ctx->main; +} static inline bool is_word_builtin(const struct forth_word *w) { @@ -594,7 +596,7 @@ bool forth_parse_string(struct forth_ctx *ctx, const char *code) } finish: - if (ctx->is_inside_word_def) { + if (is_inside_word_def(ctx)) { lwan_status_error("Word definition not finished"); return false; } @@ -665,12 +667,11 @@ BUILTIN_COMPILER("(") BUILTIN_COMPILER(":") { - if (UNLIKELY(ctx->is_inside_word_def)) { + if (UNLIKELY(is_inside_word_def(ctx))) { lwan_status_error("Already defining word"); return NULL; } - ctx->is_inside_word_def = true; ctx->defining_word = NULL; return code; } @@ -682,17 +683,11 @@ BUILTIN_COMPILER(";") return NULL; } - if (UNLIKELY(!ctx->is_inside_word_def)) { + if (UNLIKELY(!is_inside_word_def(ctx))) { lwan_status_error("Ending word without defining one"); return NULL; } - if (UNLIKELY(!ctx->defining_word)) { - lwan_status_error("No word provided"); - return NULL; - } - - ctx->is_inside_word_def = false; ctx->defining_word = ctx->main; return code; } @@ -1214,8 +1209,6 @@ struct forth_ctx *forth_new(void) if (!ctx) return NULL; - ctx->is_inside_word_def = false; - ctx->words = hash_str_new(NULL, word_free); if (!ctx->words) { free(ctx); From 5839eceb5007ef29cb03795eba69aee9b4e3bbd2 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 4 Mar 2025 14:12:45 -0800 Subject: [PATCH 2484/2505] Remove r_stack_len and d_stack_len from forth_word This was set but not really used for its intended purpose -- it was added before inlining occurred and ended up becoming useless after. --- src/samples/forthsalon/forth.c | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 5609c4d65..508d7474a 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -83,11 +83,8 @@ struct forth_word { const struct forth_builtin *builtin; }; - int d_stack_len; - int r_stack_len; - - /* This is only valid if !is_word_builtin(word); otherwise, - * access the name through builtin->name. */ + /* name[0] is '\0' for built-in words; to get the name for those, go through + * the builtin pointer. */ char name[]; }; @@ -115,7 +112,7 @@ static inline bool is_inside_word_def(const struct forth_ctx *ctx) static inline bool is_word_builtin(const struct forth_word *w) { - return w->d_stack_len < 0 && w->r_stack_len < 0; + return w->name[0] == '\0'; } static inline bool is_word_compiler(const struct forth_word *w) @@ -272,11 +269,14 @@ static bool check_stack_effects(const struct forth_ctx *ctx, } } - assert(items_in_d_stack >= 0); - assert(items_in_r_stack >= 0); - - w->d_stack_len = items_in_d_stack; - w->r_stack_len = items_in_r_stack; + if (UNLIKELY(items_in_d_stack < 0)) { + lwan_status_error("Program would underflow the D stack"); + return false; + } + if (UNLIKELY(items_in_r_stack < 0)) { + lwan_status_error("Program would underflow the R stack"); + return false; + } return true; } @@ -365,13 +365,12 @@ static bool parse_number(const char *ptr, double *number) static struct forth_word *new_builtin_word(struct forth_ctx *ctx, const struct forth_builtin *builtin) { - struct forth_word *word = malloc(sizeof(*word)); + struct forth_word *word = malloc(sizeof(*word) + 1); if (UNLIKELY(!word)) return NULL; word->builtin = builtin; - word->d_stack_len = -1; - word->r_stack_len = -1; + word->name[0] = '\0'; if (!hash_add(ctx->words, builtin->name, word)) return word; @@ -395,8 +394,6 @@ new_user_word(struct forth_ctx *ctx, const char *name) word->name[len] = '\0'; forth_code_init(&word->code); - word->d_stack_len = 0; - word->r_stack_len = 0; if (!hash_add(ctx->words, word->name, word)) return word; From 8dc569bda6ef032c50be0b64a6488fa5091029a2 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 4 Mar 2025 22:48:03 -0800 Subject: [PATCH 2485/2505] Protect strndupa() in found_word() against stack overflow --- src/samples/forthsalon/forth.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 508d7474a..e0f8d9f43 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -48,6 +48,8 @@ # endif #endif +#define MAX_WORD_LEN 64 + union forth_inst { void (*callback)(union forth_inst *, double *d_stack, @@ -383,7 +385,7 @@ static struct forth_word * new_user_word(struct forth_ctx *ctx, const char *name) { const size_t len = strlen(name); - if (UNLIKELY(len > 64)) + if (UNLIKELY(len > MAX_WORD_LEN)) return NULL; struct forth_word *word = malloc(sizeof(*word) + len + 1); @@ -425,6 +427,12 @@ static const char *found_word(struct forth_ctx *ctx, const char *word, size_t word_len) { + if (UNLIKELY(word_len > MAX_WORD_LEN)) { + lwan_status_error("Word too long: %zu characters, expecting at most 64", + word_len); + return NULL; + } + const char *word_copy = strndupa(word, word_len); double number; @@ -462,8 +470,8 @@ static const char *found_word(struct forth_ctx *ctx, if (UNLIKELY(w != NULL)) { lwan_status_error("Can't redefine %sword \"%.*s\"", - is_word_builtin(w) ? "built-in " : "", - (int)word_len, word); + is_word_builtin(w) ? "built-in " : "", (int)word_len, + word); return NULL; } From 8cad79b9bed138c65087e01df7b5ce51cc4ec7cb Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 5 Mar 2025 16:52:00 -0800 Subject: [PATCH 2486/2505] Mention `forth` and `forthsalon` executables in the README --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dea5d2c1f..b8b9db9d3 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,9 @@ This will generate a few binaries: - `src/bin/testrunner/testrunner`: Contains code to execute the test suite (`src/scripts/testsuite.py`). - `src/samples/freegeoip/freegeoip`: [FreeGeoIP sample implementation](https://freegeoip.lwan.ws). Requires SQLite. - `src/samples/techempower/techempower`: Code for the TechEmpower Web Framework benchmark. Requires SQLite and MariaDB libraries. - - `src/samples/clock/clock`: [Clock sample](https://time.lwan.ws). Generates a GIF file that always shows the local time. + - `src/samples/clock/clock`: [Clock sample](https://time.lwan.ws). Generates a never-ending animated GIF file that always shows the local time. + - `src/samples/forthsalon/forthsalon`: Generates a never-ending animated GIF from a program written in the [Forth Salon](https://forthsalon.appspot.com/) dialect of the [Forth](https://en.wikipedia.org/wiki/Forth_(programming_language) programming language. *In construction!* + - `src/samples/forthsalon/forth`: Test harness for the Forth dialect used in the `forthsalon` sample. - `src/bin/tools/mimegen`: Builds the extension-MIME type table. Used during the build process. - `src/bin/tools/bin2hex`: Generates a C file from a binary file, suitable for use with #include. Used during the build process. - `src/bin/tools/configdump`: Dumps a configuration file using the configuration reader API. Used for testing. From 4217817d821700c9923110fe551a46e6e7bdc3d6 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 5 Mar 2025 16:52:52 -0800 Subject: [PATCH 2487/2505] Fix link to the Forth article on Wikipedia --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b8b9db9d3..9eac40ef0 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ This will generate a few binaries: - `src/samples/freegeoip/freegeoip`: [FreeGeoIP sample implementation](https://freegeoip.lwan.ws). Requires SQLite. - `src/samples/techempower/techempower`: Code for the TechEmpower Web Framework benchmark. Requires SQLite and MariaDB libraries. - `src/samples/clock/clock`: [Clock sample](https://time.lwan.ws). Generates a never-ending animated GIF file that always shows the local time. - - `src/samples/forthsalon/forthsalon`: Generates a never-ending animated GIF from a program written in the [Forth Salon](https://forthsalon.appspot.com/) dialect of the [Forth](https://en.wikipedia.org/wiki/Forth_(programming_language) programming language. *In construction!* + - `src/samples/forthsalon/forthsalon`: Generates a never-ending animated GIF from a program written in the [Forth Salon](https://forthsalon.appspot.com/) dialect of the [Forth](https://en.wikipedia.org/wiki/Forth_(programming_language)) programming language. *In construction!* - `src/samples/forthsalon/forth`: Test harness for the Forth dialect used in the `forthsalon` sample. - `src/bin/tools/mimegen`: Builds the extension-MIME type table. Used during the build process. - `src/bin/tools/bin2hex`: Generates a C file from a binary file, suitable for use with #include. Used during the build process. From b9d5e1ffdf019905d95673a992a2564308da1e7d Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 7 Mar 2025 09:49:00 -0800 Subject: [PATCH 2488/2505] =?UTF-8?q?Na=C3=AFve=20peephole=20optimizer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This needs a serious overhaul if more optimizations are added, but this is good enough for now. Yields ~18% perf gain in the benchmark. --- src/samples/forthsalon/forth.c | 80 +++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index e0f8d9f43..0f61c321c 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -186,6 +186,33 @@ static void op_halt(union forth_inst *inst __attribute__((unused)), vars->final_r_stack_ptr = r_stack; } +static void op_mult2(union forth_inst *inst, + double *d_stack, + double *r_stack, + struct forth_vars *vars) +{ + *(d_stack - 1) *= 2.0; + TAIL_CALL inst[1].callback(&inst[1], d_stack, r_stack, vars); +} + +static void op_div2(union forth_inst *inst, + double *d_stack, + double *r_stack, + struct forth_vars *vars) +{ + *(d_stack - 1) /= 2.0; + TAIL_CALL inst[1].callback(&inst[1], d_stack, r_stack, vars); +} + +static void op_multpi(union forth_inst *inst, + double *d_stack, + double *r_stack, + struct forth_vars *vars) +{ + *(d_stack - 1) *= M_PI; + TAIL_CALL inst[1].callback(&inst[1], d_stack, r_stack, vars); +} + static void op_eval_code(union forth_inst *inst __attribute__((unused)), double *d_stack, double *r_stack, @@ -229,6 +256,15 @@ static bool check_stack_effects(const struct forth_ctx *ctx, if (inst->callback == op_halt) { continue; /* no immediate for halt */ } + if (inst->callback == op_mult2) { + continue; /* no immediate for mult2 */ + } + if (inst->callback == op_div2) { + continue; /* no immediate for div2 */ + } + if (inst->callback == op_multpi) { + continue; /* no immediate for multpi */ + } if (UNLIKELY(inst->callback == op_eval_code)) { lwan_status_critical("eval_code after inlining"); __builtin_unreachable(); @@ -322,6 +358,12 @@ static void dump_code(const struct forth_code *code) inst++; } else if (inst->callback == op_halt) { printf("halt\n"); + } else if (inst->callback == op_mult2) { + printf("mult2\n"); + } else if (inst->callback == op_div2) { + printf("div2\n"); + } else if (inst->callback == op_multpi) { + printf("multpi\n"); } else if (UNLIKELY(inst->callback == op_eval_code)) { lwan_status_critical("eval_code shouldn't exist here"); __builtin_unreachable(); @@ -422,6 +464,41 @@ static struct forth_word *new_word(struct forth_ctx *ctx, forth_code_get_elem_index(&ctx->defining_word->code, emitted); \ }) +static bool peephole(struct forth_ctx *ctx, const struct forth_builtin *b) +{ + struct forth_code *code = &ctx->defining_word->code; + + if (forth_code_len(code) < 2) + return false; + + union forth_inst *last = + forth_code_get_elem(code, forth_code_len(code) - 1); + + if (streq(b->name, "/")) { + if (last[-1].callback == op_number && last[0].number == 2.0) { + last[-1].callback = op_div2; + code->base.elements--; + return true; + } + } else if (streq(b->name, "*")) { + if (last[-1].callback == op_number && last[0].number == 2.0) { + last[-1].callback = op_mult2; + code->base.elements--; + return true; + } + + const struct forth_builtin *prev_b = + find_builtin_by_callback(last[0].callback); + if (prev_b && streq(prev_b->name, "pi")) { + last[0].callback = op_multpi; + code->base.elements--; + return true; + } + } + + return false; +} + static const char *found_word(struct forth_ctx *ctx, const char *code, const char *word, @@ -459,7 +536,8 @@ static const char *found_word(struct forth_ctx *ctx, if (is_word_compiler(w)) return w->builtin->callback_compiler(ctx, code); - EMIT(.callback = w->builtin->callback); + if (!peephole(ctx, w->builtin)) + EMIT(.callback = w->builtin->callback); } else { EMIT(.callback = op_eval_code); EMIT(.code = &w->code); From c55b61b9c52593c373cc899ea42e492d0c0c191b Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 7 Mar 2025 10:23:16 -0800 Subject: [PATCH 2489/2505] Add more optimizations to the peephole optimizer --- src/samples/forthsalon/forth.c | 39 ++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 0f61c321c..99f47e429 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -195,6 +195,16 @@ static void op_mult2(union forth_inst *inst, TAIL_CALL inst[1].callback(&inst[1], d_stack, r_stack, vars); } + +static void op_pow2(union forth_inst *inst, + double *d_stack, + double *r_stack, + struct forth_vars *vars) +{ + *(d_stack - 1) *= *(d_stack - 1); + TAIL_CALL inst[1].callback(&inst[1], d_stack, r_stack, vars); +} + static void op_div2(union forth_inst *inst, double *d_stack, double *r_stack, @@ -259,6 +269,9 @@ static bool check_stack_effects(const struct forth_ctx *ctx, if (inst->callback == op_mult2) { continue; /* no immediate for mult2 */ } + if (inst->callback == op_pow2) { + continue; /* no immediate for pow2 */ + } if (inst->callback == op_div2) { continue; /* no immediate for div2 */ } @@ -360,6 +373,8 @@ static void dump_code(const struct forth_code *code) printf("halt\n"); } else if (inst->callback == op_mult2) { printf("mult2\n"); + } else if (inst->callback == op_pow2) { + printf("pow2\n"); } else if (inst->callback == op_div2) { printf("div2\n"); } else if (inst->callback == op_multpi) { @@ -494,6 +509,20 @@ static bool peephole(struct forth_ctx *ctx, const struct forth_builtin *b) code->base.elements--; return true; } + } else if (streq(b->name, "**")) { + if (last[-1].callback == op_number && last[0].number == 2.0) { + last[-1].callback = op_pow2; + code->base.elements--; + return true; + } + } else if (streq(b->name, "dup")) { + if (last[0].callback == b->callback) { + struct forth_word *w = hash_find(ctx->words, "dupdup"); + assert(w != NULL); + assert(is_word_builtin(w)); + last[0].callback = w->builtin->callback; + return true; + } } return false; @@ -960,6 +989,16 @@ BUILTIN("dup", 2, 1) NEXT(); } +BUILTIN("dupdup", 4, 1) +{ + double v = POP_D(); + PUSH_D(v); + PUSH_D(v); + PUSH_D(v); + PUSH_D(v); + NEXT(); +} + BUILTIN("over", 3, 2) { double v1 = POP_D(); From 3cb362ed3b7f1e2253296063f071e2c686f4eae8 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 7 Mar 2025 17:07:12 -0800 Subject: [PATCH 2490/2505] Add some FIXMEs to the peephole optimizer --- src/samples/forthsalon/forth.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 99f47e429..092e6e681 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -481,8 +481,25 @@ static struct forth_word *new_word(struct forth_ctx *ctx, static bool peephole(struct forth_ctx *ctx, const struct forth_builtin *b) { + /* FIXME: This is small enough that the current implementation is + * fine, but if we end up adding quite a bit more rules, we should + * look into making the rule declaration a bit better. */ + /* FIXME: This should look at the generated code after inlining to + * match code between words. This isn't currently possible because + * of branching with if/else/then, but we can make it work by + * peepholing the op_nop instruction instead of handling it inside + * the inliner. */ + /* FIXME: Other optimizations are possible -- for instance, a + * constant folding step seems trivial to do. However, things like + * strength reduction are a bit tricky considering all numbers are + * floating pointing numbers (even if they're double precision). + * More sophisticated techniques could be employed but with the + * simplistic implementation of this compiler, it might not be + * worth the trouble. */ struct forth_code *code = &ctx->defining_word->code; + /* We look at the last instruction generated and the one before + * that. */ if (forth_code_len(code) < 2) return false; From d6e3858848b3518d0b81ebfafb99b4f57c0c6bb1 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 7 Mar 2025 17:07:27 -0800 Subject: [PATCH 2491/2505] Teach the peephole optimizer a few more tricks --- src/samples/forthsalon/forth.c | 163 +++++++++++++++++++++++++-------- 1 file changed, 125 insertions(+), 38 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 092e6e681..e9df83cd8 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -389,7 +389,11 @@ static void dump_code(const struct forth_code *code) const struct forth_builtin *b = find_builtin_by_callback(inst->callback); if (b) { - printf("call builtin '%s'\n", b->name); + if (b->name[0] == ' ') { + printf("call private builtin '%s'\n", b->name + 1); + } else { + printf("call builtin '%s'\n", b->name); + } } else { printf("*** inconsistency; value = %zu ***\n", inst->pc); } @@ -479,30 +483,67 @@ static struct forth_word *new_word(struct forth_ctx *ctx, forth_code_get_elem_index(&ctx->defining_word->code, emitted); \ }) -static bool peephole(struct forth_ctx *ctx, const struct forth_builtin *b) +static bool peephole1(struct forth_ctx *ctx, + struct forth_code *code, + const struct forth_builtin *b) { - /* FIXME: This is small enough that the current implementation is - * fine, but if we end up adding quite a bit more rules, we should - * look into making the rule declaration a bit better. */ - /* FIXME: This should look at the generated code after inlining to - * match code between words. This isn't currently possible because - * of branching with if/else/then, but we can make it work by - * peepholing the op_nop instruction instead of handling it inside - * the inliner. */ - /* FIXME: Other optimizations are possible -- for instance, a - * constant folding step seems trivial to do. However, things like - * strength reduction are a bit tricky considering all numbers are - * floating pointing numbers (even if they're double precision). - * More sophisticated techniques could be employed but with the - * simplistic implementation of this compiler, it might not be - * worth the trouble. */ - struct forth_code *code = &ctx->defining_word->code; + union forth_inst *last = + forth_code_get_elem(code, forth_code_len(code) - 1); - /* We look at the last instruction generated and the one before - * that. */ - if (forth_code_len(code) < 2) - return false; + if (streq(b->name, "+")) { + const struct forth_word *w = hash_find(ctx->words, "*"); + assert(w != NULL); + assert(is_word_builtin(w)); + if (last[0].callback == w->builtin->callback) { + w = hash_find(ctx->words, " fma"); + assert(w != NULL); + assert(is_word_builtin(w)); + last[0].callback = w->builtin->callback; + return true; + } + } else if (streq(b->name, "*")) { + const struct forth_builtin *prev_b = + find_builtin_by_callback(last[0].callback); + if (prev_b && streq(prev_b->name, "pi")) { + last[0].callback = op_multpi; + code->base.elements--; + return true; + } + } else if (streq(b->name, "dup")) { + if (last[0].callback == b->callback) { + const struct forth_word *w = hash_find(ctx->words, " dupdup"); + assert(w != NULL); + assert(is_word_builtin(w)); + last[0].callback = w->builtin->callback; + return true; + } + } else if (streq(b->name, "swap")) { + const struct forth_word *w = hash_find(ctx->words, "-rot"); + assert(w != NULL); + assert(is_word_builtin(w)); + if (last[0].callback == w->builtin->callback) { + w = hash_find(ctx->words, " -rotswap"); + last[0].callback = w->builtin->callback; + return true; + } + w = hash_find(ctx->words, ">="); + assert(w != NULL); + assert(is_word_builtin(w)); + if (last[0].callback == w->builtin->callback) { + w = hash_find(ctx->words, " >=swap"); + last[0].callback = w->builtin->callback; + return true; + } + } + + return false; +} + +static bool peephole_n(struct forth_ctx *ctx, + struct forth_code *code, + const struct forth_builtin *b) +{ union forth_inst *last = forth_code_get_elem(code, forth_code_len(code) - 1); @@ -518,28 +559,43 @@ static bool peephole(struct forth_ctx *ctx, const struct forth_builtin *b) code->base.elements--; return true; } - - const struct forth_builtin *prev_b = - find_builtin_by_callback(last[0].callback); - if (prev_b && streq(prev_b->name, "pi")) { - last[0].callback = op_multpi; - code->base.elements--; - return true; - } } else if (streq(b->name, "**")) { if (last[-1].callback == op_number && last[0].number == 2.0) { last[-1].callback = op_pow2; code->base.elements--; return true; } - } else if (streq(b->name, "dup")) { - if (last[0].callback == b->callback) { - struct forth_word *w = hash_find(ctx->words, "dupdup"); - assert(w != NULL); - assert(is_word_builtin(w)); - last[0].callback = w->builtin->callback; + } + + return false; +} + +static bool peephole(struct forth_ctx *ctx, const struct forth_builtin *b) +{ + /* FIXME: This is small enough that the current implementation is + * fine, but if we end up adding quite a bit more rules, we should + * look into making the rule declaration a bit better. */ + /* FIXME: This should look at the generated code after inlining to + * match code between words. This isn't currently possible because + * of branching with if/else/then, but we can make it work by + * peepholing the op_nop instruction instead of handling it inside + * the inliner. */ + /* FIXME: Other optimizations are possible -- for instance, a + * constant folding step seems trivial to do. However, things like + * strength reduction are a bit tricky considering all numbers are + * floating pointing numbers (even if they're double precision). + * More sophisticated techniques could be employed but with the + * simplistic implementation of this compiler, it might not be + * worth the trouble. */ + struct forth_code *code = &ctx->defining_word->code; + + if (forth_code_len(code) >= 1) { + if (peephole1(ctx, code, b)) + return true; + } + if (forth_code_len(code) >= 2) { + if (peephole_n(ctx, code, b)) return true; - } } return false; @@ -1006,7 +1062,7 @@ BUILTIN("dup", 2, 1) NEXT(); } -BUILTIN("dupdup", 4, 1) +BUILTIN(" dupdup", 4, 1) { double v = POP_D(); PUSH_D(v); @@ -1096,6 +1152,17 @@ BUILTIN("-rot", 3, 3) NEXT(); } +BUILTIN(" -rotswap", 3, 3) +{ + double v1 = POP_D(); + double v2 = POP_D(); + double v3 = POP_D(); + PUSH_D(v1); + PUSH_D(v2); + PUSH_D(v3); + NEXT(); +} + BUILTIN("=", 1, 2) { double v1 = POP_D(); @@ -1136,6 +1203,17 @@ BUILTIN(">=", 1, 2) NEXT(); } +BUILTIN(" >=swap", 2, 3) +{ + double v1 = POP_D(); + double v2 = POP_D(); + double v3 = POP_D(); + + PUSH_D(v1 >= v2 ? 1.0 : 0.0); + PUSH_D(v3); + NEXT(); +} + BUILTIN("<=", 1, 2) { double v1 = POP_D(); @@ -1150,6 +1228,15 @@ BUILTIN("+", 1, 2) NEXT(); } +BUILTIN(" fma", 1, 3) +{ + double m1 = POP_D(); + double m2 = POP_D(); + double a = POP_D(); + PUSH_D(fma(m1, m2, a)); + NEXT(); +} + BUILTIN("*", 1, 2) { PUSH_D(POP_D() * POP_D()); From 9bfc6753d58d39be0672411b70e847e78209aa28 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 7 Mar 2025 20:39:32 -0800 Subject: [PATCH 2492/2505] Make mult2, div2, pow2, and multpi instructions private built-ins Makes it easier to add new specializations later, without having to change both check_stack_effects() and dump_code(). --- src/samples/forthsalon/forth.c | 102 +++++++++++++-------------------- 1 file changed, 41 insertions(+), 61 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index e9df83cd8..7aa982cf8 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -186,43 +186,6 @@ static void op_halt(union forth_inst *inst __attribute__((unused)), vars->final_r_stack_ptr = r_stack; } -static void op_mult2(union forth_inst *inst, - double *d_stack, - double *r_stack, - struct forth_vars *vars) -{ - *(d_stack - 1) *= 2.0; - TAIL_CALL inst[1].callback(&inst[1], d_stack, r_stack, vars); -} - - -static void op_pow2(union forth_inst *inst, - double *d_stack, - double *r_stack, - struct forth_vars *vars) -{ - *(d_stack - 1) *= *(d_stack - 1); - TAIL_CALL inst[1].callback(&inst[1], d_stack, r_stack, vars); -} - -static void op_div2(union forth_inst *inst, - double *d_stack, - double *r_stack, - struct forth_vars *vars) -{ - *(d_stack - 1) /= 2.0; - TAIL_CALL inst[1].callback(&inst[1], d_stack, r_stack, vars); -} - -static void op_multpi(union forth_inst *inst, - double *d_stack, - double *r_stack, - struct forth_vars *vars) -{ - *(d_stack - 1) *= M_PI; - TAIL_CALL inst[1].callback(&inst[1], d_stack, r_stack, vars); -} - static void op_eval_code(union forth_inst *inst __attribute__((unused)), double *d_stack, double *r_stack, @@ -266,18 +229,6 @@ static bool check_stack_effects(const struct forth_ctx *ctx, if (inst->callback == op_halt) { continue; /* no immediate for halt */ } - if (inst->callback == op_mult2) { - continue; /* no immediate for mult2 */ - } - if (inst->callback == op_pow2) { - continue; /* no immediate for pow2 */ - } - if (inst->callback == op_div2) { - continue; /* no immediate for div2 */ - } - if (inst->callback == op_multpi) { - continue; /* no immediate for multpi */ - } if (UNLIKELY(inst->callback == op_eval_code)) { lwan_status_critical("eval_code after inlining"); __builtin_unreachable(); @@ -371,14 +322,6 @@ static void dump_code(const struct forth_code *code) inst++; } else if (inst->callback == op_halt) { printf("halt\n"); - } else if (inst->callback == op_mult2) { - printf("mult2\n"); - } else if (inst->callback == op_pow2) { - printf("pow2\n"); - } else if (inst->callback == op_div2) { - printf("div2\n"); - } else if (inst->callback == op_multpi) { - printf("multpi\n"); } else if (UNLIKELY(inst->callback == op_eval_code)) { lwan_status_critical("eval_code shouldn't exist here"); __builtin_unreachable(); @@ -505,7 +448,10 @@ static bool peephole1(struct forth_ctx *ctx, const struct forth_builtin *prev_b = find_builtin_by_callback(last[0].callback); if (prev_b && streq(prev_b->name, "pi")) { - last[0].callback = op_multpi; + const struct forth_word *w = hash_find(ctx->words, " multpi"); + assert(w != NULL); + assert(is_word_builtin(w)); + last[0].callback = w->builtin->callback; code->base.elements--; return true; } @@ -549,19 +495,28 @@ static bool peephole_n(struct forth_ctx *ctx, if (streq(b->name, "/")) { if (last[-1].callback == op_number && last[0].number == 2.0) { - last[-1].callback = op_div2; + const struct forth_word *w = hash_find(ctx->words, " div2"); + assert(w != NULL); + assert(is_word_builtin(w)); + last[-1].callback = w->builtin->callback; code->base.elements--; return true; } } else if (streq(b->name, "*")) { if (last[-1].callback == op_number && last[0].number == 2.0) { - last[-1].callback = op_mult2; + const struct forth_word *w = hash_find(ctx->words, " mult2"); + assert(w != NULL); + assert(is_word_builtin(w)); + last[-1].callback = w->builtin->callback; code->base.elements--; return true; } } else if (streq(b->name, "**")) { if (last[-1].callback == op_number && last[0].number == 2.0) { - last[-1].callback = op_pow2; + const struct forth_word *w = hash_find(ctx->words, " pow2"); + assert(w != NULL); + assert(is_word_builtin(w)); + last[-1].callback = w->builtin->callback; code->base.elements--; return true; } @@ -1398,6 +1353,31 @@ BUILTIN("random", 1, 0) NEXT(); } +BUILTIN(" mult2", 1, 1) +{ + *(d_stack - 1) *= 2.0; + TAIL_CALL inst[1].callback(&inst[1], d_stack, r_stack, vars); +} + +BUILTIN(" pow2", 1, 1) +{ + *(d_stack - 1) *= *(d_stack - 1); + TAIL_CALL inst[1].callback(&inst[1], d_stack, r_stack, vars); +} + +BUILTIN(" div2", 1, 1) +{ + *(d_stack - 1) /= 2.0; + TAIL_CALL inst[1].callback(&inst[1], d_stack, r_stack, vars); +} + +BUILTIN(" multpi", 1, 1) +{ + *(d_stack - 1) *= M_PI; + TAIL_CALL inst[1].callback(&inst[1], d_stack, r_stack, vars); +} + + #undef NEXT __attribute__((no_sanitize_address)) static void From 5b15cf5a79c61b7cc53b6cb6b66232a2f5ab8f3b Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 7 Mar 2025 20:56:11 -0800 Subject: [PATCH 2493/2505] Fix generation of multpi instruction --- src/samples/forthsalon/forth.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 7aa982cf8..ba03f5e80 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -452,7 +452,6 @@ static bool peephole1(struct forth_ctx *ctx, assert(w != NULL); assert(is_word_builtin(w)); last[0].callback = w->builtin->callback; - code->base.elements--; return true; } } else if (streq(b->name, "dup")) { From 335bb40a9bec87bc1fb6f57e437338989aaead17 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 9 Mar 2025 13:19:27 -0700 Subject: [PATCH 2494/2505] Teach the optimizer yet more tricks --- src/samples/forthsalon/forth.c | 179 +++++++++++++++++++++++++++------ 1 file changed, 147 insertions(+), 32 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index ba03f5e80..24e7e286f 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -173,8 +173,7 @@ static void op_nop(union forth_inst *inst, double *r_stack, struct forth_vars *vars) { - lwan_status_critical("nop instruction executed after inlining"); - __builtin_unreachable(); + TAIL_CALL inst[1].callback(&inst[1], d_stack, r_stack, vars); } static void op_halt(union forth_inst *inst __attribute__((unused)), @@ -229,14 +228,13 @@ static bool check_stack_effects(const struct forth_ctx *ctx, if (inst->callback == op_halt) { continue; /* no immediate for halt */ } + if (inst->callback == op_nop) { + continue; /* no immediate for nop */ + } if (UNLIKELY(inst->callback == op_eval_code)) { lwan_status_critical("eval_code after inlining"); __builtin_unreachable(); } - if (UNLIKELY(inst->callback == op_nop)) { - lwan_status_critical("nop after inlining"); - __builtin_unreachable(); - } /* all other built-ins */ const struct forth_builtin *b = find_builtin_by_callback(inst->callback); @@ -322,12 +320,11 @@ static void dump_code(const struct forth_code *code) inst++; } else if (inst->callback == op_halt) { printf("halt\n"); + } else if (inst->callback == op_nop) { + printf("nop\n"); } else if (UNLIKELY(inst->callback == op_eval_code)) { lwan_status_critical("eval_code shouldn't exist here"); __builtin_unreachable(); - } else if (UNLIKELY(inst->callback == op_nop)) { - lwan_status_critical("nop shouldn't exist here"); - __builtin_unreachable(); } else { const struct forth_builtin *b = find_builtin_by_callback(inst->callback); @@ -426,9 +423,9 @@ static struct forth_word *new_word(struct forth_ctx *ctx, forth_code_get_elem_index(&ctx->defining_word->code, emitted); \ }) -static bool peephole1(struct forth_ctx *ctx, - struct forth_code *code, - const struct forth_builtin *b) +static bool peephole_1(struct forth_ctx *ctx, + struct forth_code *code, + const struct forth_builtin *b) { union forth_inst *last = forth_code_get_elem(code, forth_code_len(code) - 1); @@ -480,6 +477,15 @@ static bool peephole1(struct forth_ctx *ctx, last[0].callback = w->builtin->callback; return true; } + } else if (streq(b->name, " div2")) { + const struct forth_word *w = hash_find(ctx->words, " multpi"); + assert(w != NULL); + assert(is_word_builtin(w)); + if (last[0].callback == w->builtin->callback) { + w = hash_find(ctx->words, " multhalfpi"); + last[0].callback = w->builtin->callback; + return true; + } } return false; @@ -501,6 +507,37 @@ static bool peephole_n(struct forth_ctx *ctx, code->base.elements--; return true; } + } else if (streq(b->name, "+")) { + if (forth_code_len(code) >= 4) { + if (last[-1].callback == op_number && + last[-3].callback == op_number) { + last[-2].number += last[0].number; + code->base.elements -= 2; + return true; + } + } + } else if (streq(b->name, "-")) { + if (forth_code_len(code) >= 4) { + if (last[-1].callback == op_number && + last[-3].callback == op_number) { + last[-2].number -= last[0].number; + code->base.elements -= 2; + return true; + } + } + } else if (streq(b->name, "/")) { + if (forth_code_len(code) >= 4) { + if (last[-1].callback == op_number && + last[-3].callback == op_number) { + if (last[0].number == 0.0) { + last[-2].number = __builtin_inf(); + } else { + last[-2].number /= last[0].number; + } + code->base.elements -= 2; + return true; + } + } } else if (streq(b->name, "*")) { if (last[-1].callback == op_number && last[0].number == 2.0) { const struct forth_word *w = hash_find(ctx->words, " mult2"); @@ -510,6 +547,15 @@ static bool peephole_n(struct forth_ctx *ctx, code->base.elements--; return true; } + + if (forth_code_len(code) >= 4) { + if (last[-1].callback == op_number && + last[-3].callback == op_number) { + last[-2].number *= last[0].number; + code->base.elements -= 2; + return true; + } + } } else if (streq(b->name, "**")) { if (last[-1].callback == op_number && last[0].number == 2.0) { const struct forth_word *w = hash_find(ctx->words, " pow2"); @@ -519,13 +565,19 @@ static bool peephole_n(struct forth_ctx *ctx, code->base.elements--; return true; } + } else if (streq(b->name, " mult2")) { + if (last[-1].callback == op_number) { + last[0].number *= 2; + return true; + } } return false; } -static bool peephole(struct forth_ctx *ctx, const struct forth_builtin *b) +static bool peephole(struct forth_ctx *ctx) { + /* FIXME: constprop? */ /* FIXME: This is small enough that the current implementation is * fine, but if we end up adding quite a bit more rules, we should * look into making the rule declaration a bit better. */ @@ -542,16 +594,75 @@ static bool peephole(struct forth_ctx *ctx, const struct forth_builtin *b) * simplistic implementation of this compiler, it might not be * worth the trouble. */ struct forth_code *code = &ctx->defining_word->code; + struct forth_code orig_code = *code; + union forth_inst *inst; + bool modified = false; + union forth_inst *new_inst; + size_t jump_stack[64]; + size_t *j = jump_stack; - if (forth_code_len(code) >= 1) { - if (peephole1(ctx, code, b)) - return true; - } - if (forth_code_len(code) >= 2) { - if (peephole_n(ctx, code, b)) - return true; + forth_code_init(code); + + LWAN_ARRAY_FOREACH (&orig_code, inst) { + const struct forth_builtin *b = + find_builtin_by_callback(inst->callback); + if (b) { + if (forth_code_len(code) > 1) { + if (peephole_1(ctx, code, b)) { + modified = true; + continue; + } + } + if (forth_code_len(code) > 2) { + if (peephole_n(ctx, code, b)) { + modified = true; + continue; + } + } + } + + new_inst = forth_code_append(code); + if (!new_inst) + goto out; + + *new_inst = *inst; + + bool has_imm = false; + if (inst->callback == op_jump_if) { + JS_PUSH(forth_code_len(code)); + has_imm = true; + } else if (inst->callback == op_jump) { + union forth_inst *if_inst = forth_code_get_elem(code, JS_POP()); + if_inst->pc = forth_code_len(code) + + forth_code_get_elem_index(code, if_inst) - 2; + + JS_PUSH(forth_code_len(code)); + has_imm = true; + } else if (inst->callback == op_nop) { + union forth_inst *else_inst = + forth_code_get_elem(code, JS_POP()); + else_inst->pc = forth_code_len(code) - + forth_code_get_elem_index(code, else_inst) + 1; + } else if (inst->callback == op_number) { + has_imm = true; + } + + if (has_imm) { + new_inst = forth_code_append(code); + if (!new_inst) + goto out; + + inst++; + *new_inst = *inst; + } } + forth_code_reset(&orig_code); + return modified; + +out: + forth_code_reset(code); + lwan_status_error("Could not run peephole optimizer"); return false; } @@ -592,8 +703,7 @@ static const char *found_word(struct forth_ctx *ctx, if (is_word_compiler(w)) return w->builtin->callback_compiler(ctx, code); - if (!peephole(ctx, w->builtin)) - EMIT(.callback = w->builtin->callback); + EMIT(.callback = w->builtin->callback); } else { EMIT(.callback = op_eval_code); EMIT(.code = &w->code); @@ -638,17 +748,13 @@ static bool inline_calls_code(const struct forth_code *orig_code, if (!inline_calls_code(inst->code, new_code, nested - 1)) return false; } else { - bool has_imm = false; - union forth_inst *new_inst; + union forth_inst *new_inst = forth_code_append(new_code); + if (!new_inst) + return false; - if (inst->callback != op_nop) { - new_inst = forth_code_append(new_code); - if (!new_inst) - return false; - - *new_inst = *inst; - } + *new_inst = *inst; + bool has_imm = false; if (inst->callback == op_jump_if) { JS_PUSH(forth_code_len(new_code)); has_imm = true; @@ -745,6 +851,9 @@ bool forth_parse_string(struct forth_ctx *ctx, const char *code) if (!inline_calls(ctx)) return false; + if (peephole(ctx)) + peephole(ctx); + #if defined(DUMP_CODE) dump_code(&ctx->main->code); #endif @@ -755,6 +864,7 @@ bool forth_parse_string(struct forth_ctx *ctx, const char *code) return true; } +/* FIXME: mark a builtin as "constant" for constprop? */ #define BUILTIN_DETAIL(name_, id_, struct_id_, d_pushes_, d_pops_, r_pushes_, \ r_pops_) \ static void id_(union forth_inst *inst, double *d_stack, double *r_stack, \ @@ -1376,6 +1486,11 @@ BUILTIN(" multpi", 1, 1) TAIL_CALL inst[1].callback(&inst[1], d_stack, r_stack, vars); } +BUILTIN(" multhalfpi", 1, 1) +{ + *(d_stack - 1) *= M_PI / 2.0; + TAIL_CALL inst[1].callback(&inst[1], d_stack, r_stack, vars); +} #undef NEXT @@ -1444,7 +1559,7 @@ void forth_free(struct forth_ctx *ctx) } size_t forth_d_stack_len(const struct forth_ctx *ctx, - const struct forth_vars *vars) + const struct forth_vars *vars) { return (size_t)(vars->final_d_stack_ptr - ctx->d_stack); } From 5f685d175ba3e28d4fbbaa14ca4bd07659afeea2 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 17 Mar 2025 14:09:47 -0300 Subject: [PATCH 2495/2505] Use GitVersionDetect to print Lwan version --- CMakeLists.txt | 5 +++ src/bin/lwan/main.c | 13 +++++- src/cmake/GitVersionDetect.cmake | 65 +++++++++++++++++++++++++++++ src/cmake/lwan-build-config.h.cmake | 2 + 4 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 src/cmake/GitVersionDetect.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 283cb0153..bdffa6b95 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,11 @@ include(EnableCFlag) include(FindPkgConfig) include(TrySanitizer) include(GNUInstallDirs) +include(GitVersionDetect) + + +message(STATUS "Lwan version ${GITVERSIONDETECT_VERSION}") +set(LWAN_VERSION ${GITVERSIONDETECT_VERSION}) if (NOT CMAKE_BUILD_TYPE) message(STATUS "No build type selected, defaulting to Debug") diff --git a/src/bin/lwan/main.c b/src/bin/lwan/main.c index dd86a20ef..f33c79ccb 100644 --- a/src/bin/lwan/main.c +++ b/src/bin/lwan/main.c @@ -168,6 +168,7 @@ print_help(const char *argv0, const struct lwan_config *config) #endif printf("\n"); printf(" -h, --help This.\n"); + printf(" -v, --version Print the Lwan version and exits.\n"); printf("\n"); printf("Examples:\n"); if (!access("/usr/share/doc", R_OK)) { @@ -196,6 +197,11 @@ print_help(const char *argv0, const struct lwan_config *config) free(current_dir); } +static void print_version(void) +{ + printf("Lwan version %s\n", LWAN_VERSION); +} + static enum args parse_args(int argc, char *argv[], struct lwan_config *config, char *root, struct lwan_straitjacket *sj) @@ -204,6 +210,7 @@ parse_args(int argc, char *argv[], struct lwan_config *config, char *root, { .name = "root", .has_arg = 1, .val = 'r' }, { .name = "listen", .has_arg = 1, .val = 'l' }, { .name = "help", .val = 'h' }, + { .name = "version", .val = 'v' }, { .name = "config", .has_arg = 1, .val = 'c' }, { .name = "chroot", .val = 'C' }, { .name = "user", .val = 'u', .has_arg = 1 }, @@ -217,7 +224,7 @@ parse_args(int argc, char *argv[], struct lwan_config *config, char *root, int c, optidx = 0; enum args result = ARGS_USE_CONFIG; - while ((c = getopt_long(argc, argv, "L:P:K:hr:l:c:u:C", opts, &optidx)) != -1) { + while ((c = getopt_long(argc, argv, "L:P:K:hvr:l:c:u:C", opts, &optidx)) != -1) { switch (c) { #if defined(LWAN_HAVE_MBEDTLS) case 'L': @@ -274,6 +281,10 @@ parse_args(int argc, char *argv[], struct lwan_config *config, char *root, print_help(argv[0], config); return ARGS_FAILED; + case 'v': + print_version(); + return ARGS_FAILED; + default: printf("Run %s --help for usage information.\n", argv[0]); return ARGS_FAILED; diff --git a/src/cmake/GitVersionDetect.cmake b/src/cmake/GitVersionDetect.cmake new file mode 100644 index 000000000..21c3d216c --- /dev/null +++ b/src/cmake/GitVersionDetect.cmake @@ -0,0 +1,65 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# Copyright 2023 Antonio Vázquez Blanco + +#[=======================================================================[.rst: +GitVersionDetect +------- + +Derives a program version from Git tags. Only tags starting with a "v" will be +used for version inference. + + +Result Variables +^^^^^^^^^^^^^^^^ + +This will define the following variables: + +``GITVERSIONDETECT_VERSION`` + Full git tag as git describe --dirty would produce. +``GITVERSIONDETECT_VERSION_MAJOR`` + Major version matched from GITVERSIONDETECT_VERSION. +``GITVERSIONDETECT_VERSION_MINOR`` + Minor version matched from GITVERSIONDETECT_VERSION. +``GITVERSIONDETECT_VERSION_PATCH`` + Patch version matched from GITVERSIONDETECT_VERSION. +``GITVERSIONDETECT_VERSION_COMMIT_NUM`` + Number of commits that separate current source from the detected version + tag. +``GITVERSIONDETECT_VERSION_COMMIT_SHA`` + Latest commit sha. + +#]=======================================================================] + +# Required packages +find_package(Git) + +# Check if a git executable was found +if(GIT_EXECUTABLE) + # Generate a git-describe version string from Git repository tags + execute_process( + COMMAND ${GIT_EXECUTABLE} describe --tags --dirty --match "v*" + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + OUTPUT_VARIABLE GIT_DESCRIBE_VERSION + RESULT_VARIABLE GIT_DESCRIBE_ERROR_CODE + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + # If no error took place, save the version + if(NOT GIT_DESCRIBE_ERROR_CODE) + string(REGEX REPLACE "^v" "" GITVERSIONDETECT_VERSION "${GIT_DESCRIBE_VERSION}") + endif() +endif() + +# Final fallback: Just use a bogus version string that is semantically older +# than anything else and spit out a warning to the developer. +if(NOT DEFINED GITVERSIONDETECT_VERSION) + set(GITVERSIONDETECT_VERSION 0.0.0-0-unknown) + message(WARNING "Failed to determine GITVERSIONDETECT_VERSION from Git tags. Using default version \"${GITVERSIONDETECT_VERSION}\".") +endif() + +# Split the version into major, minor, patch and prerelease +string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)(-([0-9]+)-([a-z0-9]+))?" GITVERSIONDETECT_VERSION_MATCH ${GITVERSIONDETECT_VERSION}) +set(GITVERSIONDETECT_VERSION_MAJOR ${CMAKE_MATCH_1}) +set(GITVERSIONDETECT_VERSION_MINOR ${CMAKE_MATCH_2}) +set(GITVERSIONDETECT_VERSION_PATCH ${CMAKE_MATCH_3}) +set(GITVERSIONDETECT_VERSION_COMMIT_NUM ${CMAKE_MATCH_5}) +set(GITVERSIONDETECT_VERSION_COMMIT_SHA ${CMAKE_MATCH_6}) diff --git a/src/cmake/lwan-build-config.h.cmake b/src/cmake/lwan-build-config.h.cmake index b46d9f307..9efc1f090 100644 --- a/src/cmake/lwan-build-config.h.cmake +++ b/src/cmake/lwan-build-config.h.cmake @@ -19,6 +19,8 @@ #pragma once +#cmakedefine LWAN_VERSION "@LWAN_VERSION@" + /* API available in Glibc/Linux, but possibly not elsewhere */ #cmakedefine LWAN_HAVE_ACCEPT4 #cmakedefine LWAN_HAVE_ALLOCA_H From 6366ba308e66ceec19af80a9390c4f56cd1de22a Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 27 Mar 2025 08:56:56 -0300 Subject: [PATCH 2496/2505] Add LWAN_SECTION_FOREACH in .clang-format's list of foreach macros --- .clang-format | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.clang-format b/.clang-format index f11ba9636..3059ab744 100644 --- a/.clang-format +++ b/.clang-format @@ -55,18 +55,19 @@ ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true ForEachMacros: - list_for_each + - list_for_each_off - list_for_each_rev - list_for_each_safe - - list_for_each_off - list_for_each_safe_off - - LWAN_ARRAY_FOREACH_REVERSE - LWAN_ARRAY_FOREACH + - LWAN_ARRAY_FOREACH_REVERSE + - LWAN_SECTION_FOREACH - STRING_SWITCH - STRING_SWITCH_L - - STRING_SWITCH_SMALL - - STRING_SWITCH_SMALL_L - STRING_SWITCH_LARGE - STRING_SWITCH_LARGE_L + - STRING_SWITCH_SMALL + - STRING_SWITCH_SMALL_L IncludeCategories: - Regex: '^"lwan-.*' Priority: 2 From 266ae077a02fdb73f8a579b18ded586a53266946 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 22 Apr 2025 16:55:58 -0700 Subject: [PATCH 2497/2505] Move struct lwan_thread to lwan-private.h API users should not poke inside this struct! --- src/lib/lwan-private.h | 13 +++++++++++++ src/lib/lwan.h | 14 -------------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/lib/lwan-private.h b/src/lib/lwan-private.h index e49c54012..ef81a7e1a 100644 --- a/src/lib/lwan-private.h +++ b/src/lib/lwan-private.h @@ -121,6 +121,19 @@ struct lwan_request_parser_helper { int urls_rewritten; /* Times URLs have been rewritten */ }; +struct lwan_thread { + struct lwan *lwan; + struct { + char date[30]; + char expires[30]; + } date; + int epoll_fd; + struct timeouts *wheel; + int listen_fd; + int tls_listen_fd; + unsigned int cpu; + pthread_t self; +}; #define LWAN_CONCAT(a_, b_) a_ ## b_ #define LWAN_TMP_ID_DETAIL(n_) LWAN_CONCAT(lwan_tmp_id, n_) diff --git a/src/lib/lwan.h b/src/lib/lwan.h index d59227964..3ac9ef819 100644 --- a/src/lib/lwan.h +++ b/src/lib/lwan.h @@ -487,20 +487,6 @@ struct lwan_url_map { } authorization; }; -struct lwan_thread { - struct lwan *lwan; - struct { - char date[30]; - char expires[30]; - } date; - int epoll_fd; - struct timeouts *wheel; - int listen_fd; - int tls_listen_fd; - unsigned int cpu; - pthread_t self; -}; - struct lwan_straitjacket { const char *user_name; const char *chroot_path; From 7bf0449951d89f5017b09b854c01cee4a48b906d Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 29 Apr 2025 15:26:08 -0700 Subject: [PATCH 2498/2505] Only print Lwan build configuration in --version --- src/bin/lwan/main.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/bin/lwan/main.c b/src/bin/lwan/main.c index f33c79ccb..f6ab95af1 100644 --- a/src/bin/lwan/main.c +++ b/src/bin/lwan/main.c @@ -168,7 +168,7 @@ print_help(const char *argv0, const struct lwan_config *config) #endif printf("\n"); printf(" -h, --help This.\n"); - printf(" -v, --version Print the Lwan version and exits.\n"); + printf(" -v, --version Print the Lwan version & build info, and exits.\n"); printf("\n"); printf("Examples:\n"); if (!access("/usr/share/doc", R_OK)) { @@ -186,10 +186,6 @@ print_help(const char *argv0, const struct lwan_config *config) printf(" %s -P /path/to/cert.pem -K /path/to/cert.key \\\n" " -l '*:8080' -L '*:8081' -r /usr/share/doc\n", argv0); #endif - printf("\n"); - print_build_time_configuration(); - print_module_info(); - print_handler_info(); printf("\n"); printf("Report bugs at .\n"); printf("For security-related reports, mail them to .\n"); @@ -200,6 +196,10 @@ print_help(const char *argv0, const struct lwan_config *config) static void print_version(void) { printf("Lwan version %s\n", LWAN_VERSION); + printf("\n"); + print_build_time_configuration(); + print_module_info(); + print_handler_info(); } static enum args From 7f93ed59f0d7073556c266b205d20888ac72610e Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 29 Apr 2025 15:36:55 -0700 Subject: [PATCH 2499/2505] Plug memory leak on some forth_new() failures --- src/samples/forthsalon/forth.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 24e7e286f..5c5c4375d 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -1537,6 +1537,7 @@ struct forth_ctx *forth_new(void) struct forth_word *word = new_word(ctx, " ", NULL); if (!word) { + hash_unref(ctx->words); free(ctx); return NULL; } From bd11ebcfd4f71059701bb5b72227e2d885a9da63 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 29 Apr 2025 15:41:31 -0700 Subject: [PATCH 2500/2505] Better check for stack overflow when compiling Forth program --- src/samples/forthsalon/forth.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 5c5c4375d..d03217f98 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -258,15 +258,15 @@ static bool check_stack_effects(const struct forth_ctx *ctx, items_in_d_stack += b->d_pushes; items_in_r_stack -= b->r_pops; items_in_r_stack += b->r_pushes; + } - if (UNLIKELY(items_in_d_stack >= (int)N_ELEMENTS(ctx->d_stack))) { - lwan_status_error("Program would cause a stack overflow in the D stack"); - return false; - } - if (UNLIKELY(items_in_r_stack >= (int)N_ELEMENTS(ctx->r_stack))) { - lwan_status_error("Program would cause a stack overflow in the R stack"); - return false; - } + if (UNLIKELY(items_in_d_stack >= (int)N_ELEMENTS(ctx->d_stack))) { + lwan_status_error("Program would cause a stack overflow in the D stack"); + return false; + } + if (UNLIKELY(items_in_r_stack >= (int)N_ELEMENTS(ctx->r_stack))) { + lwan_status_error("Program would cause a stack overflow in the R stack"); + return false; } if (UNLIKELY(items_in_d_stack < 0)) { From a5577c1b5bb512b01e78ed5d999cf5c6a3f47d31 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Tue, 29 Apr 2025 15:41:31 -0700 Subject: [PATCH 2501/2505] Better check for stack overflow when compiling Forth program --- src/samples/forthsalon/forth.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index d03217f98..3e4dadcc3 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -269,6 +269,15 @@ static bool check_stack_effects(const struct forth_ctx *ctx, return false; } + if (UNLIKELY(items_in_d_stack >= (int)N_ELEMENTS(ctx->d_stack))) { + lwan_status_error("Program would cause a stack overflow in the D stack"); + return false; + } + if (UNLIKELY(items_in_r_stack >= (int)N_ELEMENTS(ctx->r_stack))) { + lwan_status_error("Program would cause a stack overflow in the R stack"); + return false; + } + if (UNLIKELY(items_in_d_stack < 0)) { lwan_status_error("Program would underflow the D stack"); return false; From ee3017e7d486d5f430293b06ce05a0ccb1b4dd99 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Thu, 8 May 2025 09:03:24 -0700 Subject: [PATCH 2502/2505] List of modules/handlers are now in --version --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9eac40ef0..6df8e4995 100644 --- a/README.md +++ b/README.md @@ -492,8 +492,8 @@ In order to route URLs, Lwan matches the largest common prefix from the request URI with a set of prefixes specified in the listener section. How a request to a particular prefix will be handled depends on which handler or module has been declared in the listener section. Handlers and modules are similar internally; -handlers are merely functions and hold no state, and modules holds state (named -instance). Multiple instances of a module can appear in a listener section. +handlers are merely functions and hold no state, and modules holds state +("instance"). Multiple instances of a module can appear in a listener section. There is no special syntax to attach a prefix to a handler or module; all the configuration parser rules apply here. Use `${NAME} ${PREFIX}` to link the @@ -509,7 +509,7 @@ section. > [!TIP] > -> Executing Lwan with the `--help` command-line +> Executing Lwan with the `--version` command-line > argument will show a list of built-in modules and handlers. The following is some basic documentation for the modules shipped with Lwan. From 6c0c7fb17a54e1780f009b447c2020cbb918b11d Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 11 May 2025 17:15:52 -0700 Subject: [PATCH 2503/2505] Remove indirection when swapping coroutine context --- src/lib/lwan-coro.c | 8 ++++---- src/lib/lwan-coro.h | 6 +----- src/lib/lwan-template.c | 2 +- src/lib/lwan-thread.c | 4 ++-- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/lib/lwan-coro.c b/src/lib/lwan-coro.c index 12e96f5f9..2a88c680c 100644 --- a/src/lib/lwan-coro.c +++ b/src/lib/lwan-coro.c @@ -91,7 +91,7 @@ struct coro_defer { DEFINE_ARRAY_TYPE_INLINEFIRST(coro_defer_array, struct coro_defer) struct coro { - struct coro_switcher *switcher; + coro_context *switcher; coro_context context; struct coro_defer_array defer; @@ -298,7 +298,7 @@ void coro_reset(struct coro *coro, coro_function_t func, void *data) } ALWAYS_INLINE struct coro * -coro_new(struct coro_switcher *switcher, coro_function_t function, void *data) +coro_new(coro_context *switcher, coro_function_t function, void *data) { struct coro *coro; @@ -346,7 +346,7 @@ ALWAYS_INLINE int64_t coro_resume(struct coro *coro) (uintptr_t)(coro->stack + CORO_STACK_SIZE)); #endif - coro_swapcontext(&coro->switcher->caller, &coro->context); + coro_swapcontext(coro->switcher, &coro->context); return coro->yield_value; } @@ -364,7 +364,7 @@ inline int64_t coro_yield(struct coro *coro, int64_t value) assert(coro); coro->yield_value = value; - coro_swapcontext(&coro->context, &coro->switcher->caller); + coro_swapcontext(&coro->context, coro->switcher); return coro->yield_value; } diff --git a/src/lib/lwan-coro.h b/src/lib/lwan-coro.h index 82f60ad34..0ab27468d 100644 --- a/src/lib/lwan-coro.h +++ b/src/lib/lwan-coro.h @@ -40,12 +40,8 @@ typedef ssize_t coro_deferred; typedef int (*coro_function_t)(struct coro *coro, void *data); -struct coro_switcher { - coro_context caller; -}; - struct coro * -coro_new(struct coro_switcher *switcher, coro_function_t function, void *data); +coro_new(coro_context *switcher, coro_function_t function, void *data); void coro_free(struct coro *coro); void coro_reset(struct coro *coro, coro_function_t func, void *data); diff --git a/src/lib/lwan-template.c b/src/lib/lwan-template.c index 3b02fc3aa..a55a5d6cc 100644 --- a/src/lib/lwan-template.c +++ b/src/lib/lwan-template.c @@ -1502,7 +1502,7 @@ static const struct chunk *apply(struct lwan_tpl *tpl, void *variables, const void *data) { - struct coro_switcher switcher; + coro_context switcher; struct coro *coro = NULL; const struct chunk *chunk = chunks; diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index 44fa6d9cf..5ce4ff6ed 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -922,7 +922,7 @@ static void update_date_cache(struct lwan_thread *thread) } static ALWAYS_INLINE bool spawn_coro(struct lwan_connection *conn, - struct coro_switcher *switcher, + coro_context *switcher, struct timeout_queue *tq) { struct lwan_thread *t = conn->thread; @@ -1177,7 +1177,7 @@ static void *thread_io_loop(void *data) const int max_events = LWAN_MIN((int)t->lwan->thread.max_fd, 1024); struct lwan *lwan = t->lwan; struct epoll_event *events; - struct coro_switcher switcher; + coro_context switcher; struct timeout_queue tq; if (t->cpu == UINT_MAX) { From 7ba63c0805feb8e599a348e1f4cc687cba17cd43 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sun, 11 May 2025 17:16:46 -0700 Subject: [PATCH 2504/2505] Use fmin()/fmax() to implement "min" and "max" in forth --- src/samples/forthsalon/forth.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index 3e4dadcc3..adb8c3052 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -1385,17 +1385,13 @@ BUILTIN("not", 1, 1) BUILTIN("min", 1, 2) { - double v1 = POP_D(); - double v2 = POP_D(); - PUSH_D(v1 > v2 ? v2 : v1); + PUSH_D(fmin(POP_D(), POP_D())); NEXT(); } BUILTIN("max", 1, 2) { - double v1 = POP_D(); - double v2 = POP_D(); - PUSH_D(v1 > v2 ? v1 : v2); + PUSH_D(fmax(POP_D(), POP_D())); NEXT(); } From 0927e3eb8f08ab8a4e238bcb7cc2388f6082163b Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Sat, 24 May 2025 16:04:26 -0700 Subject: [PATCH 2505/2505] Use NEXT() macro for some private words I don't know why the macro wasn't used here. --- src/samples/forthsalon/forth.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/samples/forthsalon/forth.c b/src/samples/forthsalon/forth.c index adb8c3052..61c743131 100644 --- a/src/samples/forthsalon/forth.c +++ b/src/samples/forthsalon/forth.c @@ -1470,31 +1470,31 @@ BUILTIN("random", 1, 0) BUILTIN(" mult2", 1, 1) { *(d_stack - 1) *= 2.0; - TAIL_CALL inst[1].callback(&inst[1], d_stack, r_stack, vars); + NEXT(); } BUILTIN(" pow2", 1, 1) { *(d_stack - 1) *= *(d_stack - 1); - TAIL_CALL inst[1].callback(&inst[1], d_stack, r_stack, vars); + NEXT(); } BUILTIN(" div2", 1, 1) { *(d_stack - 1) /= 2.0; - TAIL_CALL inst[1].callback(&inst[1], d_stack, r_stack, vars); + NEXT(); } BUILTIN(" multpi", 1, 1) { *(d_stack - 1) *= M_PI; - TAIL_CALL inst[1].callback(&inst[1], d_stack, r_stack, vars); + NEXT(); } BUILTIN(" multhalfpi", 1, 1) { *(d_stack - 1) *= M_PI / 2.0; - TAIL_CALL inst[1].callback(&inst[1], d_stack, r_stack, vars); + NEXT(); } #undef NEXT